Compare commits

...

2 commits

Author SHA1 Message Date
b4ca67289a GUI : persister les journaux d'exécution
Chaque action (creer-vm/verifier/deployer) écrit un journal horodaté
<instance>/logs/<hote>-<action>-<date>.log en plus du streaming live.
Si le navigateur se déconnecte, l'exécution continue et le log est capturé
jusqu'au bout (au lieu d'être interrompue). node --check OK, serveur HTTP 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:49:17 -04:00
55847b6df0 GUI : compléter la checklist des secrets attendus
SECRETS_ATTENDUS listait 16 secrets ; en manquaient 8 (OIDC des 3 clients,
cookie oauth2, restic, clé SSH sauvegarde, + nextcloud admin/oidc/bd). Le
panneau Intrants du GUI montre désormais la vraie liste (noms seulement, jamais
de valeur — la garde d'écriture des secrets reste inchangée).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:07:39 -04:00
2 changed files with 43 additions and 6 deletions

View file

@ -3,6 +3,11 @@
## 2026-07-07 (soir)
### Ajouté
- **GUI : persistance des journaux d'exécution.** Chaque action lancée depuis l'interface
(créer-vm / vérifier / déployer) écrit désormais, **en plus du streaming live**, un journal
horodaté `<instance>/logs/<hôte>-<action>-<date>.log` (gitignoré). Bonus : si le navigateur se
déconnecte, l'exécution **continue** et le journal est capturé jusqu'au bout (au lieu d'être
interrompue) — utile pour un déploiement qu'on ne veut pas voir avorter à la fermeture d'un onglet.
- **GUI : vues « Flux » et « Couches » (lecture seule).** Deux nouveaux onglets rendent visibles
dans l'interface deux registres jusque-là en ligne de commande seulement : **Flux** = la matrice
d'audit réseau (rôle · sens · port · pair · chiffrement coloré · raison, depuis les `meta/flux.yml`),

View file

@ -12,6 +12,7 @@ import secrets
import socket
import subprocess
import tempfile
import time
import threading
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
@ -101,12 +102,26 @@ INTRANTS_CLES_INTERDITES = {
}
# Rappel en lecture seule (jamais de valeur) : secrets a fournir via Ansible Vault.
SECRETS_ATTENDUS = [
# Accès Proxmox (proxmox.vault.yml)
"proxmox_api_token_id", "proxmox_api_token_secret",
# PKI (step-ca)
"vault_step_ca_password", "vault_step_ca_provisioner_password",
# Identité (LDAP / Keycloak)
"vault_openldap_admin", "vault_ldap_sssd", "vault_keycloak_admin",
"vault_postgresql_keycloak", "vault_bd_keycloak", "vault_forgejo_admin",
"vault_forgejo_secret_key", "vault_forgejo_internal_token", "vault_bd_forgejo",
"vault_grafana_admin", "vault_redis", "vault_bd_icingadb",
# Bases de données
"vault_postgresql_keycloak", "vault_bd_keycloak", "vault_bd_forgejo",
"vault_bd_icingadb", "vault_bd_nextcloud",
# Forge (Forgejo)
"vault_forgejo_admin", "vault_forgejo_secret_key", "vault_forgejo_internal_token",
# Observabilité / cache
"vault_grafana_admin", "vault_redis",
# Applications (admin)
"vault_nextcloud_admin",
# Secrets OIDC des clients SSO (un par app fédérée)
"vault_grafana_oidc", "vault_forgejo_oidc", "vault_icingaweb2_oidc",
"vault_nextcloud_oidc", "vault_oauth2_cookie",
# Sauvegarde (restic hors-nœud)
"vault_restic_password", "vault_backup_ssh_privkey",
]
# Garde-fous des executions (verifier / deployer) depuis l'interface.
@ -2039,6 +2054,7 @@ class Gestionnaire(BaseHTTPRequestHandler):
self.repondre_json(409, {"erreur": "Une execution est deja en cours."})
return
fichier_vault = None
journal = None
try:
cible = {"deployer": "deployer", "pousser": "deployer",
"creer": "creer-vm"}.get(mode, "verifier-deploiement")
@ -2052,7 +2068,14 @@ class Gestionnaire(BaseHTTPRequestHandler):
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Cache-Control", "no-cache")
self.end_headers()
self._ecrire_flux(f"$ make {cible} HOTE={hote}\n\n")
# Persistance : un journal horodaté par exécution, en plus du streaming live.
dossier_logs = self.inventaire.parents[2] / "logs"
dossier_logs.mkdir(parents=True, exist_ok=True)
chemin_log = dossier_logs / f"{hote}-{cible}-{time.strftime('%Y%m%dT%H%M%S')}.log"
journal = open(chemin_log, "w", encoding="utf-8")
entete = f"$ make {cible} HOTE={hote}\n\n"
journal.write(entete)
self._ecrire_flux(entete)
proc = subprocess.Popen(
["make", cible, f"HOTE={hote}"],
cwd=str(RACINE), env=env,
@ -2061,8 +2084,11 @@ class Gestionnaire(BaseHTTPRequestHandler):
)
try:
for ligne in proc.stdout:
journal.write(ligne)
if not self._ecrire_flux(ligne):
proc.terminate()
# Navigateur déconnecté : on continue à journaliser jusqu'au bout.
for reste in proc.stdout:
journal.write(reste)
break
finally:
rc = proc.wait()
@ -2077,8 +2103,14 @@ class Gestionnaire(BaseHTTPRequestHandler):
marquer_serveur_actif(hote)
except Exception:
pass
self._ecrire_flux(f"\n__FIN__ rc={rc}\n")
journal.write(f"\n__FIN__ rc={rc}\n")
self._ecrire_flux(f"\n[journal : {os.path.relpath(chemin_log, RACINE)}]\n__FIN__ rc={rc}\n")
finally:
if journal:
try:
journal.close()
except OSError:
pass
if fichier_vault:
try:
os.remove(fichier_vault)