Files
stream-control/DEPLOY.md
jeanotx32 b85a535dd8
Some checks failed
release / build (push) Successful in 38s
release / verify-windows (push) Failing after 1m3s
Fix : obs bug 1
2026-08-11 19:04:32 +02:00

11 KiB

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://<IP_DU_CONTROLE>: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

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. git.jeanbonapp.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.


É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 :

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://git.jeanbonapp.com/jeanbon/stream-control/raw/branch/main/docker-compose.yml
curl -fsSL  https://git.jeanbonapp.com/jeanbon/stream-control/raw/branch/main/.env.production.example -o .env
chmod 600 .env

Génère les secrets et complète .env :

echo "SESSION_SECRET=$(openssl rand -hex 32)"
echo "ENROLLMENT_TOKEN=$(openssl rand -hex 24)"

À renseigner impérativement :

Variable Rôle
IMAGE git.jeanbonapp.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 :

docker login git.jeanbonapp.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 :

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

curl -fsSL https://git.jeanbonapp.com/api/packages/jeanbon/generic/stream-control-agent/latest/install-agent.sh \
  | sudo bash -s -- \
      --registry https://git.jeanbonapp.com \
      --server   ws://<IP_DU_CONTROLE>: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 :

& ([scriptblock]::Create((irm 'https://git.jeanbonapp.com/api/packages/jeanbon/generic/stream-control-agent/latest/install-agent.ps1'))) `
    -Registry 'https://git.jeanbonapp.com' `
    -Server   'ws://<IP_DU_CONTROLE>: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

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 :

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 :
    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 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é — ECONNREFUSED Rien n'écoute : OBS n'est pas lancé, ou Outils → Paramètres du serveur WebSocket n'est pas activé. Le mot de passe n'intervient pas à ce stade
Agent en ligne, OBS déconnecté — erreur d'authentification Le mot de passe du dashboard et celui d'OBS diffèrent. Laisse-le vide uniquement si l'authentification est décochée dans OBS
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

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.