Welle 3 · Wartungsfenster und Update-Regel je Gerät (So 03:00–04:15, Start: Sicherheitsupdates automatisch)
Der Steward des Homelab-Teils prüft jede Minute, ob das Fenster offen ist, und arbeitet dann einmal je Tag den Plan ab: je Gerät nach seiner Regel — nur melden, Sicherheitsupdates automatisch (Standard, User-Entscheid) oder alles im Fenster —, mit den bekannten Update-Läufen (Rückweg vorher, Prüfung danach, bei Rot zurück). Nach dem Fenster beginnt kein Schritt mehr, Erfolge kommen als eine Meldung am Ende, Fehler sofort; der Proxmox-Host startet nie von selbst neu. Der Ausführer meldet dafür die Namen der Sicherheitspakete und spielt auf Wunsch nur diese ein (Gäste und Host); neuer Baustein „sicherheit“. Dazu „Version überspringen“ je App, die Einstellungen unter Einstellungen → Homelab und „Wartung“ statt „Störung“ im Cockpit, solange das Fenster läuft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
363bc211e1
commit
9da666d04e
@@ -42,7 +42,7 @@ from kern.einstellungen import einstellungen
|
||||
from kern.zeit import LOCAL_TZ
|
||||
|
||||
from services import announce, update_verlauf
|
||||
from services.homelab import apps, arcane, inventar, kanal, karenz
|
||||
from services.homelab import apps, arcane, inventar, kanal, karenz, sicherheit
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -202,7 +202,9 @@ def _gast_lauf(lauf: dict, gast: dict) -> None:
|
||||
"Am Gerät wurde nichts geändert.")
|
||||
return
|
||||
try:
|
||||
_auftrag(lauf, "update" if baustein == "app" else "os_update", {"vmid": vmid}, ZEITLIMIT_UPDATE_S)
|
||||
# Seit 25.09.2026 „sicherheit“: nur die Pakete, die eine Lücke schließen (Wartungsfenster, Lucy).
|
||||
mehr = {"pakete": lauf["sicherheit_pakete"]} if baustein == "sicherheit" else {}
|
||||
_auftrag(lauf, "update" if baustein == "app" else "os_update", {"vmid": vmid, **mehr}, ZEITLIMIT_UPDATE_S)
|
||||
time.sleep(WARTEN_NACH_UPDATE_S)
|
||||
nachher = _frischer_gast(lauf, vmid)
|
||||
if baustein == "app" and nachher:
|
||||
@@ -366,9 +368,11 @@ def _host_lauf(lauf: dict) -> None:
|
||||
_auftrag(lauf, "host_neustart", {}, 60)
|
||||
_ende(lauf, "neustart", "Der Proxmox-Host startet neu. Die KI-Box meldet, ob alles wiederkommt.")
|
||||
return
|
||||
_auftrag(lauf, "host_update", {}, 60 * 60)
|
||||
sicherheit = lauf["baustein"] == "sicherheit"
|
||||
_auftrag(lauf, "host_update", {"pakete": lauf["sicherheit_pakete"]} if sicherheit else {}, 60 * 60)
|
||||
_auftrag(lauf, "bericht", {}, 5 * 60)
|
||||
_ende(lauf, "eingespielt", "Proxmox-Host: Pakete eingespielt. Ein Neustart ist ein eigener Knopf.")
|
||||
_ende(lauf, "eingespielt", f"Proxmox-Host: {'Sicherheitsupdates' if sicherheit else 'Pakete'} eingespielt. "
|
||||
"Ein Neustart ist ein eigener Knopf.")
|
||||
except RuntimeError as exc:
|
||||
was = "Kernel aufräumen" if lauf["baustein"] == "kernel" else "Update"
|
||||
_ende(lauf, "fehler", f"Proxmox-Host: {was} gescheitert — {exc}", dringend=True)
|
||||
@@ -385,7 +389,7 @@ def _ende(lauf: dict, ergebnis: str, text: str, dringend: bool = False) -> None:
|
||||
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)
|
||||
anlass = "Alle aktualisieren" if lauf.get("sammellauf") else "Update von Hand"
|
||||
anlass = lauf.get("anlass") or ("Alle aktualisieren" if lauf.get("sammellauf") else "Update von Hand")
|
||||
update_verlauf.eintragen(start.isoformat(timespec="seconds"), anlass, lauf["ziel"], ergebnis, text)
|
||||
except Exception:
|
||||
log.warning("homelab: Verlauf nicht geschrieben", exc_info=True)
|
||||
@@ -398,6 +402,8 @@ def _neuer_lauf(ziel_id: str, baustein: str, name: str, sammellauf: dict | None
|
||||
"start": time.time(), "ende": None, "ergebnis": None, "text": None, "schritte": [], **mehr}
|
||||
if sammellauf:
|
||||
lauf.update(sammellauf=sammellauf["id"], sammellauf_start=sammellauf["start"])
|
||||
if sammellauf.get("anlass"): # „Wartungsfenster“ (seit 25.09.2026)
|
||||
lauf["anlass"] = sammellauf["anlass"]
|
||||
_merken(lauf)
|
||||
return lauf
|
||||
|
||||
@@ -407,7 +413,7 @@ 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", "probe"):
|
||||
if baustein not in ("app", "os", "pakete", "neustart", "docker", "kernel", "probe", "sicherheit"):
|
||||
return {"ok": False, "detail": "Unbekannter Baustein."}
|
||||
with START_SPERRE:
|
||||
if sammellauf is None and _sammellauf_laeuft():
|
||||
@@ -421,6 +427,11 @@ def starten(ziel_id: str, baustein: str, sammellauf: dict | None = None) -> dict
|
||||
host = ((kanal.bericht() or {}).get("bericht") or {}).get("host") or {}
|
||||
mehr = ({"pakete": len(host.get("updates") or [])} if baustein == "pakete"
|
||||
else {"kernel": len(host.get("kernel_alt") or [])} if baustein == "kernel" else {})
|
||||
if baustein == "sicherheit":
|
||||
namen = [str(n) for n in host.get("updates_sicherheit") or []]
|
||||
if not namen:
|
||||
return {"ok": False, "detail": "Auf dem Proxmox-Host steht kein Sicherheitsupdate an."}
|
||||
mehr = {"pakete": len(namen), "sicherheit_pakete": namen}
|
||||
else:
|
||||
gast = inventar.gast(ziel_id)
|
||||
if not gast:
|
||||
@@ -453,6 +464,13 @@ def starten(ziel_id: str, baustein: str, sammellauf: dict | None = None) -> dict
|
||||
# Was vorher war, fürs Protokoll: die App-Version bzw. die Zahl der Pakete.
|
||||
mehr = ({"version_alt": (gast.get("app") or {}).get("version")} if baustein == "app"
|
||||
else {"pakete": (gast.get("os_updates") or {}).get("anzahl")})
|
||||
if baustein == "sicherheit":
|
||||
os_stand = gast.get("os_updates") or {}
|
||||
namen = os_stand.get("sicherheit_pakete") if "sicherheit_pakete" in os_stand \
|
||||
else sicherheit.alpine_pakete(os_stand)
|
||||
if not namen:
|
||||
return {"ok": False, "detail": f"Bei {name} steht kein Sicherheitsupdate an."}
|
||||
mehr = {"pakete": len(namen), "sicherheit_pakete": [str(n) for n in namen]}
|
||||
lauf = _neuer_lauf(ziel_id, baustein, name, sammellauf, **mehr)
|
||||
ziel = _host_lauf if gast is None else (lambda lf: _gast_lauf(lf, gast))
|
||||
threading.Thread(target=ziel, args=(lauf,), name=f"homelab-{ziel_id}", daemon=True).start()
|
||||
|
||||
Reference in New Issue
Block a user