homelab: Images ohne Container sind kein Docker-Update; neuer Kernel heißt Neustart
Nach dem ersten echten „Alle aktualisieren“ (25.09., 8 Schritte grün): - 9 veraltete Images, die kein Container benutzt (Basis-Images für Builds/CI), blieben für immer „neu“ – Arcanes Updater tauscht nur Container. Sie zählen jetzt nicht mehr, der Grund nennt sie als Aufräum-Hinweis. - Proxmox legt bei Kernel-Updates kein /var/run/reboot-required an: 7.0.14-19 war installiert, 7.0.14-12 lief, kein Neustart-Knopf. Der Ausführer vergleicht jetzt den neuesten installierten (oder festgepinnten) Kernel mit dem laufenden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
903fbbfbb9
commit
17d4ee3ea8
@@ -196,6 +196,13 @@ def _arcane_bausteine(basis: str | None, zid: str, gast: dict) -> list[BausteinS
|
|||||||
except Exception:
|
except Exception:
|
||||||
docker.grund = "Arcane verweigert die Auskunft oder antwortet nicht (Schlüssel prüfen)."
|
docker.grund = "Arcane verweigert die Auskunft oder antwortet nicht (Schlüssel prüfen)."
|
||||||
else:
|
else:
|
||||||
|
# Nur Images, die ein Container benutzt: Arcanes Updater tauscht Container aus. Ein veraltetes Image ohne
|
||||||
|
# Container (Basis-Images für Builds, CI) bliebe sonst für immer „neu“ (25.09.2026: 9 von 11 nach dem Lauf).
|
||||||
|
ohne = [i for i in images if not i.get("benutzt")]
|
||||||
|
images = [i for i in images if i.get("benutzt")]
|
||||||
|
if ohne:
|
||||||
|
docker.grund = (f"{len(ohne)} ältere Images ohne Container ({', '.join(i['name'] for i in ohne[:4])}"
|
||||||
|
+ (" …" if len(ohne) > 4 else "") + ") betreffen nichts; aufräumen in Arcane unter Images.")
|
||||||
if images:
|
if images:
|
||||||
docker.zustand, docker.kurz = "neu", f"{len(images)} Images"
|
docker.zustand, docker.kurz = "neu", f"{len(images)} Images"
|
||||||
docker.grund = ", ".join(i["name"] for i in images[:6]) + (" …" if len(images) > 6 else "")
|
docker.grund = ", ".join(i["name"] for i in images[:6]) + (" …" if len(images) > 6 else "")
|
||||||
@@ -259,6 +266,9 @@ def _host_ziel(host: dict) -> ZielStand:
|
|||||||
if host.get("neustart_noetig"):
|
if host.get("neustart_noetig"):
|
||||||
ziel.bausteine.append(BausteinStand(
|
ziel.bausteine.append(BausteinStand(
|
||||||
id="neustart", name="Neustart", zustand="neu", kurz="steht an",
|
id="neustart", name="Neustart", zustand="neu", kurz="steht an",
|
||||||
|
installiert=host.get("kernel"), verfuegbar=host.get("kernel_neu"),
|
||||||
|
grund=(f"Neuer Kernel {host['kernel_neu']} ist installiert, es läuft noch {host.get('kernel')}."
|
||||||
|
if host.get("kernel_neu") else "Ein Update verlangt einen Neustart."),
|
||||||
aktion=Aktion("POST", "/api/homelab/ziele/pve/neustart",
|
aktion=Aktion("POST", "/api/homelab/ziele/pve/neustart",
|
||||||
"Den Proxmox-Host jetzt neu starten? ALLE Container und die Arcane-VM sind dann ein paar "
|
"Den Proxmox-Host jetzt neu starten? ALLE Container und die Arcane-VM sind dann ein paar "
|
||||||
"Minuten weg. Die KI-Box prüft danach von außen, ob alles wiederkommt.",
|
"Minuten weg. Die KI-Box prüft danach von außen, ob alles wiederkommt.",
|
||||||
|
|||||||
@@ -421,3 +421,33 @@ def test_waechter_und_uebersicht_sehen_volle_gastplatten(daten):
|
|||||||
z = {z["id"]: z for z in inventar.ziele()["ziele"]}
|
z = {z["id"]: z for z in inventar.ziele()["ziele"]}
|
||||||
assert z["ct-100"]["platte"] == {"belegt": 2_000_000_000, "gesamt": 2_040_000_000} and z["pve"]["platte"] is None
|
assert z["ct-100"]["platte"] == {"belegt": 2_000_000_000, "gesamt": 2_040_000_000} and z["pve"]["platte"] is None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_images_ohne_container_sind_kein_update(daten, monkeypatch):
|
||||||
|
"""25.09.2026: Nach dem ersten echten Lauf blieben 9 Images „neu“ — Basis-Images, die kein Container benutzt."""
|
||||||
|
monkeypatch.setenv("MC_ARCANE_KEY", "schluessel")
|
||||||
|
ohne = [{"name": n, "aktuell": None, "neu": None, "umgebung": 0, "benutzt": []} for n in ("alpine:latest", "node:22")]
|
||||||
|
monkeypatch.setattr(inventar.arcane, "images_mit_update", lambda url: ohne)
|
||||||
|
docker = {b["id"]: b for b in _ziele(None)["vm-106"]["bausteine"]}["docker"]
|
||||||
|
assert (docker["zustand"], docker["aktion"]) == ("aktuell", None)
|
||||||
|
assert docker["grund"].startswith("2 ältere Images ohne Container (alpine:latest, node:22)")
|
||||||
|
genutzt = {"name": "ghcr.io/tobi/nerdquiz:latest", "aktuell": None, "neu": None, "umgebung": 0, "benutzt": ["quiz"]}
|
||||||
|
monkeypatch.setattr(inventar.arcane, "images_mit_update", lambda url: [*ohne, genutzt])
|
||||||
|
docker = {b["id"]: b for b in _ziele(None)["vm-106"]["bausteine"]}["docker"]
|
||||||
|
assert (docker["zustand"], docker["kurz"]) == ("neu", "1 Images")
|
||||||
|
|
||||||
|
|
||||||
|
def test_neuer_kernel_heisst_neustart(daten):
|
||||||
|
"""25.09.2026: Proxmox legt bei Kernel-Updates kein reboot-required an — der Ausführer vergleicht selbst."""
|
||||||
|
a = _ausfuehrer_modul()
|
||||||
|
vorhanden = ["6.8.12-4-pve", "7.0.14-12-pve", "7.0.14-19-pve"]
|
||||||
|
assert a.neuer_kernel("7.0.14-12-pve", vorhanden) == "7.0.14-19-pve"
|
||||||
|
assert a.neuer_kernel("7.0.14-19-pve", vorhanden) is None
|
||||||
|
assert a.neuer_kernel("7.0.14-12-pve", vorhanden, pin="7.0.14-12-pve") is None # festgepinnt
|
||||||
|
assert a.neuer_kernel(None, vorhanden) is None and a.neuer_kernel("7.0.14-12-pve", []) is None
|
||||||
|
bericht = json.loads(json.dumps(BERICHT))
|
||||||
|
bericht["host"].update(neustart_noetig=True, kernel="7.0.14-12-pve", kernel_neu="7.0.14-19-pve")
|
||||||
|
kanal.bericht_speichern(bericht)
|
||||||
|
host = {b["id"]: b for b in next(z for z in inventar.ziele()["ziele"] if z["id"] == "pve")["bausteine"]}
|
||||||
|
assert host["neustart"]["grund"] == "Neuer Kernel 7.0.14-19-pve ist installiert, es läuft noch 7.0.14-12-pve."
|
||||||
|
assert host["neustart"]["aktion"]["label"] == "Jetzt neu starten"
|
||||||
|
|||||||
@@ -637,9 +637,26 @@ def _host() -> dict:
|
|||||||
host["fehler"] = str(exc)[:200]
|
host["fehler"] = str(exc)[:200]
|
||||||
code, kernel = _laufen(["uname", "-r"], 5)
|
code, kernel = _laufen(["uname", "-r"], 5)
|
||||||
host["kernel"] = kernel if code == 0 else None
|
host["kernel"] = kernel if code == 0 else None
|
||||||
|
vorhanden = [os.path.basename(p)[len("vmlinuz-"):] for p in glob.glob("/boot/vmlinuz-*")]
|
||||||
|
host["kernel_neu"] = neuer_kernel(host["kernel"], vorhanden, _lesen("/etc/kernel/proxmox-boot-pin").strip() or None)
|
||||||
|
host["neustart_noetig"] = host["neustart_noetig"] or bool(host["kernel_neu"])
|
||||||
return host
|
return host
|
||||||
|
|
||||||
|
|
||||||
|
def _versionsschluessel(version: str) -> tuple[int, ...]:
|
||||||
|
return tuple(int(x) for x in re.findall(r"\d+", version))
|
||||||
|
|
||||||
|
|
||||||
|
def neuer_kernel(laufend: str | None, vorhanden: list[str], pin: str | None = None) -> str | None:
|
||||||
|
"""Der Kernel, mit dem der Host beim nächsten Start hochkäme, wenn es nicht der laufende ist (ein festgepinnter
|
||||||
|
geht vor). Proxmox legt bei Kernel-Updates keine /var/run/reboot-required an — am 25.09.2026 war 7.0.14-19
|
||||||
|
installiert, 7.0.14-12 lief, und nichts zeigte einen Neustart an."""
|
||||||
|
if not laufend or not vorhanden:
|
||||||
|
return None
|
||||||
|
naechster = pin if pin in vorhanden else max(vorhanden, key=_versionsschluessel)
|
||||||
|
return None if naechster == laufend else naechster
|
||||||
|
|
||||||
|
|
||||||
def bericht() -> dict:
|
def bericht() -> dict:
|
||||||
speicher = _speicher_liste()
|
speicher = _speicher_liste()
|
||||||
arten = {s["storage"]: s.get("type") for s in speicher}
|
arten = {s["storage"]: s.get("type") for s in speicher}
|
||||||
|
|||||||
Reference in New Issue
Block a user