feat(2.0): Phase 5 — Betrieb, Observability + Politur
Backup: services/backup.py (Memory-SQLite inkl. WAL/SHM + Configs, retain 7) + deploy/backup.sh + POST /api/system/backup + GET /api/system/backups. Services-Health: GET /api/system/services (aggregierte Reachability + Observability-Links). Frontend: SystemView mit Dienste-Health + Backup- Button + Links; Theme-Toggle (Hell/Dunkel, persistent). Lokal verifiziert: Backup-Snapshot + services-Aggregat (TestClient), Frontend-Build + Browser (Light-Mode bestaetigt via computed styles). Damit Phasen 0-5 lokal fertig. Docs aktualisiert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Backup der „Verfassung" (Shared-Memory-SQLite) + aller Configs.
|
||||
Snapshot in einen Zeitstempel-Ordner; die letzten N bleiben erhalten.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from config import CONFIG_PATH, GATEWAY_CONFIG_PATH, MEMORY_DB, MODELS_DIR
|
||||
|
||||
BACKUP_DIR = Path(MODELS_DIR) / "mc2-backups"
|
||||
RETAIN = 7
|
||||
|
||||
|
||||
def _safe_copy(src: Path, dst_dir: Path) -> str | None:
|
||||
try:
|
||||
if src and src.exists():
|
||||
shutil.copy2(src, dst_dir / src.name)
|
||||
return src.name
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def backup_now() -> dict:
|
||||
"""Erstellt einen Snapshot (Memory-DB inkl. WAL/SHM + Configs). Alte Snapshots
|
||||
über RETAIN hinaus werden entfernt."""
|
||||
ts = time.strftime("%Y%m%d-%H%M%S")
|
||||
dst = BACKUP_DIR / ts
|
||||
dst.mkdir(parents=True, exist_ok=True)
|
||||
saved = []
|
||||
for src in (MEMORY_DB, Path(str(MEMORY_DB) + "-wal"), Path(str(MEMORY_DB) + "-shm"),
|
||||
CONFIG_PATH, GATEWAY_CONFIG_PATH):
|
||||
if (name := _safe_copy(src, dst)):
|
||||
saved.append(name)
|
||||
# Aufräumen: nur die letzten RETAIN Snapshots behalten.
|
||||
snaps = sorted([p for p in BACKUP_DIR.iterdir() if p.is_dir()], reverse=True)
|
||||
for old in snaps[RETAIN:]:
|
||||
shutil.rmtree(old, ignore_errors=True)
|
||||
return {"ok": bool(saved), "snapshot": ts, "files": saved, "dir": str(dst)}
|
||||
|
||||
|
||||
def list_backups() -> list[dict]:
|
||||
if not BACKUP_DIR.exists():
|
||||
return []
|
||||
out = []
|
||||
for p in sorted([d for d in BACKUP_DIR.iterdir() if d.is_dir()], reverse=True):
|
||||
files = [f.name for f in p.iterdir() if f.is_file()]
|
||||
out.append({"snapshot": p.name, "files": files})
|
||||
return out
|
||||
Reference in New Issue
Block a user