Merge main in wartung/design-v5 (Rückweg-Probe, Aufräumen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-25 15:14:41 +02:00
co-authored by Claude Opus 5.5
41 changed files with 243 additions and 75 deletions
+5 -4
View File
@@ -103,10 +103,11 @@ VOICE_SERVICE_URL = os.environ.get("MC_VOICE_SERVICE_URL", "http://127.0.0.1:865
LUCY_STIMME_URL = os.environ.get("MC_LUCY_STIMME_URL", "http://127.0.0.1:8021").rstrip("/")
# Eingebaute Hermes-Web-GUI (`hermes serve`/`dashboard` — die richtige Agent-Oberfläche mit
# Threads/Tool-Calls, die auch die Electron-Desktop-App umhüllt). ‼️ Sie bindet NICHT auf Loopback:
# ein systemd-Drop-in überschreibt `--host 127.0.0.1` mit `0.0.0.0` und setzt dafür ein
# Session-Token. Dass sie trotzdem nicht im LAN hängt, liegt allein an ufw (9119 ist nicht
# freigegeben — am 27.08.2026 von einem zweiten Rechner aus gegengeprüft). Wer die Firewall
# anfasst, öffnet damit auch diese Oberfläche. MC2 erreicht sie über Loopback und reicht sie
# ein systemd-Drop-in überschreibt `--host 127.0.0.1` mit `0.0.0.0`, weil Hermes seine Anmeldung auf
# Loopback abschaltet (dann wäre /hermes-ui/ ohne Login offen). Dass sie trotzdem nicht im LAN hängt,
# liegt allein an ufw (9119 ist nicht freigegeben — 27.08. und 25.09.2026 vom PC aus gegengeprüft).
# Wer die Firewall anfasst, öffnet damit auch diese Oberfläche. Das feste Session-Token der früheren
# Desktop-Anbindung ist seit 25.09.2026 weg. MC2 erreicht sie über Loopback und reicht sie
# same-origin unter
# /hermes-ui/ durchgereicht (routers/hermes_ui.py). Anders als das ttyd-Terminal ist es eine volle
# SPA → das Bundle wird mit Vite-base=/hermes-ui/ gebaut, damit Assets/API/WS unter dem Präfix
+7
View File
@@ -5,6 +5,7 @@
POST /api/homelab/ziele/{id}/os-update Pakete im Gast einspielen
POST /api/homelab/ziele/{id}/suchen Paketlisten im Gast erneuern
POST /api/homelab/ziele/{id}/docker Docker über Arcane (zuerst Probelauf)
POST /api/homelab/ziele/{id}/rueckweg-probe Rückweg testen: Snapshot, absichtlich rot, zurück, nachprüfen
POST /api/homelab/ziele/pve/neustart Proxmox-Host neu starten (eigener Knopf)
GET /api/homelab/laeufe Die letzten Läufe (Updates, Snapshot-Aktionen) mit ihren Schritten
GET /api/homelab/snapshots Snapshots und Sicherungen je Gerät (Aktionen: siehe unten)
@@ -66,6 +67,12 @@ def docker(ziel_id: str) -> dict:
return _antwort(updates.starten(ziel_id, "docker"))
@router.post("/ziele/{ziel_id}/rueckweg-probe")
def rueckweg_probe(ziel_id: str) -> dict:
"""Rückweg testen (seit 25.09.2026): Snapshot anlegen, absichtlich rot werten, zurückgehen, nachprüfen."""
return _antwort(updates.starten(ziel_id, "probe"))
@router.post("/ziele/pve/neustart")
def host_neustart() -> dict:
return _antwort(updates.starten("pve", "neustart"))
+5
View File
@@ -227,6 +227,11 @@ ALTRESTE: tuple[Altrest, ...] = (
"selbstkritik-feed.sh", "venv-audit.sh", "worker.sh")),
"Skripte früherer Cron-Jobs (Werkstatt, Ampel, alte Radare). Kein Job, kein Cron und kein aktiver "
"Skill ruft sie auf; deploy.sh legt nur die heutigen Skripte dorthin."),
Altrest("hermes-alt-umgebung", "Alte Hermes-Umgebung (Python 3.11)",
("~/.hermes/hermes-agent/venv", "~/.hermes/hermes-agent/venv.bak-20260925-vor-pm"),
"Hermes' Umgebung von vor dem eigenen Paketmanager (hermes pm, 25.09.) und ihre Sicherung. Seit 25.09. "
"starten Gateway und Dashboard aus der neuen Umgebung; Selbstreparatur und PC-Pfad-Skill nutzen das "
"System-Python. Empfohlen: erst nach einem grünen Sonntagslauf (27.09.) löschen."),
Altrest("mc2-memory", "Plugin mc2-memory", ("~/.hermes/plugins/mc2-memory",),
"Gedächtnis-Plugin aus der mem0-Zeit, in Hermes nicht aktiviert und nicht mehr im Repo."),
Altrest("pinned-version", "Veraltete PINNED_VERSION", ("~/.hermes/PINNED_VERSION",),
+4 -2
View File
@@ -70,7 +70,7 @@ _AKTION = {
}
_HOST_AKTIONEN = {"bericht", "host_update", "host_neustart", "kernel_aufraeumen"}
_LAUF_WAS = {"app": "Update", "os": "Paket-Update", "pakete": "Paket-Update", "docker": "Docker-Update",
"neustart": "Neustart", "kernel": "Kernel-Aufräumen"}
"neustart": "Neustart", "kernel": "Kernel-Aufräumen", "probe": "Rückweg-Probe"}
_SCHRITT_WORT = {"wartet": "wartet", "laeuft": "läuft", "eingespielt": "eingespielt",
"zurueckgerollt": "zurückgerollt", "fehler": "gescheitert", "uebersprungen": "übersprungen"}
_MELDUNG = re.compile(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) "
@@ -351,7 +351,7 @@ def _lauf(x: dict, schw: Callable) -> dict | None:
titel = f"{_anzahl_pakete(x.get('pakete'), 'Paket')} · {name}"
elif ergebnis == "zurueckgerollt":
titel, stufe = f"{was} zurückgerollt · {name}", "warn"
elif ergebnis == "fehler" and "Update nicht begonnen" in str(text or ""):
elif ergebnis == "fehler" and ("Update nicht begonnen" in str(text or "") or "Probe nicht begonnen" in str(text or "")):
titel, stufe = f"{was} nicht begonnen · {name}", "warn"
elif ergebnis == "fehler":
titel, stufe = f"{was} gescheitert · {name}", "fehler"
@@ -359,6 +359,8 @@ def _lauf(x: dict, schw: Callable) -> dict | None:
titel, stufe = f"Neustart ausgelöst · {name}", "info"
elif ergebnis == "aufgeraeumt":
titel, stufe = f"Alte Kernel entfernt · {name}", "ok"
elif ergebnis == "probe-gruen":
titel, stufe = f"Rückweg-Probe grün · {name}", "ok"
elif ergebnis == "offen":
titel, stufe = f"Docker-Probelauf · {name}", "info"
else:
+44 -1
View File
@@ -20,6 +20,10 @@ danach genauso mit Snapshot vorher und Rückweg bei Rot — sofern die VM freige
Jeder Lauf steht in <Datenordner>/homelab-laeufe.json und im strukturierten Update-Verlauf. Pro Ziel
läuft höchstens ein Lauf; startet dieser Teil neu, gilt ein offener Lauf als unterbrochen.
„Rückweg testen“ (Baustein „probe“, seit 25.09.2026): Snapshot bzw. Sicherung anlegen, die Prüfung absichtlich als
rot werten, zurückgehen und nachprüfen, ob das Gerät danach läuft und antwortet. Ein Update gibt es dabei nicht; so
lässt sich der Weg zurück jederzeit echt beweisen, ohne auf ein gescheitertes Update zu warten.
„Alle aktualisieren“ (sammellauf.py, seit 24.09.2026) stößt dieselben Läufe nacheinander an. Solange es läuft,
lehnt der einzelne Knopf ab (SAMMELLAUF_SPERRE), und die Erfolgsmeldung eines Schritts entfällt — die Sammelmeldung
am Ende sagt es; Fehler werden weiter sofort gemeldet.
@@ -227,6 +231,34 @@ def _gast_lauf(lauf: dict, gast: dict) -> None:
dringend=True)
def _probe_lauf(lauf: dict, gast: dict) -> None:
"""Rückweg testen: anlegen, absichtlich rot werten, zurückgehen, nachprüfen. Der Snapshot bzw. die Sicherung
bleibt danach liegen (wie nach einem echten Rückweg) — wegräumen geht auf der Seite Snapshots."""
vmid = gast["vmid"]
try:
rueckweg = _rueckweg_anlegen(lauf, gast)
except RuntimeError as exc:
_ende(lauf, "fehler", f"{lauf['name']}: Rückweg-Probe nicht begonnen — {str(exc).rstrip('.')}. "
"Am Gerät wurde nichts geändert.")
return
if not rueckweg:
_ende(lauf, "fehler", f"{lauf['name']}: Für dieses Gerät gibt es keinen Rückweg (weder Snapshot noch "
"Sicherung) — die Probe ist nicht möglich.")
return
lauf["schritte"].append("Prüfung absichtlich auf Rot gesetzt (Probe, kein Update).")
_merken(lauf)
try:
wie = _zurueck(lauf, vmid, rueckweg)
time.sleep(WARTEN_NACH_UPDATE_S)
fehler = pruefen(gast, _frischer_gast(lauf, vmid), "probe")
except RuntimeError as exc:
wie, fehler = "", [str(exc)]
if fehler:
_ende(lauf, "fehler", f"{lauf['name']}: Rückweg-Probe rot — {' '.join(fehler)}", dringend=True)
return
_ende(lauf, "probe-gruen", f"{lauf['name']}: Rückweg-Probe grün — {wie}, danach läuft das Gerät und antwortet.")
def _aufraeumen(lauf: dict, vmid: int, rueckweg: tuple[str, str] | None) -> None:
"""Ältere Snapshots des Orchestrators für diesen Gast löschen, nach einer Sicherung auch ältere Sicherungen;
der Rückweg dieses Laufs (der neueste) bleibt. Ebenso bleiben Snapshots, die jemand auf der Seite Snapshots per
@@ -348,6 +380,8 @@ def _ende(lauf: dict, ergebnis: str, text: str, dringend: bool = False) -> None:
lauf.update(status="fertig", ergebnis=ergebnis, text=text, ende=time.time(), dringend=dringend,
gemeldet=not still)
_merken(lauf)
# Eine Rückweg-Probe ist kein Update: Sie steht im Protokoll, nicht im Update-Verlauf.
if lauf.get("baustein") != "probe":
try:
# Die Schritte eines Sammellaufs stehen im Update-Verlauf als EIN Lauf (gleicher Beginn, eigener Anlass).
start = datetime.fromtimestamp(lauf.get("sammellauf_start") or lauf["start"], LOCAL_TZ)
@@ -373,13 +407,15 @@ def starten(ziel_id: str, baustein: str, sammellauf: dict | None = None) -> dict
sammellauf ({"id", "start"}) setzt nur „Alle aktualisieren“ (sammellauf.py) für seine Schritte; ohne ihn lehnt
der Knopf ab, solange ein Sammellauf läuft."""
if baustein not in ("app", "os", "pakete", "neustart", "docker", "kernel"):
if baustein not in ("app", "os", "pakete", "neustart", "docker", "kernel", "probe"):
return {"ok": False, "detail": "Unbekannter Baustein."}
with START_SPERRE:
if sammellauf is None and _sammellauf_laeuft():
return {"ok": False, "detail": SAMMELLAUF_SPERRE}
if laeuft(ziel_id):
return {"ok": False, "detail": "Für dieses Gerät läuft schon ein Update."}
if ziel_id == "pve" and baustein == "probe":
return {"ok": False, "detail": "Für den Proxmox-Host gibt es keinen Rückweg zum Testen."}
if ziel_id == "pve":
name, gast = "Proxmox-Host", None
host = ((kanal.bericht() or {}).get("bericht") or {}).get("host") or {}
@@ -402,6 +438,13 @@ def starten(ziel_id: str, baustein: str, sammellauf: dict | None = None) -> dict
return {"ok": False, "detail": "Dieses Gerät ist nicht freigegeben (Etikett watcher fehlt)."}
app = apps.app_fuer((gast.get("app") or {}).get("kennung"), gast.get("name"))
name = app.name if app else str(gast.get("name"))
if baustein == "probe":
if not (gast.get("snapshot_moeglich") or gast.get("sicherung_moeglich")):
return {"ok": False, "detail": f"{name} hat keinen Rückweg (weder Snapshot noch Sicherung)."}
lauf = _neuer_lauf(ziel_id, baustein, name, None)
threading.Thread(target=_probe_lauf, args=(lauf, gast), name=f"homelab-{ziel_id}",
daemon=True).start()
return {"ok": True, "lauf": lauf["id"]}
if baustein == "app" and (not app or app.weg != "skript"):
return {"ok": False, "detail": f"{name} aktualisiert sich nicht über das Community-Script "
"(eigene Oberfläche oder Pakete)."}
+24
View File
@@ -222,6 +222,30 @@ def test_update_ohne_neue_version_ist_rot(ausfuehrer):
assert lauf["ergebnis"] == "zurueckgerollt" and "unverändert" in lauf["text"]
def test_rueckweg_probe_gruen(ausfuehrer):
starten, protokoll, meldungen, verlauf = ausfuehrer
starten("1.27.2")
lauf = _auf_ende(updates.starten("ct-104", "probe")["lauf"])
# Kein Update: Snapshot, zurück auf ihn, frischer Bericht — und die Probe steht nicht im Update-Verlauf.
assert protokoll == ["snapshot", "zurueck", "bericht"]
assert lauf["ergebnis"] == "probe-gruen" and "zurück auf den Snapshot mc2-20260924-190000" in lauf["text"]
assert meldungen == [(lauf["text"], False)] and verlauf == []
def test_rueckweg_probe_rot_meldet_dringend(ausfuehrer, monkeypatch):
starten, _, meldungen, _ = ausfuehrer
monkeypatch.setattr(inventar, "erreichbar", lambda url: False)
starten("1.27.2")
lauf = _auf_ende(updates.starten("ct-104", "probe")["lauf"])
assert lauf["ergebnis"] == "fehler" and "Rückweg-Probe rot" in lauf["text"] and "antwortet nicht" in lauf["text"]
assert meldungen[0][1] is True
def test_rueckweg_probe_nur_mit_rueckweg(ausfuehrer):
assert updates.starten("pve", "probe")["detail"].startswith("Für den Proxmox-Host")
assert updates.starten("vm-106", "probe")["detail"].startswith("Dieses Gerät ist nicht freigegeben")
def test_nicht_freigegeben_und_unbekannt(ausfuehrer):
assert updates.starten("vm-106", "app")["detail"].startswith("Dieses Gerät ist nicht freigegeben")
assert updates.starten("ct-999", "app")["ok"] is False
+2 -3
View File
@@ -40,10 +40,9 @@ mkdir -p "$STAGE/hermes" "$STAGE/llama-swap"
# Desktop-Verbindung und die pre_verify-Gates STILL (config.yaml referenziert die Hooks):
[ -d "$HERMES/agent-hooks" ] && cp -a "$HERMES/agent-hooks" "$STAGE/hermes/agent-hooks" || true
[ -f "$HERMES/shell-hooks-allowlist.json" ] && cp -a "$HERMES/shell-hooks-allowlist.json" "$STAGE/hermes/" || true
[ -f "$HERMES/desktop-gateway-token" ] && cp -a "$HERMES/desktop-gateway-token" "$STAGE/hermes/" || true
[ -f "$HERMES/pc-paths.yaml" ] && cp -a "$HERMES/pc-paths.yaml" "$STAGE/hermes/" || true
_TOKDROPIN="$HOME/.config/systemd/user/hermes-builtin-ui.service.d/session-token.conf"
[ -f "$_TOKDROPIN" ] && { mkdir -p "$STAGE/hermes/systemd-dropins"; cp -a "$_TOKDROPIN" "$STAGE/hermes/systemd-dropins/"; } || true
# Das feste Dashboard-Token der früheren Desktop-Anbindung (desktop-gateway-token, session-token.conf) gibt es seit
# 25.09.2026 nicht mehr — es wird nicht mehr gesichert (liegt nur noch im Archiv ~/.hermes/archiv-20260925-dashboard).
# Cron-Jobs + Radar-State (10.07.2026) — jobs.json liegt AUSSERHALB des Repos; ohne diese
# Zeilen verliert ein Restore ALLE Hermes-Crons (traum/Briefing/Monats-Review/Chef-Gutachter/
# Release-Radar) still:
+21
View File
@@ -0,0 +1,21 @@
# Units, die nur auf der Box leben — Kopien zum Nachlesen
Stand 25.09.2026, abgezogen von der KI-Box. **`deploy.sh` spielt diese Dateien nicht aus**; die Wahrheit ist die Box.
Sie stehen hier, damit man ohne SSH sieht, wie Hermes-Dashboard und llama-swap gestartet werden, und damit ein
Wiederaufbau eine Vorlage hat. Jede Sicherung (`deploy/backup.sh`) nimmt die echten Dateien ohnehin mit.
| Datei hier | Ort auf der Box |
|---|---|
| `hermes-builtin-ui.service` | `~/.config/systemd/user/hermes-builtin-ui.service` |
| `hermes-builtin-ui.service.d-override.conf` | `~/.config/systemd/user/hermes-builtin-ui.service.d/override.conf` |
| `llama-swap.service` | `/etc/systemd/system/llama-swap.service` |
| `llama-swap.service.d-vulkan.conf` | `/etc/systemd/system/llama-swap.service.d/vulkan.conf` |
| `llama-swap.service.d-warmset.conf` | `/etc/systemd/system/llama-swap.service.d/warmset.conf` |
| `llama-swap.service.d-warmup.conf` | `/etc/systemd/system/llama-swap.service.d/warmup.conf` (ruft `/usr/local/bin/llama-swap-warmup.sh`, Vorlage `deploy/warmup.sh`) |
Beim Hermes-Dashboard gilt der Drop-in, nicht die Kommentare der Basis-Unit: Es bindet an `0.0.0.0:9119`, weil Hermes
seine Anmeldung auf `127.0.0.1` abschaltet; ufw hält den Port zu, erreichbar ist es nur über `/hermes-ui/` im
Orchestrator. Seit 25.09.2026 startet es über den Starter der neuen Hermes-Umgebung (`hermes pm`), und das feste
Session-Token der früheren Desktop-Anbindung ist weg.
Die alte Unit `hermes-dashboard.service` (abgeschaltet, alte Umgebung) steht als Altrest im Aufräumen-Panel.
+19
View File
@@ -0,0 +1,19 @@
[Unit]
Description=Hermes Built-in Web UI (hermes dashboard — Agent-Dashboard/Chat, nur ueber MC2 /hermes-ui/)
After=network-online.target
Wants=network-online.target
[Service]
# Serviert die EINGEBAUTE Hermes-Web-GUI (die reiche Agent-Oberflaeche mit Threads/Tool-Calls) headless.
# Erreichbar nur ueber den MC2-Reverse-Proxy (routers/hermes_ui.py → same-origin /hermes-ui/ auf Port 9001).
# Der Drop-in override.conf bindet an 0.0.0.0, damit Hermes' Anmeldung greift (auf 127.0.0.1 schaltet Hermes
# sie ab); ufw haelt 9119 zu. --skip-build nutzt das von deploy.sh mit Vite-base=/hermes-ui/ vorgebaute web_dist.
# Seit 25.09.2026 ueber den Starter der neuen Hermes-Umgebung (hermes pm) statt der alten venv (3.11).
Type=simple
Environment=HERMES_HOME=%h/.hermes
ExecStart=%h/.hermes/hermes-agent/.hermes/bin/hermes dashboard --no-open --skip-build --host 127.0.0.1 --port 9119
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
@@ -0,0 +1,9 @@
# Stufe 1 (20.08.2026), bereinigt 21.08. und 25.09.2026: Das Dashboard bindet nach aussen, damit
# seine Anmeldung greift — auf 127.0.0.1 schaltet Hermes sie ab (hermes_cli/dashboard_auth: „no-op
# on loopback“), dann waere es ueber MC2s /hermes-ui-Proxy ohne Login offen. Port 9119 ist in ufw ZU —
# erreichbar nur ueber MC2 auf 9001. Zugangsdaten stehen NUR in config.yaml (dashboard.basic_auth).
# Seit 25.09.2026 ueber den Starter der neuen Hermes-Umgebung (hermes pm, Python 3.14) statt des alten
# venv (3.11); das feste Session-Token der frueheren Desktop-Anbindung ist weg (liegt im Archiv).
[Service]
ExecStart=
ExecStart=%h/.hermes/hermes-agent/.hermes/bin/hermes dashboard --no-open --skip-build --host 0.0.0.0 --port 9119
+16
View File
@@ -0,0 +1,16 @@
[Unit]
Description=llama-swap (lokaler LLM Router)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=hitonabi
Environment=HSA_OVERRIDE_GFX_VERSION=11.5.1
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/local/bin/llama-swap --config /etc/llama-swap/config.yaml --listen 0.0.0.0:8080 --watch-config
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,2 @@
[Service]
Environment=LD_LIBRARY_PATH=/opt/llamacpp-vulkan
@@ -0,0 +1,8 @@
[Service]
# 25.07.2026 — KORREKTUR. Hier standen kurzzeitig "fast coder kritiker": das schob
# ~35 s Modell-Laden VOR das Vorkauen von Hermes' Agenten-Prompt (warmup.sh warmt erst
# alle Chat-Modelle, dann Embeddings, DANN den Agenten-Prompt). Der Tool-Smoke im
# hermes-postcheck hat 90 s Limit und lief dadurch in die Zeitueberschreitung.
# Der Coder waermt sich beim ersten Auftrag selbst (23 s einmalig, dann 90 min warm) —
# Lucys Hirn und der Agenten-Prompt haben Vorrang.
Environment=MC_WARMUP_MODELS=fast
@@ -0,0 +1,2 @@
[Service]
ExecStartPost=-/usr/local/bin/llama-swap-warmup.sh
+3 -2
View File
@@ -137,9 +137,10 @@ systemctl --user stop $SERVICES 2>/dev/null || true
# Hermes-Desktop-/Gate-Infrastruktur (Gegenstück zu backup.sh, 09.07.2026):
[ -d "$STAGE/hermes/agent-hooks" ] && { mkdir -p "$HERMES/agent-hooks"; cp -a "$STAGE/hermes/agent-hooks/." "$HERMES/agent-hooks/"; chmod +x "$HERMES/agent-hooks/"*.sh 2>/dev/null || true; }
[ -f "$STAGE/hermes/shell-hooks-allowlist.json" ] && cp -a "$STAGE/hermes/shell-hooks-allowlist.json" "$HERMES/"
[ -f "$STAGE/hermes/desktop-gateway-token" ] && { cp -a "$STAGE/hermes/desktop-gateway-token" "$HERMES/"; chmod 600 "$HERMES/desktop-gateway-token"; }
[ -f "$STAGE/hermes/pc-paths.yaml" ] && cp -a "$STAGE/hermes/pc-paths.yaml" "$HERMES/"
[ -f "$STAGE/hermes/systemd-dropins/session-token.conf" ] && { mkdir -p "$HOME/.config/systemd/user/hermes-builtin-ui.service.d"; cp -a "$STAGE/hermes/systemd-dropins/session-token.conf" "$HOME/.config/systemd/user/hermes-builtin-ui.service.d/"; chmod 600 "$HOME/.config/systemd/user/hermes-builtin-ui.service.d/session-token.conf"; systemctl --user daemon-reload 2>/dev/null || true; }
# Ältere Sicherungen enthalten noch das feste Dashboard-Token der früheren Desktop-Anbindung (desktop-gateway-token,
# systemd-dropins/session-token.conf). Seit 25.09.2026 wird es bewusst NICHT zurückgespielt: Ohne festes Token würfelt
# das Dashboard pro Start ein neues.
# Cron-Jobs + Radar-State (Gegenstück zu backup.sh, 10.07.2026) — der Gateway-Neustart unten
# lädt die wiederhergestellten Jobs:
[ -d "$STAGE/hermes/cron" ] && { mkdir -p "$HERMES/cron"; cp -a "$STAGE/hermes/cron/." "$HERMES/cron/"; }
+3 -2
View File
@@ -19,7 +19,8 @@ HERMES="${HERMES_HOME:-$HOME/.hermes}"
CFG="$HERMES/config.yaml"
WARN_FILE="${HERMES_CONFIG_WARNINGS:-/tmp/hermes-config-warnings.txt}"
LLAMA="${LLAMA_SWAP_URL:-http://127.0.0.1:8080}"
VENV_PY="$HERMES/hermes-agent/venv/bin/python"
# System-Python (hat PyYAML) statt der alten Hermes-venv, die seit dem Umstieg auf `hermes pm` (25.09.2026) wegfällt.
YAML_PY="$(command -v python3)"
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
say(){ echo "[self-repair] $*"; }
@@ -78,7 +79,7 @@ if [ -z "$CHANGED" ]; then
fi
# ── 3) YAML noch gültig? ──────────────────────────────────────────────────────
if [ -x "$VENV_PY" ] && ! "$VENV_PY" -c "import yaml; yaml.safe_load(open('$CFG'))" 2>/dev/null; then
if [ -x "$YAML_PY" ] && ! "$YAML_PY" -c "import yaml; yaml.safe_load(open('$CFG'))" 2>/dev/null; then
say "YAML nach Fix ungültig → zurück zum Backup."
cp -a "$BAK" "$CFG"; echo "SELFREPAIR_SUGGESTION=$SUGG"; exit 2
fi
+2 -2
View File
@@ -21,14 +21,14 @@ dem PC suchen — und den Fund sofort merken, damit die nächste Frage in <1 s b
- Tobi will einen Pfad im Cache ergänzen/korrigieren („merk dir: X liegt unter Y").
## Schritte
1. **Cache zuerst:** `~/.hermes/scripts/pc_path_lookup.sh lookup "<begriff>"` —
1. **Cache zuerst:** `~/.hermes/skills/pc-pfad-cache/pc_path_lookup.sh lookup "<begriff>"` —
Treffer (exit 0) → Pfad direkt liefern, FERTIG. (Das Skript normalisiert selbst:
klein, Umlaute ausgeschrieben, Leerzeichen→Bindestrich.)
2. **Nur bei Cache-Miss (exit 3):** selbst auf dem PC suchen — mit deinem
**`pc_shell`-Tool** (NICHT als Shell-Befehl — `pc_shell` existiert nur als Tool),
z. B. `where <name>` oder eine gezielte PowerShell-Suche in den üblichen Orten
(`%ProgramFiles%`, `%LOCALAPPDATA%\Programs`, Desktop, F:\).
3. **Fund merken:** `~/.hermes/scripts/pc_path_lookup.sh merken "<begriff>" "<pfad>"` —
3. **Fund merken:** `~/.hermes/skills/pc-pfad-cache/pc_path_lookup.sh merken "<begriff>" "<pfad>"` —
dann den Pfad liefern.
4. **Pflege:** `pc_path_lookup.sh liste` zeigt alle Einträge; „merken" überschreibt.
@@ -14,8 +14,8 @@ set -euo pipefail
MODUS="${1:?FEHLT: lookup|merken|liste}"
YAML_FILE="$HOME/.hermes/pc-paths.yaml"
PY="$HOME/.hermes/hermes-agent/venv/bin/python"
[ -x "$PY" ] || PY=python3
# System-Python hat PyYAML; die alte Hermes-venv fällt seit dem Umstieg auf `hermes pm` (25.09.2026) weg.
PY=python3
normalize() {
# Umlaute ZUERST ausschreiben (beide Fälle — tr lowercased kein Ä/Ö/Ü), dann klein,
+30 -24
View File
@@ -1,6 +1,6 @@
# Offene Fäden — die eine Liste
_Stand 25.09.2026 vormittags. Neues hier rein, Erledigtes raus (die Git-Historie behält es). Entschiedenes steht in
_Stand 25.09.2026 nachmittags. Neues hier rein, Erledigtes raus (die Git-Historie behält es). Entschiedenes steht in
[VERDIKTE.md](VERDIKTE.md), nicht hier. Die Liste bis 04.09.2026, samt den Lucy-Fäden, liegt im Archiv:
[2026-09-04-offene-faeden-alt.md](../archiv/2026-09-04-offene-faeden-alt.md)._
@@ -24,22 +24,27 @@ zwei Minuten, Meldung auf Telegram, der Snapshot bleibt als Rückweg liegen. Dan
Technik: [ARCHITEKTUR.md](../ARCHITEKTUR.md), Abschnitt „Der Homelab-Teil“; Einrichtung und Betrieb:
[BETRIEB.md](../BETRIEB.md).
## Ausbauplan (Klärungsrunde 25.09.2026)
Der User hat alle Wellen des Feature-Ausbauplans freigegeben (Plan-Dokument, Abschnitt „Feature-Ausbauplan“, jede
Zeile mit Entscheid): erst Design v5 auf alle Seiten, dann Welle 1, 2, 3 und „Später“ nacheinander. Punkt 4 (Wartezeit
für frische Versionen) entfällt; die Android-App (Phase 5) wird danach gemeinsam geplant. Festgelegt: Lucy mit mehr
Freiheit (Sicherheitsupdates und Rückweg bei Rot selbst, Meldung danach; sonst nach Ja), Wartungsfenster So
03:00–04:15 mit anfangs nur Sicherheitsupdates automatisch, ein „ja“ in Telegram löst genau die gefragte Aktion aus,
„Übernehmen“ bei neuen Geräten setzt das Etikett, Konfig-Sicherung geschwärzt in ein privates Gitea-Repo, kein
Web-Push (Telegram bleibt), Arcane-Lücken-Scan sonntags 02:00, NAS-Platten über SNMP.
## Offene Einzelpunkte (Homelab)
0. **AdGuard (Container 100): Aufbewahrung des Abfrageprotokolls auf 14 Tage (User, 25.09. angesagt).** Am 24.09. war
die 2-GB-Platte voll (Abfrageprotokoll, eingestellt: 90 Tage). Mit User-Ja wurde die alte Datei `querylog.json.1`
vom 09.08. (539 MB) gepackt auf den Proxmox-Host gelegt (`/root/mc2-archiv/adguard-querylog-bis-2026-08-09.json.zst`,
44 MB) und aus dem Container genommen. Seit dem Abend schreibt AdGuard wieder (25.09.: 485 MB seit 09.08., gut
10 MB am Tag, Platte 72 %). Weg in AdGuard: Einstellungen → Allgemeine Einstellungen → Konfiguration der
Protokolle → Rotation der Abfrageprotokolle → Benutzerdefiniert, 336 Stunden → Speichern. AdGuard behält die
laufende und die vorige Datei; die jetzige (ab 09.08.) wird beim ersten Rotieren zur vorigen und fällt 14 Tage
später weg. Danach bleiben rund 300 MB. Volle Gastplatten meldet seit 24.09. der Wächter.
- **Zugang von außen:** Der Eintrag `mc.tobisniceshomelab.ddnsfree.com` in NPMplus (Proxy-Host 14 → Box :9001) hatte
keine Zugangsliste. Der User hängt die vorhandene Liste (die an nginx., pvescriptslocal. und rippy.) auch an mc.
(User, 25.09. zugesagt). Prüfen: `pct exec 101 -- grep -c auth_basic_user_file /opt/npmplus/nginx/proxy_host/14.conf`.
- **NAS-Platten (SNMP):** Der User schaltet am QNAP SNMP v1/v2 ein; den Zugangsnamen trägt er in den Einstellungen ein.
1. **Arcane-VM (106): Discard einschalten (User).** Ihre Platte belegt im Speicherpool 161 GB, genutzt sind darin
35 GB (25.09.): Discard ist aus, freigegebener Platz kommt nie im Pool an. In Proxmox: VM 106 → Hardware →
Festplatte → „Discard“ anhaken, VM einmal neu starten (Docker-Dienste kurz weg); danach gibt Ubuntu freien Platz
wöchentlich zurück. Steht als Empfehlung in der Karte „Speicher und Sicherungen“; nötig erst, wenn der Pool knapp
wird (heute 49 %).
Erledigt am 25.09. nachmittags: AdGuard-Abfrageprotokoll auf 7 Tage (User); Discard für die Arcane-VM 106 an
(`discard=on,ssd=1`, einmal `fstrim`: 122 GB zurück, Speicherpool 50 % → 17 %, Ubuntu trimmt ab jetzt wöchentlich);
**Rückweg-Probe** gebaut und mit PVE Scripts echt bewiesen (Snapshot → absichtlich rot → zurück → läuft und antwortet,
gut 3 Minuten).
Erledigt am 25.09.: Arcane-API-Schlüssel (vom User eingetragen, Docker-Updates echt), feste IP 192.168.178.31 für
Container 107 (Fritzbox, User), Etikett `watcher` für die Arcane-VM 106 (User-Ja; der Ausführer legt vor echten
@@ -49,11 +54,12 @@ Docker-Updates einen Snapshot an und geht bei Rot zurück). OS-Updates der VM ü
## Offene Einzelpunkte (Box-Wart)
1. **Session-Token des Hermes-Dashboards (nur mit User-Ja).** Das Drop-in
`hermes-builtin-ui.service.d/session-token.conf` aus der früheren Desktop-Anbindung ist weiter gesetzt; `backup.sh`
und `restore.sh` sichern es samt Kopie `desktop-gateway-token`. Entfernen ist Security-Config.
2. **Units nur auf der Box:** `hermes-builtin-ui.service` samt Drop-ins und die llama-swap-Drop-ins (darunter
`warmset.conf`) liegen nicht im Repo; seit 24.09. stehen sie als Kopie in jeder Sicherung.
Erledigt am 25.09.: Das feste Session-Token des Hermes-Dashboards (Drop-in `session-token.conf` und
`desktop-gateway-token` der früheren Desktop-Anbindung) liegt im Archiv `~/.hermes/archiv-20260925-dashboard`, wird
weder gesichert noch zurückgespielt. Das Dashboard startet über den Starter der neuen Hermes-Umgebung und bindet
weiter an 0.0.0.0 — auf 127.0.0.1 schaltet Hermes seine Anmeldung ab; ufw hält 9119 zu (vom PC gegengeprüft). Die
Units, die nur auf der Box leben, stehen als Kopie in `deploy/nur-box/`. Die tote Cron-Zeile für
`curator-idle-watchdog.sh` ist raus (alte crontab im selben Archiv).
## Aufräumen auf der Box (nur per User-Klick)
@@ -69,13 +75,13 @@ Docker-Updates einen Snapshot an und geht bei Rot zurück). OS-Updates der VM ü
sie), die Archive `profiles-archive-20260819.tar.gz`, `~/archiv-aufraeumen-20260820`, `~/wissens-vault`,
`~/.hermes/state-snapshots`, der PBS-Weg, `ampel-waechter.sh`, `night-cron-wrapper.sh` und `pc_path_lookup.*`
(aktive Skills nennen sie).
- Die crontab ruft sonntags `~/.hermes/scripts/curator-idle-watchdog.sh` auf; das Skript gibt es nicht mehr.
- Neu im Panel „Altreste“ (25.09.): die alte Hermes-Umgebung (Python 3.11) samt Sicherung, zusammen ~11 GB —
empfohlen erst nach einem grünen Sonntagslauf (27.09.) löschen. Modelle ohne Rolle (gpt-oss-120b, Qwen3-VL-30B)
hat der User zum Löschen freigegeben; er klickt es selbst (endgültiges Löschen macht Claude nicht).
## Beobachten
- **25.09. 00:30:** Radar-Neutest Ornith-1.5-35B-A3B (Hirn), jetzt mit Draft-Varianten. **01.10.:** Qwen3.8-Flash-Next
(Coder, passt nur knapp).
- **01.10.:** Radar-Test Qwen3.8-Flash-Next (Coder, passt nur knapp). Ornith-1.5-35B-A3B (Hirn) fiel am 25.09. durch.
- **So 27.09. 04:30:** erster regulärer Sonntagslauf über den Timer; der Bericht kommt mit der Morgenmeldung um 07:00,
der Verlauf steht auf der Updates-Seite. Wird das Zeitfenster vorher in den Einstellungen verschoben, gilt das neue.
- **Daily News 07:00:** Die `web_extract`-Werkzeugfehler sollten mit dem Plugin `mc2-web-lesen` verschwunden sein
(Hermes am 24.09. neu gestartet).
- **Daily News 07:00:** Am 25.09. lief sie ohne `web_extract`-Fehler (Plugin `mc2-web-lesen`); weiter beobachten.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{n as e,r as t}from"./dist-D4CVSex9.js";import{E as n,T as r}from"./index-CIOHqG23.js";import{a as i,i as a,n as o,r as s,t as c}from"./sheet-DU78qfp_.js";var l=e(),u=t();function d(e){let t=(0,l.c)(8),{onSchliessen:d}=e,f;t[0]===d?f=t[1]:(f=e=>!e&&d(),t[0]=d,t[1]=f);let p;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(p=(0,u.jsxs)(a,{className:`p-0`,children:[(0,u.jsx)(i,{asChild:!0,children:(0,u.jsx)(`div`,{children:(0,u.jsx)(r,{})})}),(0,u.jsx)(s,{className:`sr-only`,children:`Bereiche und Seiten`})]}),t[2]=p):p=t[2];let m;t[3]===d?m=t[4]:(m=(0,u.jsxs)(o,{side:`left`,className:`flex flex-col gap-6 border-linie bg-[#0c0f12] px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] data-[side=left]:w-[300px]`,children:[p,(0,u.jsx)(n,{onGewaehlt:d})]}),t[3]=d,t[4]=m);let h;return t[5]!==f||t[6]!==m?(h=(0,u.jsx)(c,{open:!0,onOpenChange:f,children:m}),t[5]=f,t[6]=m,t[7]=h):h=t[7],h}export{d as MenueSchublade};
import{n as e,r as t}from"./dist-D4CVSex9.js";import{E as n,T as r}from"./index-CKWhFN2A.js";import{a as i,i as a,n as o,r as s,t as c}from"./sheet-CHZtsS6E.js";var l=e(),u=t();function d(e){let t=(0,l.c)(8),{onSchliessen:d}=e,f;t[0]===d?f=t[1]:(f=e=>!e&&d(),t[0]=d,t[1]=f);let p;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(p=(0,u.jsxs)(a,{className:`p-0`,children:[(0,u.jsx)(i,{asChild:!0,children:(0,u.jsx)(`div`,{children:(0,u.jsx)(r,{})})}),(0,u.jsx)(s,{className:`sr-only`,children:`Bereiche und Seiten`})]}),t[2]=p):p=t[2];let m;t[3]===d?m=t[4]:(m=(0,u.jsxs)(o,{side:`left`,className:`flex flex-col gap-6 border-linie bg-[#0c0f12] px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] data-[side=left]:w-[300px]`,children:[p,(0,u.jsx)(n,{onGewaehlt:d})]}),t[3]=d,t[4]=m);let h;return t[5]!==f||t[6]!==m?(h=(0,u.jsx)(c,{open:!0,onOpenChange:f,children:m}),t[5]=f,t[6]=m,t[7]=h):h=t[7],h}export{d as MenueSchublade};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{n as e,r as t}from"./dist-D4CVSex9.js";import{a as n,i as r,n as i,r as a,t as o}from"./sheet-DU78qfp_.js";var s=e(),c=t();function l(e){let t=(0,s.c)(14),{offen:l,titel:u,text:d,onSchliessen:f}=e,p;t[0]===f?p=t[1]:(p=e=>!e&&f(),t[0]=f,t[1]=p);let m;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(m=(0,c.jsx)(n,{className:`schild text-lg`,children:`Protokoll`}),t[2]=m):m=t[2];let h;t[3]===u?h=t[4]:(h=(0,c.jsxs)(r,{children:[m,(0,c.jsx)(a,{className:`text-text-2`,children:u})]}),t[3]=u,t[4]=h);let g;t[5]===d?g=t[6]:(g=(0,c.jsx)(`pre`,{className:`ziffern mx-4 mb-4 flex-1 overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap text-text-2`,children:d}),t[5]=d,t[6]=g);let _;t[7]!==h||t[8]!==g?(_=(0,c.jsxs)(i,{side:`right`,className:`border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-2xl`,children:[h,g]}),t[7]=h,t[8]=g,t[9]=_):_=t[9];let v;return t[10]!==l||t[11]!==p||t[12]!==_?(v=(0,c.jsx)(o,{open:l,onOpenChange:p,children:_}),t[10]=l,t[11]=p,t[12]=_,t[13]=v):v=t[13],v}export{l as Protokollfenster};
import{n as e,r as t}from"./dist-D4CVSex9.js";import{a as n,i as r,n as i,r as a,t as o}from"./sheet-CHZtsS6E.js";var s=e(),c=t();function l(e){let t=(0,s.c)(14),{offen:l,titel:u,text:d,onSchliessen:f}=e,p;t[0]===f?p=t[1]:(p=e=>!e&&f(),t[0]=f,t[1]=p);let m;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(m=(0,c.jsx)(n,{className:`schild text-lg`,children:`Protokoll`}),t[2]=m):m=t[2];let h;t[3]===u?h=t[4]:(h=(0,c.jsxs)(r,{children:[m,(0,c.jsx)(a,{className:`text-text-2`,children:u})]}),t[3]=u,t[4]=h);let g;t[5]===d?g=t[6]:(g=(0,c.jsx)(`pre`,{className:`ziffern mx-4 mb-4 flex-1 overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap text-text-2`,children:d}),t[5]=d,t[6]=g);let _;t[7]!==h||t[8]!==g?(_=(0,c.jsxs)(i,{side:`right`,className:`border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-2xl`,children:[h,g]}),t[7]=h,t[8]=g,t[9]=_):_=t[9];let v;return t[10]!==l||t[11]!==p||t[12]!==_?(v=(0,c.jsx)(o,{open:l,onOpenChange:p,children:_}),t[10]=l,t[11]=p,t[12]=_,t[13]=v):v=t[13],v}export{l as Protokollfenster};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{cn as e}from"./index-CIOHqG23.js";var t={name:`chevron-right`,size:24,node:[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]};t.node;var n=e(t);export{n as t};
import{cn as e}from"./index-CKWhFN2A.js";var t={name:`chevron-right`,size:24,node:[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]};t.node;var n=e(t);export{n as t};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{cn as e}from"./index-CIOHqG23.js";var t={name:`search`,size:24,node:[[`path`,{d:`m21 21-4.34-4.34`,key:`14j7rj`}],[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}]]};t.node;var n=e(t);export{n as t};
import{cn as e}from"./index-CKWhFN2A.js";var t={name:`search`,size:24,node:[[`path`,{d:`m21 21-4.34-4.34`,key:`14j7rj`}],[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}]]};t.node;var n=e(t);export{n as t};
@@ -1 +1 @@
import{i as e,n as t,r as n,t as r}from"./dist-D4CVSex9.js";import{nn as i}from"./index-CIOHqG23.js";import{t as a}from"./button-D8rvET0Z.js";import{a as o,i as s,n as c,o as l,r as u,s as d,t as f}from"./dist-B2n2wafq.js";var p=t();e();var m=n();function h(e){let t=(0,p.c)(4),n;t[0]===e?n=t[1]:({...n}=e,t[0]=e,t[1]=n);let r;return t[2]===n?r=t[3]:(r=(0,m.jsx)(f,{"data-slot":`sheet`,...n}),t[2]=n,t[3]=r),r}function g(e){let t=(0,p.c)(4),n;t[0]===e?n=t[1]:({...n}=e,t[0]=e,t[1]=n);let r;return t[2]===n?r=t[3]:(r=(0,m.jsx)(l,{"data-slot":`sheet-portal`,...n}),t[2]=n,t[3]=r),r}function _(e){let t=(0,p.c)(8),n,i;t[0]===e?(n=t[1],i=t[2]):({className:n,...i}=e,t[0]=e,t[1]=n,t[2]=i);let a;t[3]===n?a=t[4]:(a=r(`fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0`,n),t[3]=n,t[4]=a);let s;return t[5]!==i||t[6]!==a?(s=(0,m.jsx)(o,{"data-slot":`sheet-overlay`,className:a,...i}),t[5]=i,t[6]=a,t[7]=s):s=t[7],s}function v(e){let t=(0,p.c)(17),n,o,s,l,d;t[0]===e?(n=t[1],o=t[2],s=t[3],l=t[4],d=t[5]):({className:o,children:n,side:l,showCloseButton:d,...s}=e,t[0]=e,t[1]=n,t[2]=o,t[3]=s,t[4]=l,t[5]=d);let f=l===void 0?`right`:l,h=d===void 0||d,v;t[6]===Symbol.for(`react.memo_cache_sentinel`)?(v=(0,m.jsx)(_,{}),t[6]=v):v=t[6];let y;t[7]===o?y=t[8]:(y=r(`fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10`,o),t[7]=o,t[8]=y);let b;t[9]===h?b=t[10]:(b=h&&(0,m.jsx)(c,{"data-slot":`sheet-close`,asChild:!0,children:(0,m.jsxs)(a,{variant:`ghost`,className:`absolute top-3 right-3`,size:`icon-sm`,children:[(0,m.jsx)(i,{}),(0,m.jsx)(`span`,{className:`sr-only`,children:`Schließen`})]})}),t[9]=h,t[10]=b);let x;return t[11]!==n||t[12]!==s||t[13]!==f||t[14]!==y||t[15]!==b?(x=(0,m.jsxs)(g,{children:[v,(0,m.jsxs)(u,{"data-slot":`sheet-content`,"data-side":f,className:y,...s,children:[n,b]})]}),t[11]=n,t[12]=s,t[13]=f,t[14]=y,t[15]=b,t[16]=x):x=t[16],x}function y(e){let t=(0,p.c)(8),n,i;t[0]===e?(n=t[1],i=t[2]):({className:n,...i}=e,t[0]=e,t[1]=n,t[2]=i);let a;t[3]===n?a=t[4]:(a=r(`flex flex-col gap-0.5 p-4`,n),t[3]=n,t[4]=a);let o;return t[5]!==i||t[6]!==a?(o=(0,m.jsx)(`div`,{"data-slot":`sheet-header`,className:a,...i}),t[5]=i,t[6]=a,t[7]=o):o=t[7],o}function b(e){let t=(0,p.c)(8),n,i;t[0]===e?(n=t[1],i=t[2]):({className:n,...i}=e,t[0]=e,t[1]=n,t[2]=i);let a;t[3]===n?a=t[4]:(a=r(`font-heading text-base font-medium text-foreground`,n),t[3]=n,t[4]=a);let o;return t[5]!==i||t[6]!==a?(o=(0,m.jsx)(d,{"data-slot":`sheet-title`,className:a,...i}),t[5]=i,t[6]=a,t[7]=o):o=t[7],o}function x(e){let t=(0,p.c)(8),n,i;t[0]===e?(n=t[1],i=t[2]):({className:n,...i}=e,t[0]=e,t[1]=n,t[2]=i);let a;t[3]===n?a=t[4]:(a=r(`text-sm text-muted-foreground`,n),t[3]=n,t[4]=a);let o;return t[5]!==i||t[6]!==a?(o=(0,m.jsx)(s,{"data-slot":`sheet-description`,className:a,...i}),t[5]=i,t[6]=a,t[7]=o):o=t[7],o}export{b as a,y as i,v as n,x as r,h as t};
import{i as e,n as t,r as n,t as r}from"./dist-D4CVSex9.js";import{nn as i}from"./index-CKWhFN2A.js";import{t as a}from"./button-D8rvET0Z.js";import{a as o,i as s,n as c,o as l,r as u,s as d,t as f}from"./dist-VQuEcoq-.js";var p=t();e();var m=n();function h(e){let t=(0,p.c)(4),n;t[0]===e?n=t[1]:({...n}=e,t[0]=e,t[1]=n);let r;return t[2]===n?r=t[3]:(r=(0,m.jsx)(f,{"data-slot":`sheet`,...n}),t[2]=n,t[3]=r),r}function g(e){let t=(0,p.c)(4),n;t[0]===e?n=t[1]:({...n}=e,t[0]=e,t[1]=n);let r;return t[2]===n?r=t[3]:(r=(0,m.jsx)(l,{"data-slot":`sheet-portal`,...n}),t[2]=n,t[3]=r),r}function _(e){let t=(0,p.c)(8),n,i;t[0]===e?(n=t[1],i=t[2]):({className:n,...i}=e,t[0]=e,t[1]=n,t[2]=i);let a;t[3]===n?a=t[4]:(a=r(`fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0`,n),t[3]=n,t[4]=a);let s;return t[5]!==i||t[6]!==a?(s=(0,m.jsx)(o,{"data-slot":`sheet-overlay`,className:a,...i}),t[5]=i,t[6]=a,t[7]=s):s=t[7],s}function v(e){let t=(0,p.c)(17),n,o,s,l,d;t[0]===e?(n=t[1],o=t[2],s=t[3],l=t[4],d=t[5]):({className:o,children:n,side:l,showCloseButton:d,...s}=e,t[0]=e,t[1]=n,t[2]=o,t[3]=s,t[4]=l,t[5]=d);let f=l===void 0?`right`:l,h=d===void 0||d,v;t[6]===Symbol.for(`react.memo_cache_sentinel`)?(v=(0,m.jsx)(_,{}),t[6]=v):v=t[6];let y;t[7]===o?y=t[8]:(y=r(`fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10`,o),t[7]=o,t[8]=y);let b;t[9]===h?b=t[10]:(b=h&&(0,m.jsx)(c,{"data-slot":`sheet-close`,asChild:!0,children:(0,m.jsxs)(a,{variant:`ghost`,className:`absolute top-3 right-3`,size:`icon-sm`,children:[(0,m.jsx)(i,{}),(0,m.jsx)(`span`,{className:`sr-only`,children:`Schließen`})]})}),t[9]=h,t[10]=b);let x;return t[11]!==n||t[12]!==s||t[13]!==f||t[14]!==y||t[15]!==b?(x=(0,m.jsxs)(g,{children:[v,(0,m.jsxs)(u,{"data-slot":`sheet-content`,"data-side":f,className:y,...s,children:[n,b]})]}),t[11]=n,t[12]=s,t[13]=f,t[14]=y,t[15]=b,t[16]=x):x=t[16],x}function y(e){let t=(0,p.c)(8),n,i;t[0]===e?(n=t[1],i=t[2]):({className:n,...i}=e,t[0]=e,t[1]=n,t[2]=i);let a;t[3]===n?a=t[4]:(a=r(`flex flex-col gap-0.5 p-4`,n),t[3]=n,t[4]=a);let o;return t[5]!==i||t[6]!==a?(o=(0,m.jsx)(`div`,{"data-slot":`sheet-header`,className:a,...i}),t[5]=i,t[6]=a,t[7]=o):o=t[7],o}function b(e){let t=(0,p.c)(8),n,i;t[0]===e?(n=t[1],i=t[2]):({className:n,...i}=e,t[0]=e,t[1]=n,t[2]=i);let a;t[3]===n?a=t[4]:(a=r(`font-heading text-base font-medium text-foreground`,n),t[3]=n,t[4]=a);let o;return t[5]!==i||t[6]!==a?(o=(0,m.jsx)(d,{"data-slot":`sheet-title`,className:a,...i}),t[5]=i,t[6]=a,t[7]=o):o=t[7],o}function x(e){let t=(0,p.c)(8),n,i;t[0]===e?(n=t[1],i=t[2]):({className:n,...i}=e,t[0]=e,t[1]=n,t[2]=i);let a;t[3]===n?a=t[4]:(a=r(`text-sm text-muted-foreground`,n),t[3]=n,t[4]=a);let o;return t[5]!==i||t[6]!==a?(o=(0,m.jsx)(s,{"data-slot":`sheet-description`,className:a,...i}),t[5]=i,t[6]=a,t[7]=o):o=t[7],o}export{b as a,y as i,v as n,x as r,h as t};
@@ -1 +1 @@
import{cn as e}from"./index-CIOHqG23.js";var t={name:`circle-x`,size:24,node:[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]],aliases:[`x-circle`]};t.node;var n=e(t),r={name:`undo-2`,size:24,node:[[`path`,{d:`M9 14 4 9l5-5`,key:`102s5s`}],[`path`,{d:`M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11`,key:`f3b9sd`}]]};r.node;var i=e(r);export{n,i as t};
import{cn as e}from"./index-CKWhFN2A.js";var t={name:`circle-x`,size:24,node:[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]],aliases:[`x-circle`]};t.node;var n=e(t),r={name:`undo-2`,size:24,node:[[`path`,{d:`M9 14 4 9l5-5`,key:`102s5s`}],[`path`,{d:`M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11`,key:`f3b9sd`}]]};r.node;var i=e(r);export{n,i as t};
+1 -1
View File
@@ -9,7 +9,7 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<title>Homelab Orchestrator</title>
<script type="module" crossorigin src="/assets/index-CIOHqG23.js"></script>
<script type="module" crossorigin src="/assets/index-CKWhFN2A.js"></script>
<link rel="modulepreload" crossorigin href="/assets/dist-D4CVSex9.js">
<link rel="modulepreload" crossorigin href="/assets/useSelector-uUwrspuX.js">
<link rel="modulepreload" crossorigin href="/assets/useMatch-CiyhmkMV.js">
+3 -2
View File
@@ -332,7 +332,8 @@ export interface HomelabStand {
}
/** Ein Lauf im Homelab (GET /api/homelab/laeufe): „Jetzt updaten“ und seit 25.09.2026 die Aktionen der Seite
* Snapshots (Bausteine snapshot-anlegen, snapshot-loeschen, snapshot-zurueck, sicherung-loeschen). */
* Snapshots (Bausteine snapshot-anlegen, snapshot-loeschen, snapshot-zurueck, sicherung-loeschen) sowie die
* Rückweg-Probe (Baustein probe). */
export interface HomelabLauf {
id: string
ziel: string
@@ -343,7 +344,7 @@ export interface HomelabLauf {
ende: number | null
ergebnis:
| "eingespielt" | "zurueckgerollt" | "fehler" | "neustart" | "aufgeraeumt" | "offen"
| "angelegt" | "geloescht" | "zurueckgesetzt" | null
| "angelegt" | "geloescht" | "zurueckgesetzt" | "probe-gruen" | null
text: string | null
schritte: string[]
}
+3 -2
View File
@@ -517,8 +517,9 @@ function Zuletzt({ homelabAktiv }: { homelabAktiv: boolean }) {
...(box.data?.laeufe ?? []).slice(0, 8).map((l) => ({
zeit: new Date(l.start).getTime(), bereich: "box" as Bereich, titel: l.anlass, chip: l.titel, art: ERGEBNIS[l.stufe] ?? "grau",
})),
// Die Aktionen der Seite Snapshots sind keine Updates.
...(lab.data?.laeufe ?? []).filter((l) => l.status !== "laeuft" && !istSnapshotLauf(l)).slice(0, 8).map((l) => ({
// Die Aktionen der Seite Snapshots und die Rückweg-Probe sind keine Updates.
...(lab.data?.laeufe ?? []).filter((l) => l.status !== "laeuft" && !istSnapshotLauf(l) && l.baustein !== "probe")
.slice(0, 8).map((l) => ({
zeit: l.start * 1000, bereich: "homelab" as Bereich, titel: l.name,
chip: l.ergebnis ? (ERGEBNIS_TEXT[l.ergebnis] ?? l.ergebnis) : "unterbrochen",
art: (l.ergebnis ? ERGEBNIS[l.ergebnis] : "grau") ?? "grau",