`external-ip` pointait sur 91.121.90.228, l'adresse d'admin.opytex.org, alors que la stack tourne sur chioggia (188.165.240.182) : coturn annonçait aux clients une machine qui n'écoute rien sur ces ports. 3478 n'était par ailleurs publié qu'en TCP, alors qu'un client TURN tente l'UDP en premier. Les deux défauts se cumulaient, le relais était inopérant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Matrix — matrix.poneyworld.net
Homeserver Synapse et ses satellites. Huit conteneurs :
| Service | Rôle | Exposé sur |
|---|---|---|
synapse |
le homeserver | synapse.poneyworld.net (fédération) |
db |
PostgreSQL 18 | — |
redis |
Valkey, cache interne de Synapse (bloc redis: du homeserver) |
— |
nginx |
sert /_matrix (vers synapse:8008) et /.well-known/matrix/ |
matrix.poneyworld.net (API client) |
web |
Element Web | chat.poneyworld.net |
livekit |
SFU pour Element Call | livekit.opytex.org |
lk-jwt |
délivre les jetons LiveKit à Element | livekit.opytex.org/sfu/get, /get_token, /healthz |
coturn |
serveur TURN (appels 1-à-1) | ports 3478 / 5349 / 49160-49200 |
Deux entrées différentes, à ne pas confondre : matrix.poneyworld.net est le
server_name et l'API client, synapse.poneyworld.net est la cible de
délégation annoncée par /.well-known/matrix/server, empruntée par les
serveurs fédérés. Les deux sont vivantes.
Déploiement
Depuis ce dossier, sur chioggia :
sops exec-env secrets.env.yaml 'docker compose up -d'
Les secrets sont déclarés ${VAR:?} : toute commande docker compose
échoue sans l'enveloppe sops exec-env, y compris stop, logs, ps et
exec. Pour en enchaîner plusieurs, une seule enveloppe suffit :
sops exec-env secrets.env.yaml '
docker compose stop synapse &&
docker compose exec -T db psql -U synapse synapse -c "..." &&
docker compose up -d
'
Après une rotation de secret, docker compose up -d répond « up to date » et
garde l'ancienne valeur. Il faut --force-recreate sur le service concerné.
Le nom de projet
name: matrix_poneyworld, en tête du compose, ne suit pas le nom du dossier.
C'est délibéré et c'est porteur : les volumes s'appellent
matrix_poneyworld_matrix_data et matrix_poneyworld_matrix_db.
Retirer cette ligne, ou la modifier, rendrait invisibles les médias, les
clés de signature du serveur, homeserver.yaml et toute la base — la stack
repartirait sur un homeserver vide, sans la moindre erreur.
Où vit vraiment la configuration
Presque rien de la configuration de Synapse n'est dans ce dépôt : elle est
dans /data/homeserver.yaml, à l'intérieur du volume matrix_data.
Les variables SYNAPSE_* de l'env_file ne sont lues que par le mode
generate de l'image, au tout premier démarrage. Elles sont inertes au
runtime : SYNAPSE_ENABLE_REGISTRATION=yes y cohabite sans conséquence avec
un enable_registration: False effectif dans homeserver.yaml. Ne pas s'y
fier pour lire l'état du serveur.
Corollaire pour les secrets : turn_shared_secret, macaroon_secret_key,
registration_shared_secret et le mot de passe de la base sont dans
homeserver.yaml, donc hors SOPS et hors dépôt. Une rotation se fait des
deux côtés (voir plus bas).
Secrets
Cinq valeurs dans secrets.env.yaml, injectées par sops exec-env :
| Variable | Consommé par |
|---|---|
POSTGRES_PASSWORD |
db |
LIVEKIT_KEY, LIVEKIT_SECRET |
livekit (via LIVEKIT_KEYS) et lk-jwt |
TURN_STATIC_AUTH_SECRET, TURN_CLI_PASSWORD |
coturn |
LIVEKIT_KEY est un identifiant plus qu'un secret, et la convention du dépôt
laisserait ce genre de valeur en clair. Il est chiffré quand même : il ne se
tourne jamais seul, toujours en paire avec son secret, et le séparer sur deux
mécanismes rendrait la rotation facile à rater à moitié.
sops edit secrets.env.yaml
Deux mécanismes différents pour deux logiciels
Ni coturn ni LiveKit ne lisent une variable d'environnement depuis leur fichier de configuration — un fichier monté n'est pas interpolé par Compose. C'est le même problème que la clé LAPI de CrowdSec sur Traefik, mais les deux n'offrent pas la même sortie.
LiveKit accepte la paire par variable d'environnement : LIVEKIT_KEYS, au
format clé: secret. livekit.yaml n'a donc pas de bloc keys:, et la
valeur est composée dans environment: du compose, où l'interpolation
${...} s'applique. lk-jwt consomme les deux mêmes variables.
coturn n'offre rien d'équivalent : pas d'interpolation dans
turnserver.conf, pas d'option ...File, pas de lecture d'environnement. La
seule voie sans bricolage est la ligne de commande :
command:
- -c
- /etc/coturn/turnserver.conf
- --static-auth-secret=${TURN_STATIC_AUTH_SECRET:?}
- --cli-password=${TURN_CLI_PASSWORD:?}
Le compromis est explicite : les valeurs ne sont pas dans un fichier
versionnable, mais elles sont visibles dans docker inspect coturn et dans la
table des processus de l'hôte. C'est strictement mieux qu'un fichier
versionné, ce n'est pas aussi bon qu'un secret Compose.
turnserver.conf ne doit donc jamais reprendre de lignes
static-auth-secret= ou cli-password= : ce serait à la fois un secret en
clair dans le dépôt et une seconde source concurrente.
Rotation
turn_shared_secret ne se change pas seul : la même valeur doit être
identique dans homeserver.yaml (Synapse la signe) et côté coturn (il la
vérifie). Deux valeurs différentes ne produisent aucune erreur au démarrage —
les appels échouent simplement à établir un relais.
sops edit secrets.env.yaml # TURN_STATIC_AUTH_SECRET
docker compose exec synapse vi /data/homeserver.yaml # turn_shared_secret
sops exec-env secrets.env.yaml 'docker compose up -d --force-recreate coturn'
docker compose restart synapse
macaroon_secret_key (dans homeserver.yaml) signe les jetons d'accès : le
changer déconnecte tous les utilisateurs, sur tous leurs appareils. Ce
n'est pas une opération anodine, c'est une opération annoncée.
registration_shared_secret (dans homeserver.yaml) n'est utilisé que par
register_new_matrix_user. Il se change librement.
POSTGRES_PASSWORD ne se change pas seul non plus : PostgreSQL ignore la
variable sur un PGDATA déjà initialisé. Il faut un ALTER USER dans la base
et la même valeur dans le bloc database: de homeserver.yaml.
Volumes et sauvegarde
| Volume | Contenu | Sauvegarde |
|---|---|---|
matrix_poneyworld_matrix_data |
médias, clé de signature du serveur, homeserver.yaml |
label backup.enable=true + MANUAL_VOLUMES |
matrix_poneyworld_matrix_db |
base PostgreSQL | dump SQL (backup.type=postgres sur db) |
Les labels de volume ne s'appliquent qu'à la création : en ajouter ou en
retirer un ne changerait rien au volume réel, mais ferait diverger sa
déclaration, et Compose proposerait alors de le recréer. Ne pas toucher au
bloc volumes: du compose.
Pourquoi POSTGRES_DB est indispensable
backup_docker.sh détermine la base à dumper ainsi :
db_name=$(docker exec "$container" printenv POSTGRES_DB 2>/dev/null || echo "postgres")
Sans POSTGRES_DB dans l'environnement du conteneur db, printenv sort en
erreur, le repli s'applique, et le script dumpe postgres — la base système,
vide — avec succès et sans le moindre avertissement. Le dump obtenu fait
quelques centaines d'octets et ne contient aucune table.
POSTGRES_DB=synapse dans l'env_file est ce qui garantit que le dump porte
sur la bonne base. La variable n'est lue par PostgreSQL qu'à l'initialisation
du cluster : elle est sans effet sur une base existante, sa seule fonction ici
est d'être visible par printenv.
Vérification, après tout changement touchant db :
zcat /other/backups/db-dumps/matrix_poneyworld.sql.gz | grep -c "CREATE TABLE"
Un 0 signifie que la sauvegarde est vide.
L'IP publique est écrite en dur, à deux endroits
coturn/turnserver.conf (external-ip) et livekit/livekit.yaml (node_ip)
portent tous deux l'adresse publique de chioggia, 188.165.240.182. Les deux
services tournent en bridge : ils ne voient qu'une IP privée et ne peuvent pas
deviner celle qu'il faut annoncer aux clients.
Si la machine change d'adresse, ces deux fichiers doivent changer avec elle. Aucun des deux ne le signalera : coturn et LiveKit démarrent normalement en annonçant une adresse erronée, et seuls les appels échouent — sans erreur dans les logs, puisque du point de vue des serveurs tout va bien.
C'est déjà arrivé : external-ip a pointé pendant des mois sur
91.121.90.228, l'adresse d'admin.opytex.org.
Défauts connus
Trois images en tag flottant : nginx:latest,
livekit/livekit-server:latest, ghcr.io/element-hq/lk-jwt-service:latest.
La convention du dépôt veut une version en dur, et Renovate ne sait rien faire
d'un latest.
Références :