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:
+5
-4
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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",),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)."}
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
@@ -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/"; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -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};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -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};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -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};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -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
@@ -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
@@ -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};
|
||||
Vendored
+1
-1
@@ -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">
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user