homelab: Arcane-Docker ehrlich zum Rückweg – Snapshot der VM nur mit Etikett watcher
Die Update-Liste zeigte bei Arcane „Snapshot vorher, bei Rot zurück“, obwohl das Docker-Update keinen Snapshot anlegte (und der Ausführer VM 106 ohne Etikett gar nicht anfassen darf). Jetzt: - Gäste ohne Freigabe haben Rückweg „keiner“, Rückfrage und Plan sagen es. - Echte Docker-Updates legen vorher einen Snapshot der VM an, wenn sie freigegeben ist, prüfen danach (Fehler-Container, Arcane antwortet binnen 3 min) und gehen bei Rot zurück – wie bei den Containern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c37c0dccbb
commit
67ed0458a9
@@ -13,6 +13,9 @@ Ablauf für einen Gast:
|
||||
4. Prüfen: läuft der Gast, antwortet die Weboberfläche, ist die App-Version jetzt neuer?
|
||||
5. Rot → zurück auf den Snapshot bzw. die Sicherung zurückspielen → dringende Meldung.
|
||||
Grün → ältere Snapshots bzw. Sicherungen des Orchestrators für diesen Gast weg → Meldung „eingespielt“.
|
||||
Docker-Images (Arcane, VM 106) gehen über Arcanes eigenen Updater: bis zum Schalter „echt“ nur als Probelauf,
|
||||
danach genauso mit Snapshot vorher und Rückweg bei Rot — sofern die VM freigegeben ist (Etikett watcher), sonst
|
||||
ohne; Rückfrage und Update-Liste sagen es.
|
||||
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.
|
||||
|
||||
@@ -42,6 +45,7 @@ WARTEN_NACH_UPDATE_S = 20
|
||||
ZEITLIMIT_UPDATE_S = 45 * 60
|
||||
ZEITLIMIT_KURZ_S = 15 * 60
|
||||
ZEITLIMIT_SICHERUNG_S = 40 * 60 # Sicherung und Zurückspielen (der Ausführer gibt nach 30 min auf)
|
||||
ARCANE_FRIST_S = 180 # so lange darf Arcane nach einem Docker-Update brauchen, bis es wieder antwortet
|
||||
BETREFF = "[Homelab-Update]"
|
||||
BETREFF_ALARM = "[Alarm] Homelab-Update"
|
||||
SAMMELLAUF_SPERRE = "Es läuft gerade ‚Alle aktualisieren‘."
|
||||
@@ -243,28 +247,65 @@ def _aufraeumen(lauf: dict, vmid: int, rueckweg: tuple[str, str] | None) -> None
|
||||
lauf["schritte"].append(f"Alte Sicherung {volid} blieb stehen.")
|
||||
|
||||
|
||||
def _docker_lauf(lauf: dict, basis: str) -> None:
|
||||
"""Docker über Arcanes Updater — zuerst nur als Probelauf (User-Entscheid 24.09.2026)."""
|
||||
def _antwortet_wieder(url: str) -> bool:
|
||||
"""Nach dem Update starten Container neu: Arcane darf sich ARCANE_FRIST_S lang sammeln, bevor es rot heißt."""
|
||||
ende = time.monotonic() + ARCANE_FRIST_S
|
||||
while not inventar.erreichbar_frisch(url):
|
||||
if time.monotonic() >= ende:
|
||||
return False
|
||||
time.sleep(10)
|
||||
return True
|
||||
|
||||
|
||||
def _docker_lauf(lauf: dict, basis: str, gast: dict) -> None:
|
||||
"""Docker über Arcanes Updater — ein Probelauf, bis der Schalter in den Einstellungen auf „echt“ steht
|
||||
(User-Entscheid 24.09.2026). Echt wie bei den Containern: vorher ein Snapshot der VM, wenn der Ausführer sie
|
||||
anfassen darf (Etikett watcher); ist die Prüfung danach rot, geht es darauf zurück."""
|
||||
probe = not arcane.echt()
|
||||
rueckweg = None
|
||||
if not probe and gast.get("erlaubt"):
|
||||
try:
|
||||
rueckweg = _rueckweg_anlegen(lauf, gast)
|
||||
except RuntimeError as exc:
|
||||
_ende(lauf, "fehler", f"Arcane: Update nicht begonnen — {str(exc).rstrip('.')}. "
|
||||
"Am Gerät wurde nichts geändert.")
|
||||
return
|
||||
try:
|
||||
ergebnisse = arcane.updater(basis, probelauf=probe)
|
||||
except Exception as exc:
|
||||
_ende(lauf, "fehler", f"Arcane: Updater gescheitert — {exc}", dringend=not probe)
|
||||
return
|
||||
if probe:
|
||||
_ende(lauf, "fehler", f"Arcane: Probelauf gescheitert — {exc}")
|
||||
return
|
||||
ergebnisse, fehler = {}, [f"Der Updater brach ab ({exc})."]
|
||||
else:
|
||||
fehler = []
|
||||
gesamt = {k: sum(int((e or {}).get(k) or 0) for e in ergebnisse.values()) for k in ("checked", "updated", "failed")}
|
||||
namen = [str(i.get("resourceName")) for e in ergebnisse.values() for i in (e or {}).get("items") or []
|
||||
if isinstance(i, dict) and i.get("updateAvailable")]
|
||||
if probe:
|
||||
_ende(lauf, "offen", f"Arcane-Probelauf: {len(namen)} von {gesamt['checked']} Containern würden aktualisiert"
|
||||
+ (f" ({', '.join(namen[:8])})" if namen else "") + ". Geändert wurde nichts.")
|
||||
elif gesamt["failed"]:
|
||||
_ende(lauf, "fehler", f"Arcane: {gesamt['failed']} Container ließen sich nicht aktualisieren, "
|
||||
f"{gesamt['updated']} schon.", dringend=True)
|
||||
else:
|
||||
ok = inventar.erreichbar_frisch(basis + "/")
|
||||
_ende(lauf, "eingespielt" if ok else "fehler",
|
||||
f"Arcane: {gesamt['updated']} Container aktualisiert" + ("." if ok else ", aber Arcane antwortet danach nicht."),
|
||||
dringend=not ok)
|
||||
return
|
||||
if gesamt["failed"]:
|
||||
fehler.append(f"{gesamt['failed']} Container ließen sich nicht aktualisieren, {gesamt['updated']} schon.")
|
||||
if not _antwortet_wieder(basis + "/"):
|
||||
fehler.append("Arcane antwortet danach nicht.")
|
||||
if not fehler:
|
||||
if rueckweg:
|
||||
_aufraeumen(lauf, gast["vmid"], rueckweg)
|
||||
_ende(lauf, "eingespielt", f"Arcane: {gesamt['updated']} Container aktualisiert, Prüfung grün.")
|
||||
return
|
||||
if rueckweg:
|
||||
try:
|
||||
wie = _zurueck(lauf, gast["vmid"], rueckweg)
|
||||
_ende(lauf, "zurueckgerollt", f"Arcane: Update gescheitert ({' '.join(fehler)}) — {wie}. "
|
||||
"Läuft wieder wie vorher.", dringend=True)
|
||||
return
|
||||
except RuntimeError as exc:
|
||||
fehler.append(f"Auch der Rückweg scheiterte: {exc}")
|
||||
_ende(lauf, "fehler", f"Arcane: Update gescheitert — {' '.join(fehler)}"
|
||||
+ ("" if rueckweg else " Es gab keinen Snapshot, also keinen automatischen Rückweg."),
|
||||
dringend=True)
|
||||
|
||||
|
||||
def _host_lauf(lauf: dict) -> None:
|
||||
@@ -332,7 +373,7 @@ def starten(ziel_id: str, baustein: str, sammellauf: dict | None = None) -> dict
|
||||
if not (app and app.kennung == "arcane" and basis and arcane.schluessel()):
|
||||
return {"ok": False, "detail": "Docker-Updates gehen nur über Arcane mit API-Schlüssel."}
|
||||
lauf = _neuer_lauf(ziel_id, baustein, app.name, sammellauf)
|
||||
threading.Thread(target=_docker_lauf, args=(lauf, basis), name=f"homelab-{ziel_id}",
|
||||
threading.Thread(target=_docker_lauf, args=(lauf, basis, gast), name=f"homelab-{ziel_id}",
|
||||
daemon=True).start()
|
||||
return {"ok": True, "lauf": lauf["id"]}
|
||||
if not gast.get("erlaubt"):
|
||||
|
||||
Reference in New Issue
Block a user