feat(stasks): migrate matrix

This commit is contained in:
2026-08-19 17:10:23 +02:00
parent b002113473
commit 0584856ea3
10 changed files with 532 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
# 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 :
```bash
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 :
```bash
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é.
```bash
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 :
```yaml
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.
```bash
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 :
```bash
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` :
```bash
zcat /other/backups/db-dumps/matrix_poneyworld.sql.gz | grep -c "CREATE TABLE"
```
Un `0` signifie que la sauvegarde est vide.
## Défauts connus
1. **`external-ip` de coturn pointe sur une autre machine.**
`coturn/turnserver.conf` annonce `91.121.90.228`, alors que chioggia est en
`188.165.240.182` (l'adresse que `livekit.yaml` déclare en `node_ip`).
coturn annonce donc aux clients une adresse qui n'est pas la sienne.
2. **UDP 3478 n'est pas publié.** `ports:` déclare `3478:3478`, sans `/udp`
donc TCP seul. Un client TURN parle UDP en premier. Combiné au point 1,
coturn est très probablement inopérant.
3. **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 :
- https://element-hq.github.io/synapse/latest/setup/turn/coturn.html
- https://docs.livekit.io/home/self-hosting/deployment/