""" Gestion des builds : exécution, état, historique. Un seul build peut tourner à la fois. """ import json import subprocess import threading import time from datetime import datetime from pathlib import Path STATE_FILE = Path("/var/lib/alpinux-admin/builds.json") LOG_FILE = Path("/var/lib/alpinux-admin/current.log") DEPLOY_SCRIPT = Path("/home/alpinux/site/scripts") / "deploy-wiki.sh" _lock = threading.Lock() def _load_state() -> dict: if STATE_FILE.exists(): try: return json.loads(STATE_FILE.read_text()) except Exception: pass return {"running": False, "history": []} def _save_state(state: dict): STATE_FILE.parent.mkdir(parents=True, exist_ok=True) STATE_FILE.write_text(json.dumps(state, indent=2)) def get_state() -> dict: return _load_state() def get_log(lines: int = 200) -> str: if LOG_FILE.exists(): all_lines = LOG_FILE.read_text(encoding="utf-8", errors="replace").splitlines() return "\n".join(all_lines[-lines:]) return "" def is_running() -> bool: return _load_state().get("running", False) def start_build(triggered_by: str) -> bool: """Lance le build dans un thread. Retourne False si déjà en cours.""" with _lock: state = _load_state() if state.get("running"): return False state["running"] = True state["started_at"] = datetime.now().isoformat(timespec="seconds") state["triggered_by"] = triggered_by _save_state(state) LOG_FILE.parent.mkdir(parents=True, exist_ok=True) LOG_FILE.write_text("") threading.Thread(target=_run_build, args=(triggered_by,), daemon=True).start() return True def _run_build(triggered_by: str): started = time.time() success = False try: with open(LOG_FILE, "w", buffering=1) as log: log.write(f"=== Build démarré le {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} par {triggered_by} ===\n\n") proc = subprocess.Popen( ["bash", str(DEPLOY_SCRIPT)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, ) for line in proc.stdout: log.write(line) log.flush() proc.wait() success = proc.returncode == 0 log.write(f"\n=== Terminé (code {proc.returncode}) en {int(time.time()-started)}s ===\n") except Exception as exc: with open(LOG_FILE, "a") as log: log.write(f"\n=== ERREUR : {exc} ===\n") with _lock: state = _load_state() state["running"] = False state["last_success"] = success state["last_duration_s"] = int(time.time() - started) entry = { "at": datetime.now().isoformat(timespec="seconds"), "triggered_by": triggered_by, "success": success, "duration_s": int(time.time() - started), } state.setdefault("history", []).insert(0, entry) state["history"] = state["history"][:20] _save_state(state)