From 528198de60d7b5fc7401d5c393a07601a9e69ef7 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Thu, 24 Sep 2026 19:20:04 +0200 Subject: [PATCH] homelab: Update-Weg je App (Knopf nur, wo das Community-Script die App wirklich aktualisiert), Version von PVE Scripts Local aus der App selbst Nachgelesen in ct/.sh: AdGuard und PVE Scripts Local verweisen auf ihren eingebauten Updater, Gitea/NetBird/NPMplus aktualisieren wirklich, PBS ueber die Pakete. /root/.proxmoxve-local stand noch auf 0.5.8, die App selbst schon auf der neuesten Veroeffentlichung (v1.2.1, untagged). Co-Authored-By: Claude Opus 5.5 --- backend/kern/github.py | 3 ++- backend/services/homelab/apps.py | 17 +++++++++++++---- backend/services/homelab/inventar.py | 10 ++++++++++ backend/services/homelab/updates.py | 3 +++ backend/tests/fixtures/pve-bericht.json | 2 +- backend/tests/test_homelab.py | 20 +++++++++++++++++--- backend/tests/test_wartung.py | 3 ++- deploy/homelab/ausfuehrer.py | 4 +++- 8 files changed, 51 insertions(+), 11 deletions(-) diff --git a/backend/kern/github.py b/backend/kern/github.py index 41283f6..71cb766 100644 --- a/backend/kern/github.py +++ b/backend/kern/github.py @@ -41,4 +41,5 @@ def neueste_version(repo: str) -> dict | None: return None tag, name = str(daten["tag_name"]), str(daten.get("name") or "") roh = tag if _VERSION.match(tag) or not _VERSION.match(name) else name - return {"tag": roh, "version": roh.lstrip("vV"), "datum": str(daten.get("published_at") or "")[:10] or None} + return {"tag": roh, "tag_roh": tag, "version": roh.lstrip("vV"), + "datum": str(daten.get("published_at") or "")[:10] or None} diff --git a/backend/services/homelab/apps.py b/backend/services/homelab/apps.py index a3342c1..f4a477f 100644 --- a/backend/services/homelab/apps.py +++ b/backend/services/homelab/apps.py @@ -1,6 +1,13 @@ """Was in welchem Container steckt (Community-Scripts-Kennung aus /usr/bin/update im Gast). Stand der Bestandsaufnahme vom 24.09.2026: sechs Container, alle mit dem Etikett community-script. + +Wie eine App aktualisiert wird, ist je Community-Script verschieden (nachgelesen am 24.09. in ct/.sh): + skript `update` im Gast spielt die App wirklich neu ein (Gitea: neues Programm von GitHub, + NetBird: apt aus dem NetBird-Repository, NPMplus: neues Docker-Image) + eigene-oberflaeche das Skript verweist auf den eingebauten Updater der App (AdGuard, PVE Scripts Local) + pakete die App kommt als Paket; das Update der Pakete im Gast ist ihr Update (PBS) +Nur beim Weg „skript“ bietet die Übersicht „Jetzt updaten“ für die App an. Neue Container mit dem Etikett erscheinen von selbst; fehlt ihre Kennung hier, zeigt die Übersicht sie ohne Versionsvergleich und ohne Webprüfung — nachtragen genügt. """ @@ -17,20 +24,22 @@ class App: port: int | None = None # Weboberfläche; None = keine (z. B. NetBird-Client) https: bool = False pfad: str = "/" + weg: str = "skript" # „skript“, „eigene-oberflaeche“ oder „pakete“ (siehe oben) KATALOG: dict[str, App] = {a.kennung: a for a in ( - App("adguard", "AdGuard Home", "AdguardTeam/AdGuardHome", port=8080), + App("adguard", "AdGuard Home", "AdguardTeam/AdGuardHome", port=8080, weg="eigene-oberflaeche"), App("npmplus", "NPMplus", "ZoeyVid/NPMplus", vergleich="datum", port=81, https=True), App("netbird", "NetBird", "netbirdio/netbird"), - App("pve-scripts-local", "PVE Scripts Local", "community-scripts/ProxmoxVE-Local", port=3000), + App("pve-scripts-local", "PVE Scripts Local", "community-scripts/ProxmoxVE-Local", port=3000, + weg="eigene-oberflaeche"), App("gitea", "Gitea", "go-gitea/gitea", port=3000), - App("proxmox-backup-server", "Proxmox Backup Server", port=8007, https=True), + App("proxmox-backup-server", "Proxmox Backup Server", port=8007, https=True, weg="pakete"), )} # Gäste, die keine Community-Scripts sind, erkennt man am Namen. NACH_NAME: dict[str, App] = { - "arcane": App("arcane", "Arcane (Docker)", port=3552), + "arcane": App("arcane", "Arcane (Docker)", port=3552, weg="eigene-oberflaeche"), } diff --git a/backend/services/homelab/inventar.py b/backend/services/homelab/inventar.py index a3197a6..e55b35e 100644 --- a/backend/services/homelab/inventar.py +++ b/backend/services/homelab/inventar.py @@ -83,6 +83,13 @@ def _app_baustein(gast: dict, app: apps.App, zid: str) -> BausteinStand: stand.zustand, stand.grund = "unbekannt", "Die neueste Version ist gerade nicht abrufbar (GitHub)." return stand stand.verfuegbar = neu["version"] + if installiert and installiert.startswith("untagged-"): + # Manche Projekte veröffentlichen ohne Versions-Tag; die App kennt dann nur dieses Tag (PVE Scripts Local). + if installiert == neu.get("tag_roh"): + stand.installiert = neu["version"] + return stand + stand.zustand, stand.grund = "unbekannt", "Die installierte Fassung lässt sich keiner Version zuordnen." + return stand if app.vergleich == "datum": # Docker-Image ohne Versionsnummer: neuer, wenn die Veröffentlichung jünger ist als das Image. bild = re.search(r"\d{4}-\d{2}-\d{2}", installiert or "") @@ -91,6 +98,9 @@ def _app_baustein(gast: dict, app: apps.App, zid: str) -> BausteinStand: ist_neuer = neuer(neu["version"], installiert) if installiert else None if ist_neuer is None: stand.zustand, stand.grund = "unbekannt", "Die installierte Version ließ sich nicht lesen." + elif ist_neuer and app.weg != "skript": + stand.zustand, stand.kurz = "neu", f"{installiert} → {neu['version']}" + stand.grund = f"{app.name} aktualisiert sich über die eigene Oberfläche; das Community-Script tut es nicht." elif ist_neuer: stand.zustand, stand.kurz = "neu", f"{installiert} → {neu['version']}" rueckweg = ("Vorher wird ein Snapshot angelegt; ist die Prüfung danach rot, geht es von selbst zurück." diff --git a/backend/services/homelab/updates.py b/backend/services/homelab/updates.py index f5677af..037ef84 100644 --- a/backend/services/homelab/updates.py +++ b/backend/services/homelab/updates.py @@ -244,6 +244,9 @@ def starten(ziel_id: str, baustein: str) -> 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 == "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)."} lauf = _neuer_lauf(ziel_id, baustein, name) 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() diff --git a/backend/tests/fixtures/pve-bericht.json b/backend/tests/fixtures/pve-bericht.json index d9e38c7..097c003 100644 --- a/backend/tests/fixtures/pve-bericht.json +++ b/backend/tests/fixtures/pve-bericht.json @@ -138,7 +138,7 @@ "ip": "192.168.178.149", "app": { "kennung": "pve-scripts-local", - "version": "0.5.8" + "version": "untagged-80028680f0b29a379726" }, "os_updates": { "anzahl": 0, diff --git a/backend/tests/test_homelab.py b/backend/tests/test_homelab.py index cad5aa7..363959a 100644 --- a/backend/tests/test_homelab.py +++ b/backend/tests/test_homelab.py @@ -27,9 +27,10 @@ def daten(tmp_path, monkeypatch): # Keine Netzabfragen: neueste Versionen und Weboberflächen sind fest. neueste = {"AdguardTeam/AdGuardHome": "0.107.79", "go-gitea/gitea": "1.27.3", "netbirdio/netbird": "0.79.0", "community-scripts/ProxmoxVE-Local": "1.2.1"} + roh = {"community-scripts/ProxmoxVE-Local": "untagged-80028680f0b29a379726"} monkeypatch.setattr(inventar, "neueste_version", lambda repo: { - "tag": neueste.get(repo, "2026-07-24-r1"), "version": neueste.get(repo, "2026-07-24-r1"), - "datum": "2026-07-24"}) + "tag": neueste.get(repo, "2026-07-24-r1"), "tag_roh": roh.get(repo, neueste.get(repo, "2026-07-24-r1")), + "version": neueste.get(repo, "2026-07-24-r1"), "datum": "2026-07-24"}) monkeypatch.setattr(inventar, "erreichbar", lambda url: True) monkeypatch.setattr(inventar.arcane, "app_version", lambda url: {"installiert": "2.12.0", "neu": "2.13.1", "update": True}) @@ -106,7 +107,9 @@ def test_ziele_aus_dem_echten_bericht(daten): assert (pbs["os"]["zustand"], pbs["os"]["kurz"]) == ("neu", "58 Pakete") assert "Bind-Mount" in z["ct-105"]["rueckweg"] - assert {b["id"]: b for b in z["ct-103"]["bausteine"]}["app"]["kurz"] == "0.5.8 → 1.2.1" + # PVE Scripts Local steht auf der neuesten (untagged) Veröffentlichung: aktuell, angezeigt als 1.2.1. + pvesl = {b["id"]: b for b in z["ct-103"]["bausteine"]}["app"] + assert (pvesl["zustand"], pvesl["installiert"], pvesl["aktion"]) == ("aktuell", "1.2.1", None) assert {b["id"]: b for b in z["ct-101"]["bausteine"]}["app"]["zustand"] == "aktuell" # Image jünger # NetBird hat keine Weboberfläche: erreichbar heißt hier „läuft“. assert z["ct-102"]["erreichbar"] is True @@ -334,3 +337,14 @@ def test_hinweise_des_homelab_waechters(daten, monkeypatch): encoding="utf-8") antwort = _client().get("/api/homelab/hinweise").json() assert [h["titel"] for h in antwort["hinweise"]] == ["Gitea antwortet nicht"] + + +def test_apps_mit_eigenem_updater_bekommen_keinen_knopf(daten, monkeypatch): + """AdGuard und PVE Scripts Local aktualisiert das Community-Script nicht (ct/.sh, 24.09.2026).""" + bericht = json.loads(json.dumps(BERICHT)) + next(g for g in bericht["gaeste"] if g["vmid"] == 100)["app"]["version"] = "0.107.70" + kanal.bericht_speichern(bericht) + adguard = {b["id"]: b for b in {z["id"]: z for z in inventar.ziele()["ziele"]}["ct-100"]["bausteine"]}["app"] + assert adguard["zustand"] == "neu" and adguard["aktion"] is None and "eigene Oberfläche" in adguard["grund"] + antwort = updates.starten("ct-103", "app") + assert antwort["ok"] is False and "Community-Script" in antwort["detail"] diff --git a/backend/tests/test_wartung.py b/backend/tests/test_wartung.py index 7ba4ac1..22aac5b 100644 --- a/backend/tests/test_wartung.py +++ b/backend/tests/test_wartung.py @@ -46,7 +46,8 @@ def test_github_antworten_werden_gemerkt_fehler_nicht(monkeypatch): monkeypatch.setattr(github.httpx, "get", lambda url, **kw: aufrufe.append(url) or antworten.pop(0)) with pytest.raises(httpx.HTTPStatusError): github.github_json("repos/a/b/releases/latest") - assert github.neueste_version("a/b") == {"tag": "v258", "version": "258", "datum": "2026-09-20"} + assert github.neueste_version("a/b") == {"tag": "v258", "tag_roh": "v258", "version": "258", + "datum": "2026-09-20"} assert github.github_json("repos/a/b/releases/latest")["tag_name"] == "v258" assert len(aufrufe) == 2 assert maintenance._github_json is github.github_json diff --git a/deploy/homelab/ausfuehrer.py b/deploy/homelab/ausfuehrer.py index 6827a9f..33d4628 100644 --- a/deploy/homelab/ausfuehrer.py +++ b/deploy/homelab/ausfuehrer.py @@ -50,7 +50,9 @@ VERSION_PROBEN = { "proxmox-backup-server": "dpkg-query -W proxmox-backup-server", "npmplus": "docker image inspect zoeyvid/npmplus:latest --format '{{.Created}}'", } -VERSIONSDATEIEN = {"pve-scripts-local": "/root/.proxmoxve-local"} +# PVE Scripts Local aktualisiert sich über den eingebauten Updater; /root/.proxmoxve-local bleibt dabei auf dem +# Stand der Erstinstallation stehen (24.09.2026: „0.5.8“, tatsächlich die neueste Veröffentlichung). +VERSIONSDATEIEN = {"pve-scripts-local": "/opt/ProxmoxVE-Local/VERSION"} log = logging.getLogger("ausfuehrer")