boxwart: Update-Verlauf zeigt das echte Ergebnis je Baustein, festgehaltene Bausteine sichtbar
Ampel / ampel (push) Failing after 22s

Der Verlauf las bisher nur den Hermes-Cron-Status ("Skript lief") und zeigte die
Laeufe vom 06. und 13.09. als "durchgelaufen", obwohl dort zurueckgerollt und
festgehalten wurde; ein abgebrochener Lauf stand als "laeuft" da. Jetzt kommt der
Verlauf aus dem Meldeprotokoll (~/mc2-notify.log), also aus dem, was autoupdate.sh
selbst je Baustein meldet.

Festgehaltene Bausteine (Pin-Register) werden zum Waechter-Hinweis mit Knopf
"Freigeben", auf der Updates-Seite markiert und in der Updates-Lampe gezaehlt.
Hermes zeigt seine laufende Version und die Zahl der neuen Aenderungen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-23 21:56:19 +02:00
co-authored by Claude Opus 5.5
parent f3af2adec3
commit 790de19f1a
21 changed files with 544 additions and 80 deletions
+20 -24
View File
@@ -10,6 +10,7 @@ gesehen. Dieser Wächter schaut deshalb breiter hin:
• Hermes-Jobs Status der Cron-Jobs + Werkzeugfehler im letzten Lauf (errors.log)
• Kern Engine, Hirn, Hermes, Hör-Dienst, MC2, Gateway antworten per HTTP
• Platte Füllstand des Modell-Laufwerks
• Updates Bausteine, die der Sonntags-Lauf nach einem Fehlschlag festhält
Jeder Befund wird zum Hinweis mit Stufe (rot = jetzt, gelb = bei Gelegenheit), Beginn und
Knöpfen. Einfaches behebt er selbst (User-Entscheid 23.09.): Ein ABGESTÜRZTER Dienst
@@ -43,7 +44,7 @@ import httpx
import psutil
from config import HERMES_API_KEY, HERMES_API_URL, HERMES_HOME, MODELS_DIR, VOICE_SERVICE_URL
from services import announce, llamaswap, maintenance
from services import announce, llamaswap, maintenance, update_verlauf
log = logging.getLogger(__name__)
@@ -246,28 +247,6 @@ def _letzter_lauf(job_id: str) -> dict | None:
return {"status": row[0], "started_at": row[1], "finished_at": row[2], "error": row[3]}
def update_laeufe(anzahl: int = 8) -> list[dict]:
"""Letzte Läufe aller Update-Jobs (Name enthält „update“), neueste zuerst."""
ids = {str(j.get("id")): str(j.get("name", "")) for j in _hermes_jobs()
if "update" in str(j.get("name", "")).lower()}
if not ids or not HERMES_EXEC_DB.exists():
return []
platz = ",".join("?" * len(ids))
try:
con = sqlite3.connect(f"file:{HERMES_EXEC_DB}?mode=ro", uri=True, timeout=2)
try:
rows = con.execute(
f"select job_id, status, started_at, finished_at, error from executions "
f"where job_id in ({platz}) order by started_at desc limit ?",
(*ids, anzahl)).fetchall()
finally:
con.close()
except sqlite3.Error:
return []
return [{"job": ids.get(r[0], r[0]), "status": r[1], "start": r[2], "ende": r[3],
"fehler": (r[4] or None)} for r in rows]
def werkzeugfehler_im_lauf(zeilen: list[str], job_id: str, start_iso: str) -> tuple[int, str]:
"""Zählt 'Tool X returned error'-Zeilen eines Laufs in errors.log. Hermes markiert jede
Zeile mit [cron_<job>_<JJJJMMTT>_<HHMMSS>]; der Tag des Laufstarts reicht zur Zuordnung."""
@@ -389,7 +368,20 @@ def pruefe_platte() -> list[Befund]:
quelle="platte", sofort=True)]
PRUEFUNGEN = (pruefe_dienste, pruefe_timer_dienste, pruefe_hermes_jobs, pruefe_kern, pruefe_platte)
def pruefe_festgehalten() -> list[Befund]:
"""Festgehaltene Bausteine (Pin-Register von autoupdate.sh). Vom 06. bis 17.09.2026 hielt
die Box Motor und Hermes fest, ohne dass es jemand sah — elf Tage ohne Updates."""
return [Befund(id=f"pin:{p['baustein']}", stufe="gelb",
titel=f"{p['name']} bekommt keine Updates mehr",
text=(f"Seit {p['seit']} auf {p['version']} festgehalten: {p['grund']}. "
"Der Sonntags-Lauf überspringt ihn, bis du ihn freigibst."),
quelle="updates", sofort=True,
aktionen=[_aktion("freigeben", "Freigeben", baustein=p["baustein"])])
for p in update_verlauf.festgehalten()]
PRUEFUNGEN = (pruefe_dienste, pruefe_timer_dienste, pruefe_hermes_jobs, pruefe_kern, pruefe_platte,
pruefe_festgehalten)
# --- Zustand, Takt, Selbstreparatur -----------------------------------------------
@@ -564,6 +556,10 @@ def fuehre_aktion_aus(hinweis_id: str, aktion_id: str) -> dict:
if aktion.get("job"):
return _job_protokoll(aktion["job"])
return maintenance.logs(aktion["dienst"], lines=120)
if aktion_id == "freigeben":
if not update_verlauf.freigeben(aktion["baustein"]):
return {"ok": False, "detail": "Der Baustein war schon freigegeben."}
return {"ok": True, "text": update_verlauf.FREIGEGEBEN_TEXT}
if aktion_id == "job-wiederholen":
try:
r = subprocess.run(["hermes", "cron", "run", aktion["job"]],