Feat: System-Wartung-Rework + Hermes-Update-Button

Wartungs-Tab im SystemDrawer neu strukturiert:
- UPDATES: eine einheitliche Liste (OS, Engine, Hermes-Agent, Modell-Upgrades) mit
  Status + Aktion-Button, der nur aktiv ist wenn ein Update ansteht (statt Klick-Karten,
  die sofort updaten). Konsistent mit der Dashboard-UpdatesCard.
- DIENSTE: neue Sektion, alle systemd-Units mit Status-Punkt (aus /api/system/services,
  jetzt inkl. mem0-service) + Restart + Logs-Sprung.
- BACKUP: kompakt (letztes Backup + Snapshot-Button + Restore-Hinweis).
- GEFAHRENZONE: Reboot abgetrennt. Jobs nur wenn vorhanden.

Hermes-Update-Button: POST /api/maintenance/hermes-update -> Job (Backup -> `hermes update
--yes` (git pull + deps) -> hermes-gateway restart). Backend: hermes_update_job + USER_SERVICES
um mem0-service/hermes-terminal ergaenzt (Restart ging vorher nicht), mem0 in services-API.

Live verifiziert: Button hat Hermes d470ed0 -> 3b44a3c aktualisiert, Integration intakt
(memory.provider, Plugin laedt), Pre-Update-Backup angelegt, Drawer rendert fehlerfrei.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 21:59:04 +02:00
parent b38e3360c5
commit 58dda66f84
10 changed files with 681 additions and 611 deletions
+5
View File
@@ -45,6 +45,11 @@ def engine_update(body: SudoReq) -> dict:
return res
@router.post("/maintenance/hermes-update")
def hermes_update() -> dict:
return maintenance.hermes_update_job()
@router.post("/maintenance/reboot")
def reboot(body: SudoReq) -> dict:
return maintenance.reboot(body.sudo_password)
+11 -1
View File
@@ -12,7 +12,9 @@ import subprocess
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL
import httpx
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, MEM0_SERVICE_URL
from services import backup as backup_svc
from services.agent import agent_status
from services.gateway import gateway_reachable
@@ -36,6 +38,13 @@ def status() -> dict:
return system_status()
def _mem0_reachable() -> bool:
try:
return httpx.get(f"{MEM0_SERVICE_URL}/health", timeout=2).status_code == 200
except Exception:
return False
@router.get("/system/services")
def services() -> dict:
"""Aggregierte Erreichbarkeit aller Stack-Dienste (für die Health-Anzeige)."""
@@ -47,6 +56,7 @@ def services() -> dict:
{"name": "Gateway (integriert)", "url": gw_url, "ok": gateway_reachable()},
{"name": "Hermes-Gateway", "url": HERMES_API_URL, "ok": a["gateway_reachable"]},
{"name": "Hermes-Terminal", "url": a["terminal_url"], "ok": a["terminal_reachable"]},
{"name": "Mem0 (Gedächtnis)", "url": MEM0_SERVICE_URL, "ok": _mem0_reachable()},
],
"links": {
"engine_ui": f"{LLAMA_SWAP_URL}/ui",
+20 -1
View File
@@ -19,7 +19,7 @@ from services import catalog, discover, jobengine, llamaswap, system
# System-Dienste (root, via sudo -n NOPASSWD) vs. User-Dienste (systemctl --user).
SYSTEM_SERVICES = {"llama-swap"}
USER_SERVICES = {"mission-control-2", "hermes-gateway", "hermes-dashboard", "hermes-webui"}
USER_SERVICES = {"mission-control-2", "hermes-gateway", "hermes-terminal", "mem0-service"}
# Engine-Update: lädt den neuesten Vulkan-Build (deploy/update-engine.sh, läuft als root).
_REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
@@ -346,6 +346,25 @@ def engine_update_job(sudo_password: str | None = None) -> dict | None:
return {"ok": True, "job_id": job_id}
def hermes_update_job() -> dict:
"""Hermes-Agent aktualisieren wie die CLI (`hermes update` = git pull + Deps), danach
den Gateway neu starten. Davor ein Sicherheits-Backup (unser deploy/backup.sh). Kein sudo
(alles im User-Space). Läuft als Hintergrund-Job (kann ~1 Min dauern)."""
git = system.find_hermes_agent_git()
path = (git or {}).get("path") or os.path.expanduser("~/.hermes/hermes-agent")
py = os.path.join(path, "venv", "bin", "python")
backup = os.path.join(_REPO_ROOT, "deploy", "backup.sh")
cmd = (f"bash {backup} || true; "
f"cd {path} && {py} -m hermes_cli.main update --yes "
f"&& systemctl --user restart hermes-gateway")
def on_done():
_comp_cache.update(ts=0.0, data=[]) # Update-Status neu berechnen lassen
job_id = jobengine.start_job(["bash", "-c", cmd], "Hermes-Agent-Update", on_done=on_done)
return {"ok": True, "job_id": job_id}
def reboot(sudo_password: str | None = None) -> dict:
if err := check_sudo_needs_password(sudo_password):
return err