Feat: Stack-Funktionsprüfung nach OS-/Engine-/Router-Update
Bisher hatte nur das Hermes-Update einen echten Post-Check; OS/Engine/Router liefen mit Exit 0 durch, auch wenn der neue Build den Stack zerschoss (gruener Job trotz totem Stack). Neu: deploy/stack-postcheck.sh prueft nach jedem Update funktional — llama-swap aktiv, /v1/models 200, ECHTE 1-Token-Inferenz auf dem Hirn-Modell (beweist Laden+Generieren), MC2 /api/health engine_reachable, Mem0 erreichbar. Eingehaengt als `<update> && bash stack-postcheck.sh` in os/engine/ swap-update-job → Exit 1 macht den jobengine-Job ROT. Pendant zu hermes-postcheck.sh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,10 @@ SWAP_BIN = os.environ.get("MC_SWAP_BIN", "/usr/local/bin/llama-swap")
|
|||||||
SWAP_REPO = os.environ.get("MC_SWAP_REPO", "mostlygeek/llama-swap")
|
SWAP_REPO = os.environ.get("MC_SWAP_REPO", "mostlygeek/llama-swap")
|
||||||
_swap_cache = {"ts": 0.0, "avail": False}
|
_swap_cache = {"ts": 0.0, "avail": False}
|
||||||
|
|
||||||
|
# Stack-Funktionsprüfung NACH jedem Update (OS/Engine/Router): verifiziert per echter
|
||||||
|
# Inferenz, dass der Stack noch läuft → Job wird rot, wenn ein Update etwas zerschossen hat.
|
||||||
|
STACK_POSTCHECK = os.path.join(_REPO_ROOT, "deploy", "stack-postcheck.sh")
|
||||||
|
|
||||||
|
|
||||||
def _installed_engine_build() -> int | None:
|
def _installed_engine_build() -> int | None:
|
||||||
"""Build-Nummer der installierten llama-server-Binary (z.B. 9821), oder None.
|
"""Build-Nummer der installierten llama-server-Binary (z.B. 9821), oder None.
|
||||||
@@ -475,7 +479,9 @@ def check_updates_job(sudo_password: str | None = None) -> dict:
|
|||||||
def os_update_job(sudo_password: str | None = None) -> dict:
|
def os_update_job(sudo_password: str | None = None) -> dict:
|
||||||
if err := check_sudo_needs_password(sudo_password):
|
if err := check_sudo_needs_password(sudo_password):
|
||||||
return err
|
return err
|
||||||
cmd = "sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y"
|
# Nach dem apt-Upgrade den Stack funktional prüfen (Job wird rot, wenn etwas kaputt ging).
|
||||||
|
cmd = ("sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y "
|
||||||
|
f"&& bash {STACK_POSTCHECK}")
|
||||||
job_id = jobengine.start_job(["bash", "-c", cmd], "OS-Update (apt)", sudo_password=sudo_password)
|
job_id = jobengine.start_job(["bash", "-c", cmd], "OS-Update (apt)", sudo_password=sudo_password)
|
||||||
return {"ok": True, "job_id": job_id}
|
return {"ok": True, "job_id": job_id}
|
||||||
|
|
||||||
@@ -490,7 +496,9 @@ def engine_update_job(sudo_password: str | None = None) -> dict | None:
|
|||||||
_engine_cache.update(ts=0.0, avail=False) # Cache leeren → frischer Build-Vergleich
|
_engine_cache.update(ts=0.0, avail=False) # Cache leeren → frischer Build-Vergleich
|
||||||
|
|
||||||
# update-engine.sh läuft via sudo als root und startet llama-swap am Ende selbst neu.
|
# update-engine.sh läuft via sudo als root und startet llama-swap am Ende selbst neu.
|
||||||
job_id = jobengine.start_job(["bash", "-c", ENGINE_UPDATE_CMD],
|
# Danach Stack-Funktionsprüfung (echte Inferenz) → Job wird rot, wenn der neue Build kaputt ist.
|
||||||
|
cmd = f"{ENGINE_UPDATE_CMD} && bash {STACK_POSTCHECK}"
|
||||||
|
job_id = jobengine.start_job(["bash", "-c", cmd],
|
||||||
"Engine-Update (llama.cpp Vulkan)",
|
"Engine-Update (llama.cpp Vulkan)",
|
||||||
on_done=on_done, sudo_password=sudo_password)
|
on_done=on_done, sudo_password=sudo_password)
|
||||||
return {"ok": True, "job_id": job_id}
|
return {"ok": True, "job_id": job_id}
|
||||||
@@ -506,7 +514,9 @@ def swap_update_job(sudo_password: str | None = None) -> dict | None:
|
|||||||
_swap_cache.update(ts=0.0, avail=False) # Cache leeren → frischer Versions-Vergleich
|
_swap_cache.update(ts=0.0, avail=False) # Cache leeren → frischer Versions-Vergleich
|
||||||
|
|
||||||
# update-swap.sh läuft via sudo als root, ersetzt die Binary und startet llama-swap neu.
|
# update-swap.sh läuft via sudo als root, ersetzt die Binary und startet llama-swap neu.
|
||||||
job_id = jobengine.start_job(["bash", "-c", SWAP_UPDATE_CMD],
|
# Danach Stack-Funktionsprüfung (echte Inferenz) → Job wird rot, wenn der Router kaputt ist.
|
||||||
|
cmd = f"{SWAP_UPDATE_CMD} && bash {STACK_POSTCHECK}"
|
||||||
|
job_id = jobengine.start_job(["bash", "-c", cmd],
|
||||||
"Router-Update (llama-swap)",
|
"Router-Update (llama-swap)",
|
||||||
on_done=on_done, sudo_password=sudo_password)
|
on_done=on_done, sudo_password=sudo_password)
|
||||||
return {"ok": True, "job_id": job_id}
|
return {"ok": True, "job_id": job_id}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Stack-Post-Update-Check: läuft NACH jedem Update (OS / Engine / Router) und verifiziert,
|
||||||
|
# dass der komplette Inferenz-Stack noch FUNKTIONIERT — nicht nur "Befehl lief durch".
|
||||||
|
# Exit 0 = alles ok, sonst 1 → der jobengine-Job wird im UI ROT (state=failed).
|
||||||
|
#
|
||||||
|
# Pendant zu hermes-postcheck.sh (prüft das Gehirn/Mem0); dieser prüft Router+Engine+MC2
|
||||||
|
# inkl. einer ECHTEN 1-Token-Inferenz (beweist, dass ein Modell wirklich lädt & generiert).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
SWAP_URL="${MC_LLAMA_SWAP_URL:-http://127.0.0.1:8080}"
|
||||||
|
MC_URL="${MC_URL:-http://127.0.0.1:9001}"
|
||||||
|
MEM0_URL="${MEM0_SERVICE_URL:-http://127.0.0.1:8765}"
|
||||||
|
BRAIN="${MC_WARMUP_MODELS:-fast}"; BRAIN="${BRAIN%% *}" # erstes Modell, falls Liste
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
echo "=== Stack Post-Update: Funktionsprüfung ==="
|
||||||
|
|
||||||
|
# 0. Auf llama-swap warten — ein Engine/Router-Update startet den Dienst neu, der Erststart
|
||||||
|
# (+ erstes Modell-Laden) kann dauern. Max ~120s.
|
||||||
|
swap_up=0
|
||||||
|
for _ in $(seq 1 60); do
|
||||||
|
curl -sf -m 5 "$SWAP_URL/v1/models" >/dev/null 2>&1 && { swap_up=1; break; }
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# 1. Router-Dienst aktiv? (is-active ist read-only → kein sudo nötig)
|
||||||
|
if systemctl is-active --quiet llama-swap; then
|
||||||
|
echo "PASS · llama-swap-Dienst aktiv"
|
||||||
|
else
|
||||||
|
echo "FAIL · llama-swap-Dienst NICHT aktiv"; fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Engine erreichbar (Modell-Liste)?
|
||||||
|
if [ "$swap_up" -eq 1 ]; then
|
||||||
|
echo "PASS · Engine /v1/models antwortet"
|
||||||
|
else
|
||||||
|
echo "FAIL · Engine /v1/models antwortet nicht (nach 120s)"; fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Echte Inferenz: lädt ein Modell und generiert es ein Token?
|
||||||
|
resp="$(curl -s -m 240 -X POST "$SWAP_URL/v1/chat/completions" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "{\"model\":\"$BRAIN\",\"max_tokens\":1,\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" 2>/dev/null)"
|
||||||
|
if printf '%s' "$resp" | grep -q '"choices"'; then
|
||||||
|
echo "PASS · Inferenz auf '$BRAIN' liefert eine Antwort"
|
||||||
|
else
|
||||||
|
echo "FAIL · Inferenz auf '$BRAIN' fehlgeschlagen (Modell lädt/generiert nicht)"; fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. MC2 selbst gesund (Engine + Gateway erreichbar)?
|
||||||
|
if curl -sf -m 8 "$MC_URL/api/health" 2>/dev/null | grep -qE '"engine_reachable":[[:space:]]*true'; then
|
||||||
|
echo "PASS · MC2 /api/health: engine_reachable=true"
|
||||||
|
else
|
||||||
|
echo "FAIL · MC2 /api/health meldet Engine nicht erreichbar"; fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Mem0-Sidecar (Gedächtnis) erreichbar?
|
||||||
|
if curl -sf -m 5 "$MEM0_URL/health" >/dev/null 2>&1; then
|
||||||
|
echo "PASS · Mem0-Sidecar erreichbar"
|
||||||
|
else
|
||||||
|
echo "FAIL · Mem0-Sidecar NICHT erreichbar"; fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$fail" -eq 0 ]; then
|
||||||
|
echo "=== STACK OK ✓ ==="
|
||||||
|
else
|
||||||
|
echo "=== STACK-CHECK FEHLGESCHLAGEN — bitte prüfen ==="
|
||||||
|
fi
|
||||||
|
exit "$fail"
|
||||||
Reference in New Issue
Block a user