Files
stream-control/DEPLOY.md
jeanotx32 20f8efa428
Some checks failed
release / build (push) Successful in 20s
release / verify-windows (push) Failing after 1m10s
Fix : docker statrtup crash 2
2026-08-11 18:04:28 +02:00

261 lines
10 KiB
Markdown

# Mise en production
Déploiement de Stream Control : l'interface en Docker Compose, les agents en une
commande, les artefacts construits et publiés par Gitea Actions.
```
┌──────────────────────────┐ ┌───────────────────────────────┐
│ Gitea │ │ Machine du plan de contrôle │
│ ├─ registre d'images ───┼───────►│ docker compose up -d │
│ └─ paquets génériques │ │ → http://control.lan:8080 │
│ │ agent.cjs │ └───────────────┬───────────────┘
└─────────┼────────────────┘ │ ws:// (réseau privé)
│ install-agent.sh/.ps1 ┌──────────┴──────────┐
└─────────────────────────────►│ VM Ubuntu/Windows │
│ agent + OBS │
└─────────────────────┘
```
L'interface est conteneurisée ; **les agents ne le sont pas**. Ils doivent voir
l'OBS local et la fenêtre du navigateur, ce qu'un conteneur ne permet pas.
## Prérequis
| Où | Quoi |
| --- | --- |
| Gitea | Paquets activés, un runner `ubuntu-latest` avec accès au socket Docker, un runner `windows-latest` |
| Plan de contrôle | Docker + Docker Compose, sur le réseau privé |
| VM d'enregistrement | OBS 28+ avec obs-websocket activé. Node.js est installé par le script si absent |
---
## Étape 1 — Préparer Gitea
### Secret
Dans **Paramètres du dépôt → Actions → Secrets**, ajoute :
| Nom | Valeur |
| --- | --- |
| `REGISTERYKEY` | Jeton d'accès du compte `jeanbon`, portées **repository** et **package** |
### Variable optionnelle
Le workflow déduit l'hôte du registre de l'URL de ton instance. Si elle est
publiée sous un autre nom que celui vu par les runners, ajoute la variable
`REGISTRY_HOST` (par ex. `gitea.exemple.com`, sans schéma).
### Vérifier les runners
Dans **Site Administration → Runners**, les labels doivent inclure
`ubuntu-latest` et `windows-latest`. S'ils diffèrent, adapte les deux champs
`runs-on:` de [.gitea/workflows/release.yml](.gitea/workflows/release.yml).
---
## Étape 2 — Premier build
Pousse sur `main`, ou lance le workflow à la main. Il produit :
| Artefact | Emplacement |
| --- | --- |
| Image du plan de contrôle | `<gitea>/jeanbon/stream-control-server:latest` |
| Bundle d'agent | paquet générique `stream-control-agent`, fichier `agent.cjs` |
| Scripts d'installation | même paquet, `install-agent.sh` et `install-agent.ps1` |
Chaque build publie une version immuable (`0.1.0-<sha>`, ou `1.2.3` sur une
étiquette `v1.2.3`). L'alias `latest` ne suit que `main` et les étiquettes.
Le pipeline refuse de publier un bundle qui ne démarre pas : il exécute
`agent.cjs --check` sur Linux **et** sur Windows, et valide la syntaxe de
`install-agent.ps1` sur le runner Windows.
---
## Étape 3 — Déployer l'interface
Sur la machine du plan de contrôle :
```bash
mkdir -p /opt/stream-control && cd /opt/stream-control
# Récupérer compose et modèle d'environnement depuis le dépôt
curl -fsSLO https://gitea.exemple.com/jeanbon/stream-control/raw/branch/main/docker-compose.yml
curl -fsSL https://gitea.exemple.com/jeanbon/stream-control/raw/branch/main/.env.production.example -o .env
chmod 600 .env
```
Génère les secrets et complète `.env` :
```bash
echo "SESSION_SECRET=$(openssl rand -hex 32)"
echo "ENROLLMENT_TOKEN=$(openssl rand -hex 24)"
```
À renseigner impérativement :
| Variable | Rôle |
| --- | --- |
| `IMAGE` | `gitea.exemple.com/jeanbon/stream-control-server:latest` |
| `ADMIN_PASSWORD` | Mot de passe du dashboard |
| `SESSION_SECRET` | Clé de signature des sessions |
| `ENROLLMENT_TOKEN` | Jeton présenté par les agents à leur première connexion |
| `BIND_ADDR` | Interface d'écoute — mets l'IP LAN/VPN si la machine a une patte publique |
Puis :
```bash
docker login gitea.exemple.com -u jeanbon # jeton avec la portée package
docker compose up -d
docker compose logs -f
```
Contrôle : `curl http://localhost:8080/healthz` doit répondre
`{"ok":true,"agents":0}`.
La base SQLite vit dans `./data`, monté depuis l'hôte — **c'est le seul état à
sauvegarder**.
La base tourne en mode WAL : les écritures récentes vivent dans
`stream-control.sqlite-wal` et pas encore dans le fichier principal. Copier le
seul `.sqlite` pendant que le serveur tourne donne une sauvegarde tronquée. Le
plus sûr est un arrêt de deux secondes :
```bash
docker compose stop server
tar czf ~/stream-control-$(date +%F).tar.gz data/
docker compose start server
```
À chaud, copie impérativement les trois fichiers ensemble (`.sqlite`,
`-wal`, `-shm`) — jamais le premier seul.
---
## Étape 4 — Déployer un agent
Une seule commande par VM. Le `<JETON_ENROLEMENT>` est l'`ENROLLMENT_TOKEN` du
serveur : l'agent s'enregistre tout seul, reçoit un jeton permanent et le
persiste.
### Ubuntu
```bash
curl -fsSL https://gitea.exemple.com/api/packages/jeanbon/generic/stream-control-agent/latest/install-agent.sh \
| sudo bash -s -- \
--registry https://gitea.exemple.com \
--server ws://control.lan:8080/ws/agent \
--token <JETON_ENROLEMENT> \
--name vm-rec-01 \
--obs-password <MDP_OBS>
```
Le script installe Node.js et `xdotool` si besoin, dépose le bundle dans
`/opt/stream-control-agent`, écrit un service systemd et lance un diagnostic.
L'agent tourne sous le compte qui a invoqué `sudo` — celui qui ouvre la session
graphique d'OBS. Force-le avec `--user` si ce n'est pas le bon.
### Windows
Dans un PowerShell **administrateur**, sur le compte qui lance OBS :
```powershell
& ([scriptblock]::Create((irm 'https://gitea.exemple.com/api/packages/jeanbon/generic/stream-control-agent/latest/install-agent.ps1'))) `
-Registry 'https://gitea.exemple.com' `
-Server 'ws://control.lan:8080/ws/agent' `
-Token '<JETON_ENROLEMENT>' `
-Name 'vm-rec-02' `
-ObsPassword '<MDP_OBS>'
```
L'agent est enregistré comme **tâche planifiée à l'ouverture de session**, pas
comme service : un service Windows tourne en session 0 et ne verrait ni OBS ni la
fenêtre du navigateur.
### Si le paquet est privé
Ajoute `--package-token <JETON>` (Linux) ou `-PackageToken '<JETON>'` (Windows),
avec un jeton en lecture sur les paquets.
### Vérifier
```bash
sudo -u <user> node /opt/stream-control-agent/agent.cjs --check # Linux
node C:\stream-control-agent\agent.cjs --check # Windows
```
Le diagnostic contrôle la configuration, la joignabilité du serveur et d'OBS, et
les prérequis du rappel plein écran (`xdotool`, `DISPLAY`, PowerShell).
---
## Mises à jour
**Interface** — le tag `latest` bouge à chaque build sur `main` :
```bash
cd /opt/stream-control && docker compose pull && docker compose up -d
```
Épingle une version précise dans `IMAGE` si tu préfères maîtriser le moment.
**Agents** — relance exactement la même commande d'installation. Le script
détecte l'installation existante, remplace le binaire et **conserve
`agent.config.json`** : l'identité de l'agent est préservée. Sans cela, l'agent
se ré-enrôlerait et apparaîtrait en double dans le dashboard.
Pour installer une version figée plutôt que `latest`, remplace `latest` par la
version voulue dans l'URL **et** passe `--version <VERSION>`.
---
## Sécurité
Ce déploiement est en **HTTP sur réseau privé**, conformément au choix retenu.
Ce que cela implique concrètement :
- Les jetons d'agent, le mot de passe du dashboard et les jetons de session
circulent **en clair**. Toute machine capable de sniffer le segment réseau peut
les récupérer et prendre la main sur les agents.
- Le port ne doit **jamais** être joignable depuis Internet. Restreins `BIND_ADDR`
à l'IP LAN ou VPN, et ferme le port au pare-feu :
```bash
ufw allow from 10.0.0.0/8 to any port 8080 proto tcp
```
- Le mot de passe obs-websocket est stocké en base et poussé aux agents ; il n'est
jamais renvoyé au navigateur (masqué en `********`).
- `agent.config.json` est en `600` et porte le jeton permanent de l'agent.
Passer en HTTPS plus tard ne demande qu'un reverse proxy devant le conteneur et
le remplacement de `ws://` par `wss://` dans les commandes d'installation —
[deploy/nginx.conf.example](deploy/nginx.conf.example) donne le bloc de conf.
La rotation des jetons se fait alors depuis le dashboard, agent par agent.
---
## Dépannage
| Symptôme | Piste |
| --- | --- |
| `docker compose up` : *manifest unknown* | `docker login` non fait, ou build jamais lancé |
| Le conteneur redémarre en boucle | `docker compose logs server` — le plus souvent `ADMIN_PASSWORD` ou `SESSION_SECRET` absent de `.env` |
| `unable to open database file` (`ERR_SQLITE_ERROR`, errcode 14) | Le répertoire `./data` de l'hôte n'appartient pas à l'uid du conteneur. L'entrypoint le corrige automatiquement ; si tu as forcé un `user:` dans le compose, il ne peut plus le faire — aligne alors la propriété à la main : `sudo chown -R 1000:1000 data` |
| Agent absent du dashboard | `--server` erroné, ou `ENROLLMENT_TOKEN` différent de celui du serveur. `journalctl -u stream-control-agent -f` |
| Agent en ligne, OBS déconnecté | obs-websocket désactivé ou mot de passe erroné. Le diagnostic teste le port |
| Rappel plein écran sans effet (Linux) | Session Wayland au lieu de X11, ou `DISPLAY` inaccessible au service. Le diagnostic signale les deux |
| Rappel plein écran sans effet (Windows) | La tâche ne tourne pas dans la session interactive : vérifie que le compte de `-RunAsUser` est bien celui ouvert sur la VM |
| Agent en double après réinstallation | `agent.config.json` avait été supprimé : l'agent s'est ré-enrôlé. Supprime le doublon dans le dashboard |
| Workflow : *unauthorized* au push | `REGISTERYKEY` sans la portée **package**, ou expiré |
### Journaux
```bash
docker compose logs -f server # plan de contrôle
journalctl -u stream-control-agent -f # agent Linux
Get-ScheduledTaskInfo -TaskName StreamControlAgent # agent Windows
```
Le dashboard affiche aussi le journal consolidé de tous les agents, persisté en
base — souvent le point de départ le plus rapide.