"""Plattengesundheit der KI-Box (Ausbauplan „Später“, Punkt 18, seit 26.09.2026): smartctl (Paket smartmontools, seit 26.09.2026 auf der Box) je Platte aus lsblk, höchstens stündlich, bewertet mit kern/platten.py. smartctl braucht root (sudo -n); fehlt es oder verweigert sudo, gibt es schlicht keine Werte — nie einen Absturz (Windows, Tests). """ import json import logging import shutil import subprocess import threading import time from kern import platten as kern_platten log = logging.getLogger(__name__) CACHE_S = 3600 _lock = threading.Lock() _cache: dict = {"zeit": 0.0, "platten": None} def _laufen(befehl: list[str], zeitlimit: int = 30) -> tuple[int, str]: try: r = subprocess.run(befehl, capture_output=True, text=True, timeout=zeitlimit) except (OSError, subprocess.SubprocessError): return 127, "" return r.returncode, r.stdout def geraete() -> list[str]: code, aus = _laufen(["lsblk", "-J", "-d", "-o", "NAME,TYPE"], 10) try: return [f"/dev/{d['name']}" for d in json.loads(aus).get("blockdevices") or [] if d.get("type") == "disk" and not str(d.get("name", "")).startswith(("zram", "loop"))] if code == 0 else [] except (ValueError, AttributeError): return [] def lesen() -> list[dict]: if not shutil.which("sudo"): # Windows, Tests ohne sudo: keine Werte return [] ergebnis = [] for geraet in geraete(): _, aus = _laufen(["sudo", "-n", "smartctl", "-j", "-a", geraet]) try: daten = json.loads(aus) except ValueError: continue # smartctl setzt Bits im Exit-Code auch für „Fehler im Protokoll“ — gelesen ist gelesen. if not isinstance(daten, dict) or not (daten.get("model_name") or daten.get("nvme_smart_health_information_log")): continue ergebnis.append(kern_platten.aus_smartctl(daten, geraet, "KI-Box")) return ergebnis def platten() -> list[dict]: with _lock: if _cache["platten"] is not None and time.time() - _cache["zeit"] < CACHE_S: return _cache["platten"] werte = lesen() with _lock: _cache.update(zeit=time.time(), platten=werte) return werte def lage() -> dict: """Für GET /api/platten.""" return {"platten": kern_platten.bewertet(platten())} def hinnehmen(pid: str) -> dict: return kern_platten.hinnehmen(pid, platten()) def pruefe_platten_gesundheit() -> list: """Für den Wächter (Rolle box).""" from services.waechter import Befund, _aktion befunde = [] for p in lage()["platten"]: if p["stufe"] == "ok": continue fehler = any("→" in s for s in p["saetze"]) befunde.append(Befund( id=f"platte:{p['id']}", stufe=p["stufe"], titel=kern_platten.befund_titel(p), text="; ".join(p["saetze"]) + ". Wichtiges sichern; wird es schlechter, die Platte ersetzen.", quelle="platte", aktionen=[_aktion("hinnehmen", "Zur Kenntnis genommen", platte=p["id"])] if fehler else [], sofort=True)) return befunde