Ampel / ampel (push) Successful in 26s
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>
143 lines
5.9 KiB
Python
143 lines
5.9 KiB
Python
"""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
|
|
import os
|
|
import subprocess
|
|
|
|
import httpx
|
|
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, V1_UPSTREAM, VOICE_SERVICE_URL
|
|
from fastapi import APIRouter
|
|
from pydantic import BaseModel
|
|
from services import backup as backup_svc
|
|
from services import maintenance, waechter
|
|
from services.agent import agent_status
|
|
from services.gateway import gateway_reachable
|
|
from services.llamaswap import engine_reachable, 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()
|
|
|
|
|
|
def _voice_reachable() -> bool:
|
|
try:
|
|
return httpx.get(f"{VOICE_SERVICE_URL}/health", timeout=2).status_code == 200
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def _user_unit_active(unit: str) -> bool:
|
|
"""systemd-User-Unit ohne eigenen HTTP-Port (z. B. mc2-steward) — Zustand via is-active.
|
|
Auf Windows/Dev schlägt das harmlos fehl (False)."""
|
|
if os.name != "posix":
|
|
return False
|
|
try:
|
|
r = subprocess.run(["systemctl", "--user", "is-active", unit],
|
|
capture_output=True, text=True, timeout=3)
|
|
return r.stdout.strip() == "active"
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
@router.get("/system/services")
|
|
def services() -> dict:
|
|
"""Aggregierte Erreichbarkeit aller Stack-Dienste — die EINE Quelle für Cockpit-Kachel,
|
|
Schubladen-Liste und Log-Auswahl (Frontend rendert diese Liste, keine UI-Kopie mehr).
|
|
`scope`: user|system (Restart-Weg), `unit`: echter systemd-Name (Restart/Logs/Matching)."""
|
|
a = agent_status()
|
|
gw_url = f"{GATEWAY_URL}/v1"
|
|
rows = [
|
|
{"name": "Engine (llama-swap)", "unit": "llama-swap", "url": LLAMA_SWAP_URL,
|
|
"ok": engine_reachable(), "scope": "system"},
|
|
]
|
|
# Seit UMBAU v3 P1 ist der LLM-Gateway ein EIGENER Prozess — ohne diese Zeile war er
|
|
# in der Dienste-Ampel unsichtbar (Kachel "6/6 grün" bei totem Denkpfad).
|
|
if V1_UPSTREAM:
|
|
rows.append({"name": "LLM-Gateway (Denkpfad)", "unit": "mc2-gateway", "url": V1_UPSTREAM,
|
|
"ok": gateway_reachable(), "scope": "user"})
|
|
# MC2 selbst antwortet gerade auf diesen Request — ok=True ist hier ehrlich;
|
|
# die Zeile existiert für Restart/Logs, nicht als Erreichbarkeits-Orakel.
|
|
rows.append({"name": "Box-Wart (MC2)", "unit": "mission-control-2", "url": gw_url,
|
|
"ok": True, "scope": "user"})
|
|
else:
|
|
rows.append({"name": "Gateway (integriert)", "unit": "mission-control-2", "url": gw_url,
|
|
"ok": gateway_reachable(), "scope": "user"})
|
|
rows += [
|
|
{"name": "Wächter (Steward)", "unit": "mc2-steward", "url": "",
|
|
"ok": _user_unit_active("mc2-steward"), "scope": "user"},
|
|
{"name": "Hermes-Gateway (Agent & Gedächtnis)", "unit": "hermes-gateway", "url": HERMES_API_URL,
|
|
"ok": a["gateway_reachable"], "scope": "user"},
|
|
{"name": "Hermes-Dashboard", "unit": "hermes-builtin-ui", "url": a["hermes_ui_url"],
|
|
"ok": a["hermes_ui_reachable"], "scope": "user"},
|
|
{"name": "Spracherkennung (Desktop-Lucy)", "unit": "voice-service", "url": VOICE_SERVICE_URL,
|
|
"ok": _voice_reachable(), "scope": "user"},
|
|
# Lucys Stimme (pocket-tts) spricht Telegram und Lucy-Desktop — bis 09/2026 fehlte
|
|
# sie hier; die Box-Konsole ist mit dem Box-Wart-Umbau aus MC2 raus.
|
|
{"name": "Lucys Stimme", "unit": "lucy-stimme", "url": "",
|
|
"ok": _user_unit_active("lucy-stimme"), "scope": "user"},
|
|
]
|
|
# Bewusst abgeschaltete Dienste (24.09.2026: Spracherkennung bis zur Android-App) sind kein
|
|
# Fehler — die Oberfläche zeigt sie als „schläft“ mit Knopf zum Wecken statt rot.
|
|
for row in rows:
|
|
row["schlaeft"] = (not row["ok"] and row["scope"] == "user"
|
|
and waechter.schlaeft(waechter.dienst_zustand(row["unit"])))
|
|
return {
|
|
"services": rows,
|
|
"links": {
|
|
"engine_ui": f"{LLAMA_SWAP_URL}/ui",
|
|
"gateway": gw_url,
|
|
"hermes_ui": a["hermes_ui_url"],
|
|
},
|
|
}
|
|
|
|
|
|
@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)
|