""" Software-Stand einer Instanz (seit 24.09.2026): welcher Commit seit wann live ist. Nach einem erfolgreichen Umschalten schreibt deploy/deploy.sh (KI-Box) bzw. deploy/homelab/ausrollen.sh (Container des Homelab-Teils) die Stand-Datei mc2-stand.json in den Datenordner (deploy/stand-schreiben.py): Commit kurz, Betreff, Commit-Datum und den Zeitpunkt des Deploys. Die Oberfläche zeigt sie unter Einstellungen → Software-Stand, für diese Instanz über /api/instanz und für die andere über /api/partner/instanz. Ohne Stand-Datei (Entwicklung am PC, Box vor dem ersten Deploy mit dieser Datei) kommt der Stand aus git, sonst heißt er „unbekannt“. Nur lesend. """ import json import os import subprocess import threading import time from pathlib import Path from kern.einstellungen import einstellungen REPO = Path(__file__).resolve().parents[2] GIT_CACHE_S = 60.0 _lock = threading.Lock() _git_cache: dict = {"ts": 0.0, "stand": None} def stand_datei() -> Path: return Path(os.environ.get("MC_STAND_DATEI", str(einstellungen().daten_dir / "mc2-stand.json"))) def _text(wert: object) -> str | None: return wert.strip() if isinstance(wert, str) and wert.strip() else None def _aus_datei() -> dict | None: try: daten = json.loads(stand_datei().read_text(encoding="utf-8")) except (OSError, ValueError): return None if not isinstance(daten, dict) or not _text(daten.get("commit")): return None return {"quelle": "deploy", **{k: _text(daten.get(k)) for k in ("commit", "betreff", "commit_datum", "deploy_zeit")}} def _git_log() -> str | None: """Kopf des Checkouts, in dem dieser Code liegt. Die Schicht, die Tests ersetzen.""" try: r = subprocess.run(["git", "-C", str(REPO), "log", "-1", "--format=%h%n%cI%n%s"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5) except (OSError, subprocess.SubprocessError): return None return r.stdout if r.returncode == 0 else None def _aus_git() -> dict | None: with _lock: if time.time() - _git_cache["ts"] < GIT_CACHE_S: return _git_cache["stand"] ausgabe = _git_log() stand = None if ausgabe: commit, datum, betreff = ([z.strip() for z in ausgabe.splitlines()] + ["", "", ""])[:3] if commit: stand = {"quelle": "git", "commit": commit, "betreff": betreff or None, "commit_datum": datum or None, "deploy_zeit": None} with _lock: _git_cache.update(ts=time.time(), stand=stand) return stand def stand() -> dict: """{"quelle": "deploy"|"git"|"unbekannt", "commit", "betreff", "commit_datum", "deploy_zeit"}.""" return _aus_datei() or _aus_git() or {"quelle": "unbekannt", "commit": None, "betreff": None, "commit_datum": None, "deploy_zeit": None}