Files
mission-control-v2/backend/routers/zeitmaschine.py
T
HitonabiandClaude Opus 5.5 e9f488b56c
Ampel / ampel (push) Successful in 26s
phase1b: Backend entruempelt und robuster (35 tote Routen raus, Sperren, ehrliche Update-Pruefung)
Ballast raus:
- 35 Routen ohne Nutzer entfernt (agent/*, fit, roles, ctx, drafts, groups, routing/policy,
  system/history, system/self-update, maintenance/reboot, zeitmaschine/inhalt, zeitplan,
  voice/health|metrics|trace|voices|reference|tts). Von 95 auf 60.
- Tote Module geloescht: agent-Router, roles, agent_aktivitaet, metrics_history (samt
  10-s-Sampler), voice_metrics, migrate_config, parse_mc2_timeout, scripts/.
- Unbenutzte Funktionen und Konstanten entfernt (Modell-Upgrade-Empfehlung, Draft-/Kontext-
  Setzer, Konsole, PC-Ausfuehrer-Probe, Routing-Policy-Editor ...).

Robuster:
- Jobs in eigener Prozessgruppe (Abbrechen beendet wirklich alles), Zeitlimit je Job-Art,
  start_job_exklusiv: zwei Klicks starten kein doppeltes Update mehr; alte Jobs raeumen sich auf.
- Update-Pruefung meldet Fehler (pruef_fehler, Lampe "Pruefung unklar") statt "aktuell".
- Nach jedem Update sofort neu pruefen (update_stand) statt 10 Minuten alten Stand zeigen.
- llama-swap-Config: Sperre (RLock + flock) fuer UI, Radar, Aufraeumen und Hirn-Umstellung.
- Hermes-Config: bei Lesefehler nichts schreiben, atomar, mit Sicherung.
- Live-Strom und Gateway-Warnung blockieren den Event-Loop nicht mehr (Lucy, OpenChamber).
- Gateway antwortet bei Engine-Ausfall im OpenAI-Fehlerformat (502) statt nacktem 500.
- Abgestuerzte Waechter-Pruefung wird ein gelber Hinweis statt still zu verschwinden.
- Download laedt nur den gewuenschten Quant (vorher bei Fehlen alle Teile aller Varianten),
  Download-Jobs in Gruppe "download"; HF-Suche kodiert den Suchbegriff.
- Herkunftspruefung: schreibende /api-Aufrufe fremder Webseiten werden abgelehnt (keine
  Anmeldung, User-Entscheid); Skripte, Desktop-Lucy und /v1 unveraendert.
- Modellpfade: Eintragen und Loeschen nur innerhalb von MODELS_DIR.
- Dienste-Liste fragt keine abgebauten Dienste mehr ab (PC-Ausfuehrer haette 3 s gekostet).
- SSE-Fehlerzeilen von /api/voice/chat als gueltiges JSON.
- mission-control-2.service: --timeout-graceful-shutdown 3 (Neustart ohne 10-s-Haenger).

Tests: 92 gruen (neu: Herkunft, Quant-Auswahl, abgestuerzte Pruefung).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:08:44 +02:00

60 lines
2.2 KiB
Python

"""Zeitmaschine — Snapshots ansehen und per Klick zu einem Stand zurückkehren.
Die Maschinerie existiert komplett (deploy/backup.sh Timer 03:30, Off-Box-Kopie,
deploy/restore.sh mit Pre-Restore-Sicherung); hier kommt nur der letzte Meter dazu:
Liste + Inhalt in der UI und ein Restore-Start als EIGENE systemd-Unit — restore.sh
stoppt mission-control-2 selbst, als Kind des Backends stürbe der Lauf mittendrin."""
import os
import re
import subprocess
import time
from pathlib import Path
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from services import backup as backup_svc
router = APIRouter(prefix="/api")
_REPO_ROOT = Path(__file__).resolve().parents[2]
RESTORE_SH = _REPO_ROOT / "deploy" / "restore.sh"
_FILE_RX = re.compile(r"^mc2-state-[0-9T:_-]+\.tar\.gz$")
class RestoreIn(BaseModel):
file: str
@router.get("/zeitmaschine")
def list_snapshots() -> dict:
return {"available": os.name == "posix", "backups": backup_svc.list_backups()}
@router.post("/zeitmaschine/restore")
def restore(body: RestoreIn) -> dict:
"""Wiederherstellung starten (detached). restore.sh macht VORHER selbst ein
Sicherheits-Backup des aktuellen Zustands — der Schritt ist also reversibel."""
if os.name != "posix":
raise HTTPException(400, "Wiederherstellen geht nur auf der Box.")
if not _FILE_RX.match(body.file):
raise HTTPException(400, "Ungültiger Snapshot-Name.")
if not (backup_svc.BACKUP_DIR / body.file).is_file():
raise HTTPException(404, "Snapshot nicht gefunden.")
try:
from services import announce
announce.add(f"Zeitmaschine: Wiederherstellung von {body.file} gestartet — "
"die Dienste starten gleich neu.", "[Zeitmaschine]", "zeitmaschine", "silent")
except Exception:
pass
unit = f"mc2-restore-{int(time.time())}"
r = subprocess.run(
["systemd-run", "--user", "--collect", f"--unit={unit}",
"/bin/bash", str(RESTORE_SH), "--yes", body.file],
capture_output=True, text=True, timeout=20)
if r.returncode != 0:
raise HTTPException(500, f"Start fehlgeschlagen: {(r.stderr or r.stdout).strip()[:300]}")
return {"ok": True, "unit": unit}