234 lines
9.5 KiB
Markdown
234 lines
9.5 KiB
Markdown
# 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` 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é.
|
|
|
|
```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 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.
|
|
|
|
```bash
|
|
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` :
|
|
|
|
```bash
|
|
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` :
|
|
|
|
```bash
|
|
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 :
|
|
|
|
```sql
|
|
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 :
|
|
|
|
- https://element-hq.github.io/synapse/latest/setup/turn/coturn.html
|
|
- https://docs.livekit.io/home/self-hosting/deployment/
|