Später · 18 Plattengesundheit: Proxmox-Host, NAS über SNMP, NVMe der KI-Box
- kern/platten.py: SMART aus pvesh, smartctl und SNMP in einer Form; rot bei SMART FAILED, kritischer Warnung, aufgebrauchter Reserve, ≥ 90 % verbraucht; gelb bei ≥ 80 %, Hitze, gewachsenen Fehlerzählern (Ausgangslage je Platte, „Zur Kenntnis genommen“ setzt sie neu) - Ausführer: stündlich disks/list + disks/smart im Bericht (nur lesend) - services/homelab/nas_snmp.py: kleiner SNMPv2c-GetBulk-Leser ohne neue Abhängigkeit, QNAP hdTable/diskTable, Zugang 0600, Einstellungen „Verbindung testen und speichern“ - services/platten.py: NVMe der KI-Box über sudo smartctl (smartmontools am 26.09. nachinstalliert) - Wächter beider Rollen, Plattenfeld in beiden Cockpits, Doku Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5325544d89
commit
904598031a
@@ -0,0 +1,89 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user