33d89c66c0
- 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
962 lines
47 KiB
Markdown
962 lines
47 KiB
Markdown
# Suivi – VPN on-demand studioE5 (client A)
|
||
|
||
## ✅ Ce qui fonctionne
|
||
|
||
1. **Agent standalone (mode console / systray)**
|
||
- Exécutable : `agent/studioE5-agent`
|
||
- Config lu depuis `<data-dir>/studioE5-config.json`
|
||
- Mode console : `-no-tray`
|
||
|
||
2. **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` (pas `tsnet`, car `tsnet` ne loguait pas automatiquement avec une authkey sur un state vierge).
|
||
|
||
3. **Commandes serveur → agent**
|
||
- Endpoint de test : `POST /api/internal/send-to-node`
|
||
- Actions supportées : `start_vpn`, `stop_vpn`, `start`, `stop`, `reset`, `delete`.
|
||
|
||
4. **Resolver/serveur dans le tailnet studioe5**
|
||
- Service `resolver-vpn` (conteneur Tailscale) partage le netns du `resolver`.
|
||
- Le resolver peut joindre les IPs Tailscale des nodes (`ping 100.64.0.x` OK).
|
||
|
||
5. **Instance WordPress démarrée avec succès**
|
||
- Le resolver a renvoyé une 302 WordPress via `http://resolver:2020/`.
|
||
|
||
6. **Activation zéro-config de l’agent (modèle commercialisable)**
|
||
- L’agent démarre sans `headscale_url` ni `headscale_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.
|
||
|
||
## ✅ 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é :
|
||
```bash
|
||
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é :
|
||
|
||
```text
|
||
Client (HTTPS) → Caddy (:443) → resolver (:2020) → Tailnet (100.64.0.8) → agent → WordPress (:8001)
|
||
```
|
||
|
||
Résultat :
|
||
```bash
|
||
$ 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 `Location` et le contenu HTML pour passer de `http://` à `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 `Template` de Prisma dispose d’un nouveau champ `initScript` (`TEXT?`).
|
||
- Le seed génère le template avec :
|
||
- une section `dns` dans le service `app` du `docker-compose.yml` ;
|
||
- un service sidecar `wp-init` (image `wordpress:cli`) exécutant le script d’initialisation.
|
||
- L’agent écrit le script `wp-init.sh` dans le dossier de l’instance au démarrage.
|
||
- Le conteneur `wp-init` attend que WordPress soit prêt, puis exécute WP-CLI en tant que `www-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` – champ `initScript` sur `Template`.
|
||
- `server/prisma/seed.ts` – génération du template `wordpress-ready-wordpress-latest`.
|
||
- `server/templates/wordpress-ready/wp-init.sh` – script d’initialisation WP-CLI.
|
||
- `server/app/api/instances/route.ts` – envoi de `initScript` à l’agent avec remplacement des placeholders.
|
||
- `agent/websocket.go` – réception et transmission de `InitScript`.
|
||
- `agent/docker.go` – écriture du script dans le dossier instance (`writeInitScript`).
|
||
|
||
### Validation
|
||
|
||
Instance de test créée via l’API (`cmqv03a6v0001vg8zrpe8zqfy`) :
|
||
|
||
```bash
|
||
$ 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.php` avec **admin / admin** fonctionnelle.
|
||
- Tableau de bord en français.
|
||
- Plugins : Spectra actif, Yoast SEO inactif.
|
||
- `wp-config.php` contient 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` – lancement `tailscaled` + `tailscale up`, gestion start/stop/status.
|
||
- `agent/websocket.go` – handlers `start_vpn` / `stop_vpn`, `ensureTailscale()`.
|
||
- `agent/docker.go` – remplacement des placeholders `{PORT}` et `{INSTANCE_ID}` dans les compose.
|
||
- `docker-compose.yml` – ajout du sidecar `resolver-vpn`, suppression des `cap_add`/`ip route` obsolètes sur `server`/`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:8001` **et en HTTPS public sur `https://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 `online` dans le dashboard mais sans IP Tailscale.
|
||
- `tailscale.exe ip -4` retournait une erreur de connexion au socket local.
|
||
|
||
Cause racine :
|
||
- L’agent lançait `tailscaled` avec `--socket=<fichier>.sock`, mais **Tailscale sur Windows utilise des named pipes** (`\\.\pipe\...`), pas des sockets Unix.
|
||
- De plus, les commandes `podman`/`docker`/`tailscale` ouvraient 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.log` et des logs `tailscaled` vers `<data-dir>/tailscale/tailscaled.log`.
|
||
- Suppression de `--operator=root` sur Windows (non pertinent).
|
||
- Ajout de `--unattended` au `tailscale up` sur Windows pour que le daemon reste connecté après la déconnexion du client CLI.
|
||
- Correction du chemin `dataDir` passé à `startTailscale` (évitait un double dossier `tailscale/tailscale`).
|
||
|
||
Validation manuelle sur Windows :
|
||
```powershell
|
||
.\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 :
|
||
```text
|
||
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 :
|
||
```powershell
|
||
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` (ou `up -d` la 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()` dans `handleStartInstance` ; en cas de panic, l’instance passe en statut `error` et un message `instance_error` est envoyé au serveur.
|
||
- Ajout d’un `defer recover()` dans toutes les goroutines critiques du WebSocket :
|
||
- `start_vpn`
|
||
- `stop_vpn`
|
||
- `start`
|
||
- `reset`
|
||
- `startTailscaleAndReport`
|
||
- 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/download` mise à 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()` dans `notifyUI` pour chaque goroutine de notification.
|
||
- Ajout d’un `defer recover()` dans `sendUILog` (logs diffusés aux clients UI).
|
||
- Ajout d’un `defer recover()` dans `broadcastUI` (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 :
|
||
```text
|
||
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 :
|
||
```bash
|
||
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
|
||
|
||
1. **Action `stop` du dashboard envoyée comme `delete` à l’agent** (`server/app/api/instances/route.ts`).
|
||
L’agent exécutait alors `docker compose down -v` + suppression des fichiers, c’est-à-dire une suppression réelle, tout en marquant l’instance `stopped` en base.
|
||
2. **L’agent ne confirmait pas les actions serveur** (`agent/websocket.go`).
|
||
Les handlers `stop` et `delete` ne renvoyaient jamais les messages `instance_stopped` / `instance_deleted` au serveur ; seule l’UI locale le faisait.
|
||
3. **Le handler `stop` de l’agent utilisait `dockerComposeDown`** au lieu de `dockerComposeStop`, 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` → statut `stopped`.
|
||
- **Démarrer** : `docker compose up -d` → statut `running`.
|
||
- **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`, `.zip` et `server/public/` mis à jour).
|
||
- **Serveur rebuildé et redémarré** (`docker compose up -d --build server`) pour intégrer les corrections TypeScript.
|
||
- **Page `/dashboard/download` mise à 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 :
|
||
- `sendMessage` exécuté de manière asynchrone (`go`) dans les handlers `stop`, `delete`, `stop_vpn` et cleanup, pour ne pas bloquer la boucle de lecture WebSocket.
|
||
- Ajout d’un `recover` dans `handleMessage` pour capturer d’éventuels panics sans tuer l’agent.
|
||
- Correction du cleanup `main.go` : modification de `inst[id].Status` (et non de la copie locale `info`).
|
||
- **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
|
||
```bash
|
||
pgrep -a studioe5-agent
|
||
```
|
||
|
||
### Relancer l’agent de test (si besoin)
|
||
```bash
|
||
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
|
||
```bash
|
||
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
|
||
```bash
|
||
cd /opt/studioe5-client-a
|
||
docker compose exec -T headscale headscale nodes list studioe5
|
||
```
|
||
|
||
### Tester le resolver (depuis Caddy)
|
||
```bash
|
||
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)
|
||
```bash
|
||
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 :
|
||
|
||
1. **Authentification NextAuth** sur `/api/auth/callback/credentials`.
|
||
2. **Création d’instance** via `POST /api/instances` :
|
||
```json
|
||
{
|
||
"nodeId": "vps-8fc665eb",
|
||
"templateId": "wordpress-wordpress-latest",
|
||
"port": 8002
|
||
}
|
||
```
|
||
→ Instance créée : `cmqqgrur20001lw67t2bdgzkg`.
|
||
3. Le serveur a automatiquement envoyé l’action `start` au node via WebSocket.
|
||
4. L’agent a démarré le VPN (si besoin), écrit le compose et a lancé les conteneurs WordPress.
|
||
5. Caddy a obtenu un certificat Let’s Encrypt pour `cmqqgrur20001lw67t2bdgzkg.studioe5.edudeploy.com`.
|
||
6. **Validation HTTPS** :
|
||
```bash
|
||
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`.
|
||
- **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/`.
|
||
- **Linux** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.10`
|
||
|
||
### Builder / préparer les binaires
|
||
|
||
```bash
|
||
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 :
|
||
|
||
1. **Télécharger** l’agent Windows (`studioE5-agent-v0.3.10-windows.zip`).
|
||
2. **Extraire** et **lancer** `studioE5-agent.exe`.
|
||
3. **Entrer le code d’activation** à 6 caractères fourni par l’établissement (affiché dans l’UI locale `http://localhost:7070`).
|
||
4. 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
|
||
5. L’agent sauvegarde ces informations localement et **démarre automatiquement le VPN**.
|
||
6. 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` :
|
||
|
||
```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_key` doit être une clé pré-auth réutilisable valide pour le tailnet studioe5. Ne jamais commiter cette clé.
|
||
|
||
Lancement :
|
||
```powershell
|
||
.\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 de `Math.random`).
|
||
- Longueur conservée à 6 caractères, alphabet sans ambiguïté (`ABCDEFGHJKLMNPQRSTUVWXYZ23456789`).
|
||
- Un champ `activationCodeExpiresAt` a été ajouté au modèle `Student` ; 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 `nodeId`** sur la même fenêtre.
|
||
- Au-delà, le serveur répond `activation_failed` avec `Too many attempts`.
|
||
|
||
### Cycle de vie
|
||
|
||
- Le code est **invalide après une activation réussie** (`activationCode` et `activationCodeExpiresAt` mis à `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.yaml` pointe vers ce fichier via `policy.path`.
|
||
- Le resolver a été déplacé dans un utilisateur Headscale dédié `resolver` (clé `HEADSCALE_RESOLVER_AUTH_KEY`).
|
||
- Les agents utilisent l’utilisateur `studioe5` et sont tagués `tag: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
|
||
|
||
```json
|
||
{
|
||
"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 `Node` dispose d’un champ `token` unique.
|
||
- 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` (fermeture `1008`).
|
||
- 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` (permissions `0600`).
|
||
- 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 `superadmin` peut 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
|
||
|
||
```bash
|
||
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.json` cô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) :
|
||
|
||
```bash
|
||
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` :
|
||
|
||
```bash
|
||
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 :
|
||
|
||
1. Créer une nouvelle clé API :
|
||
```bash
|
||
docker compose exec headscale headscale apikeys create -e 87600h
|
||
```
|
||
2. Mettre à jour `.env` :
|
||
```bash
|
||
HEADSCALE_API_KEY=<nouvelle_clé>
|
||
```
|
||
3. Redémarrer le serveur :
|
||
```bash
|
||
docker compose up -d server
|
||
```
|
||
4. Révoquer l’ancienne clé :
|
||
```bash
|
||
docker compose exec headscale headscale apikeys expire --id <id_ancienne>
|
||
```
|
||
|
||
### Déploiement effectué
|
||
|
||
- Clé API créée et ajoutée au `.env` de 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.com` est désormais du ressort du **deployeur** (voir `docs/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_add` au 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.exe` pour é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-node` par IP source si possible.
|
||
- Vérifier l’exposition publique du dashboard Headscale et la durcir si nécessaire.
|
||
- Évaluer si `headscale.studioe5.edudeploy.com` doit 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 init` puis `podman machine start`.
|
||
- Créer un raccourci de démarrage et/ou un service Windows.
|
||
|
||
- **macOS** (`pkgbuild`) :
|
||
- Installer l’agent dans `/Applications/studioE5-agent/`.
|
||
- Installer Podman CLI.
|
||
- Exécuter `podman machine init` puis `podman machine start`.
|
||
- Optionnellement créer un LaunchAgent pour démarrer l’agent au login.
|
||
|
||
- **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.
|
||
|
||
### 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 start` si 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é
|
||
|
||
- [x] Rate limit Let’s Encrypt levé.
|
||
- [x] Flux HTTPS public validé (`test-wp-001.studioe5.edudeploy.com`).
|
||
- [x] Branche `feat/studioe5-vpn-ondemand` créée, commit `124543d`.
|
||
- [x] **Pousser la branche** vers Gitea : la branche est synchronisée sur `origin/feat/studioe5-vpn-ondemand` (commit `cf8b663`).
|
||
- [x] Flux complet UI → API → WebSocket → agent → Docker → VPN → Caddy validé.
|
||
- [x] Packager les binaires Tailscale pour Windows (`studioE5-agent-v0.3.5-windows.zip`).
|
||
- [x] **Sécurité – authentification du canal serveur → agent** (token par nœud, clé API interne, sessions NextAuth sur les routes API).
|
||
- [x] **Sécurité – durcissement du code d’activation** (`crypto.randomBytes`, expiration 60 min, rate-limiting, invalidation après usage).
|
||
- [x] **Sécurité – ACL Headscale** (isolation agent ↔ agent, resolver → agent autorisé).
|
||
- [x] **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).
|
||
- [x] **Agent v0.3.5 – forwarding entrant Windows** (`tailscale serve` automatique au démarrage de chaque instance).
|
||
- [x] **Agent v0.3.5 – UI locale moderne** (dashboard, logs, progression, actions d’instance).
|
||
- [x] **Agent v0.3.5 – cycle de vie des instances** (`stop`/`start` préservent les volumes, `reset`/`delete` effacent).
|
||
- [x] **Agent v0.3.5 – cleanup au shutdown** (arrêt propre de Tailscale et des instances, notification serveur).
|
||
- [x] **Synchronisation dashboard** (messages `instance_stopped` / `instance_deleted` traités côté serveur, et agent renvoyant correctement ces messages après un ordre serveur `stop`/`delete`).
|
||
- [x] **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, DNS `8.8.8.8`/`1.1.1.1` pour `api.wordpress.org`.
|
||
- [x] **Nettoyer les instances/agent de test** (2026-06-27) : agent de test arrêté (`vps-8fc665eb`), `tailscaled` associé arrêté, data-dir `/tmp/studioe5-test-clienta` supprimé ; **13 instances de test supprimées de la base PostgreSQL** (`vps-8fc665eb` + `OMEGA-GAMER-60d7f87c`).
|
||
- [x] **Nettoyer les anciens nodes/volumes Headscale de test** (2026-06-27) : nœuds `edubox`, `prof`, `invalid-*`, anciens `vps-8fc665eb`, anciens `studioe5-resolver` et `test-node-b` supprimés ; volume Docker anonyme orphelin supprimé.
|
||
- [x] **Centralisation de la version agent** : fichier unique `agent/VERSION`, API `GET /api/agent/version`, dashboard et route `/api/download` alignés.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **Agent v0.3.10 – handlers asynchrones** : `start`, `stop`, `delete`, `reset` exécutés dans des goroutines pour ne plus bloquer la boucle WebSocket.
|
||
- [x] **Agent v0.3.10 – nettoyage des dossiers instances orphelins au démarrage** : supprime les répertoires résiduels laissés par des `delete` incomplets (souvent `compose.log` verrouillé 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 le `docker-compose.yml` pour 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).
|
||
- [ ] **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 successful` pour 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** :
|
||
```bash
|
||
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
|
||
|
||
1. **Générer un token API Infomaniak** (compte client A ou compte dédié avec accès au domaine).
|
||
2. **Builder une image Caddy custom** avec le module :
|
||
```dockerfile
|
||
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
|
||
```
|
||
3. **Modifier le `Caddyfile`** pour gérer le wildcard :
|
||
```caddy
|
||
*.studioe5.edudeploy.com {
|
||
tls {
|
||
dns infomaniak {env.INFOMANIAK_API_TOKEN}
|
||
}
|
||
reverse_proxy resolver:2020 {
|
||
header_up Host {host}
|
||
}
|
||
}
|
||
```
|
||
4. **Ajouter le token dans `.env`** et le passer au conteneur Caddy.
|
||
5. Supprimer ou ajuster le bloc `:443` actuel qui utilise `on_demand` pour les instances.
|
||
|
||
### Alternative sans module DNS
|
||
|
||
Obtenir le certificat wildcard manuellement (Certbot DNS-01, acheté, etc.) et le charger dans Caddy :
|
||
```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-vpn` utilise `network_mode: service:resolver` pour partager le netns avec le resolver.
|
||
- L’agent utilise `tailscaled --tun=userspace-networking` ; le resolver-vpn utilise un vrai TUN (`tailscale0`).
|
||
- Le `Caddyfile` actuel utilise `tls { on_demand }` pour les instances. En cas de nouvelle rate limit, on peut temporairement remettre `tls internal` dans le bloc `:443` pour valider le flux sans certificat public.
|