diff --git a/SUIVI_VPN_ONDEMAND.md b/SUIVI_VPN_ONDEMAND.md
index 4ad022f..99cf7b6 100644
--- a/SUIVI_VPN_ONDEMAND.md
+++ b/SUIVI_VPN_ONDEMAND.md
@@ -426,11 +426,11 @@ L’agent est servi par Caddy depuis le dossier `agent/` monté dans le conteneu
### Binaires disponibles
-- **Windows (archive complète)** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.5-windows.zip`
+- **Windows (archive complète)** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.9-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.5.exe`
+- **Windows (exécutable seul)** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.9.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.5`
+- **Linux** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.9`
### Builder / préparer les binaires
@@ -444,13 +444,13 @@ cd /opt/studioe5-client-a/agent
./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/`.
+Le `build.sh` génère automatiquement `studioE5-agent-v0.3.9-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.4-windows.zip`).
+1. **Télécharger** l’agent Windows (`studioE5-agent-v0.3.9-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** :
@@ -776,6 +776,11 @@ Créer un package d’installation unique et professionnel par OS, incluant l’
- 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.9.
+- **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
@@ -785,6 +790,7 @@ Créer un package d’installation unique et professionnel par OS, incluant l’
- [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).
@@ -797,13 +803,17 @@ Créer un package d’installation unique et professionnel par OS, incluant l’
- [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.9 – 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.9 – 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.9 – 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.9 – handlers asynchrones** : `start`, `stop`, `delete`, `reset` exécutés dans des goroutines pour ne plus bloquer la boucle WebSocket.
### ⏳ 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.).
- [ ] **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)** :
diff --git a/agent/VERSION b/agent/VERSION
new file mode 100644
index 0000000..940ac09
--- /dev/null
+++ b/agent/VERSION
@@ -0,0 +1 @@
+0.3.9
diff --git a/agent/build.sh b/agent/build.sh
index f3db3e9..0aed78c 100755
--- a/agent/build.sh
+++ b/agent/build.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -e
-VERSION="0.3.8"
+VERSION="$(cat "$(dirname "$0")/VERSION")"
APP_NAME="studioE5"
BIN_NAME="studioE5-agent"
LDFLAGS="-X main.version=${VERSION}"
diff --git a/agent/main.go b/agent/main.go
index 871b150..bbb05f4 100644
--- a/agent/main.go
+++ b/agent/main.go
@@ -71,6 +71,7 @@ func main() {
}
go startWebSocket(cfg.Server, cfg.NodeID, *dataDir, cfg.HeadscaleURL, cfg.HeadscaleAuthKey)
+ go updateCheckerLoop(*dataDir, cfg.Server)
shutdownCh := make(chan struct{})
diff --git a/agent/ui.go b/agent/ui.go
index 4bcb4cb..80249a0 100644
--- a/agent/ui.go
+++ b/agent/ui.go
@@ -56,6 +56,8 @@ func startUI(dataDir, nodeID, serverAddr string) {
return
}
// Expose a merged view with the agent version for the UI.
+ serverVersion := getServerAgentVersion()
+ updateAvailable := serverVersion != "" && serverVersion != version
response := map[string]interface{}{
"server": cfg.Server,
"headscale_url": cfg.HeadscaleURL,
@@ -63,6 +65,8 @@ func startUI(dataDir, nodeID, serverAddr string) {
"node_id": cfg.NodeID,
"data_dir": cfg.DataDir,
"version": version,
+ "server_version": serverVersion,
+ "update_available": updateAvailable,
}
json.NewEncoder(w).Encode(response)
case http.MethodPost:
@@ -104,6 +108,34 @@ func startUI(dataDir, nodeID, serverAddr string) {
}()
})
+ http.HandleFunc("/api/update", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+ cfg, _, err := loadOrCreateConfig(dataDir)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ go func() {
+ broadcastUI(map[string]interface{}{
+ "action": "update_progress",
+ "percent": "10",
+ "message": "Téléchargement de la mise à jour...",
+ })
+ if err := startAgentUpdate(dataDir, cfg.Server); err != nil {
+ log.Printf("Agent update failed: %v", err)
+ broadcastUI(map[string]interface{}{
+ "action": "update_progress",
+ "percent": "0",
+ "message": "Échec de la mise à jour : " + err.Error(),
+ })
+ }
+ }()
+ w.WriteHeader(http.StatusNoContent)
+ })
+
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
diff --git a/agent/ui/index.html b/agent/ui/index.html
index 7a9bbd9..45b9b1a 100644
--- a/agent/ui/index.html
+++ b/agent/ui/index.html
@@ -479,6 +479,8 @@