# 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 | `/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-`, 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 `` 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 \ --name vm-rec-01 \ --obs-password ``` 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 '' ` -Name 'vm-rec-02' ` -ObsPassword '' ``` 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 ` (Linux) ou `-PackageToken ''` (Windows), avec un jeton en lecture sur les paquets. ### Vérifier ```bash sudo -u 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 `. --- ## 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.