alpinux.site.2026/admin/builds.py
Cédrix e50eec1785 security: remplace les chemins système hardcodés par des variables d'env
admin/builds.py : STATE_FILE, LOG_FILE, DEPLOY_SCRIPT lus depuis l'environnement
dynamic/db.py   : supprime le fallback de chemin système pour DATABASE
admin/.env.example : documente les nouvelles variables STATE_FILE, LOG_FILE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:34:38 +02:00

103 lines
3.1 KiB
Python

"""
Gestion des builds : exécution, état, historique.
Un seul build peut tourner à la fois.
"""
import json
import os
import subprocess
import threading
import time
from datetime import datetime
from pathlib import Path
STATE_FILE = Path(os.environ.get("STATE_FILE", "/tmp/admin-state.json"))
LOG_FILE = Path(os.environ.get("LOG_FILE", "/tmp/admin-build.log"))
DEPLOY_SCRIPT = Path(os.environ.get("DEPLOY_SCRIPT", "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)