Aller au contenu

Exécuter Signal Lab comme serveur ​

signal-lab-server est Signal Lab sans fenêtre : le même moteur, servant la même interface à un navigateur. Placez-le sur une machine à côté du matériel — un PC en baie, une VM de contrôle de spectacle, une machine de laboratoire partagée — et ouvrez-le depuis Chrome, Firefox ou Edge n’importe où sur le réseau : chaque écran fonctionne comme dans l’application de bureau, et les exécutions, les rapports et les exports se téléchargent via le navigateur.

Les scripts et les pipelines utilisent le même serveur via son API HTTP, et signallab --server lui envoie des exécutions.

Quelques points diffèrent de l’application de bureau :

  • Un serveur est un moteur. Chaque page connectée à lui voit les mêmes tâches en cours, les mêmes bibliothèques de signaux et d’émulateurs et la même réserve de cookies de l’écran HTTP (Conserver les cookies). Les personnes qui partagent un serveur partagent cela.
  • Les secrets sont ceux du serveur, lus dans son environnement ou dans des fichiers ; ils ne peuvent pas être définis depuis le navigateur (voir Secrets).
  • Le pare-feu est celui de l’hôte. Le serveur ne le modifie jamais ; l’avertissement de pare-feu de l’application de bureau n’apparaît pas.
  • Il se met à jour avec son image, non via le programme de mise à jour de l’application (voir Mise à jour).

Sur un hôte Linux, en une commande ​

Sur une machine Linux ayant accès à Internet :

bash
curl -fsSL https://raw.githubusercontent.com/ProAnima/SignalLab/main/deploy/install.sh | sh

Le script :

  1. installe Docker s’il manque — en demandant d’abord, avec l’installateur de Docker lui-même (get.docker.com) ;
  2. écrit un compose.yaml dans /opt/signallab (~/signallab lorsque vous n’êtes pas root) ;
  3. récupère l’image et démarre le serveur avec la mise en réseau de l’hôte, pour qu’OSC, UDP, la diffusion, la multidiffusion et la découverte atteignent le vrai réseau ;
  4. attend que le serveur réponde à son contrôle de santé (jusqu’à 90 secondes) ;
  5. affiche les adresses à ouvrir, le jeton d’accès avec lequel se connecter, et les commandes pour mettre à jour, lire les journaux et le supprimer ;
  6. lorsque ufw ou firewalld est actif, propose d’ouvrir le port du serveur (voir Le pare-feu de l’hôte).

Passez les options après sh -s -- :

bash
curl -fsSL https://raw.githubusercontent.com/ProAnima/SignalLab/main/deploy/install.sh | sh -s -- --version 1.0.0 --port 8430
OptionCe qu’elle faitDéfaut
--version X.Y.ZLa version de l’image (latest ou X.Y.Z ; un v initial est retiré).latest
--port NLe port utilisé par les navigateurs.1430
--listen IP:PORTN’écouter que sur une adresse.0.0.0.0:<port>
--dir DIROù va le fichier compose./opt/<name> en root, ~/<name> sinon
--name NAMELe conteneur et son volume de données : lettres minuscules, chiffres, - et _. Un second serveur sur le même hôte a besoin de son propre nom.signallab
--image NAMEUne autre image ou un autre registre ; un NAME:TAG complet est utilisé tel quel.ghcr.io/proanima/signallab
--open-udp PORTSAvec un pare-feu actif, laisse aussi entrer l’UDP sur ces ports, pour les moniteurs et les attentes : 9000,9100:9110.
--no-firewallNe jamais modifier ufw ni firewalld.
--yes, -yRépondre oui : installer Docker, ouvrir le pare-feu, supprimer les données avec --purge.
--uninstallArrêter et supprimer le serveur ; les données restent.
--purgeAvec --uninstall : supprimer aussi les données et le jeton.
--help, -hAfficher les options.

Il a besoin du plugin Compose de Docker (le paquet docker-compose-plugin), que l’installateur de Docker apporte. L’image est construite pour x86_64 et arm64.

Relancez-le pour le mettre à jour : la même commande récupère l’image la plus récente (ou la --version que vous donnez) et redémarre le serveur ; les données et le jeton restent. Avec --name, redonnez le même nom.

Vos propres réglages — secrets d’expérience, SIGNALLAB_ALLOWED_HOSTS, SIGNALLAB_SECURE_COOKIE derrière HTTPS — vont dans compose.override.yaml à côté du fichier compose. Docker Compose le fusionne, et le script réécrit compose.yaml à chaque exécution mais ne touche jamais à l’override. Un compose.yaml qu’il n’a pas écrit est conservé sous compose.yaml.before-install.

yaml
# compose.override.yaml
services:
  signallab:
    environment:
      SIGNALLAB_ALLOWED_HOSTS: lab-pc.example.com,192.0.2.10
      SIGNALLAB_SECRET_API_TOKEN: ${API_TOKEN}

Pour le supprimer :

bash
curl -fsSL https://raw.githubusercontent.com/ProAnima/SignalLab/main/deploy/install.sh | sh -s -- --uninstall

Les données restent dans leur volume Docker, et une réinstallation les ramène avec le même jeton. --uninstall --purge supprime aussi les données et le jeton, après avoir demandé.

Le pare-feu de l’hôte ​

Avec la mise en réseau de l’hôte, le serveur écoute sur les ports de l’hôte lui-même, si bien que le pare-feu de l’hôte décide qui l’atteint. Lorsque ufw ou firewalld est actif, le script demande avant d’ouvrir le port TCP du serveur et les ports UDP de --open-udp, note ce qu’il a ouvert (.firewall à côté du fichier compose), et --uninstall referme exactement cela. Si vous refusez, les navigateurs des autres machines n’atteignent le serveur que lorsque le pare-feu les laisse, et les moniteurs et les attentes n’entendent les autres machines que sur les ports UDP qu’il ouvre.

Docker ​

L’image ​

ghcr.io/proanima/signallab, pour linux/amd64 et linux/arm64, publiée à chaque version :

ÉtiquetteCe que c’est
X.Y.ZCette version.
X.YLa dernière version stable de cette série.
latestLa dernière version stable.

Elle contient signal-lab-server, l’interface construite et la ligne de commande signallab, et démarre le serveur avec ces réglages :

VariableValeur dans l’image
SIGNALLAB_LISTEN0.0.0.0:1430
SIGNALLAB_DATA_DIR/data
SIGNALLAB_UI_DIR/usr/share/signal-lab/ui
SIGNALLAB_GENERATE_TOKENtrue

Elle tourne sous un utilisateur non privilégié (uid et gid 10001), n’écrit que dans /data (un volume), expose le port 1430 et contrôle sa propre santé toutes les 30 secondes.

Le démarrer ​

bash
docker run -d --name signallab --network host --restart unless-stopped \
  -v signallab-data:/data --read-only --cap-drop ALL --security-opt no-new-privileges \
  ghcr.io/proanima/signallab:1.0.0
docker logs signallab                    # on the first start: "Sign in with it:" and the token

Ouvrez ensuite http://<host>:1430 et connectez-vous avec le jeton. Pour revoir le jeton plus tard :

bash
docker exec signallab cat /data/token

--read-only, --cap-drop ALL et no-new-privileges sont facultatifs et ne coûtent rien : le serveur n’a besoin d’aucun privilège et n’écrit que dans /data.

Docker Compose ​

Le deploy/compose.yaml du dépôt est identique à un fichier Compose :

yaml
name: signallab

services:
  signallab:
    image: ${SIGNALLAB_IMAGE:-ghcr.io/proanima/signallab:latest}
    container_name: signallab
    network_mode: host
    environment:
      SIGNALLAB_GENERATE_TOKEN: "true"
    volumes:
      - signallab-data:/data
    read_only: true
    cap_drop: [ALL]
    security_opt: ["no-new-privileges:true"]
    restart: unless-stopped
    stop_grace_period: 15s

volumes:
  signallab-data:
bash
docker compose up -d
docker exec signallab cat /data/token

Définissez SIGNALLAB_IMAGE=ghcr.io/proanima/signallab:X.Y.Z pour épingler une version.

Mise en réseau ​

Mise en réseau DockerCe qui fonctionneCe qui ne fonctionne pas
--network host (network_mode: host), sur un hôte LinuxTout : OSC, UDP, TCP, HTTP, WebSocket et MQTT vers le réseau local, les ports d’écoute, la diffusion, la multidiffusion, la découverte.—
Bridge (par défaut), avec des ports publiésL’unicast vers les hôtes que le conteneur atteint ; les écouteurs sur les ports publiés (-p 1430:1430 -p 9000:9000/udp).La diffusion et la multidiffusion ; les réponses vers des ports non publiés.
Docker Desktop sur Windows ou macOSL’unicast et les écouteurs publiés.La mise en réseau de l’hôte vers le réseau physique. Sous Windows, utilisez l’application de bureau.

Le volume de données ​

/data contient tout ce que le serveur conserve : l’expérience, les bibliothèques de signaux et d’émulateurs, les rapports d’exécution, les exports et le jeton. Un volume nommé, comme ci-dessus, appartient au départ à l’utilisateur de l’image. Un dossier de l’hôte monté à cet endroit doit être accessible en écriture à l’uid 10001 :

bash
sudo mkdir -p /srv/signallab && sudo chown 10001:10001 /srv/signallab
docker run -d --name signallab --network host -v /srv/signallab:/data ghcr.io/proanima/signallab:1.0.0

Sans Docker ​

Le serveur est publié sous forme d’image. Pour exécuter signal-lab-server directement, construisez-le depuis les sources (voir Construction) :

bash
npm install
npm run build                            # the interface, into dist/
cargo run --release -p signal-lab-server

Il sert dist/ sur http://127.0.0.1:1430, pour cette machine uniquement, sans jeton nécessaire. Ajoutez --listen et un jeton pour l’ouvrir au réseau.

Options ​

Chaque option a une variable d’environnement, pour les conteneurs. Les options l’emportent sur les variables.

OptionVariableDéfautCe qu’elle fait
--listen IP:PORTSIGNALLAB_LISTEN127.0.0.1:1430Où écouter. Toute adresse autre que la boucle locale a besoin d’un jeton.
--token-file PATHSIGNALLAB_TOKEN_FILEUn fichier contenant le jeton d’accès (un secret Docker, par exemple).
--token TOKENSIGNALLAB_TOKENLe jeton d’accès lui-même. Préférez le fichier : les arguments sont visibles par les autres utilisateurs de la machine.
--generate-tokenSIGNALLAB_GENERATE_TOKENdésactivéSans jeton fourni, sur une adresse au-delà de la boucle locale : utiliser le jeton conservé dans <data folder>/token, en le créant au premier démarrage.
--data-dir PATHSIGNALLAB_DATA_DIRDocuments/SignalLab dans le dossier personnel de l’utilisateurLe dossier de données.
--secrets-dir PATHSIGNALLAB_SECRETS_DIR/run/secrets/signallabUn dossier de secrets en lecture seule, un fichier par nom.
--ui-dir PATHSIGNALLAB_UI_DIRui à côté du programme, sinon ./distL’interface construite. Sans elle, seule l’API est servie.
--allowed-host NAMESIGNALLAB_ALLOWED_HOSTSLes noms d’hôte par lesquels le serveur peut être atteint, séparés par des virgules. Eux et les noms de boucle locale sont toujours acceptés, avec ou sans jeton ; sans aucun nom donné, un serveur avec un jeton répond à tout nom et un serveur sans jeton uniquement aux noms de boucle locale (voir Noms d’hôte).
--secure-cookieSIGNALLAB_SECURE_COOKIEdésactivéN’envoyer le cookie de session que via HTTPS. À activer derrière un proxy HTTPS.
--log FILTERSIGNALLAB_LOGinfoQuoi journaliser : error, warn, info, debug, ou par module (signal_lab_server=debug).
--log-format text|jsonSIGNALLAB_LOG_FORMATtextLes lignes de journal en texte, ou un objet JSON par ligne.

Les interrupteurs prennent true ou false depuis leur variable : SIGNALLAB_GENERATE_TOKEN=true.

CommandeCe qu’elle fait
signal-lab-server tokenAffiche un nouveau jeton aléatoire : 64 caractères hexadécimaux.
signal-lab-server healthcheckSort avec 0 lorsqu’un serveur répond sur --listen (le contrôle de santé de l’image).
signal-lab-server --versionAffiche la version.
signal-lab-server --helpAffiche toutes les options.

Codes de sortie : 0 lorsqu’il est arrêté par Ctrl+C ou SIGTERM ; 2 lorsque les réglages sont refusés (pas de jeton sur une adresse joignable, un jeton trop court, un jeton fourni deux fois, un fichier de jeton illisible, --generate-token sans dossier de données) ; 1 lorsqu’il ne peut pas écouter sur l’adresse, ou que le dossier de données ne peut pas être écrit. La raison est affichée sur la sortie d’erreur.

Le jeton d’accès ​

Sans jeton, le serveur n’écoute que sur la boucle locale et ne sert que cette machine. Sur toute autre adresse, il en a besoin et refuse de démarrer sans. Trois façons de le fournir :

CommentQuand l’utiliser
--generate-token (actif dans l’image)Rien à configurer : au premier démarrage, le serveur crée un jeton, l’enregistre dans <data folder>/token — lisible par son seul utilisateur — et l’affiche une fois dans le journal. Les démarrages suivants le réutilisent, si bien que les navigateurs connectés et les scripts continuent de fonctionner à travers les redémarrages et les mises à jour. Il a besoin d’un dossier de données (--data-dir).
--token-file PATHUn jeton à vous dans un fichier, comme un secret Docker. Un saut de ligne à la fin n’en fait pas partie.
SIGNALLAB_TOKENLe jeton dans l’environnement.

Un jeton a au moins 24 caractères et aucun espace ni saut de ligne ; donnez-le d’une seule façon. signal-lab-server token en fabrique un bon :

bash
docker run --rm ghcr.io/proanima/signallab:1.0.0 token > signallab_token.txt

et Compose le transmet comme secret (le fichier doit être lisible par l’uid 10001) :

yaml
services:
  signallab:
    environment:
      SIGNALLAB_TOKEN_FILE: /run/secrets/signallab_token
    secrets:
      - signallab_token

secrets:
  signallab_token:
    file: ./signallab_token.txt

Un jeton donné explicitement l’emporte sur --generate-token, et rien n’est créé alors. Pour changer un jeton créé, arrêtez le serveur, supprimez <data folder>/token et redémarrez-le : il en crée et en affiche un nouveau. Un fichier de jeton endommagé est signalé, jamais remplacé.

Se connecter ​

Ouvrez http://<host>:1430. Un serveur avec un jeton envoie d’abord un navigateur vers sa page de connexion, dans la langue du navigateur ; collez le jeton une fois, et le navigateur reste connecté 7 jours. Se déconnecter dans l’interface met fin à la session. Les sessions sont conservées dans la mémoire du serveur : un redémarrage déconnecte tout le monde.

Sur la boucle locale sans jeton, il n’y a pas de connexion.

Les scripts envoient le jeton avec chaque requête, sous la forme Authorization: Bearer <token> :

bash
curl -fsS http://192.0.2.10:1430/api/invoke/app_info \
  -H "Authorization: Bearer $(cat signallab_token.txt)" \
  -H "Content-Type: application/json" -d 'null'

Voir L’API HTTP, et Sécurité du serveur pour les règles qui sous-tendent tout cela.

Le dossier de données ​

Le serveur conserve ses fichiers dans le dossier de données : l’expérience, les bibliothèques de signaux et d’émulateurs, les rapports d’exécution, les exports et un jeton créé. C’est --data-dir (SIGNALLAB_DATA_DIR), /data dans l’image, et sinon Documents/SignalLab dans le dossier personnel de l’utilisateur sous lequel il tourne. Un dossier de données donné avec --data-dir est créé s’il manque et vérifié au démarrage : si le serveur ne peut pas y écrire, il s’arrête et nomme le dossier. Voir Fichiers et dossiers.

Les téléchargements (rapports, exports) ne viennent que de l’intérieur de ce dossier.

Secrets ​

Les expériences lisent les secrets sous la forme {{secret.NAME}}. Sur un serveur, ils sont en lecture seule, depuis :

  1. la variable d’environnement SIGNALLAB_SECRET_NAME, sinon
  2. le fichier NAME dans le dossier des secrets (--secrets-dir, par défaut /run/secrets/signallab — la disposition des secrets Docker).

Un saut de ligne final dans un fichier ne fait pas partie de la valeur ; une valeur vide compte comme non définie ; une valeur fait au plus 16 Kio. Les noms sont des lettres, des chiffres et _, ne commençant pas par un chiffre. L’interface indique quels secrets sont définis sur le serveur, mais en définir un depuis le navigateur est refusé — les valeurs ne vont jamais dans un endroit plus faible, et n’en ressortent jamais (voir Secrets).

Avec Compose, un fichier de secret par nom :

yaml
services:
  signallab:
    secrets:
      - source: api_token
        target: /run/secrets/signallab/API_TOKEN

secrets:
  api_token:
    file: ./api_token.txt

Le fichier doit être lisible par l’uid 10001.

Derrière un proxy HTTPS ​

Le serveur parle HTTP en clair. Pour HTTPS, placez un proxy inverse devant lui (Caddy, nginx, Traefik) qui :

  • transmet l’en-tête Host sans le modifier ;
  • transmet les mises à niveau WebSocket (l’interface en garde une ouverte, vers /api/events) ;

et démarrez le serveur avec --secure-cookie, pour que le cookie de session ne circule que via HTTPS, et avec --allowed-host réglé sur le nom que les gens utilisent.

Mise à jour ​

Un serveur se met à jour avec son image : le programme de mise à jour de l’application de bureau n’a rien à voir avec cela.

  • Installé avec le script : relancez la même commande.
  • Avec Compose : docker compose pull && docker compose up -d.
  • Avec docker run : récupérez la nouvelle image, puis supprimez le conteneur et redémarrez-le avec le même volume.

Les données et le jeton sont dans le volume, donc ils restent.

Contrôle de santé ​

GET /api/health répond à tout le monde, sans jeton :

bash
curl -s http://127.0.0.1:1430/api/health
# {"auth":true,"status":"ok","version":"1.0.0"}

auth indique si le serveur demande un jeton. La commande signal-lab-server healthcheck demande la même chose sur cette machine et sort avec 0 lorsqu’il répond ; l’image l’exécute toutes les 30 secondes (délai de 5 secondes, 3 essais), si bien que docker ps montre le conteneur comme sain.

Journaux ​

Le serveur journalise sur stdout : docker logs -f signallab. Au niveau par défaut (info), il indique où il écoute et s’il a besoin d’un jeton, ses dossiers de données et d’interface, chaque tâche démarrée — moniteurs, générateurs, tempêtes, scans, exécutions, émulateurs — avec l’adresse du client, chaque connexion, chaque connexion avec un mauvais jeton (comme avertissement), et quand une page prend du retard sur les événements. Le niveau --log debug ajoute chaque commande. Les lignes sont colorées seulement sur un terminal (jamais quand NO_COLOR est défini) ; --log-format json écrit un objet JSON par ligne pour un collecteur de journaux.

Arrêt ​

Ctrl+C, docker stop ou SIGTERM ferme la connexion de chaque page, arrête chaque tâche — une exécution en cours se termine comme arrêtée — et sort avec 0. Les fichiers compose lui accordent 15 secondes pour cela.