- deploy/homelab/ausfuehrer.py: laeuft als root auf dem Proxmox-Host, holt Auftraege beim Homelab-Teil ab (Pull, kein offener Port), feste Aktionsliste, prueft Etiketten selbst; Bericht gegen den echten Host erprobt (nur lesend). Kein Proxmox-Schluessel im Container noetig. - services/homelab: Kanal mit gemeinsamem Geheimnis, App-Katalog, Inventar -> Ziele im gemeinsamen Modell (GitHub-Versionen, Webpruefung, alte Paketlisten = unklar), Jetzt updaten: Snapshot -> Update -> Pruefung -> bei Rot zurueck + dringende Meldung - Waechter in der Rolle homelab: Ausfuehrer schweigt, Gaeste antworten nicht - kern/github.py fuer beide Rollen (auch untagged Releases mit Version im Namen) - Oberflaeche: Seite Homelab zeigt alle Geraete als Karten (KI-Box ueber /api/ziele, Homelab ueber /api/homelab/ziele) mit Stand, Rueckweg und Knopf samt Rueckfrage - Einrichtung als Skripte (container-anlegen, ausrollen, ausfuehrer-einrichten, box-partner) — noch nicht ausgefuehrt, jeder Schritt braucht das User-OK - frontend-bauen-box.sh: Frontend-Pruefung und Build auf der Box, wenn der PC keinen Speicher hat Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.8 KiB
Python
61 lines
2.8 KiB
Python
"""Ziele und Bausteine (Phase 2, 24.09.2026): ein Modell für alles, was Updates bekommt.
|
|
|
|
Ein Ziel ist ein Gerät: die KI-Box, der Proxmox-Host, ein Container, die Arcane-VM. Es besteht aus
|
|
Bausteinen (Betriebssystem, Motor, die App im Container, Docker-Images …). Jeder Baustein sagt, wie
|
|
sein Stand ist — neu, aktuell, unbekannt (mit Grund) oder festgehalten — und wie man ihn einspielt.
|
|
|
|
Die KI-Box meldet ihre Bausteine über den Box-Adapter (services/box_updates.py), der Homelab-Teil
|
|
ab Phase 3 über Proxmox- und Arcane-Adapter. So wird aus beiden Instanzen EINE Liste offener Updates
|
|
(User-Entscheid 24.09.: eine Anlaufstelle für Updates im ganzen Homelab).
|
|
"""
|
|
|
|
from dataclasses import asdict, dataclass, field
|
|
from typing import Literal
|
|
|
|
Zustand = Literal["neu", "aktuell", "unbekannt", "festgehalten", "wird-geprueft"]
|
|
# Reihenfolge für die Anzeige: was Aufmerksamkeit braucht, zuerst.
|
|
_RANG = {"unbekannt": 0, "festgehalten": 1, "neu": 2, "wird-geprueft": 3, "aktuell": 4}
|
|
|
|
|
|
@dataclass
|
|
class Aktion:
|
|
"""Wie ein Update angestoßen wird: eine Schnittstelle der Instanz, die das Ziel pflegt."""
|
|
methode: str # "POST"
|
|
pfad: str # "/api/maintenance/engine-update"
|
|
frage: str # Rückfrage vor dem Klick
|
|
label: str = "Jetzt updaten"
|
|
|
|
|
|
@dataclass
|
|
class BausteinStand:
|
|
id: str # "engine"
|
|
name: str # "Motor (llama.cpp)"
|
|
zustand: Zustand
|
|
installiert: str | None = None
|
|
verfuegbar: str | None = None
|
|
kurz: str = "" # „10 Pakete", „b11160 → b11172", „743 Änderungen"
|
|
grund: str | None = None # bei „unbekannt" und „festgehalten": warum
|
|
aktion: Aktion | None = None # None = kein Knopf (aktuell, festgehalten, unbekannt)
|
|
|
|
|
|
@dataclass
|
|
class ZielStand:
|
|
id: str # "ki-box"
|
|
name: str # "KI-Box"
|
|
art: str # "ki-box" | "proxmox-host" | "container" | "vm"
|
|
instanz: str # welche Instanz es pflegt: "box" | "homelab"
|
|
erreichbar: bool | None = None # None = nicht geprüft
|
|
bausteine: list[BausteinStand] = field(default_factory=list)
|
|
geprueft: float | None = None # Unix-Sekunden der letzten Prüfung
|
|
rueckweg: str | None = None # „Sicherung vor jedem Lauf", „Snapshot", „nur Backup"
|
|
|
|
def als_dict(self) -> dict:
|
|
daten = asdict(self)
|
|
daten["bausteine"].sort(key=lambda b: _RANG.get(b["zustand"], 9))
|
|
zaehlung: dict[str, int] = {}
|
|
for b in self.bausteine:
|
|
zaehlung[b.zustand] = zaehlung.get(b.zustand, 0) + 1
|
|
daten["zaehlung"] = zaehlung
|
|
daten["offen"] = zaehlung.get("neu", 0)
|
|
return daten
|