Files
mission-control-v2/docs/ARCHITEKTUR.md
T

44 KiB
Raw Blame History

Architektur des Homelab Orchestrators (Teil Box-Wart)

Stand 24.09.2026, Code in main = 75611be. Für Agenten und Techniker. Versionen, Ports und Modelle mit Messwerten: wissen/STACK.md.

Der Homelab Orchestrator (vormals Mission Control 2, kurz „MC2") hat zwei Bereiche: „Box-Wart" für die KI-Box und „Homelab" für den Proxmox-PC. Zwei Instanzen, eine Oberfläche: Dieselbe Codebasis läuft auf der KI-Box (Rolle box) und als Container 107 auf dem Proxmox-PC (Rolle homelab, seit 24.09.). Der Homelab-Teil hat unten einen eigenen Abschnitt; der Rest beschreibt vor allem die Rolle box.

In der Oberfläche sind beide Bereiche getrennt (seit 24.09.): Links steht die Menüführung mit einem Block je Bereich — KI-Box (Cockpit /, Updates /updates, Modelle /modelle, dazu Dienste und das Hermes-Dashboard) und Homelab (Cockpit /homelab, Updates /homelab/updates, Snapshots /homelab/snapshots) —, jeder mit eigener Verbindungsanzeige und der Zahl offener Updates. Keine Seite zeigt etwas aus dem anderen Bereich. Am Handy steckt dieselbe Leiste hinter dem Menü-Knopf (frontend/src/app/Seitenleiste.tsx, lib/navigation.ts).

Der Box-Wart hält die KI-Box aktuell, passt auf sie auf und sucht bessere Modelle. Er besteht aus vier eigenen Python-Prozessen, einer Bash-Schicht für Updates, Sicherung und Meldungen und aus Zustandsdateien unter /srv/models. Er steuert zwei fremde Programme: llama-swap mit llama.cpp (die Modelle) und Hermes (der Agent „Lucy" mit Telegram).

Überblick

flowchart LR
  subgraph Netz["Heimnetz"]
    BR["Browser (PC, Handy)"]
    OC["OpenChamber (PC)"]
    LD["Lucy-Desktop (PC)"]
    NQ["NerdQuiz (Arcane)"]
  end
  subgraph Box["KI-Box"]
    MC2["MC2 :9001<br/>Oberfläche, /api"]
    GW["mc2-gateway :9010<br/>/v1, Bild-Weiche"]
    ST["mc2-steward<br/>Wächter, Re-Warm"]
    RD["mc2-radar<br/>00:30"]
    HE["Hermes :8642<br/>Lucy, Telegram"]
    LS["llama-swap :8080"]
  end
  BR --> MC2
  OC -->|/v1| MC2
  LD -->|/api, /v1| MC2
  MC2 -->|/v1 roh| GW
  HE -->|/v1| GW
  GW --> LS
  NQ -->|/v1, Alias fast| LS
  RD -->|Baseline| LS
  ST -.->|prüft| MC2

Alle Modell-Anfragen enden bei llama-swap, das je Modell einen llama-server startet. NerdQuiz geht als einziger Client an MC2 vorbei direkt auf :8080. Nur den Radar-Kandidaten startet der Radar-Lauf nachts als eigenen llama-server auf :5899, neben llama-swap.

Prozesse

Prozess (Unit) Einstieg Aufgabe Eigener Zustand
mission-control-2 (:9001) backend/app.py Oberfläche aus frontend/dist; 62 /api-Routen; reicht /v1 roh an den Gateway weiter (MC_V1_UPSTREAM); reicht das Hermes-Dashboard unter /hermes-ui/ durch (HTTP und WebSocket); Erinnerungen; startet Update- und Download-Aufträge als eigene systemd-Einheiten mc2-job-<id> (services/jobengine.py) Briefkasten, Erinnerungen, Routing-Policy, Hugging-Face-Zugang, ausgeblendete Hinweise
mc2-gateway (127.0.0.1:9010) backend/gateway_app.py /v1 mit model: auto und Bild-Weiche, Kontext-Warnung, /gw/health Token-Zähler (~/.hermes/token_stats.json)
mc2-steward backend/steward.py Re-Warm (alle 90 s, lädt das Hirn nach, wenn nichts geladen ist), Config-Watch (5 s), Wächter (jede Minute), Messwerte (ein Punkt je Minute, seit 24.09.) mc2-waechter.json (einziger Schreiber), mc2-messwerte/box-*.jsonl
mc2-radar (Timer 00:30) backend/radar_lauf.py Suche und Nachttest neuer Modelle mc2-radar.json, Baseline, /srv/models/radar/
Bash-Schicht deploy/*.sh Updates (autoupdate.sh mit update-swap.sh, update-engine.sh, Postchecks, self-repair.sh), Sicherung (backup.sh, restore.sh), Meldungen (notify.sh, morgenmeldung.sh), Vorwärmen (warmup.sh), projekte-sync.sh, Deploy (deploy.sh, pruefen.sh) Pins, Meldeprotokoll, Nacht-Warteschlange, Deploy-Log

Alle vier Python-Prozesse teilen die flachen Pakete backend/services/ und backend/kern/ (Import über PYTHONPATH und WorkingDirectory der Units). Router bleiben dünn, die Logik liegt in backend/services/ (siehe AGENTS.md).

Warum getrennte Prozesse (Umbau v3, 15.07.): Der Gateway ist klein, wird kaum angefasst und startet sich selbst neu (Restart=always, TimeoutStopSec=5). MC2 darf deshalb neu starten, ohne dass Anfragen von Lucy und OpenChamber abreißen. Der Steward überlebt MC2-Neustarts und kann MC2 selbst überwachen. Rückweg ist jeweils eine Zeile in deploy/mission-control-2.service (MC_V1_UPSTREAM bzw. MC_REWARM_ENABLED=0/MC_SENTRY_ENABLED=0 entfernen).

Fremde Dienste, die der Box-Wart nur steuert oder überwacht: llama-swap (System-Unit, --watch-config), hermes-gateway, hermes-builtin-ui, lucy-stimme, voice-service und box-console (beide schlafen seit 24.09.).

Rollen und Partner-Instanz (seit 24.09., Phase 2a)

  • backend/kern/einstellungen.py: MC_ROLLE (box oder homelab, Standard box), MC_INSTANZ (Anzeigename, Standard „Box-Wart" bzw. „Homelab"), MC_DATEN_DIR (Zustandsdateien; Standard /srv/models auf der Box, /var/lib/mc2 im Homelab), MC_PARTNER_URL (Basis-URL der anderen Instanz, leer = keine) und MC_PARTNER_NAME.
  • backend/kern/zeit.py: die eine Zeitzone MC_LOCAL_TZ (Standard Europe/Berlin) statt einer Kopie je Modul.
  • backend/kern/partner.py: Lebenszeichen der anderen Instanz über deren /api/health (5 s Zeitlimit, 15 s zwischengespeichert).
  • Rolle box hängt alle Box-Router ein. Rolle homelab lädt /api/health, /api/partner, die Homelab-Router (/api/homelab/…) und einen eigenen Live-Strom; alles andere reicht die Oberfläche über /api/partner/… an die Box weiter. steward.py betreibt Re-Warm und Config-Watch nur in der Rolle box; die Prüfungen des Wächters im Homelab stehen im Abschnitt „Der Homelab-Teil“.
  • /api/health nennt rolle und instanz; engine_reachable, gateway_reachable und brain gibt es nur auf der Box.
  • GET /api/partner liefert das Lebenszeichen der anderen Instanz. /api/partner/<pfad> reicht an <partner>/api/<pfad> weiter (alle Methoden, nach der Herkunftsprüfung). partner/… und stream werden nicht weitergereicht: Zwei Instanzen sollen sich keine Anfrage endlos zureichen, und der Live-Strom würde offen bleiben.
  • Wächter: Antwortet die andere Instanz nicht, entsteht der Hinweis „ antwortet nicht", rot erst nach MC_WAECHTER_PARTNER_TAKTE = 5 Takten, damit ein Neustart kein Alarm ist.
  • Die Units setzen weder MC_ROLLE noch MC_PARTNER_URL: Die Box läuft in der Rolle box und ohne Partner, bis die zweite Instanz eingerichtet ist.

Aufträge (seit 24.09., Phase 2b)

  • Eigene Einheiten: services/jobengine.py startet jeden Auftrag (Update, Modell-Download) per systemd-run als Einheit mc2-job-<id> des Nutzer-Managers (als root: des System-Managers). RuntimeMaxSec ist das Zeitlimit, „Abbrechen" stoppt die Einheit samt Kindern. Ein Neustart von MC2 (Deploy, Absturz) würgt den Auftrag nicht mehr ab.
  • Akten: <Datenordner>/mc2-jobs/<id>.json (Zustand), .log (Ausgabe), .exit (Exit-Code, atomar von einer Bash-Hülle geschrieben). MC2 liest die Akten beim Start (wiederaufnehmen() im Lebenszyklus der App) und beobachtet laufende Aufträge weiter; endet eine Einheit ohne Exit-Code, gilt der Auftrag als gescheitert bzw. abgebrochen oder als Zeitlimit. Beendete Aufträge verschwinden nach einem Tag bzw. ab 40 Stück.
  • Geheimnisse (z. B. HF_TOKEN) gehen über eine nur für den Nutzer lesbare Umgebungsdatei <id>.env, die der Auftrag beim Start liest und löscht — nicht über Befehlszeile oder Einheit (auf der Box nachgeprüft).
  • Nacharbeiten sind benannt (@jobengine.nacharbeit): wartung:nach_update (Zwischenspeicher leeren) und modell:rolle (Rolle nach dem Download setzen). Sie stehen mit ihren Daten in der Akte, laufen also auch nach einem Neustart, und zwar vor dem Endzustand: Wer „done" sieht, sieht auch ihre Wirkung.
  • Ohne systemd (PC, Tests, MC_JOBS_ART=prozess) läuft der Auftrag als Kindprozess und überlebt keinen Neustart. Ein Probelauf daneben (MC_PROBELAUF=1) nimmt keine Aufträge auf.

Ziele, Bausteine und Update-Verlauf (seit 24.09., Phase 2d)

  • Gemeinsames Modell backend/kern/ziele.py: Ein Ziel ist ein Gerät (KI-Box, später Proxmox-Host, Container, Arcane-VM) mit Bausteinen. Jeder Baustein hat einen Stand (neu, aktuell, unbekannt mit Grund, festgehalten, wird-geprueft), Versionen und den Knopf, der das Update anstößt (Methode, Pfad, Rückfrage).
  • Box-Adapter services/box_updates.py (ki_box_ziel()): Betriebssystem, Motor, llama-swap und Hermes aus dem Zwischenspeicher der Update-Prüfung (10 Minuten, nach jedem Update sofort neu), Pins als festgehalten, gescheiterte Prüfungen als unbekannt. GET /api/ziele liefert die KI-Box; der Homelab-Teil liefert seine Ziele unter /api/homelab/ziele. Die Oberfläche zeigt beide getrennt, jedes in seinem Bereich.
  • Strukturierter Update-Verlauf <Datenordner>/mc2-update-verlauf.jsonl: je Baustein eine JSON-Zeile (lauf, anlass, ts, baustein, ergebnis, text). Es schreiben autoupdate.sh (Helfer ergebnis/verlauf; die Telegram-Texte bleiben Zeichen für Zeichen gleich) und die Update-Knöpfe (Abschluss-Haken wartung:verlauf der Aufträge, bei „Alle aktualisieren" mit dem Teil, an dem die Kette scheiterte). services/update_verlauf.py liest ab dem ersten strukturierten Eintrag nur noch ihn, ältere Läufe weiter aus ~/mc2-notify.log. Den Hermes-Auftrag des Sonntags-Laufs startet autoupdate.sh mit ?verlauf=0, damit er nicht doppelt erscheint.
  • Sammel-Schreiben der llama-swap-Config llamaswap.sammeln(): Alle Änderungen darin lesen dieselbe Config aus dem Speicher; geschrieben wird einmal am Ende, bei einem Fehler gar nicht. Ein Radar-Tausch (Eintragen, Befehl, Zwilling, Alias, Gruppe, ttl) und die Hirn-Umstellung sind damit je ein Schreibvorgang statt bis zu sieben; jeder Schreibvorgang lässt llama-swap alle Modelle entladen. Hermes wird erst danach umgestellt.

Modelle und Modell-Rollen

  • Rollen-Aliase statt Namen: Clients fragen hermes/fast (Hirn), coder/heavy (Coder), vision und coder-bild (Bild-Zwillinge), embed, reranker. Die Eintragsnamen ändern sich beim Tausch, die Aliase nicht.
  • Gruppen: brains (swap: false, persistent: true, exclusive: false) enthält nur das Hirn, es bleibt geladen. bild (swap: true, exclusive: false) enthält die zwei Zwillinge, höchstens einer ist geladen. Alles andere lädt bei Bedarf und geht nach ttl wieder raus.
  • Bild-Weiche (routers/gateway_proxy.py, services/router_logic.py): Hat der aktuelle Schritt ein Bild, geht die Anfrage an den Zwilling der Rolle (Coder-Ziele an coder-bild, alles andere an vision). Ältere Bilder beschreibt vision einmal als Text (Cache je Bild), damit der Rest beim schnellen Modell mit Draft bleibt.
  • model: auto ist die Chat-Spur: normal fast, bei langen oder schweren Anfragen heavy (Schwellen in der Routing-Policy mc2-routing.json, ohne Datei gelten die Env-Defaults).
  • Warm halten: warmup.sh (Root-Kopie in /usr/local/bin/llama-swap-warmup.sh) läuft nach jedem Start von llama-swap. Danach übernimmt der Re-Warm im Steward; welches Modell warm bleibt, sagt MC_WARMSET im Drop-in mc2-steward.service.d/warmset.conf (heute nur das Hirn).
  • Speicherregel: Warm-Set plus größtes Bedarfsmodell ≤ ~115 GB, sonst droht ein Kernel-OOM.

Wer schreibt was

Datei Schreiber Regel
/etc/llama-swap/config.yaml MC2 (Modelle, Rollen, Aufräumen, Hirn-Umstellung), Radar („Übernehmen"), deploy.sh, restore.sh Die lebende Datei ist die Wahrheit. MC2 schreibt nur unter llamaswap.config_sperre() (Thread-Sperre plus flock auf .mc2-config.lock) und atomar. deploy.sh überschreibt nur, wenn sich deploy/llama-swap.config.yaml im selben Deploy geändert hat. Jede Änderung lädt llama-swap neu und entlädt alle Modelle.
~/.hermes/config.yaml Hermes; MC2 nur bei der Hirn-Umstellung; autoupdate.sh und restore.sh beim Rückweg; self-repair.sh MC2 ändert nur model.default/model.model, atomar, vorher config.yaml.bak-hirn-<Zeit>; bei einem Lesefehler schreibt es nichts.
mc2-steward.service.d/warmset.conf deploy.sh; das Radar beim Übernehmen eines neuen Hirns Das Radar tauscht den Namen in MC_WARMSET und startet den Steward neu.
/srv/models/mc2-waechter.json nur der Steward MC2 liest und führt Knöpfe aus; der nächste Takt sieht das Ergebnis. Der Ordner folgt MC_DATEN_DIR.
/srv/models/mc2-quittiert.json nur MC2 (Knopf „Ausblenden bis zum nächsten Lauf") Der Steward liest; ein ausgeblendeter Werkzeugfehler-Hinweis kommt wieder, wenn der nächste Lauf des Jobs erneut Fehler hat.
/srv/models/mc2-radar.json (+ mc2-radar-baseline.json) Radar-Lauf und MC2 jede Änderung unter flock, atomar
/srv/models/mc2-jobs/ nur MC2 (Akten) und die Hülle des Auftrags (.log, .exit) Akten atomar; Protokoll und Exit-Code schreibt der Auftrag selbst, MC2 hängt nur [mc]-Zeilen an.
/srv/models/mc2-pins.json autoupdate.sh hält fest; MC2 gibt frei Format: Baustein → pinned, version, grund, datum
/srv/models/mc2-announce.json (Briefkasten, 200 Einträge) nur MC2 Steward und Gateway liefern per HTTP (MC_ANNOUNCE_HTTP), notify.sh per POST /api/voice/announce
/srv/models/mc2-reminders.json nur MC2 Erinnerungs-Schleife und /api/reminders im selben Prozess
/srv/models/mc2-geheimnisse.json MC2 (Einstellungen) Hugging-Face-Zugang, Rechte 0600
/srv/models/mc2-discover.json MC2, Radar Cache der Hugging-Face-Entdeckung (12 h)
/srv/models/mc2-deploy.log deploy.sh eine Zeile je Deploy, auch gescheiterte
~/mc2-notify.log notify.sh (anhängen), morgenmeldung.sh (über 3 MB auf 2 MB kürzen) Quelle des Update-Verlaufs für Läufe vor dem 24.09.; der Wortlaut „QUEUED für Morgen-Digest" muss bleiben
/srv/models/mc2-update-verlauf.jsonl autoupdate.sh, MC2 (Update-Knöpfe) nur anhängen, eine JSON-Zeile je Baustein
/srv/models/mc2-messwerte/box-JJJJ-MM-TT.jsonl nur der Steward (Box); im Homelab /var/lib/mc2/mc2-messwerte/<gerät>-… nur MC2 nur anhängen unter flock, eine Zeile je Minute; Tage älter als 8 löscht der Schreiber selbst (siehe „Monitoring“)
~/.hermes/night-queue.txt notify.sh (anhängen), morgenmeldung.sh (übernehmen, senden) nicht gesendeter Stapel bleibt als .senden liegen

Meldeweg

deploy/notify.sh ist der eine Meldeweg: Die Meldung geht in Lucys Briefkasten (POST /api/voice/announce) und an Telegram. Zwischen 00:00 und 06:59 sammelt notify.sh alles Nicht-Dringende in der Nacht-Warteschlange; morgenmeldung.sh (Timer 07:00) schickt es als eine Nachricht. Dringend ist -d, ein Betreff mit „Alarm" oder „Notfall" oder ein Text, der mit „KRITISCH" beginnt. Lucy-Desktop fragt den Briefkasten ab (/api/voice/announcements) und spricht neue Einträge.

Telegram hat zwei Wege: zuerst hermes send --to telegram; scheitert das oder fehlt Hermes (die Homelab-Instanz hat keins), geht die Meldung direkt an die Telegram-Bot-API (seit 24.09., live bewiesen 15:41). Die Zugangsdaten liest notify.sh aus der Datei in MC_TELEGRAM_ENV (Standard ~/.hermes/.env: TELEGRAM_BOT_TOKEN, TELEGRAM_HOME_CHANNEL, optional TELEGRAM_HOME_CHANNEL_THREAD_ID) und gibt das Token über stdin an curl, damit es nicht in der Prozessliste steht. Erst wenn beide Wege scheitern, bleiben Protokoll und wall (MC_NOTIFY_OHNE_WALL=1 schaltet wall ab). Einzelheiten und Betreffzeilen: BETRIEB.md.

Schnittstellen

In der Rolle box 62 Routen unter /api: 60 des Box-Warts (seit 24.09., vorher 95) und 2 für die Partner-Instanz. In der Rolle homelab gibt es nur /api/health und die Partner-Routen.

Gruppe Routen
Cockpit (routers/boxwart.py) GET /start, GET /hinweise, POST /hinweise/{id}/aktion/{aktion}, GET /modelle/nutzung, GET /updates/verlauf, POST /updates/festgehalten/{baustein}/freigeben, GET /modelle/aufraeumen, POST /modelle/aufraeumen/loeschen
Radar (routers/radar.py) GET /radar, POST /radar/suche, POST /radar/{id}/uebernehmen, POST /radar/{id}/verwerfen
Modelle (routers/models.py) GET /models, GET /discover, POST /models/register, GET /hf/search, GET /hf/quants, POST /models/install, GET /jobs, POST /jobs/{id}/cancel, POST /models/{id}/role, POST /models/unload, POST /models/{id}/unload, POST /models/{id}/load, DELETE /models/{id}
Wartung (routers/maintenance.py) GET /maintenance/updates, GET /maintenance/update-details, POST check-updates, os-update, engine-update, swap-update, hermes-update, update-all, restart; GET /maintenance/logs; GET/POST /maintenance/geheimnisse
System (routers/system.py) GET /system/status, GET /system/services (Dienste mit Zustand, Speicher, CPU, Laufzeit und Neustarts aus einem systemctl show je Bereich, seit 25.09.; services/dienste.py), POST /system/backup, GET /system/backups, POST /system/restart, GET /system/token-stats
Messwerte (routers/messwerte.py, seit 24.09.) GET /messwerte?zeitraum=1h|24h|7d
Lucy und Sprache (routers/voice.py) POST /alarm, POST /voice/announce, GET /voice/announcements, POST /voice/stt, POST /voice/turn, POST /voice/chat, GET /lucy/stimme/health, POST /lucy/stimme/tts, POST /lucy/stimme/tts/stream
Sonstiges GET /health, GET /stream (Live-Strom, SSE), GET /routing, GET/POST /reminders, DELETE /reminders/{id}, GET /zeitmaschine, POST /zeitmaschine/restore
Partner (routers/partner.py) GET /partner (Lebenszeichen), /partner/{pfad} (Durchreiche, alle Methoden)

Dazu /v1/* (Weiterleitung an den Gateway), /hermes-ui/ und die Auslieferung der Oberfläche. Unbekannte /api-Pfade liefern einen Fehler (bei GET 404) statt der Startseite. Die MCP-Server in mcp/ sprechen dieselben Routen.

Lucy bedient den Orchestrator (seit 25.09.2026, User-Ja: „Lucy ist die Systemadministratorin und Assistentin“): MCP-Server homelab-orchestrator (mcp/mcp_orchestrator.py, Logik ohne MCP testbar in mcp/orchestrator_werkzeuge.py), in Hermes eingetragen mit hermes mcp add (Werkzeuge heißen dort mcp__homelab_orchestrator__…). Lesen: lage, updates, speicher, protokoll, radar. Handeln — genau die Knöpfe der Oberfläche über dieselben Routen, mit ihren Leitplanken: update_starten (nimmt den Pfad aus der Aktion des Bausteins, Homelab über /api/partner/homelab/…), alle_aktualisieren, nach_updates_suchen, dienst_neustarten (Allowlist in services.maintenance), kernel_aufraeumen, host_neustarten, radar_testen, radar_suchen. Jede Aktion braucht einen grund (die Bitte des Commanders) und steht in ~/.hermes/logs/orchestrator-werkzeuge.log; die Beschreibungen sagen dem Modell: nur auf ausdrücklichen Wunsch, nie auf eigene Initiative (außer einem Dienst-Neustart als Reparatur). Nichts davon geht an Proxmox oder die Gäste vorbei. Warum nicht „alle Meldungen an Hermes“: Alarme blieben aus, wenn Box oder Hermes selbst hängen — Meldungen kommen deshalb weiter aus Skripten (in Lucys Stimme, siehe BETRIEB.md) und landen zusätzlich in ihrem Briefkasten.

Herkunftsprüfung (backend/services/herkunft.py, statt Anmeldung): Schreibende Aufrufe (POST, PUT, PATCH, DELETE) auf /api/ mit dem Origin einer fremden Webseite lehnt MC2 mit 403 ab. Erlaubt bleiben Aufrufe ohne Origin (Skripte, curl), Origin: null bzw. file:// (Lucy-Desktop), dieselbe Adresse wie die Oberfläche und die Entwicklungs-Ports 5173, 5180, 5181. /v1 ist nicht betroffen.

Der Homelab-Teil (Phasen 3 und 4, live seit 24.09.)

Derselbe Code in der Rolle homelab, in einem eigenen Container auf dem Proxmox-PC (deploy/homelab/). Er braucht keinen Proxmox-Schlüssel: Alles, was vom Host kommt, liefert der Ausführer.

flowchart LR
  AF["Ausführer<br/>Proxmox-Host, root"] -->|holt Aufträge, Bericht alle 10 min| HL["Homelab-Teil<br/>Container, :9001"]
  HL -->|Ziele, Läufe| UI["Oberfläche<br/>Seite Homelab"]
  BOX["KI-Box :9001"] <-->|prüfen sich, /api/partner| HL
  HL -->|Webprüfung| G["Gäste<br/>AdGuard, Gitea …"]
  • Ausführer deploy/homelab/ausfuehrer.py (root auf dem Host, nur Standardbibliothek, mc2-ausfuehrer.service): holt sich Arbeit beim Homelab-Teil ab („Pull“, kein offener Port auf dem Host) und weist sich mit einem gemeinsamen Geheimnis aus (Kopfzeile X-MC2-Ausfuehrer; erzeugt der Homelab-Teil in /var/lib/mc2/ausfuehrer.token, liegt auf dem Host in /etc/mc2-ausfuehrer.json, beides 0600). Er führt nur eine feste Liste von Aktionen aus (bericht, snapshot, update, os_update, suchen, zurueck, snapshot_loeschen, sichern, sicherung_zurueck, sicherung_loeschen, host_update, host_neustart) und prüft selbst, ob ein Gast das Etikett community-script oder watcher trägt und nicht watcher-aus — dem Server vertraut er dabei nicht. Seit 24.09. schickt er außerdem jede Minute Messwerte (eigener Faden, siehe „Monitoring“). snapshot nimmt seit 25.09. einen Anlass (update, Standard, oder knopf) und wählt damit eine feste Beschreibung („Vor einem Update durch …“ bzw. „Auf Knopfdruck im Homelab Orchestrator angelegt“) — freien Text nimmt er nicht.
  • Bericht (nur lesend, auch von Hand: python3 ausfuehrer.py --bericht): Host-Version und Paket-Updates, je Gast Status, IP, Etiketten, Snapshots, ob ein Snapshot geht (Bind-Mounts wie beim PBS verhindern ihn), die Community-Script-Kennung (aus /usr/bin/update im Gast), die App-Version (je App eigener Weg: /root/.<app>, AdGuardHome --version, netbird version, dpkg-query, Docker-Image-Datum) und die Paket-Updates im Gast samt Alter der Paketlisten. Dazu der Sicherungsspeicher (host.sicherung: Name und frei, oder was fehlt), je Container ohne Snapshot, ob eine Sicherung geht (sicherung_moeglich, sonst sicherung_grund), und die eigenen Sicherungen je Gast. Seit 25.09. dazu snapshot_details (Name, zeit = snaptime, Beschreibung, eigen = Name mc2-…; ohne Belegung, die kennt Proxmox bei LVM-Thin nicht) und sicherung_details (Archiv, zeit = ctime, groesse); die Namenslisten snapshots und sicherungen bleiben unverändert.
  • Sicherung statt Snapshot (Ausführer, seit 24.09.): Wo kein Snapshot geht, sichert sichern den Container per vzdump — auf den ersten lokalen Speicher, der Sicherungen annimmt (Art dir/btrfs, nicht geteilt; heute local = /var/lib/vz), oder auf sicherung_speicher aus /etc/mc2-ausfuehrer.json. Nie auf einen Speicher der Art pbs: Der PBS würde sich selbst sichern. Nimmt kein lokaler Speicher Sicherungen an, lehnt er ab und sagt im Bericht, was fehlt; die Speicher-Konfiguration ändert er nicht. Vorher prüft er den Platz (frei > belegt × 1,2; belegt = rootfs, Bind-Mounts sichert vzdump nie mit). vzdump läuft mit --mode snapshot (rootfs auf lvmthin: der Gast läuft durch), sonst stop, dazu --remove 0 (keine Aufräumregeln des Speichers) und der Notiz mc2-sicherung: …. Nur Sicherungen mit dieser Notiz spielt er zurück (sicherung_zurueck: Gast stoppen, pct restore <vmid> <archiv> --force 1 --storage <bisheriger rootfs-Speicher>, starten; Bind-Mount-Daten bleiben unberührt) oder löscht er (sicherung_loeschen, pvesm free). Zeitlimit 30 min; läuft es ab, bekommt vzdump bzw. pct restore erst SIGTERM, damit Sperre und Snapshot aufgeräumt werden.
  • Homelab-Teil backend/services/homelab/: kanal.py (Geheimnis, Auftragsliste unter Dateisperre, Bericht), apps.py (App-Katalog: Name, GitHub-Quelle, Weboberfläche, Update-Weg), inventar.py (Bericht + neueste Versionen von GitHub + eigene Webprüfung → Ziele im gemeinsamen Modell; Paketlisten älter als 14 Tage = „unklar“ mit Knopf „Nach Updates suchen“), karenz.py (Wartezeit nach einer Skriptänderung), updates.py („Jetzt updaten“), pflege.py (wöchentliches Suchen), messwerte.py (Minutenpunkte des Ausführers, siehe „Monitoring“). Schnittstellen routers/homelab.py unter /api/homelab/….
  • „Jetzt updaten“ (nur per Knopf): Snapshot, wo keiner geht Sicherung (wo auch die nicht geht: ohne Rückweg, mit Warnung in der Rückfrage; scheitert der Schritt, beginnt das Update nicht) → update des Community-Scripts (PHS_SILENT=1) bzw. Pakete → 20 s warten → frischer Bericht → Prüfung (Gast läuft, Weboberfläche antwortet, App-Version neu). Rot → zurück auf den Snapshot bzw. die Sicherung zurückspielen + dringende Meldung; grün → ältere mc2--Snapshots bzw. mc2-sicherung-Sicherungen dieses Gasts weg (die neueste bleibt, ebenso per Knopf auf der Seite Snapshots angelegte: snapshots.knopf_namen) + Meldung. Läufe in /var/lib/mc2/homelab-laeufe.json und im strukturierten Update-Verlauf. Host: Pakete per Knopf mit Warnung, Neustart als eigener Knopf.
  • Wartezeit nach Skriptänderung (karenz.py): update lädt ct/<kennung>.sh ungepinnt von GitHub (community-scripts/ProxmoxVE, Zweig main) und führt es als root aus. Für Apps mit dem Weg „skript“ fragt der Homelab-Teil deshalb, wann das Skript zuletzt geändert wurde (/repos/community-scripts/ProxmoxVE/commits?path=…, über kern/github.py, 15 min gemerkt). Jünger als MC_HOMELAB_KARENZ_H (Standard 48 h): der Baustein bleibt „neu“, aber ohne Knopf, mit „Das Update-Skript wurde am TT.MM. geändert; zur Sicherheit erst ab TT.MM. HH:MM.“ (Berliner Zeit); updates.starten lehnt mit demselben Satz ab. Antwortet GitHub nicht, blockiert nichts; die Rückfrage sagt dann „Ob das Skript kürzlich geändert wurde, ließ sich nicht prüfen.“
  • Wöchentliches Suchen (pflege.py, im Wächter-Takt des Stewards): Sind die Paketlisten eines freigegebenen, laufenden Containers älter als 7 Tage, legt der Homelab-Teil selbst den Auftrag suchen an — höchstens einmal je Gast und Tag, nie während eines Update-Laufs oder neben einem offenen Auftrag für diesen Gast, nur wenn der Ausführer gerade berichtet, bevorzugt nachts 02:00–05:00 (war die Instanz letzte Nacht aus, eben gleich). Keine Meldungen; scheitert die Suche für einen Gast zweimal hintereinander, wird es ein gelber Hinweis des Wächters. Zustand in /var/lib/mc2/homelab-pflege.json. Weil damit zwei Prozesse Aufträge anlegen, schreibt kanal.py unter flock.
  • „Alle aktualisieren“ (sammellauf.py, seit 24.09.): alle Updates mit Knopf nacheinander, jeder Schritt ein gewöhnlicher Lauf von „Jetzt updaten“ (updates.starten), auf dessen Ende der nächste wartet. Dabei ist nur, was „neu“ ist und eine Aktion hat; was in der Wartezeit steht, fehlt. Docker über Arcane nur, wenn es echt läuft (ein Probelauf ändert nichts). Reihenfolge: die Gäste nach VMID außer NPMplus und AdGuard, dann NPMplus, dann AdGuard (Nadelöhre für Proxy und DNS), ganz zuletzt die Pakete des Proxmox-Hosts; der Neustart des Hosts ist nie dabei. GET /api/homelab/alle/plan zeigt diese Reihenfolge mit dem Rückweg je Schritt und einem Hinweis (was keinen Rückweg hat, dass der Host zuletzt kommt). POST /api/homelab/alle startet, wenn es etwas zu tun gibt, der Ausführer verbunden ist (und nicht im Nur-Lesen-Modus) und gerade kein einzelnes Update läuft.
    • Endet ein Schritt mit zurueckgerollt oder fehler (auch „Update nicht begonnen“), hört der Sammellauf auf: die übrigen Schritte uebersprungen, Status abgebrochen, dringende Meldung. Lehnt updates.starten einen Schritt ab (etwa weil die Wartezeit inzwischen greift), wird nur dieser übersprungen. Läuft alles durch, kommt eine Sammelmeldung („Homelab: 5 Updates eingespielt, alle Prüfungen grün.“); die Erfolgsmeldungen der Schritte hält updates._ende so lange zurück (gemeldet: false am Lauf), Fehlermeldungen nicht. Im Update-Verlauf stehen die Schritte als ein Lauf mit dem Anlass „Alle aktualisieren“.
    • Höchstens ein Sammellauf; solange er läuft, lehnt „Jetzt updaten“ ab („Es läuft gerade ‚Alle aktualisieren‘.“). Prüfen und Anlegen geschehen unter updates.START_SPERRE. Stand in /var/lib/mc2/homelab-sammellauf.json (die letzten 20; GET /api/homelab/sammellauf liefert den laufenden oder den letzten). Startet der Homelab-Teil mitten im Lauf neu, macht updates.unterbrochene_abschliessen() beim Start daraus abgebrochen mit dem Text „unterbrochen (Neustart)“ und meldet es dringend.
  • Protokoll (protokoll.py, seit 24.09.): GET /api/homelab/protokoll?grenze=200&berichte=0 führt zusammen, was geschah, neueste zuerst: Aufträge an den Ausführer (kanal.py hebt jetzt die letzten 500 erledigten auf; die jüngsten 60 mit ganzer Ausgabe, ältere mit den letzten 4000 Zeichen), Update-Läufe mit ihren Schritten, die Aktionen der Seite Snapshots (Art snapshot, seit 25.09.), Sammelläufe, den Verlauf der Hinweise des Wächters (der Verlauf in mc2-waechter.json trägt seit 24.09. die Stufe mit), die Zeilen des Melde-Logs (MC_NOTIFY_LOG, im Container /var/lib/mc2/notify.log) und die Ereignisse des wöchentlichen Suchens (homelab-pflege.json, Liste ereignisse). Berichte nur mit berichte=1: die Aufträge bericht der Läufe und die Berichte, die der Ausführer von sich aus schickt (ihr Eingang steht in /var/lib/mc2/ausfuehrer-berichte.json, die letzten 150). Den Betreff einer Meldung schreibt notify.sh nicht ins Log; er folgt aus dem Absender (Lauf, Sammellauf, Wächter, Telegram-Test, Morgenmeldung). Nichts im Protokoll enthält Geheimnisse: Die Schlüssel dieser Instanz (Arcane, Ausführer) und alles, was wie ein Token aussieht (Bot-Token, Bearer, name=wert mit verräterischem Namen, Zugangsdaten in Adressen), wird zu ***; Steuerzeichen der Konsole fallen weg.
  • Einstellungen des Homelab-Teils (einstellungen.py, seit 24.09.): GET /api/homelab/einstellungen (Arcane: Adresse aus dem Bericht, Herkunft des Schlüssels hinterlegt/fehlt/umgebung, echt und woher; Wartezeit in Stunden; Ausführer). POST …/arcane-schluessel prüft den Schlüssel zuerst bei Arcane mit denselben Aufrufen, die die Übersicht braucht (/api/environments, dann deren Images); nur wenn Arcane ihn annimmt, landet er in /var/lib/mc2/arcane.key (0600). Er steht nie in einer Antwort, einem Protokoll oder einer Fehlermeldung; den Body liest der Dienst selbst, damit keine 422-Antwort ihn wiederholt. DELETE …/arcane-schluessel löscht die Datei, POST …/arcane-echt ({"an": bool}) schaltet echte Docker-Updates in /var/lib/mc2/homelab-einstellungen.json. Was nicht geht, beantworten diese Endpunkte wie „Alle aktualisieren“ mit {"ok": false, "detail": …} und HTTP 200.
  • Arcane und Docker (services/homelab/arcane.py): Arcanes eigene Version kommt öffentlich über /api/app-version; die Docker-Images mit neuerem Stand und der Updater brauchen einen Arcane-API-Schlüssel (Kopfzeile X-API-Key): MC_ARCANE_KEY in /etc/mc2/homelab.env, sonst der Schlüssel aus den Einstellungen (arcane.key); beides wird bei jedem Zugriff gelesen, ein neuer Schlüssel gilt ohne Neustart. Docker-Updates laufen zuerst nur als Probelauf (dryRun); echt, wenn der Schalter in den Einstellungen an ist. Setzt die Umgebung MC_ARCANE_ECHT, gewinnt sie (1 = echt). Die Arcane-VM braucht dafür kein Etikett, weil der Ausführer nicht beteiligt ist — nur für den Rückweg: Mit Etikett watcher legt der Ausführer vor einem echten Docker-Update einen Snapshot der VM an (qm snapshot), und ist die Prüfung danach rot (ein Container scheiterte, oder Arcane antwortet nach 3 Minuten noch nicht), geht es darauf zurück. Ist sie grün, wird der Snapshot gleich wieder gelöscht, anders als bei den Containern: Die VM schreibt laufend (Docker, Rippy; ihre Thin-Platte war am 25.09. zu 99,9 % belegt), ein stehender Snapshot wüchse im Pool mit. Ohne Etikett gibt es keinen Rückweg; Update-Liste, Rückfrage und der Plan von „Alle aktualisieren“ sagen das (rueckweg_art = keiner für jeden Gast ohne Freigabe). VM 106 trägt watcher seit 25.09. (User-Ja).
  • Wächter in der Rolle homelab: Platte, Partner (die KI-Box), Ausführer (kein Bericht seit 30 min = rot), jede Weboberfläche der freigegebenen Gäste (außer mitten in ihrem Update-Lauf; das Ergebnis meldet der Lauf), die Platten der laufenden Container (ab 80 % gelb, ab 90 % rot — ext4 hält 5 % für root zurück; der Ausführer schickt platte mit), das wöchentliche Suchen (gelb, wenn es wiederholt scheitert) und seit 24.09. den Proxmox-Host selbst aus den Messwerten (gelb, wenn er 10 Minuten lang über 95 % RAM oder 90 °C CPU liegt).
  • Speicher und Sicherungen (speicher.py, GET /api/homelab/speicher, seit 25.09.): Der Bericht des Ausführers enthält dafür host.pools (Thin-Pools: belegt, gesamt, Metadaten-Anteil, verwaiste Volumes), je Gast volumes, pool_belegt (echte Belegung im Pool laut lvs), ungenutzt (unusedN) und bei VMs discard und platte (über den Gast-Agenten, get-fsinfo), dazu host.netzlaufwerke (findmnt + fstab; df läuft mit 10 s Zeitlimit in einem eigenen Prozess, den der Ausführer bei einem hängenden harten NFS-Mount zurücklässt: „haengt“), host.sicherung_jobs (/cluster/backup) und host.sicherungsziele (je Zielspeicher aktiv, Belegung und die jüngste Sicherung je Gast). Der Homelab-Teil prüft selbst, ob das NAS auf dem Port seines Dienstes antwortet (NFS 2049, SMB 445). Wächter: Pool und NAS gelb ab 80 %, rot ab 90 % (User-Vorgabe: bei 10 % frei), Metadaten ebenso; NAS antwortet nicht, nicht eingebunden oder hängt = rot; eine Sicherung älter als erlaubt (Zeitplan nur mit Uhrzeit = täglich: 26 h, sonst 8 Tage) = gelb, bei allen Geräten oder unerreichbarem Ziel rot. Empfehlungen, größter Gewinn zuerst: Platz, den ein Gast freigegeben hat, der im Pool aber belegt bleibt (VM ohne Discard; Container per pct fstrim, zusammengefasst ab 1 GB), ungenutzte und verwaiste Platten, Snapshots (seit 25.09. ein einziger Vorschlag mit Verweis auf die Seite Snapshots; Größe unbekannt, zuletzt). Seit 25.09. auch die Systemplatte des Hosts (host.systemplatte, statvfs("/") wie df) und alte Kernel: host.kernel_alt = was apt-get -s autoremove entfernen würde, nur Kernel-Pakete, nie laufender, nächster (kernel_neu) oder festgepinnter Kernel, mit Größe samt initrd; host.kernel_bleiben. Der Vorschlag hat einen Knopf (POST /api/homelab/ziele/pve/kernel-aufraeumen → Lauf kernel → Ausführer-Aktion kernel_aufraeumen, die die Liste selbst neu bestimmt und apt-get -y purge ausführt; Ergebnis aufgeraeumt). neustart_noetig kommt außerdem, wenn ein neuerer Kernel installiert ist als der laufende (Proxmox legt dafür kein reboot-required an).
  • Snapshots verwalten (snapshots.py, seit 25.09., User-Wunsch): GET /api/homelab/snapshots liefert je Gerät (ohne den Container des Orchestrators) die Snapshots mit Zeitpunkt, Beschreibung und Herkunft (update, knopf, proxmox), die Sicherungen des Orchestrators mit Größe, was gerade läuft (laeuft) und das Ergebnis der letzten Aktion der Seite (6 h lang); Knöpfe samt Rückfrage schickt der Homelab-Teil mit, wie im Ziel-Modell. Ohne snapshot_details (Ausführer vor dem 25.09.) kommt der Zeitpunkt aus dem Namen. Aktionen: POST …/snapshots/{id}/anlegen, …/snapshots/{id}/{name}/loeschen, …/snapshots/{id}/{name}/zuruecksetzen, …/sicherungen/{id}/{datei}/loeschen — nur freigegebene Gäste, löschen und zurücksetzen nur mc2-…, Sicherungen nur, was der Bericht als eigene nennt; der Ausführer prüft das ohnehin selbst noch einmal. Jede Aktion ist ein Lauf in homelab-laeufe.json (Bausteine snapshot-anlegen, snapshot-loeschen, snapshot-zurueck, sicherung-loeschen) mit denselben Sperren wie „Jetzt updaten“ (START_SPERRE, je Gerät einer, SAMMELLAUF_SPERRE), zusätzlich nicht, solange irgendwo ein Update läuft (_sperre: Der Ausführer arbeitet einen Auftrag nach dem anderen ab — hinter einem Update liefe die Aktion in ihr Zeitlimit und käme später unbeobachtet dran), und nur mit verbundenem Ausführer (nicht im Nur-Lesen-Modus). Abgelehnt wird mit 409; die Liste nennt denselben Grund (sperre). Danach ein frischer Bericht. Zurücksetzen: zurueck (stoppt, setzt zurück, startet), 20 s, Prüfung wie nach einem Update, immer eine Meldung (Betreff „[Homelab-Snapshot]“, bei Fehler oder roter Prüfung „[Alarm] Homelab-Snapshot“); anlegen und löschen melden nur ihr Scheitern. Nicht im Update-Verlauf, in der Oberfläche nicht unter „Verlauf“ oder „Zuletzt eingespielt“. Per Knopf angelegte Snapshots erkennt knopf_namen an der Beschreibung oder am Lauf; kein grünes Update räumt sie weg.
  • Oberfläche (Bereich „Homelab“, nur Daten aus /api/homelab/…):
    • Cockpit (/homelab): oben die Lage wie das Warnpanel der KI-Box — die große Leuchte (Störung vor Hinweis vor laufendem Update vor offenen Updates) und eine Lampe je Gerät —, darunter die Hinweise des Homelab-Wächters und die Geräteliste (App-Version, Paketstand, Erreichbarkeit; „Nach Updates suchen“ nur, wo der Stand unklar ist).
    • Updates (/homelab/updates): nur die offenen Updates, eine Zeile je Update mit alter und neuer Version, Rückweg in Kurzform (rueckweg_art) und Knopf samt Rückfrage aus dem Ziel-Modell; darunter der Verlauf der Läufe.
    • Snapshots (/homelab/snapshots, seit 25.09.): je Gerät die Snapshots und Sicherungen mit Knöpfen (components/homelab/SnapshotListe.tsx, Texte in lib/snapshots.ts); am Handy untereinander, ab md die Knöpfe rechts, ab xl fünf Spalten.
    • Die Logik (Lage, Lampen, Reihenfolge) liegt in frontend/src/lib/homelab.ts, die Ansichten in components/homelab/ und views/Homelab.tsx.
  • Meldungen ohne Hermes: notify.sh im Container nimmt den Zweitweg direkt an die Bot-API (/etc/mc2/telegram.env), nachts sammelt eine eigene Morgenmeldung („[Morgenmeldung Homelab]“).
  • Einrichtung (Reihenfolge, jeder Schritt braucht das User-OK): container-anlegen.sh → ausrollen.sh <ip> → ausfuehrer-einrichten.sh <ip> → box-partner.sh <ip>; Details in BETRIEB.md.

Monitoring: Messwerte mit Verlauf (seit 24.09.)

Der Live-Strom der KI-Box (/api/stream, jede Sekunde ein Messpunkt) zeigt nur den Augenblick. Für den Verlauf schreiben beide Bereiche jede Minute einen Punkt, gelesen wird er für die letzte Stunde, den letzten Tag oder die letzte Woche.

flowchart LR
  ST["mc2-steward (Box)<br/>jede Minute"] -->|box-…jsonl| MR[("mc2-messwerte/<br/>kern/messreihen.py")]
  AF["Ausführer (pve)<br/>eigener Faden, jede Minute"] -->|POST …/ausfuehrer/messwerte| HL["Homelab-Teil<br/>services/homelab/messwerte.py"]
  HL -->|pve-…, ct-…, vm-…jsonl| MR2[("mc2-messwerte/<br/>im Container")]
  MR -->|GET /api/messwerte| UI["Oberfläche"]
  MR2 -->|GET /api/homelab/messwerte| UI
  MR2 -->|RAM, Temperatur| W["Wächter (homelab)"]
  • Speicher backend/kern/messreihen.py (beide Rollen): je Quelle und Tag eine Datei <Datenordner>/mc2-messwerte/<quelle>-JJJJ-MM-TT.jsonl (Tag nach Berliner Zeit), eine JSON-Zeile je Minute ({"t": Unix-Sekunden, "cpu": …, …}, null = nicht gemessen). Schreiben hängt eine Zeile an, unter Thread-Sperre und flock; fehlt am Dateiende der Zeilenumbruch (Absturz), beginnt die neue Zeile auf einer eigenen. Das erste Schreiben eines neuen Tages löscht Dateien, deren Tag mehr als 8 Tage zurückliegt. Lesen verdichtet: 1h in 60-s-Schritten, 24h als 5-min-Mittel, 7d als 30-min-Mittel (60, 288 bzw. 336 Werte, Schritte auf vollen Vielfachen, der letzte ist der laufende). Ein Schritt ohne Messung bleibt null, nichts wird aufgefüllt; kaputte Zeilen werden übersprungen. Die Summen vergangener Tage merkt sich der Prozess je Datei, solange sie sich nicht ändert. Gemessen wird zur halben Minute, damit jeder 60-s-Schritt genau einen Punkt bekommt.
  • KI-Box (services/messwerte.py, Taktgeber im Steward; MC_MESSWERTE_ENABLED=0 schaltet ab, ein Trocken- oder Probelauf schreibt nie mit): cpu = Mittel der Minute (aus eigenen psutil.cpu_times-Ständen gerechnet wie cpu_percent, denn psutil merkt sich den letzten Aufruf je Thread), ram und platte (Modell-Laufwerk) beim Messen, gpu, temp_cpu, temp_gpu als Mittel von Proben alle 5 s (MC_MESSWERTE_PROBE_S), netz_rx/netz_tx in Bytes/s über alle Schnittstellen außer lo, tokens = Prompt- plus Antwort-Tokens pro Minute (Differenz der Gesamtzähler aus services/token_stats.py). Ein Zähler, der kleiner wird, ergibt in dieser Minute null.
  • Homelab: Der Ausführer schickt in einem eigenen Faden jede Minute POST /api/homelab/ausfuehrer/messwerte (gleiche Anmeldung wie seine anderen Endpunkte), unabhängig vom 10-min-Bericht und von Aufträgen, die bis zu einer Stunde laufen. Host-Werte liest er selbst aus /proc (stat für das CPU-Mittel der Minute, meminfo, loadavg, uptime) und statvfs("/"), denn pvesh get /nodes/<node>/status liefert in einem frischen pvesh-Prozess immer cpu: 0 (nachgesehen am 24.09.); belegt wie bei Proxmox: RAM = gesamt − verfügbar, rootfs = gesamt − frei. Netz des Hosts = Summe der physischen Schnittstellen aus /proc/net/dev (wie die Knoten-Anzeige in Proxmox; vmbr0 sähe den Verkehr der Gäste nach draußen nicht), ohne physische Schnittstelle vmbr0. CPU-Temperatur aus hwmon (k10temp bzw. zenpower mit Tdie vor Tctl, coretemp mit „Package id 0“), sonst null; auf dem Proxmox-PC gibt es nur k10temp/Tctl, kein thermal_zone und kein lm-sensors. Die Gäste kommen aus einem einzigen Aufruf pvesh get /cluster/resources --type vm (cpu schon auf die eigenen Kerne gerechnet, mem, disk, …), ihre Netz-Zähler aber aus denselben /proc/net/dev-Zeilen (veth<vmid>iN, tap<vmid>iN; die Stände in /cluster/resources sind bis zu 10 s alt und verzerrten die Minutenrate). Fehler bleiben still; ein Fehler steht einmal im Journal, bis wieder ein Punkt durchgeht. Von Hand: python3 ausfuehrer.py --messwerte.
  • Homelab-Teil (services/homelab/messwerte.py): rechnet die kumulativen Zähler in Bytes/s um. Keine Rate (null) gibt es, wenn der Zähler oder die Laufzeit kleiner wurde (Neustart von Gast oder Host), der vorige Stand fehlt (Neustart des Homelab-Teils; die Stände liegen nur im Speicher) oder mehr als 5 Minuten zurückliegt, oder der Sprung über 5 GB/s liegt. Gestoppte Gäste haben keine Messwerte (null, keine Nullen); die Platte einer VM sieht Proxmox nicht (null). Der eigene Container (Etikett mc2) wird wie im Inventar nicht erfasst. Quellen pve, ct-<vmid>, vm-<vmid>.
  • Schnittstellen: GET /api/messwerte?zeitraum=1h|24h|7d (nur Rolle box) liefert {zeitraum, schritt_s, reihen: {cpu, ram, gpu, temp_cpu, temp_gpu, platte, netz_rx, netz_tx, tokens}, aktuell}; GET /api/homelab/messwerte?zeitraum=… liefert {zeitraum, schritt_s, geraete: [{id, name, art, reihen, aktuell}]} mit den Geräten wie im Inventar (Host, dann die Gäste nach Nummer; Namen aus apps.py). Jede Reihe ist eine Liste [t, wert] (t = Beginn des Schritts); temp_cpu gibt es als Reihe nur beim Host, in aktuell stehen temp_cpu und load1 bei Gästen auf null. aktuell ist der letzte Minutenpunkt, solange er höchstens 3 Minuten alt ist, sonst lauter null. Ein unbekannter Zeitraum ergibt 400. Neue Punkte stößt der Live-Strom nicht an: Die Oberfläche fragt die Messwerte selbst nach (einmal je Minute genügt).
  • Wächter (Rolle homelab, pruefe_host): gelb, wenn der Proxmox-Host in den letzten 10 Minuten (mindestens 8 Minutenpunkte) durchgehend über 95 % RAM (MC_WAECHTER_HOST_RAM) oder über 90 °C CPU (MC_WAECHTER_HOST_TEMP) lag.