- Configure tailscale serve automatically for each instance on Windows userspace networking. - Add local UI buttons: start/stop/reset/delete instances (stop/start preserve volumes). - Clean shutdown: stop tailscaled and instances, notify server with instance_stopped. - Restart tailscaled on agent boot using persisted state when pre-auth key is absent. - Sync instance stopped/deleted status to dashboard (server/lib/websocket.ts). - Security: include prior authz/scoping changes across API routes, ephemeral pre-auth keys, ACL policy, internal API key. - Update SUIVI_VPN_ONDEMAND.md and docs/ONBOARDING_CLIENT.md. - Bump agent version to 0.3.5.
32 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://.
📁 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 :
3151830(lancé le 2026-06-23 09:36 UTC)
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
🛠️ 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.5-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.5.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.5
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.5-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.4-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.
📋 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. - 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).
⏳ Reste à faire
- Certificat wildcard : transféré au deployeur (
docs/ONBOARDING_CLIENT.md). L’étude technique reste disponible ci-dessous pour référence. - Nettoyer les instances/agent de test une fois le push effectué.
- Nettoyer les anciens nodes/volumes Headscale de test (nœuds
edubox,prof,invalid-*hors ligne à supprimer). - Pousser la branche vers Gitea dès que le remote sera accessible.
- Documenter la procédure de mise en production pour le client A (config agent, clés Headscale, ports, ACL, etc.).
- É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.