This repository has been archived on 2026-08-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

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 tient plus de l'identifiant que du secret. Il est chiffré avec son secret parce que les deux se tournent toujours ensemble : les séparer sur deux mécanismes rendrait la rotation facile à ne faire qu'à 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 là : les deux valeurs sont visibles dans docker inspect coturn et dans la table des processus de l'hôte. Un secret Compose ferait mieux, coturn ne sait pas le lire.

turnserver.conf ne doit jamais porter de ligne static-auth-secret= ou cli-password= : ce serait un secret en clair dans le dépôt, et une seconde source concurrente de celle du command:.

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

# homeserver.yaml s'édite depuis l'hôte : l'image Synapse n'a pas d'éditeur.
sudo vi "$(docker volume inspect matrix_poneyworld_matrix_data \
             --format '{{.Mountpoint}}')/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.

POSTGRES_DB conditionne le dump

backup_docker.sh lit la base à dumper dans l'environnement du conteneur db :

db_name=$(docker exec "$container" printenv POSTGRES_DB 2>/dev/null || echo "postgres")

Sans POSTGRES_DB, le repli s'applique : le script dumpe la base système postgres, vide, et journalise un succès. POSTGRES_DB=synapse dans l'env_file n'a pas d'autre fonction que d'être visible par ce printenv — PostgreSQL, lui, ne lit cette variable qu'à l'initialisation du cluster.

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.

Le chemin des requêtes clientes

matrix.poneyworld.net passe par Traefik, puis nginx, puis Synapse. Deux conséquences à ne pas défaire :

  • nginx doit propager la chaîne avec $proxy_add_x_forwarded_for, jamais $remote_addr : ce dernier vaut l'adresse de Traefik, et l'utiliser écrase l'en-tête posé en amont. Synapse voit alors tous les clients sous une seule adresse — la limitation de débit à la connexion devient un compteur partagé par tous les comptes, et les journaux d'authentification perdent leur intérêt. Côté Synapse, le listener 8008 doit porter x_forwarded: true.
  • crowdsec@file est posé sur les routeurs matrix_nginx (API client) et element (interface web), délibérément pas sur synapse : ce dernier sert la fédération, qui arrive de milliers de serveurs distants. Une décision de bannissement à cet endroit couperait les correspondants.

Contrôle de la chaîne, une seule adresse en 172.x signalant le problème :

SELECT ip, count(*) FROM user_ips GROUP BY ip ORDER BY count DESC LIMIT 10;

Versions d'images

Toutes les images sont épinglées au patch. Les tags d'amont ne suivent pas la même forme : livekit-server préfixe d'un v, lk-jwt-service non — bien que son tag git le porte. Le label OCI org.opencontainers.image.version du conteneur donne la forme du tag d'image, c'est lui qui fait foi.

livekit et lk-jwt se montent ensemble : ils s'authentifient mutuellement et suivent le même protocole MatrixRTC.

Références :