- Add cleanupOrphanInstanceDirs() to remove leftover instance directories after failed deletes (common on Windows when compose.log is locked) - Log RemoveAll errors in dockerComposeRm for better visibility - Bump version to 0.3.10 and rebuild binaries
47 KiB
Suivi – VPN on-demand studioE5 (client A)
✅ Ce qui fonctionne
-
Agent standalone (mode console / systray)
- Exécutable :
agent/studioE5-agent - Config lu depuis
<data-dir>/studioE5-config.json - Mode console :
-no-tray
- Exécutable :
-
VPN on-demand dans l'agent
- L’agent ne démarre plus Tailscale au boot.
- Le VPN se lance automatiquement à la création/démarrage d’une instance, ou sur commande serveur.
- Implémentation basée sur les binaires
tailscaled+tailscale up(pastsnet, cartsnetne loguait pas automatiquement avec une authkey sur un state vierge).
-
Commandes serveur → agent
- Endpoint de test :
POST /api/internal/send-to-node - Actions supportées :
start_vpn,stop_vpn,start,stop,reset,delete.
- Endpoint de test :
-
Resolver/serveur dans le tailnet studioe5
- Service
resolver-vpn(conteneur Tailscale) partage le netns duresolver. - Le resolver peut joindre les IPs Tailscale des nodes (
ping 100.64.0.xOK).
- Service
-
Instance WordPress démarrée avec succès
- Le resolver a renvoyé une 302 WordPress via
http://resolver:2020/.
- Le resolver a renvoyé une 302 WordPress via
-
Activation zéro-config de l’agent (modèle commercialisable)
- L’agent démarre sans
headscale_urlniheadscale_auth_key. - L’utilisateur entre seulement un code d’activation.
- Le serveur envoie la config Headscale, l’agent la sauvegarde et démarre le VPN automatiquement.
- L’agent démarre sans
✅ Blocage levé
Rate limit Let’s Encrypt pour edudeploy.com est levé.
Le 2026-06-23 vers 09:35 UTC, Caddy a pu obtenir un certificat Let’s Encrypt pour test-wp-001.studioe5.edudeploy.com :
tls.obtain: certificate obtained successfully identifier=test-wp-001.studioe5.edudeploy.com issuer=acme-v02.api.letsencrypt.org-directory
Le flux complet HTTPS public est désormais validé :
curl -sS -I -L https://test-wp-001.studioe5.edudeploy.com/
# => HTTP/2 302 -> HTTP/2 200 (WordPress install.php)
Le DNS wildcard *.studioe5.edudeploy.com est en place. Caddy utilise toujours tls { on_demand } et émet un certificat par sous-domaine d’instance.
🎯 Validation du flux HTTPS public
Le 2026-06-23 09:39 UTC, le flux complet a été validé :
Client (HTTPS) → Caddy (:443) → resolver (:2020) → Tailnet (100.64.0.8) → agent → WordPress (:8001)
Résultat :
$ curl -sS -I -L https://test-wp-001.studioe5.edudeploy.com/
HTTP/2 302
location: https://test-wp-001.studioe5.edudeploy.com/wp-admin/install.php
...
HTTP/2 200
- Certificat Let’s Encrypt obtenu automatiquement par Caddy (
tls { on_demand }). - Le resolver réécrit les en-têtes
Locationet le contenu HTML pour passer dehttp://àhttps://.
📝 Template WordPress prêt à l’emploi
Un nouveau template wordpress-ready-wordpress-latest a été créé et validé le 2026-06-26. Il fournit un WordPress déjà initialisé en français, prêt à l’usage en classe ou en examen.
Contenu du template
| Élément | Valeur / État |
|---|---|
| Langue | Français (fr_FR) |
| Titre du site | Mon site wordpress |
| Compte administrateur | admin / admin |
| Thème actif | Astra |
| Spectra | installé et actif |
| Yoast SEO | installé mais inactif |
| Mises à jour automatiques | désactivées (core, plugins, thèmes) |
| DNS conteneur | 8.8.8.8 + 1.1.1.1 pour permettre l’accès à api.wordpress.org |
Architecture technique
- Le modèle
Templatede Prisma dispose d’un nouveau champinitScript(TEXT?). - Le seed génère le template avec :
- une section
dnsdans le serviceappdudocker-compose.yml; - un service sidecar
wp-init(imagewordpress:cli) exécutant le script d’initialisation.
- une section
- L’agent écrit le script
wp-init.shdans le dossier de l’instance au démarrage. - Le conteneur
wp-initattend que WordPress soit prêt, puis exécute WP-CLI en tant quewww-data. - Un fichier flag
.studioe5-init-doneévite de réinitialiser l’instance à chaque redémarrage.
Fichiers modifiés / ajoutés
server/prisma/schema.prisma– champinitScriptsurTemplate.server/prisma/seed.ts– génération du templatewordpress-ready-wordpress-latest.server/templates/wordpress-ready/wp-init.sh– script d’initialisation WP-CLI.server/app/api/instances/route.ts– envoi deinitScriptà l’agent avec remplacement des placeholders.agent/websocket.go– réception et transmission deInitScript.agent/docker.go– écriture du script dans le dossier instance (writeInitScript).
Validation
Instance de test créée via l’API (cmqv03a6v0001vg8zrpe8zqfy) :
$ curl -sS -I -L https://cmqv03a6v0001vg8zrpe8zqfy.studioe5.edudeploy.com/
HTTP/2 200
- Page d’accueil en français, titre « Mon site wordpress ».
- Connexion admin
/wp-login.phpavec admin / admin fonctionnelle. - Tableau de bord en français.
- Plugins : Spectra actif, Yoast SEO inactif.
wp-config.phpcontient les constantes de désactivation des mises à jour automatiques.
Les instances de test ont été nettoyées après validation.
Template versionné WordPress 7.0.0
Un second template wordpress-ready-wordpress-7.0.0-php8.3 a été ajouté pour figer WordPress sur la version 7.0.0 (PHP 8.3) au lieu de latest. Cela garantit que tous les postes déploient exactement la même version, sans dépendre du cache local de wordpress:latest.
| Template | Image Docker |
|---|---|
wordpress-ready-wordpress-latest |
wordpress:latest |
wordpress-ready-wordpress-7.0.0-php8.3 |
wordpress:7.0.0-php8.3 |
📁 Fichiers modifiés (non exhaustif)
agent/tailscale.go– lancementtailscaled+tailscale up, gestion start/stop/status.agent/websocket.go– handlersstart_vpn/stop_vpn,ensureTailscale().agent/docker.go– remplacement des placeholders{PORT}et{INSTANCE_ID}dans les compose.docker-compose.yml– ajout du sidecarresolver-vpn, suppression descap_add/ip routeobsolètes surserver/resolver.Caddyfile– configuration on-demand TLS pour les instances..env– clé pré-auth Headscale mise à jour (clé réutilisable).
🧪 Tests / environnement de test actuel
Agent de test lancé en arrière-plan :
- data-dir :
/tmp/studioe5-test-clienta - node-id :
vps-8fc665eb - tailnet IP actuelle :
100.64.0.8 - PID : voir
/tmp/studioe5-test-clienta/agent.pid(relancé le 2026-06-26 11:53 UTC avec l’agent v0.3.5 corrigé)
Instance de test créée :
- ID :
test-wp-001 - Node :
vps-8fc665eb - Port :
8001 - Template :
wordpress-wordpress-latest - État : WordPress répond sur
http://127.0.0.1:8001et en HTTPS public surhttps://test-wp-001.studioe5.edudeploy.com/.
🪟 Fix agent Windows v0.3.1
Problème rencontré sur le PC de test (OMEGA-GAMER-dc166b1a) :
- Le nœud apparaissait
onlinedans le dashboard mais sans IP Tailscale. tailscale.exe ip -4retournait une erreur de connexion au socket local.
Cause racine :
- L’agent lançait
tailscaledavec--socket=<fichier>.sock, mais Tailscale sur Windows utilise des named pipes (\\.\pipe\...), pas des sockets Unix. - De plus, les commandes
podman/docker/tailscaleouvraient une fenêtre console à chaque exécution.
Corrections apportées (agent/tailscale.go, agent/docker.go, agent/instance.go, agent/systray.go, agent/ui.go, agent/main.go) :
- Sur Windows, utilisation de la named pipe
\\.\pipe\studioe5-tailscaled. - Application de
hideWindowà tous les processus enfants (Tailscale, Podman, Docker, ouverture navigateur, redémarrage agent). - Redirection des logs agent vers
<data-dir>/agent.loget des logstailscaledvers<data-dir>/tailscale/tailscaled.log. - Suppression de
--operator=rootsur Windows (non pertinent). - Ajout de
--unattendedautailscale upsur Windows pour que le daemon reste connecté après la déconnexion du client CLI. - Correction du chemin
dataDirpassé àstartTailscale(évitait un double dossiertailscale/tailscale).
Validation manuelle sur Windows :
.\tailscaled.exe --state="C:\...\data\tailscale.state" --socket="\\.\pipe\studioe5-tailscaled" --tun=userspace-networking
.\tailscale.exe --socket="\\.\pipe\studioe5-tailscaled" status # => Logged out (NeedsLogin)
🪟 Agent v0.3.5 – forwarding entrant Windows + UI locale + cycle de vie
Problème
Sur Windows, Tailscale en userspace-networking ne forwarde pas automatiquement les connexions entrantes du Tailnet vers localhost. Résultat : les URLs publiques retournaient une erreur 502/timeout, bien que l’agent soit online.
Logs caractéristiques :
client -> backend close connection: close tcp 100.64.0.12:8080->100.64.0.11:xxxxx: endpoint not connected
Solution : tailscale serve automatique
L’agent configure automatiquement un proxy TCP pour chaque instance démarrée :
tailscale serve --bg --tcp=<port> tcp://localhost:<port>
| Action agent | Commande Tailscale |
|---|---|
| Démarrage d’instance | serve --bg --tcp=<port> tcp://localhost:<port> |
| Arrêt d’instance | serve --bg --tcp=<port> off |
| Suppression d’instance | serve --bg --tcp=<port> off |
| Redémarrage de l’agent | reconfiguration pour les instances déjà running |
Fichiers modifiés : agent/tailscale.go, agent/websocket.go, agent/main.go, agent/ui.go.
UI locale modernisée
- Tableau de bord avec indicateurs de service.
- Liste des applications avec badges de statut.
- Boutons d’action par instance : Démarrer, Arrêter, Redémarrer, Supprimer.
- Panneau de logs et diagnostic intégré.
- Panneau de configuration (URL serveur, Headscale, node ID).
Cycle de vie des instances
- Arrêter →
docker compose stop(volumes conservés). - Démarrer →
docker compose start(ouup -dla première fois). - Redémarrer →
docker compose down -v+ recréation (données remises à zéro). - Supprimer →
docker compose down -v+ suppression des fichiers. - À la fermeture de l’agent, les instances en cours sont arrêtées proprement (
stop) et le serveur est notifié (instance_stopped).
Démarrage du VPN après activation
L’agent redémarre tailscaled automatiquement au lancement, même si la clé pré-auth a déjà été utilisée. Il se base sur l’état persistant tailscaled.state (tailscale up sans --authkey).
Téléchargement
- Windows (archive) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.5-windows.zip - Windows (exe) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.5.exe - Linux :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.5
🪟 Agent v0.3.6 – recover() dans les goroutines de démarrage d’instance
Problème
Lors de la création d’une instance depuis le dashboard vers certains agents (notamment Windows), l’agent s’arrêtait brutalement. Le recover() présent dans handleMessage ne capturait pas le panic car celui-ci survenait dans les goroutines lancées par go handleStartInstance(...).
Corrections apportées
- Ajout d’un
defer recover()danshandleStartInstance; en cas de panic, l’instance passe en statuterroret un messageinstance_errorest envoyé au serveur. - Ajout d’un
defer recover()dans toutes les goroutines critiques du WebSocket :start_vpnstop_vpnstartresetstartTailscaleAndReport- cleanup au shutdown
- Ajout de logs de traçage au début de
handleStartInstance(instance,type,port,dataDir,initScriptLen).
Téléchargement
- Windows (archive) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.6-windows.zip - Windows (exe) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.6.exe - Linux :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.6
Redeploiement
- Agent rebuildé en v0.3.6 pour Windows et Linux.
- Binaires versionnés copiés dans
server/public/. - Page
/dashboard/downloadmise à jour vers la v0.3.6. - Serveur rebuildé et redémarré.
🪟 Agent v0.3.7 – recover() dans les notifications UI
Problème
L’agent continuait de s’arrêter brutalement lors de la création d’une instance depuis le dashboard. Le crash survenait juste après les logs Start instance ... et notifyUI: broadcasting to ..., sans laisser de trace de panic. Cela pointait vers une panique dans les goroutines de notification UI ou dans l’écriture des logs vers les clients UI locaux.
Corrections apportées
- Ajout d’un
defer recover()dansnotifyUIpour chaque goroutine de notification. - Ajout d’un
defer recover()danssendUILog(logs diffusés aux clients UI). - Ajout d’un
defer recover()dansbroadcastUI(messages diffusés aux clients UI).
Téléchargement
- Windows (archive) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.7-windows.zip - Windows (exe) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.7.exe - Linux :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.7
🪟 Agent v0.3.8 – DNS automatique pour Podman machine (Windows/macOS)
Problème
Après correction du crash, l’agent Windows avec Podman échouait au docker compose up avec :
lookup registry-1.docker.io: Temporary failure in name resolution
La VM Podman machine n’avait pas de DNS fonctionnel, ce qui empêchait le téléchargement des images Docker. Le DNS des conteneurs (dns: 8.8.8.8 dans le compose) résout le problème à l’intérieur des conteneurs, mais pas pour le pull d’images par Podman machine.
Solution
L’agent configure automatiquement le DNS des machines Podman en cours d’exécution au démarrage :
- Détection de Podman sur Windows/macOS.
- Liste des machines Podman (
podman machine list --format json). - Pour chaque machine
running, exécution de :podman machine ssh <name> sudo sh -c 'echo nameserver 8.8.8.8 > /etc/resolv.conf && echo nameserver 1.1.1.1 >> /etc/resolv.conf'
Fichier ajouté : agent/podman.go. Appel depuis agent/main.go au démarrage.
Téléchargement
- Windows (archive) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.8-windows.zip - Windows (exe) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.8.exe - Linux :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.8
🐛 Fix synchronisation agent / dashboard
Problème
Le statut affiché dans le dashboard pouvait diverger de l’état réel de l’agent :
- Après un Arrêter lancé depuis le dashboard, l’instance restait affichée comme elle l’était avant, ou disparaissait avec perte des données.
- Après une Suppression, l’instance n’était pas retirée de la liste.
Causes racines
- Action
stopdu dashboard envoyée commedeleteà l’agent (server/app/api/instances/route.ts).
L’agent exécutait alorsdocker compose down -v+ suppression des fichiers, c’est-à-dire une suppression réelle, tout en marquant l’instancestoppeden base. - L’agent ne confirmait pas les actions serveur (
agent/websocket.go).
Les handlersstopetdeletene renvoyaient jamais les messagesinstance_stopped/instance_deletedau serveur ; seule l’UI locale le faisait. - Le handler
stopde l’agent utilisaitdockerComposeDownau lieu dedockerComposeStop, ne respectant pas le cycle de vie documenté (arrêt = conteneurs et volumes conservés).
Corrections apportées
| Fichier | Changement |
|---|---|
server/app/api/instances/route.ts |
L’action dashboard stop envoie désormais action: "stop" à l’agent (et non plus "delete"). |
agent/websocket.go |
Le cas stop utilise dockerComposeStop, puis envoie instance_stopped au serveur. Le cas delete envoie instance_deleted au serveur. |
server/lib/websocket.ts |
Utilisation de updateMany/deleteMany pour ignorer silencieusement les messages d’instances déjà absentes/supprimées (évite les erreurs Prisma en double suppression). |
Résultat
Le dashboard reflète désormais l’état réel après une action serveur-initiée, dès le rechargement de la page. Le cycle de vie respecte la sémantique attendue :
- Arrêter :
docker compose stop→ statutstopped. - Démarrer :
docker compose up -d→ statutrunning. - Redémarrer :
docker compose down -v+ recréation. - Supprimer :
docker compose down -v+ suppression fichiers.
Redeploiement effectué le 2026-06-26
- Agent rebuildé en v0.3.5 (
agent/studioE5-agent,.exe,.zipetserver/public/mis à jour). - Serveur rebuildé et redémarré (
docker compose up -d --build server) pour intégrer les corrections TypeScript. - Page
/dashboard/downloadmise à jour : passage à la version 0.3.5 et ajout des liens Windows (.exe, .zip) et Linux. - Corrections défensives agent après signalement d’arrêt brutal lors d’actions dashboard :
sendMessageexécuté de manière asynchrone (go) dans les handlersstop,delete,stop_vpnet cleanup, pour ne pas bloquer la boucle de lecture WebSocket.- Ajout d’un
recoverdanshandleMessagepour capturer d’éventuels panics sans tuer l’agent. - Correction du cleanup
main.go: modification deinst[id].Status(et non de la copie localeinfo).
- Agent de test Linux relancé (PID dans
/tmp/studioe5-test-clienta/agent.pid). - Agents clients : il faut redémarrer l’agent sur chaque poste, ou télécharger à nouveau le binaire v0.3.5 depuis le dashboard pour Windows.
🛠️ Commandes utiles pour reprendre
Voir l’agent de test
pgrep -a studioe5-agent
Relancer l’agent de test (si besoin)
mkdir -p /tmp/studioe5-test-clienta
cat > /tmp/studioe5-test-clienta/studioE5-config.json <<EOF
{
"server": "wss://studioe5.edudeploy.com/api/websocket",
"headscale_url": "https://headscale.studioe5.edudeploy.com",
"headscale_auth_key": "$(grep HEADSCALE_AUTH_KEY /opt/studioe5-client-a/.env | cut -d= -f2)",
"node_id": "vps-8fc665eb",
"data_dir": "/tmp/studioe5-test-clienta"
}
EOF
cd /opt/studioe5-client-a/agent
./studioE5-agent -no-tray -data-dir /tmp/studioe5-test-clienta
Démarrer le VPN manuellement
curl -sS -X POST https://studioe5.edudeploy.com/api/internal/send-to-node \
-H "Content-Type: application/json" \
-d '{"nodeId":"vps-8fc665eb","message":{"action":"start_vpn"}}'
Voir les nodes Headscale
cd /opt/studioe5-client-a
docker compose exec -T headscale headscale nodes list studioe5
Tester le resolver (depuis Caddy)
cd /opt/studioe5-client-a
docker exec studioe5-caddy curl -sS -I -H "Host: test-wp-001.studioe5.edudeploy.com" http://resolver:2020/
Tester en HTTPS public (dès que la limite sera levée)
curl -sS -I -L https://test-wp-001.studioe5.edudeploy.com/
🌐 Flux complet testé via l’API web
Test réalisé le 2026-06-23 en utilisant le compte superadmin :
- Authentification NextAuth sur
/api/auth/callback/credentials. - Création d’instance via
POST /api/instances:→ Instance créée :{ "nodeId": "vps-8fc665eb", "templateId": "wordpress-wordpress-latest", "port": 8002 }cmqqgrur20001lw67t2bdgzkg. - Le serveur a automatiquement envoyé l’action
startau node via WebSocket. - L’agent a démarré le VPN (si besoin), écrit le compose et a lancé les conteneurs WordPress.
- Caddy a obtenu un certificat Let’s Encrypt pour
cmqqgrur20001lw67t2bdgzkg.studioe5.edudeploy.com. - Validation HTTPS :
curl -sS -I -L https://cmqqgrur20001lw67t2bdgzkg.studioe5.edudeploy.com/ # => HTTP/2 302 -> HTTP/2 200 (WordPress install.php)
Le flux UI → API → WebSocket → agent → Docker → VPN → Caddy → HTTPS public est fonctionnel.
💻 Téléchargement de l’agent
L’agent est servi par Caddy depuis le dossier agent/ monté dans le conteneur Caddy (./agent:/usr/share/caddy/agent).
Binaires disponibles
- Windows (archive complète) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.10-windows.zip- Contient
studioE5-agent.exe+tailscale-bin/windows/(tailscale.exe,tailscaled.exe,wintun.dll) +README-Windows.txt.
- Contient
- Windows (exécutable seul) :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.10.exe- Nécessite d’avoir installé Tailscale Windows séparément ou d’avoir les binaires dans
tailscale-bin/windows/.
- Nécessite d’avoir installé Tailscale Windows séparément ou d’avoir les binaires dans
- Linux :
https://studioe5.edudeploy.com/studioE5-agent-v0.3.10
Builder / préparer les binaires
cd /opt/studioe5-client-a/agent
# 1. Télécharger les binaires Tailscale Windows (nécessite msitools)
./download-tailscale-bins.sh 1.98.4
# 2. Builder l’agent pour Windows et Linux (macOS nécessite CGO)
./build.sh
Le build.sh génère automatiquement studioE5-agent-v0.3.10-windows.zip et copie les binaires versionnés dans server/public/.
Flow d’activation zéro-config (modèle commercialisable)
L’élève/employé n’a aucune configuration technique à saisir :
- Télécharger l’agent Windows (
studioE5-agent-v0.3.10-windows.zip). - Extraire et lancer
studioE5-agent.exe. - Entrer le code d’activation à 6 caractères fourni par l’établissement (affiché dans l’UI locale
http://localhost:7070). - L’agent contacte le serveur, le serveur vérifie le code et renvoie automatiquement :
- l’identité de l’élève (
studentName) - l’URL Headscale
- la clé pré-auth Headscale
- l’identité de l’élève (
- L’agent sauvegarde ces informations localement et démarre automatiquement le VPN.
- L’agent est alors visible dans le dashboard et peut recevoir des instances.
Configuration manuelle (mode debug / admin)
Si besoin, on peut toujours forcer une config via data/studioE5-config.json :
{
"server": "wss://studioe5.edudeploy.com/api/websocket",
"headscale_url": "https://headscale.studioe5.edudeploy.com",
"headscale_auth_key": "CLE_PREAUTH_ICI",
"node_id": "IDENTIFIANT_DU_POSTE",
"data_dir": "C:\\studioE5-agent\\data"
}
⚠️
headscale_auth_keydoit être une clé pré-auth réutilisable valide pour le tailnet studioe5. Ne jamais commiter cette clé.
Lancement :
.\studioE5-agent.exe -no-tray -data-dir C:\studioE5-agent\data
🔒 Durcissement du code d’activation
Génération
- Les codes sont générés avec
crypto.randomBytes(au lieu deMath.random). - Longueur conservée à 6 caractères, alphabet sans ambiguïté (
ABCDEFGHJKLMNPQRSTUVWXYZ23456789). - Un champ
activationCodeExpiresAta été ajouté au modèleStudent; les codes expirent après 60 minutes.
Rate-limiting
- Maximum de 5 tentatives d’activation par code sur une fenêtre de 15 minutes.
- Maximum de 5 tentatives par
nodeIdsur la même fenêtre. - Au-delà, le serveur répond
activation_failedavecToo many attempts.
Cycle de vie
- Le code est invalide après une activation réussie (
activationCodeetactivationCodeExpiresAtmis ànull). - Un code expiré renvoie
Code expired. - Un code déjà utilisé renvoie
Invalid code.
Tests validés
- Activation valide →
activated+ token node reçu. - Code expiré →
Code expired. - Code déjà utilisé →
Invalid code. - 5+ tentatives invalides →
Too many attempts.
🔒 ACL Headscale (isolation du tailnet)
Objectif
Par défaut, tous les nœuds du tailnet peuvent communiquer entre eux. Les ACL restreignent la connectivité au strict nécessaire :
- les agents élèves ne peuvent pas se parler entre eux ;
- le resolver peut atteindre les agents sur leurs ports d’instance ;
- les agents peuvent joindre le resolver sur son port HTTP interne.
Mise en œuvre
- Fichier de politique :
headscale/acl_policy.hujson. headscale/config.yamlpointe vers ce fichier viapolicy.path.- Le resolver a été déplacé dans un utilisateur Headscale dédié
resolver(cléHEADSCALE_RESOLVER_AUTH_KEY). - Les agents utilisent l’utilisateur
studioe5et sont taguéstag:student-agent. - Les nouveaux agents recevront automatiquement le tag via la nouvelle clé pré-auth
HEADSCALE_AUTH_KEY(créée avec--tags tag:student-agent).
Contenu de la politique
{
"groups": {
"group:agents": ["studioe5@studioe5.local"],
"group:resolvers": ["resolver@studioe5.local"]
},
"tagOwners": {
"tag:student-agent": ["studioe5@studioe5.local"],
"tag:resolver": ["resolver@studioe5.local"]
},
"acls": [
{ "action": "accept", "src": ["tag:resolver"], "dst": ["tag:student-agent:*"] },
{ "action": "accept", "src": ["tag:student-agent"], "dst": ["tag:resolver:2020"] }
]
}
Tests validés
| Test | Résultat |
|---|---|
resolver ping agent |
✅ OK |
| Agent → agent (port instance) | ❌ bloqué (timeout) |
| Agent → resolver:2020 | ✅ OK |
| Flux HTTPS public | ✅ HTTP 200 |
🔒 Authentification du canal serveur → agent
Token d’authentification par nœud
- Le modèle
Nodedispose d’un champtokenunique. - L’agent envoie son token dans l’en-tête
Authorization: Bearer <token>lors de la connexion WebSocket. - Le serveur rejette toute connexion/register dont le token ne correspond pas au
nodeId(fermeture1008). - Lors de l’activation, le serveur génère un token aléatoire (32 octets hex) et le renvoie dans le message
activated; l’agent le sauvegarde dans<data-dir>/node.token(permissions0600). - Pour les nœuds existants sans token, le serveur en génère un à la première connexion et l’envoie via
set_token.
Endpoint /api/internal/send-to-node
- Protégé par la variable d’environnement
INTERNAL_API_KEY. - Requiert l’en-tête
Authorization: Bearer <INTERNAL_API_KEY>. - Appel sans clé →
401 Unauthorized.
Routes API métier
- Les routes de gestion des instances (
/api/instances) requièrent une session NextAuth valide. - Un administrateur ne peut agir que sur les ressources de son établissement ; le
superadminpeut tout voir/tout faire.
Endpoint /api/resolve
- Protégé par la même clé
INTERNAL_API_KEY. - Requiert l’en-tête
Authorization: Bearer <INTERNAL_API_KEY>. - Le resolver (
resolver:2020) ne l’utilise pas ; il interroge directement PostgreSQL. Cette route est donc réservée aux outils/scripts internes authentifiés.
Exemples de commandes avec la clé interne
KEY=$(grep INTERNAL_API_KEY /opt/studioe5-client-a/.env | cut -d= -f2)
curl -sS -X POST https://studioe5.edudeploy.com/api/internal/send-to-node \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $KEY" \
-d '{"nodeId":"vps-8fc665eb","message":{"action":"start_vpn"}}'
curl -sS -H "Authorization: Bearer $KEY" \
"https://studioe5.edudeploy.com/api/resolve?subdomain=test-wp-001"
🔒 Clés pré-auth Headscale éphémères
Principe
À l’activation zero-config, le serveur génère désormais une clé pré-auth unique et à usage unique pour chaque agent, au lieu d’envoyer la clé réutilisable HEADSCALE_AUTH_KEY.
Avantages :
- une clé compromise ne permet pas d’enregistrer d’autres nœuds ;
- traçabilité directe entre une activation et une clé Headscale ;
- expiration courte (15 min) ;
- la clé n’est pas persistée dans
studioE5-config.jsoncôté agent.
Implémentation
| Composant | Changement |
|---|---|
server/lib/headscale.ts |
Nouveau helper : getHeadscaleUserId() + createEphemeralPreAuthKey() appelant POST /api/v1/preauthkey. |
server/lib/websocket.ts |
Sur activate, génère une clé éphémère taguée tag:student-agent pour l’utilisateur studioe5. Fallback sur HEADSCALE_AUTH_KEY si HEADSCALE_API_KEY n’est pas configurée. |
agent/websocket.go |
La clé reçue est utilisée immédiatement mais n’est plus écrite dans studioE5-config.json. |
agent/tailscale.go |
tailscale up fonctionne sans --authkey quand le state Tailscale existe déjà (reconnexion). |
.env.example / docker-compose.yml |
Ajout de HEADSCALE_API_KEY pour le service server. |
Configuration requise
Générer une clé API Headscale (depuis le conteneur ou la CLI) :
cd /opt/studioe5-client-a
# Clé valable 10 ans (87600h) pour éviter un renouvellement fréquent.
docker compose exec headscale headscale apikeys create -e 87600h
Puis l’ajouter dans .env :
HEADSCALE_API_KEY=hskey-api-...
⚠️ La clé API Headscale a une expiration par défaut de 90 jours. La clé de production a été créée avec une expiration de 10 ans (
-e 87600h). Les anciennes clés ont été révoquées.
Rotation / renouvellement
Si la clé doit être changée :
- Créer une nouvelle clé API :
docker compose exec headscale headscale apikeys create -e 87600h - Mettre à jour
.env:HEADSCALE_API_KEY=<nouvelle_clé> - Redémarrer le serveur :
docker compose up -d server - Révoquer l’ancienne clé :
docker compose exec headscale headscale apikeys expire --id <id_ancienne>
Déploiement effectué
- Clé API créée et ajoutée au
.envde production. - Image serveur rebuildée et redémarrée.
- Agents Linux/Windows rebuildés en v0.3.4 et copiés dans
server/public/.
🔒 Sécurité — points restants à traiter
Le certificat wildcard
*.studioe5.edudeploy.comest désormais du ressort du deployeur (voirdocs/ONBOARDING_CLIENT.md). Les points ci-dessous concernent l’application studioE5 proprement dite.
Gestion et rotation des secrets
| Secret | Où ? | Action |
|---|---|---|
INTERNAL_API_KEY |
.env serveur |
Prévoir une procédure de rotation régulière. |
HEADSCALE_API_KEY |
.env serveur |
Rotation tous les 10 ans max, stockage sécurisé. |
NEXTAUTH_SECRET |
.env serveur |
Génération robuste, rotation si suspicion de fuite. |
DATABASE_URL |
.env serveur |
Utilisateur DB dédié, mot de passe fort. |
node.token |
<data-dir>/node.token |
Vérifier permissions 0600 sur tous les OS. |
Durcissement des conteneurs
- Limiter les
cap_addau strict minimum. - Faire tourner les services avec un utilisateur non-root quand possible.
- Mettre à jour régulièrement les images de base (Caddy, Node, Postgres, Headscale).
- Scanner les images Docker pour les CVE.
Mises à jour de sécurité
- Mise à jour des binaires Tailscale (Windows et Linux).
- Mise à jour des images Docker (
server,resolver,caddy,postgres,headscale). - Mise à jour de l’OS des VPS et des postes agents.
- Mécanisme de mise à jour automatique ou notification de l’agent.
Logs d’audit
- Tracer la création / suppression d’instances.
- Tracer la génération et l’usage des codes d’activation.
- Tracer les actions admin (connexion, création d’élève, démarrage/arrêt VPN).
- Conservation et consultation des logs d’audit.
Backups et reprise d’activité
- Backup régulier de la base PostgreSQL.
- Backup du state Headscale.
- Backup des states Tailscale côté agents.
- Procédure de restauration documentée et testée.
Sécurité du build et distribution de l’agent
- Vérifier l’intégrité des binaires Tailscale téléchargés (checksum / signature).
- Signer l’exécutable Windows
studioE5-agent.exepour éviter les alertes Defender. - Fournir un hash SHA256 des archives d’agent.
RGPD et données personnelles
- Justifier la conservation des noms/prénoms des élèves.
- Gérer les droits d’accès, la suppression de compte et l’export de données.
- Définir la durée de conservation des logs et historiques.
Sécurité réseau complémentaire
- Restreindre l’accès à
/api/internal/send-to-nodepar IP source si possible. - Vérifier l’exposition publique du dashboard Headscale et la durcir si nécessaire.
- Évaluer si
headscale.studioe5.edudeploy.comdoit rester public.
Rate limiting et quotas
- Rate-limiting global sur les routes publiques (
/api/auth/*, activation, création d’instance). - Limitation du nombre d’instances par élève et par établissement.
- Protection contre les abus sur la génération de codes d’activation.
Tests de sécurité
- Tests d’intrusion légers (agent → agent, accès aux endpoints internes sans clé, accès à une instance d’un autre élève).
- Tests automatisés du flux complet avant chaque release.
🖥️ Installateur agent professionnel
Objectif
Créer un package d’installation unique et professionnel par OS, incluant l’agent studioE5, Tailscale et Podman CLI, afin de ne plus dépendre d’installations manuelles préalables par l’utilisateur.
Choix des outils
| OS | Outil | Format | Justification |
|---|---|---|---|
| Windows | Inno Setup | .exe |
Gratuit, open source, très répandu, personnalisable, exécution de scripts PowerShell/silencieux. |
| macOS | pkgbuild |
.pkg |
Outil natif Apple, gratuit, format professionnel pour la distribution macOS. |
| Linux | Script shell (+ .deb/.rpm optionnels) |
.sh |
Universel, détecte le package manager, simple à maintenir. |
Contenu du package par OS
-
Windows (Inno Setup) :
- Installer l’agent dans
C:\Program Files\studioE5-agent\. - Extraire Tailscale dans
C:\Program Files\studioE5-agent\tailscale-bin\windows\. - Installer Podman CLI via le MSI officiel en mode silencieux.
- Exécuter
podman machine initpuispodman machine start. - Créer un raccourci de démarrage et/ou un service Windows.
- Installer l’agent dans
-
macOS (
pkgbuild) :- Installer l’agent dans
/Applications/studioE5-agent/. - Installer Podman CLI.
- Exécuter
podman machine initpuispodman machine start. - Optionnellement créer un LaunchAgent pour démarrer l’agent au login.
- Installer l’agent dans
-
Linux (script shell) :
- Détecter le package manager (
apt,dnf,pacman, etc.). - Installer Podman et Podman Compose.
- Copier l’agent dans
/opt/studioe5-agent/. - Créer le service systemd
studioe5-agent.service. - Activer et démarrer le service.
- Détecter le package manager (
Adaptations nécessaires dans l’agent
- Détecter si Podman est utilisé et si une machine est requise (Windows/macOS).
- Vérifier au démarrage que la machine Podman est démarrée, et lancer
podman machine startsi besoin. - Gérer proprement l’arrêt de la machine à la fermeture de l’agent (optionnel).
Mise à jour de l’agent vs dépendances système
- L’agent peut se mettre à jour lui-même (binaire + fichiers) depuis la v0.3.10.
- Podman / Docker / Tailscale restent gérés par l’installateur : l’agent vérifie leur présence et alertera l’utilisateur si une dépendance est manquante ou trop ancienne, mais ne les met pas à jour automatiquement (droits élevés, risque de casser les machines Podman, etc.).
📋 Prochaines étapes à faire
✅ Terminé
- Rate limit Let’s Encrypt levé.
- Flux HTTPS public validé (
test-wp-001.studioe5.edudeploy.com). - Branche
feat/studioe5-vpn-ondemandcréée, commit124543d. - Pousser la branche vers Gitea : la branche est synchronisée sur
origin/feat/studioe5-vpn-ondemand(commitcf8b663). - Flux complet UI → API → WebSocket → agent → Docker → VPN → Caddy validé.
- Packager les binaires Tailscale pour Windows (
studioE5-agent-v0.3.5-windows.zip). - Sécurité – authentification du canal serveur → agent (token par nœud, clé API interne, sessions NextAuth sur les routes API).
- Sécurité – durcissement du code d’activation (
crypto.randomBytes, expiration 60 min, rate-limiting, invalidation après usage). - Sécurité – ACL Headscale (isolation agent ↔ agent, resolver → agent autorisé).
- Sécurité – clés pré-auth Headscale éphémères (génération côté serveur via
HEADSCALE_API_KEY, non persistées côté agent). - Agent v0.3.5 – forwarding entrant Windows (
tailscale serveautomatique au démarrage de chaque instance). - Agent v0.3.5 – UI locale moderne (dashboard, logs, progression, actions d’instance).
- Agent v0.3.5 – cycle de vie des instances (
stop/startpréservent les volumes,reset/deleteeffacent). - Agent v0.3.5 – cleanup au shutdown (arrêt propre de Tailscale et des instances, notification serveur).
- Synchronisation dashboard (messages
instance_stopped/instance_deletedtraités côté serveur, et agent renvoyant correctement ces messages après un ordre serveurstop/delete). - Template WordPress prêt à l’emploi (
wordpress-ready-wordpress-latest) : WordPress en français, titre « Mon site wordpress », compte admin/admin, thème Astra, Spectra actif, Yoast SEO inactif, mises à jour automatiques désactivées, DNS8.8.8.8/1.1.1.1pourapi.wordpress.org. - Nettoyer les instances/agent de test (2026-06-27) : agent de test arrêté (
vps-8fc665eb),tailscaledassocié arrêté, data-dir/tmp/studioe5-test-clientasupprimé ; 13 instances de test supprimées de la base PostgreSQL (vps-8fc665eb+OMEGA-GAMER-60d7f87c). - Nettoyer les anciens nodes/volumes Headscale de test (2026-06-27) : nœuds
edubox,prof,invalid-*, anciensvps-8fc665eb, anciensstudioe5-resolverettest-node-bsupprimés ; volume Docker anonyme orphelin supprimé. - Centralisation de la version agent : fichier unique
agent/VERSION, APIGET /api/agent/version, dashboard et route/api/downloadalignés. - Agent v0.3.10 – synchronisation agent ↔ serveur au démarrage : protocole
sync/sync_response, suppression/lancement automatique des instances décalées pendant un offline. - Agent v0.3.10 – détails techniques dans l’UI locale : version de l’agent, nodeId, version attendue par le serveur, notification de mise à jour.
- Agent v0.3.10 – mise à jour automatique de l’agent : détection de nouvelle version, téléchargement, remplacement du binaire via script helper et redémarrage.
- Agent v0.3.10 – handlers asynchrones :
start,stop,delete,resetexécutés dans des goroutines pour ne plus bloquer la boucle WebSocket. - Agent v0.3.10 – nettoyage des dossiers instances orphelins au démarrage : supprime les répertoires résiduels laissés par des
deleteincomplets (souventcompose.logverrouillé sous Windows).
⏳ Reste à faire
- Certificat wildcard : transféré au deployeur (
docs/ONBOARDING_CLIENT.md). L’étude technique reste disponible ci-dessous pour référence. - Documenter la procédure de mise en production pour le client A (config agent, clés Headscale, ports, ACL, etc.).
- Installateur agent professionnel (Windows / macOS / Linux) : créer un package d’installation unique incluant l’agent studioE5, Tailscale et Podman CLI. Voir la section « 🖥️ Installateur agent professionnel » ci-dessous pour le détail des outils (Inno Setup, pkgbuild, script shell) et du contenu par OS.
- Template WordPress prêt à l’emploi (usage examen/classe) :
- Forcer le DNS (
8.8.8.8,1.1.1.1) dans ledocker-compose.ymlpour permettre l’accès à la bibliothèque de plugins/mises à jour depuis le conteneur. - Pré-installer WordPress en français via WP-CLI avec le titre “Mon site wordpress” et le compte admin / admin.
- Désactiver les mises à jour automatiques (core, plugins, thèmes) pour figer l’environnement.
- Installer et activer le thème Astra.
- Installer Yoast SEO (inactif) et Spectra (actif).
- Forcer le DNS (
- Barre de progression basée sur les logs d’installation : enrichir la barre de progression agent/dashboard en lisant les logs Podman/Docker (
podman logs -f) pendant le premier démarrage d’une instance. Définir des patterns de logs par template (ex.Installation successfulpour PrestaShop) et relayer les étapes réelles au dashboard via WebSocket. - Étude – interface de déploiement multi-clients : outil de provisionning d’un nouveau serveur client + agent générique (option A : URL serveur déterminée à l’activation).
- Sécurité – gestion et rotation des secrets (
INTERNAL_API_KEY,HEADSCALE_API_KEY,NEXTAUTH_SECRET,DATABASE_URL). - Sécurité – durcissement des conteneurs (
cap_add, utilisateurs non-root, scans CVE). - Sécurité – mises à jour de sécurité (Tailscale, images Docker, OS agents).
- Sécurité – logs d’audit (instances, codes d’activation, actions admin).
- Sécurité – backups et reprise d’activité (DB, state Headscale, states agents).
- Sécurité – intégrité et signature de l’agent (checksum Tailscale, signature Windows, hash SHA256).
- Sécurité – conformité RGPD (données élèves, suppression de compte, export).
- Sécurité – restriction réseau (endpoint interne, dashboard Headscale).
- Sécurité – rate limiting et quotas (routes publiques, instances par élève/établissement).
- Sécurité – tests de sécurité (intrusion légère, tests automatisés avant release).
💡 Améliorations UI
✅ Console / log intégrée dans l’agent (v0.3.5)
Les logs de l’agent sont redirigés vers <data-dir>/agent.log et diffusés en temps réel dans l’UI locale (http://localhost:7070) via le WebSocket existant.
✅ Barre de progression (v0.3.5)
L’agent envoie des messages progress au frontend pendant le démarrage d’une instance :
| Étape | Poids |
|---|---|
| Préparation de l’application | 10 % |
| Configuration de l’application | 30 % |
| Application en cours de démarrage | 60 % |
| Connexion sécurisée active | 80 % |
| Finalisation de l’installation | 90 % |
| Application prête | 100 % |
Boutons d’action par instance (v0.3.5)
L’UI locale affiche désormais des boutons Démarrer, Arrêter, Redémarrer et Supprimer pour chaque instance.
🚀 Scalabilité commerciale — déploiement multi-clients
Objectif
Permettre de déployer facilement une stack studioE5 complète pour un nouvel établissement/client sur un VPS dédié, sans intervention technique lourde.
Architecture cible
- Un serveur = un client : chaque établissement a sa propre stack Docker Compose, sa base PostgreSQL, son Headscale et son Caddy.
- Agent générique (option A) : un seul binaire agent pour tous les clients. L’URL du serveur cible est déterminée au moment de l’activation, pas hardcodée dans l’agent.
- Pistes : code d’activation résolu par un hub central, code structuré contenant l’identifiant du serveur, ou champ URL serveur saisi dans l’UI locale.
- Interface de déploiement : dashboard superadmin (hub) permettant de créer un client, provisionner le VPS, générer les secrets et retourner les informations de connexion.
Prérequis techniques à préparer
Avant de pouvoir déployer un nouveau client en quelques clics, il faut encore préparer les éléments suivants :
| # | Élément | État | Détail |
|---|---|---|---|
| 1 | Agent générique | ⏳ À faire | defaultServerURL est hardcodé (wss://studioe5.edudeploy.com/api/websocket). L’agent doit pouvoir déterminer l’URL serveur cible à l’activation (option A : champ URL, hub de résolution, ou code structuré). |
| 2 | Script de provisionning | ⏳ À faire | Aucun outil automatisé pour provisionner un VPS vierge : installation Docker, déploiement de la stack, génération des secrets, création des clés Headscale, configuration DNS wildcard. |
| 3 | Registry d’images | ⏳ À faire | Les images server et resolver sont buildées sur le serveur cible. Il faut un registry privé pour builder une fois et déployer partout. |
| 4 | Hub central | ⏳ À faire | Dashboard superadmin listant les clients, état des serveurs, versions déployées, logs distants et mises à jour. |
| 5 | Mises à jour à distance | ⏳ À faire | Mécanisme pour pousser une nouvelle version du serveur et de l’agent sur tous les déploiements clients. |
| 6 | Monitoring / support | ⏳ À faire | Collecte centralisée de logs, alertes (serveur down, certificat expiré, agent hors ligne). |
| 7 | Branding / personnalisation | ⏳ À faire | Logo, nom de l’établissement, couleurs configurables par client. |
| 8 | Tests automatisés | ⏳ À faire | Tests du flux activation → VPN → instance → HTTPS public pour valider chaque nouveau déploiement. |
| 9 | Documentation procédure prod | ⏳ À faire | Procédure complète de mise en production pour un nouveau client. |
Statut
- ⏳ À étudier et planifier plus tard. L’architecture actuelle (un serveur par client + agent zero-config) est déjà compatible avec cette vision, mais le code n’est pas encore industrialisé pour un déploiement à grande échelle.
🔒 Étude certificat wildcard *.studioe5.edudeploy.com
Pourquoi passer en wildcard ?
Avec tls { on_demand }, Caddy émet un certificat Let’s Encrypt par sous-domaine d’instance. Cela expose au rate limit de 50 certificats par domaine principal (edudeploy.com) sur 7 jours. Un certificat wildcard unique (*.studioe5.edudeploy.com) couvre tous les sous-domaines d’instances et évite ce problème.
Contrainte technique
Un certificat wildcard nécessite le challenge DNS-01 (le challenge HTTP-01 ne permet pas de valider *.domain.tld). Caddy doit donc pouvoir créer un enregistrement TXT automatiquement chez le registrar DNS.
Infomaniak (registrar actuel)
Le DNS de edudeploy.com est chez Infomaniak :
dig NS edudeploy.com +short
# nsany1.infomaniak.com.
# nsany2.infomaniak.com.
Il existe un module Caddy DNS pour Infomaniak :
- Repository :
github.com/caddy-dns/infomaniak - Nécessite un token API Infomaniak avec droits DNS.
Implémentation à envisager
- Générer un token API Infomaniak (compte client A ou compte dédié avec accès au domaine).
- Builder une image Caddy custom avec le module :
FROM caddy:2-builder AS builder RUN xcaddy build --with github.com/caddy-dns/infomaniak FROM caddy:2-alpine COPY --from=builder /usr/bin/caddy /usr/bin/caddy - Modifier le
Caddyfilepour gérer le wildcard :*.studioe5.edudeploy.com { tls { dns infomaniak {env.INFOMANIAK_API_TOKEN} } reverse_proxy resolver:2020 { header_up Host {host} } } - Ajouter le token dans
.envet le passer au conteneur Caddy. - Supprimer ou ajuster le bloc
:443actuel qui utiliseon_demandpour les instances.
Alternative sans module DNS
Obtenir le certificat wildcard manuellement (Certbot DNS-01, acheté, etc.) et le charger dans Caddy :
*.studioe5.edudeploy.com {
tls /data/certs/wildcard.crt /data/certs/wildcard.key
reverse_proxy resolver:2020 {
header_up Host {host}
}
}
Inconvénient : renouvellement manuel.
🔧 Notes techniques
- Le conteneur
resolver-vpnutilisenetwork_mode: service:resolverpour partager le netns avec le resolver. - L’agent utilise
tailscaled --tun=userspace-networking; le resolver-vpn utilise un vrai TUN (tailscale0). - Le
Caddyfileactuel utilisetls { on_demand }pour les instances. En cas de nouvelle rate limit, on peut temporairement remettretls internaldans le bloc:443pour valider le flux sans certificat public.