"""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 ablaeufe, dienste, maintenance from services import backup as backup_svc 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.get("/system/ablaeufe") def ablaeufe_liste() -> dict: """Die geplanten Abläufe der Box mit letztem und nächstem Lauf — der Totmannschalter (services/ablaeufe.py).""" return {"ablaeufe": ablaeufe.ablaeufe()} @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)