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>
103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
"""Wartungs-Endpoints: Update-Badge, OS-/Engine-Update, Reboot, Restart, Logs.
|
|
|
|
v3-Umbau P1 (28.08.2026): Das Sudo-Passwort ist hier ersatzlos entfallen. Auf der Box
|
|
gemessen — `sudo -n true` läuft durch, weil `/etc/sudoers` den Dienst-Nutzer mit
|
|
`NOPASSWD: ALL` führt. Das Passwort wurde also nie gebraucht, lag aber im
|
|
`localStorage` des Browsers und reiste bei jedem mutierenden Request mit. Sollte die
|
|
sudoers-Zeile je fallen, meldet `services.maintenance` sauber `password_required`
|
|
statt still zu scheitern — das ist dann ein Konfigurations-Signal und nichts, was man
|
|
mit einem im Browser geparkten Geheimnis übertüncht.
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
from services import geheimnisse, maintenance
|
|
|
|
router = APIRouter(prefix="/api")
|
|
|
|
|
|
class RestartReq(BaseModel):
|
|
service: str
|
|
|
|
|
|
class GeheimnisReq(BaseModel):
|
|
schluessel: str
|
|
wert: str | None = None
|
|
|
|
|
|
@router.get("/maintenance/updates")
|
|
def updates() -> dict:
|
|
return maintenance.updates()
|
|
|
|
|
|
@router.get("/maintenance/update-details")
|
|
def update_details(kind: str) -> dict:
|
|
if kind not in ("os", "engine", "swap", "hermes"):
|
|
raise HTTPException(400, "Unbekannte Update-Art.")
|
|
return maintenance.update_details(kind)
|
|
|
|
@router.post("/maintenance/check-updates")
|
|
def check_updates() -> dict:
|
|
res = maintenance.check_updates_job()
|
|
if isinstance(res, dict) and not res.get("ok", True):
|
|
return res
|
|
return res
|
|
|
|
|
|
@router.post("/maintenance/os-update")
|
|
def os_update() -> dict:
|
|
res = maintenance.os_update_job()
|
|
if isinstance(res, dict) and not res.get("ok", True):
|
|
return res
|
|
return res
|
|
|
|
|
|
@router.post("/maintenance/engine-update")
|
|
def engine_update() -> dict:
|
|
res = maintenance.engine_update_job()
|
|
if not res:
|
|
raise HTTPException(400, "Kein Engine-Update-Befehl gesetzt (MC_ENGINE_UPDATE_CMD).")
|
|
return res
|
|
|
|
|
|
@router.post("/maintenance/swap-update")
|
|
def swap_update() -> dict:
|
|
res = maintenance.swap_update_job()
|
|
if not res:
|
|
raise HTTPException(400, "Kein Router-Update-Befehl gesetzt (MC_SWAP_UPDATE_CMD).")
|
|
return res
|
|
|
|
|
|
@router.post("/maintenance/hermes-update")
|
|
def hermes_update() -> dict:
|
|
return maintenance.hermes_update_job()
|
|
|
|
|
|
@router.post("/maintenance/update-all")
|
|
def update_all() -> dict:
|
|
return maintenance.update_all_job()
|
|
|
|
|
|
@router.post("/maintenance/restart")
|
|
def restart(body: RestartReq) -> dict:
|
|
return maintenance.restart_service(body.service)
|
|
|
|
|
|
@router.get("/maintenance/logs")
|
|
def logs(service: str, lines: int = 200) -> dict:
|
|
return maintenance.logs(service, lines)
|
|
|
|
|
|
@router.get("/maintenance/geheimnisse")
|
|
def geheimnisse_status() -> dict:
|
|
"""Nur der Zustand — ob ein Token gesetzt ist, niemals sein Wert."""
|
|
return geheimnisse.status()
|
|
|
|
|
|
@router.post("/maintenance/geheimnisse")
|
|
def geheimnisse_setzen(body: GeheimnisReq) -> dict:
|
|
"""Setzt (oder löscht bei leerem Wert) ein Geheimnis in der Box-Ablage."""
|
|
if not geheimnisse.setzen(body.schluessel, body.wert):
|
|
raise HTTPException(400, f"Geheimnis '{body.schluessel}' konnte nicht gespeichert werden.")
|
|
return {"ok": True, **geheimnisse.status()}
|