diff --git a/SUIVI_VPN_ONDEMAND.md b/SUIVI_VPN_ONDEMAND.md index 99cf7b6..46118e7 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.9-windows.zip` +- **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.9.exe` +- **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.9` +- **Linux** : `https://studioe5.edudeploy.com/studioE5-agent-v0.3.10` ### 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.9-windows.zip` et copie les binaires versionnés dans `server/public/`. +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.9-windows.zip`). +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** : @@ -778,7 +778,7 @@ Créer un package d’installation unique et professionnel par OS, incluant l’ ### 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. +- **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.). --- @@ -806,10 +806,11 @@ Créer un package d’installation unique et professionnel par OS, incluant l’ - [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. +- [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 diff --git a/agent/VERSION b/agent/VERSION index 940ac09..5503126 100644 --- a/agent/VERSION +++ b/agent/VERSION @@ -1 +1 @@ -0.3.9 +0.3.10 diff --git a/agent/docker.go b/agent/docker.go index 9c3aff9..2088c10 100644 --- a/agent/docker.go +++ b/agent/docker.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "log" "os" "os/exec" "path/filepath" @@ -92,7 +93,11 @@ func dockerComposeRm(dataDir, instanceID string) error { if err := cmd.Run(); err != nil { return err } - return os.RemoveAll(dir) + if err := os.RemoveAll(dir); err != nil { + log.Printf("dockerComposeRm: failed to remove %s: %v (will retry on next startup)", dir, err) + return err + } + return nil } // extractPublicURL tries to find the public URL from a WordPress compose config. diff --git a/agent/instance.go b/agent/instance.go index 6779223..2ac71f2 100644 --- a/agent/instance.go +++ b/agent/instance.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "log" "os" "os/exec" "path/filepath" @@ -130,3 +131,34 @@ func getInstanceStatus(dataDir, instanceID string) string { } return "stopped" } + +// cleanupOrphanInstanceDirs removes instance directories that have no entry in +// instances.json. This typically happens on Windows when a delete operation +// could not fully remove the directory because compose.log was locked. +func cleanupOrphanInstanceDirs(dataDir string) { + instancesDir := filepath.Join(dataDir, "instances") + inst, err := loadInstances(dataDir) + if err != nil { + log.Printf("cleanupOrphanInstanceDirs: loadInstances error: %v", err) + return + } + entries, err := os.ReadDir(instancesDir) + if err != nil { + if !os.IsNotExist(err) { + log.Printf("cleanupOrphanInstanceDirs: ReadDir error: %v", err) + } + return + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + if _, ok := inst[entry.Name()]; !ok { + dir := filepath.Join(instancesDir, entry.Name()) + log.Printf("cleanupOrphanInstanceDirs: removing orphan directory %s", dir) + if err := os.RemoveAll(dir); err != nil { + log.Printf("cleanupOrphanInstanceDirs: RemoveAll error for %s: %v", dir, err) + } + } + } +} diff --git a/agent/main.go b/agent/main.go index bbb05f4..d6a8d23 100644 --- a/agent/main.go +++ b/agent/main.go @@ -62,6 +62,10 @@ func main() { log.Printf("[%s Agent] version=%s node=%s data-dir=%s server=%s", APP_NAME, version, cfg.NodeID, *dataDir, cfg.Server) + // Clean up instance directories left behind by failed deletes (common on + // Windows when compose.log is locked during removal). + cleanupOrphanInstanceDirs(*dataDir) + // Ensure Podman machine DNS is configured on Windows/macOS so images can be // pulled and containers can reach the internet. ensurePodmanMachineDNS()