"""System-Endpoints: Live-Status, Dienste-Liste, Sicherung, Neustart, Token-Verbrauch. Wartung läuft als systemd-USER-Dienst → KEIN sudo/Passwort (Nordstern). Lokal (Windows) schlagen die Shell-Befehle harmlos fehl und werden als Fehler zurückgegeben statt zu crashen. """ import logging from fastapi import APIRouter from pydantic import BaseModel from services import backup as backup_svc from services import dienste, maintenance from services.llamaswap import list_models from services.pricing import compute_savings from services.system import system_status from services.token_stats import get_stats log = logging.getLogger(__name__) router = APIRouter(prefix="/api") @router.get("/system/status") def status() -> dict: return system_status() @router.get("/system/services") def services() -> dict: """Alle Dienste der Box mit Erreichbarkeit, Zustand und Kennzahlen (Speicher, CPU, Laufzeit, Neustarts) — die EINE Quelle für die Seite „Dienste und Protokolle“ und die MCP-Werkzeuge. Logik in services/dienste.py; `scope`: user|system (Restart-Weg), `unit`: echter systemd-Name.""" return dienste.liste() @router.post("/system/backup") def backup() -> dict: return backup_svc.backup_now() @router.get("/system/backups") def backups() -> dict: return {"backups": backup_svc.list_backups()} class RestartReq(BaseModel): service: str @router.post("/system/restart") def restart(req: RestartReq) -> dict: """Dienst neustarten. Delegiert an den Wartungs-Service (EINE Allowlist-Wahrheit): der kennt System-Dienste (llama-swap, via sudo -n) UND User-Dienste und wird auch von der UI (/api/maintenance/restart) genutzt. Vorher lag hier eine zweite, veraltete Liste (ohne llama-swap/hermes-terminal, mit Geist-Eintrag hermes-webui) → Engine-Neustart per Sprache/MCP schlug fehl.""" return maintenance.restart_service(req.service) @router.get("/system/token-stats") def token_stats() -> dict: """Token-Verbrauch + Cloud-Ersparnis. Logik im pricing-Service (SSoT).""" # Rolle je Modell/Alias (lowercase) für die Tarif-Auflösung auflösen. role_map: dict[str, str | None] = {} try: for m in list_models(): role_map[m["name"].lower()] = m.get("role") for alias in m.get("aliases", []): role_map[alias.lower()] = m.get("role") except Exception: log.warning("token_stats: list_models fehlgeschlagen, Tarife per Name", exc_info=True) return compute_savings(get_stats(), role_map)