sicherung: PBS-Sicherung zurueck ins Repo (ohne mem0), monatliche Probe-Wiederherstellung

- PBS: deploy/pbs-backup.sh, pbs-backup.service und .timer (User-Units, 03:50) aus der Box
  uebernommen. Die Sicherung lief vom 09.07. bis 20.08. taeglich gruen und brach am 21.08. ab,
  weil ihr erster Quellordner /srv/models/mem0 (altes Gedaechtnis) fehlte; danach im
  KISS-Umbau abgeschaltet. Das neue Skript sichert ~/.hermes und /etc/llama-swap (Pflicht)
  und ~/wissens-vault (optional), prueft Zugangsdatei und Client, und liest den Zugang erst
  direkt vor dem Aufruf ein. deploy.sh spielt die Units aus, schaltet sie aber nicht ein
  (Einschalten von Hand, docs/BETRIEB.md). Der Waechter zeigt einen gescheiterten Lauf gelb.
- Probe-Wiederherstellung: services/probe_wiederherstellung.py packt die juengste Sicherung
  in /var/tmp/mc2-probe-* aus (ohne Geheimnisse und Verknuepfungen), prueft Alter (<= 48 h),
  Lesbarkeit und Pflichtinhalte (Gedaechtnis und SOUL.md auch gegen den lebenden Stand,
  Skills, Cron-Skripte und -Jobs, Hermes-Config, .env, llama-swap-Config, Box-Wart-Zustand,
  Units) und loescht den Ordner wieder. Ergebnis in /srv/models/mc2-probe-wiederherstellung.json,
  bei Rot Meldung "[Sicherung]" ueber notify.sh in normaler Dringlichkeit.
  Timer mc2-probe-wiederherstellung.timer: erster Montag im Monat 05:15 (nie sonntags, lange
  nach 03:30), Persistent=true; deploy.sh aktiviert ihn.
- Waechter: gelb, wenn die letzte Probe rot war, aelter als 40 Tage ist oder (mit Timer) noch
  nie lief; Knoepfe "Jetzt pruefen" und "Protokoll" (Allowlist in services.maintenance).
- Tests: Probe gruen/rot je Pflichtinhalt, Abgleich, kaputte und alte Sicherung, Meldung,
  Waechter-Hinweis, Unit-Listen in deploy.sh, Takt, LF, pbs-backup.sh mit Client-Attrappe.
  Trockenlauf gegen die echte Sicherung der Box (nur in /tmp): alle Pflichtinhalte gruen, 0,4 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-24 20:41:33 +02:00
co-authored by Claude Opus 5.5
parent 93f6613146
commit 2e0ad70a85
11 changed files with 947 additions and 6 deletions
+40 -2
View File
@@ -11,6 +11,7 @@ gesehen. Dieser Wächter schaut deshalb breiter hin:
• 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
• Probe monatliche Probe-Wiederherstellung der Sicherung (rot oder zu alt = gelb)
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
@@ -45,8 +46,9 @@ import psutil
from config import HERMES_API_KEY, HERMES_API_URL, HERMES_HOME, VOICE_SERVICE_URL
from kern import partner
from kern.einstellungen import einstellungen
from kern.zeit import LOCAL_TZ
from services import announce, llamaswap, maintenance, update_verlauf
from services import announce, llamaswap, maintenance, probe_wiederherstellung, update_verlauf
log = logging.getLogger(__name__)
ROLLE = einstellungen().rolle
@@ -98,6 +100,9 @@ TIMER_DIENSTE: dict[str, tuple[str, bool]] = {
"mc2-morgenmeldung": ("Morgenmeldung", True),
# Seit 24.09.2026 ein eigener Timer statt Hermes-Cron (Hermes startet sich beim Update selbst neu).
"mc2-autoupdate": ("Updates am Sonntag", True),
# Seit 24.09.2026 im Repo (vom 21.08. an aus, weil sie rot lief und niemand es sah). Gelb: Die tägliche
# Sicherung (mc2-backup) bleibt die erste Schicht, der PBS ist die Kopie auf dem QNAP. Ausgeschaltet kein Befund.
"pbs-backup": ("Sicherung auf den PBS", False),
}
HERMES_JOBS_PATH = HERMES_HOME / "cron" / "jobs.json"
@@ -465,6 +470,39 @@ def pruefe_platte() -> list[Befund]:
quelle="platte", sofort=True)]
def pruefe_probe_wiederherstellung() -> list[Befund]:
"""Monatliche Probe-Wiederherstellung (services/probe_wiederherstellung.py, seit 24.09.2026): gelb, wenn die
letzte Probe rot war oder älter als WARN_TAGE ist. Gab es noch keine, erst, wenn ihr Timer eingerichtet ist."""
einheit = probe_wiederherstellung.EINHEIT
aktionen = [_aktion("neustart", "Jetzt prüfen", dienst=einheit), _aktion("protokoll", "Protokoll", dienst=einheit)]
stand = probe_wiederherstellung.lese_stand()
if stand is None:
z = _systemctl_show(f"{einheit}.timer", False)
if not z or z.get("LoadState") in ("not-found", ""):
return []
return [Befund(id="probe:wiederherstellung", stufe="gelb",
titel="Die Sicherung wurde noch nie probeweise ausgepackt",
text=("Die Probe läuft am ersten Montag im Monat um 05:15. „Jetzt prüfen“ startet sie sofort; "
"sie braucht Sekunden und fasst nichts Lebendes an."),
quelle=einheit, aktionen=aktionen, sofort=True)]
datum = datetime.fromtimestamp(float(stand["zeit"]), LOCAL_TZ).strftime("%d.%m.%Y")
if stand.get("ergebnis") != "gruen":
fehler = [str(f) for f in stand.get("fehler") or []] or ["ohne Begründung"]
weitere = f" (und {len(fehler) - 1} weitere)" if len(fehler) > 1 else ""
return [Befund(id="probe:wiederherstellung", stufe="gelb",
titel="Die letzte Probe-Wiederherstellung war rot",
text=f"Probe vom {datum} ({stand.get('sicherung') or 'keine Sicherung'}): {fehler[0]}{weitere}"[:400],
quelle=einheit, aktionen=aktionen, sofort=True)]
tage = (time.time() - float(stand["zeit"])) / 86400
if tage > probe_wiederherstellung.WARN_TAGE:
return [Befund(id="probe:wiederherstellung", stufe="gelb",
titel=f"Die Sicherung wurde seit {int(tage)} Tagen nicht mehr probeweise ausgepackt",
text=(f"Die letzte Probe am {datum} war grün. Sie soll am ersten Montag im Monat laufen — "
f"ist {einheit}.timer eingeschaltet?"),
quelle=einheit, aktionen=aktionen, sofort=True)]
return []
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."""
@@ -520,7 +558,7 @@ def pruefe_gaeste() -> list[Befund]:
# den Ausführer auf dem Proxmox-Host und seine Gäste.
PRUEFUNGEN = {
"box": (pruefe_dienste, pruefe_timer_dienste, pruefe_hermes_jobs, pruefe_kern, pruefe_platte,
pruefe_festgehalten, pruefe_partner),
pruefe_festgehalten, pruefe_partner, pruefe_probe_wiederherstellung),
"homelab": (pruefe_platte, pruefe_partner, pruefe_ausfuehrer, pruefe_gaeste),
}[ROLLE]
BETREFF_PROBLEM, BETREFF_OK = {"box": ("[Box-Problem]", "[Box wieder ok]"),