Welle 2 · Neu entdeckt: neue Geräte mit Vorschlägen, Übernehmen per Klick (Etikett), App-Katalog pflegt sich selbst

Jeder Bericht wird mit einem Register bekannter Geräte verglichen; ein neuer Container oder eine neue VM erscheint im
Homelab-Cockpit unter „Neu entdeckt“, und Lucy meldet ihn einmal über Telegram. Übernehmen: „Für Updates freigeben“
(der Ausführer setzt das Etikett watcher — neue Aktion etikett_setzen, sie darf nur watcher und watcher-aus setzen oder
entfernen), „Nur beobachten“ oder „Nicht anfassen“ (watcher-aus). Unbekannte Community-Scripts-Kennungen trägt
katalog.py aus ihrem Update-Skript nach (Name, GitHub-Projekt, Port, Weg); ein Port, der nicht antwortet, schaltet die
Webprüfung nicht ein. Der Ausführer braucht ausfuehrer-einrichten.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-25 17:25:38 +02:00
co-authored by Claude Opus 5.5
parent f2bffc7203
commit c7c396f56d
13 changed files with 798 additions and 7 deletions
+36 -1
View File
@@ -34,6 +34,9 @@ Container des Homelab-Teils braucht keinen Proxmox-Schlüssel.
• Zertifikate im Bericht (seit 25.09.2026, höchstens alle 30 Minuten neu gelesen): die https-Zertifikate von
NPMplus samt dem, was es für den Namen gerade ausliefert, das des Proxmox-Hosts und das des PBS —
Laufzeit, Aussteller, Namen. Nur lesend.
• Etikett (seit 25.09.2026, Knopf „Übernehmen“ bei neu entdeckten Geräten): setzt oder entfernt genau eines der
beiden Etiketten watcher und watcher-aus — die beiden schließen sich aus, alle anderen Etiketten
bleiben, wie sie sind. Das geht auch bei Gästen ohne Freigabe: Es IST die Freigabe.
Nur die Standardbibliothek (Debian-Python des Hosts). Konfiguration: /etc/mc2-ausfuehrer.json
{"server": "http://192.168.178.x:9001", "token": "<gemeinsames Geheimnis>", "node": "pve", "nur_lesen": false}
@@ -1227,6 +1230,37 @@ def a_host_neustart(p: dict) -> tuple[int, str]:
return _laufen(["systemctl", "reboot"], 30)
# Die einzigen Etiketten, die der Server setzen oder entfernen lassen kann (seit 25.09.2026).
SETZBARE_ETIKETTEN = ("watcher", AUSGENOMMEN)
def etiketten_neu(vorher: list[str], etikett: str, an: bool) -> list[str]:
"""Die Etiketten nach dem Setzen oder Entfernen: watcher und watcher-aus schließen sich aus, alles andere bleibt."""
if etikett not in SETZBARE_ETIKETTEN:
raise ValueError(f"Etikett {etikett!r} darf der Orchestrator nicht setzen")
ohne = [t for t in vorher if t not in SETZBARE_ETIKETTEN] if an else [t for t in vorher if t != etikett]
return ohne + [etikett] if an else ohne
def a_etikett_setzen(p: dict) -> tuple[int, str]:
"""Freigabe per Etikett (Knopf „Übernehmen“): kein _gast_pruefen — der Gast hat die Freigabe ja noch nicht."""
vmid, etikett, an = p.get("vmid"), p.get("etikett"), p.get("an", True)
if not isinstance(vmid, int) or not 100 <= vmid <= 999_999_999 or not isinstance(an, bool):
raise ValueError("ungültige Gast-Nummer oder Angabe")
eintrag = next((e for e in _pvesh("/cluster/resources", "--type", "vm") or []
if int(e.get("vmid", -1)) == vmid and e.get("node") == NODE), None)
if not eintrag:
raise ValueError(f"Gast {vmid} gibt es auf diesem Host nicht")
art = eintrag["type"]
vorher = _etiketten(_pvesh(f"/nodes/{NODE}/{art}/{vmid}/config").get("tags"))
neu = etiketten_neu(vorher, str(etikett), an)
if neu == vorher:
return 0, f"etiketten={';'.join(neu)}\nSchon so."
zusatz = ["--tags", ";".join(neu)] if neu else ["--delete", "tags"]
code, text = _laufen(["pvesh", "set", f"/nodes/{NODE}/{art}/{vmid}/config", *zusatz], 60)
return code, f"etiketten={';'.join(neu)}\n{text}"
def a_bericht(p: dict) -> tuple[int, str]:
return 0, json.dumps(bericht(), ensure_ascii=False)
@@ -1265,7 +1299,8 @@ AKTIONEN = {"snapshot": a_snapshot, "update": a_update, "os_update": a_os_update
"snapshot_loeschen": a_snapshot_loeschen, "host_update": a_host_update,
"host_neustart": a_host_neustart, "bericht": a_bericht,
"sichern": a_sichern, "sicherung_zurueck": a_sicherung_zurueck, "sicherung_loeschen": a_sicherung_loeschen,
"kernel_aufraeumen": a_kernel_aufraeumen, "sicherung_probe": a_sicherung_probe}
"kernel_aufraeumen": a_kernel_aufraeumen, "sicherung_probe": a_sicherung_probe,
"etikett_setzen": a_etikett_setzen}
# Was auch im Nur-Lesen-Modus geht: Es liest nur.
NUR_LESEND = {a_bericht, a_sicherung_probe}