Commit 96d9521f authored by Kourser's avatar Kourser
Browse files

La configuration parle anglais, et sait où vit le serveur média

Deux changements que les mêmes fichiers portent, et qu'il aurait fallu
séparer si les diffs ne s'entrelaçaient pas dans compose.yaml et dans
l'installateur : les variables d'environnement passent à l'anglais, et le
serveur média cesse d'être une fatalité sur cinq ports fixes.

── Les noms ───────────────────────────────────────────────────────────────

Un fichier de configuration est lu par des gens qui n'ont pas écrit le
logiciel, souvent dans l'urgence, et parfois sans parler la langue de celui
qui l'a nommé. CLAMAV_HOTE, SECRET_SESSION ou APNS_EQUIPE_ID demandaient de
traduire avant de comprendre.

  KASTELL_URL_PUBLIQUE → KASTELL_PUBLIC_URL, KASTELL_PROXY_DE_CONFIANCE →
  KASTELL_TRUSTED_PROXY, KASTELL_RELAIS_POUSSEE → KASTELL_PUSH_RELAY,
  KASTELL_RELAIS_CLE → KASTELL_RELAY_KEY, CLAMAV_HOTE → CLAMAV_HOST,
  LIVEKIT_URL_INTERNE → LIVEKIT_INTERNAL_URL, LIVEKIT_CLE → LIVEKIT_KEY,
  S3_ACCES → S3_ACCESS, SECRET_SESSION → SESSION_SECRET. Côté relais : HOTE,
  RELAIS_CLES, DEBIT_PAR_MINUTE, et les familles APNS_ et FCM_.

  239 occurrences, 31 fichiers, documentation comprise.

Ce qui ne casse pas, et pourquoi c'est le cœur du commit
  Une instance en service ne se met pas à jour en même temps que son fichier.
  Refuser de démarrer sur un .env ancien transformerait un renommage en
  panne, et ce n'est pas le jour d'une crise qu'on veut le découvrir.

  La composition lit « ${NOUVEAU:-${ANCIEN:-}} ». C'est le filet qui compte :
  l'interpolation a lieu avant que le conteneur existe, et sans lui un .env
  ancien ne donnerait pas une erreur mais des valeurs vides.

  Les deux schémas recopient l'ancien nom vers le nouveau quand celui-ci
  manque, et le disent. Jamais l'inverse. Ce filet sert les exécutions hors
  conteneur — « pnpm verif », « pnpm amorcer-root ».

  preparer-env.mjs renomme sur place au lieu de compléter. Le plus dangereux
  à omettre : une clé renommée passait pour absente, l'outil ajoutait la
  valeur d'exemple à côté de la vraie, et comme la composition préfère le
  nouveau nom, l'exemple l'emportait — une URL publique remplacée par
  « localhost » sans que personne l'ait demandé, un secret de session neuf
  qui déconnecte tout le monde. Constaté sur une instance de développement
  avant d'être corrigé.

── Le serveur média ───────────────────────────────────────────────────────

Une machine qui porte déjà un LiveKit ne pouvait pas accueillir Kastell : les
cinq groupes de ports étaient écrits en dur, et « address already in use »
arrivait sans que rien ne l'ait annoncé. L'installateur propose maintenant
trois voies — le serveur livré sur ses ports habituels, le même sur d'autres
ports, ou le LiveKit que l'exploitant a déjà.

  Les ports deviennent des variables, publiées à l'identique hôte et
  conteneur : le TURN et les candidats RTC annoncent aux navigateurs le port
  que LiveKit croit occuper, et une translation les rendrait faux. Déplacer
  les ports demande donc aussi un fichier de configuration qui les répète,
  que l'installateur écrit — docker/livekit.local.yaml, hors git, avec
  use_external_ip et le domaine TURN déduits de l'adresse publique.

  La mutualisation lève tout sauf « livekit ». Le piège qu'elle cache est
  l'enregistreur : il ne reçoit pas ses tâches par HTTP mais s'inscrit sur le
  Redis du serveur média. Le laisser sur celui de Kastell donnerait une visio
  qui marche et un enregistrement qui échoue sur « no egress workers
  available » — au moment précis où l'on enregistre. LIVEKIT_REDIS existe
  pour ça, et l'installateur pose la question plutôt que de la supposer.

  L'egress perd sa dépendance à « livekit » : Compose l'aurait satisfaite en
  démarrant le service, ressuscitant le conflit de ports qu'on fuyait.

Contrôlé
  tsc passe. La pile se lève et répond sur un .env resté entièrement aux
  anciens noms, puis sur le même une fois migré, valeurs conservées au
  caractère près et sans doublon. Les trois voies du serveur média ont été
  jouées sur une copie isolée : conflit détecté puis contourné, jeu de ports
  libres proposé et écrit des deux côtés, mutualisation avec secret laissé
  vide et services réduits.

Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent 618b1b97
Loading
Loading
Loading
Loading
+58 −18
Changes for .env.example: 58 added lines, 18 removed lines.
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
# Le serveur refuse de démarrer si une valeur par défaut subsiste (EX-26, EX-27).

# Identité de l'instance
KASTELL_URL_PUBLIQUE=http://localhost:8080
KASTELL_PUBLIC_URL=http://localhost:8080

# Où trouver le code source de cette instance (AGPL-3.0, section 13). Si vous
# avez modifié Kastell, faites pointer cette adresse vers votre dépôt : elle
@@ -31,7 +31,7 @@ REDIS_URL=redis://localhost:6379
S3_ENDPOINT=http://localhost:9000
S3_REGION=eu-west-1
S3_BUCKET=kastell
S3_ACCES=kastell
S3_ACCESS=kastell
S3_SECRET=CHANGEZ-MOI-secret-stockage

# Visioconférence — démarrée par défaut avec la composition.
@@ -42,20 +42,60 @@ LIVEKIT_URL=ws://localhost:7880
# Adresse de l'API média vue depuis le serveur. Dans la composition Docker,
# l'hôte est le nom du service : « localhost » y désigne le conteneur
# applicatif lui-même. Laissez vide hors conteneur.
LIVEKIT_URL_INTERNE=http://livekit:7880
LIVEKIT_CLE=kastell
LIVEKIT_INTERNAL_URL=http://livekit:7880
LIVEKIT_KEY=kastell
LIVEKIT_SECRET=CHANGEZ-MOI-secret-media-au-moins-32-caracteres

# ── Où vit le serveur média ────────────────────────────────────────────────
#
# « livre » : celui de la composition. « mutualise » : un LiveKit que vous
# exploitez déjà, auquel cas le service n'est pas levé et seules les adresses
# ci-dessus comptent. Vide désactive la visioconférence.
LIVEKIT_MODE=livre
# Lever l'enregistreur de réunions. Il ne sert à rien sans serveur média.
LIVEKIT_EGRESS=oui

# ── Ports du serveur média ─────────────────────────────────────────────────
#
# À ne déplacer que si la machine porte déjà un LiveKit. Hôte et conteneur
# gardent le MÊME numéro : le TURN et les candidats RTC annoncent aux
# navigateurs le port que LiveKit croit occuper, et une translation les rendrait
# faux — la salle s'ouvrirait et personne ne s'y verrait.
#
# Ces numéros vivent aussi dans le fichier de configuration de LiveKit. Les
# changer ici seul ne suffit pas : LIVEKIT_CONFIG doit désigner un fichier qui
# porte les mêmes, ce que l'installateur écrit pour vous.
LIVEKIT_PORT=7880
LIVEKIT_PORT_RTC_TCP=7881
LIVEKIT_PORT_TURN=3478
LIVEKIT_PORT_TURN_TLS=5349
LIVEKIT_PORTS_MEDIA=50000-50100
LIVEKIT_CONFIG=../docker/livekit.yaml

# ── Enregistreur : par où lui parvient le travail ──────────────────────────
#
# L'enregistreur ne reçoit pas ses tâches par HTTP : il s'inscrit sur le Redis
# qu'utilise le serveur média, et c'est de là que le travail vient. Avec le
# serveur livré, c'est le Redis de la composition et il n'y a rien à faire.
#
# Avec un serveur mutualisé, il faut désigner LE SIEN — sans quoi la visio
# marchera et l'enregistrement échouera sur « no egress workers available »,
# au moment précis où l'on enregistre, c'est-à-dire en crise. Conséquence
# assumée : l'autre déploiement pourra confier ses propres enregistrements à
# cet enregistreur.
LIVEKIT_REDIS=redis:6379
LIVEKIT_WS_INTERNAL=ws://livekit:7880

# Analyse antivirale des dépôts — démarrée par défaut avec la composition.
# Laisser CLAMAV_HOTE vide désactive l'analyse : les documents sont alors
# Laisser CLAMAV_HOST vide désactive l'analyse : les documents sont alors
# marqués « non analysée », jamais « saine ».
# Au premier démarrage, ClamAV télécharge ses signatures — quelques minutes
# pendant lesquelles les dépôts restent possibles, mais non analysés.
CLAMAV_HOTE=clamav
CLAMAV_HOST=clamav
CLAMAV_PORT=3310

# Secrets applicatifs — générer avec: openssl rand -hex 32
SECRET_SESSION=CHANGEZ-MOI-secret-session-64-caracteres-hexadecimaux
SESSION_SECRET=CHANGEZ-MOI-secret-session-64-caracteres-hexadecimaux

# ── Notification mobile : le relais de heurtoir (EM-82 à EM-84) ─────────────
#
@@ -65,23 +105,23 @@ SECRET_SESSION=CHANGEZ-MOI-secret-session-64-caracteres-hexadecimaux
# canaux de mobilisation de premier rang.
#
# Pour le lancer avec la composition : docker compose --profile mobile up -d
KASTELL_RELAIS_POUSSEE=http://relais:8081
KASTELL_RELAIS_CLE=CHANGEZ-MOI
KASTELL_PUSH_RELAY=http://relais:8081
KASTELL_RELAY_KEY=CHANGEZ-MOI

# La clé .p8 remise par Apple, en PEM, sur une seule ligne avec des \n.
# Sans elle, le relais démarre et refuse chaque heurtoir avec un motif clair —
# plutôt que d'accepter en silence et de laisser croire qu'un téléphone sonnera.
APNS_CLE=
APNS_CLE_ID=
APNS_EQUIPE_ID=
APNS_PAQUET=eu.cythin.kastell
APNS_ENVIRONNEMENT=bac_a_sable
APNS_KEY=
APNS_KEY_ID=
APNS_TEAM_ID=
APNS_BUNDLE_ID=eu.cythin.kastell
APNS_ENVIRONMENT=bac_a_sable

# EM-12 : à laisser sur « false » tant qu'Apple n'a pas instruit la demande
# d'habilitation d'alerte critique. Son obtention n'est jamais présumée : sans
# elle, la notification ne franchit pas le mode silencieux, et l'application le
# dit à l'installation plutôt que de le laisser découvrir la nuit venue.
APNS_ALERTE_CRITIQUE=false
APNS_CRITICAL_ALERT=false

# ── Android : le compte de service Firebase ────────────────────────────────
#
@@ -96,6 +136,6 @@ APNS_ALERTE_CRITIQUE=false
# Android n'a pas d'équivalent de l'alerte critique d'Apple : franchir le mode
# « Ne pas déranger » est une autorisation que la personne accorde au canal de
# notification, appareil par appareil. Aucun réglage ici ne l'obtient.
FCM_PROJET=
FCM_COMPTE=
FCM_CLE=
FCM_PROJECT=
FCM_ACCOUNT=
FCM_KEY=
+3 −3
Changes for .github/workflows/verification.yml: 3 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -73,15 +73,15 @@ jobs:
        env:
          KASTELL_ENV: developpement
          KASTELL_PORT: 8080
          KASTELL_URL_PUBLIQUE: http://localhost:8080
          KASTELL_PUBLIC_URL: http://localhost:8080
          DATABASE_URL: postgres://kastell:verification@localhost:5432/kastell
          REDIS_URL: redis://localhost:6379
          S3_ENDPOINT: http://localhost:9000
          S3_REGION: eu-west-1
          S3_BUCKET: kastell
          S3_ACCES: kastell
          S3_ACCESS: kastell
          S3_SECRET: verification-minio
          SECRET_SESSION: verification-verification-verification-01
          SESSION_SECRET: verification-verification-verification-01
        run: pnpm --filter @kastell/api exec tsx src/verif.ts

  image:
+3 −0
Changes for .gitignore: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -5,6 +5,9 @@ dist/
# Les sauvegardes qu'écrit outils/installer.sh portent les secrets
# vivants de l'instance précédente : elles ne doivent pas être suivies.
.env.sauvegarde-*
# Configuration LiveKit écrite par l'installateur quand les ports du
# serveur média doivent être déplacés : propre à la machine.
docker/livekit.local.yaml
*.log
.DS_Store
coverage/
+4 −4
Changes for README.md: 4 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -430,7 +430,7 @@ Deux points valent d'être connus :
  démarrage il télécharge ses signatures pendant quelques minutes, durant
  lesquelles les dépôts restent possibles mais marqués « non analysée ».
- **Deux adresses pour le serveur média.** `LIVEKIT_URL` est celle que compose
  le navigateur, `LIVEKIT_URL_INTERNE` celle que compose le serveur — dans la
  le navigateur, `LIVEKIT_INTERNAL_URL` celle que compose le serveur — dans la
  composition, l'hôte est le nom du service, et « localhost » y désigne le
  conteneur applicatif lui-même.

@@ -530,7 +530,7 @@ passage obligé le jour où il tombe, ni un intermédiaire qui voit passer les
messages de crise de ses clients.

Les identifiants sont **chiffrés au repos** (AES-256-GCM, clé dérivée de
`SECRET_SESSION` avec un contexte distinct) et ne ressortent jamais : ni par
`SESSION_SECRET` avec un contexte distinct) et ne ressortent jamais : ni par
l'API, ni par la console root. Administrer une instance n'est pas lire les
secrets de ses clients. Le journal d'administration retient qu'un canal a été
branché, par qui, chez quel fournisseur — jamais la clé : **un journal en ajout
@@ -618,8 +618,8 @@ une phrase, et le contrôle vérifie qu'aucun nom d'organisation ni de personne
n'en sort.

```
KASTELL_RELAIS_POUSSEE=https://relais.exemple.org
KASTELL_RELAIS_CLE=…
KASTELL_PUSH_RELAY=https://relais.exemple.org
KASTELL_RELAY_KEY=…
```

Les serveurs d'Apple comme ceux de Google n'acceptent que des messages signés
+1 −1
Changes for apps/api/src/adaptateurs/media.ts: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -232,7 +232,7 @@ export function definirMedia(m: Media): void {
export function media(): Media {
  if (!courant) {
    throw new Error(
      "Aucun serveur média configuré. Renseignez LIVEKIT_URL, LIVEKIT_CLE et "
      "Aucun serveur média configuré. Renseignez LIVEKIT_URL, LIVEKIT_KEY et "
      + "LIVEKIT_SECRET, ou démarrez la composition sous le profil « complet ».",
    );
  }
Loading