21 KiB
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 später als Container auf dem Proxmox-PC (Rolle homelab). Die Homelab-Instanz ist noch nicht eingerichtet
(Phase 3, braucht User-OK); dieses Dokument beschreibt deshalb vor allem die Rolle box.
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) | mc2-waechter.json (einziger Schreiber) |
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(boxoderhomelab, Standardbox),MC_INSTANZ(Anzeigename, Standard „Box-Wart" bzw. „Homelab"),MC_DATEN_DIR(Zustandsdateien; Standard/srv/modelsauf der Box,/var/lib/mc2im Homelab),MC_PARTNER_URL(Basis-URL der anderen Instanz, leer = keine) undMC_PARTNER_NAME.backend/kern/zeit.py: die eine ZeitzoneMC_LOCAL_TZ(StandardEurope/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
boxhängt alle Box-Router ein. Rollehomelablädt nur/api/healthund/api/partner; die Homelab-Router folgen in Phase 3.steward.pybetreibt Re-Warm und Config-Watch nur in der Rollebox; der Wächter prüft im Homelab nur Platte und Partner. /api/healthnenntrolleundinstanz;engine_reachable,gateway_reachableundbraingibt es nur auf der Box.GET /api/partnerliefert das Lebenszeichen der anderen Instanz./api/partner/<pfad>reicht an<partner>/api/<pfad>weiter (alle Methoden, nach der Herkunftsprüfung).partner/…undstreamwerden 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_ROLLEnochMC_PARTNER_URL: Die Box läuft in der Rolleboxund ohne Partner, bis die zweite Instanz eingerichtet ist.
Aufträge (seit 24.09., Phase 2b)
- Eigene Einheiten:
services/jobengine.pystartet jeden Auftrag (Update, Modell-Download) persystemd-runals Einheitmc2-job-<id>des Nutzer-Managers (als root: des System-Managers).RuntimeMaxSecist 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) undmodell: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,unbekanntmit 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 alsfestgehalten, gescheiterte Prüfungen alsunbekannt.GET /api/zieleliefert die KI-Box; der Homelab-Teil liefert seine Ziele ab Phase 3 unter/api/homelab/ziele, die Oberfläche legt beide zu einer Liste zusammen. - Strukturierter Update-Verlauf
<Datenordner>/mc2-update-verlauf.jsonl: je Baustein eine JSON-Zeile (lauf,anlass,ts,baustein,ergebnis,text). Es schreibenautoupdate.sh(Helferergebnis/verlauf; die Telegram-Texte bleiben Zeichen für Zeichen gleich) und die Update-Knöpfe (Abschluss-Hakenwartung:verlaufder Aufträge, bei „Alle aktualisieren" mit dem Teil, an dem die Kette scheiterte).services/update_verlauf.pyliest ab dem ersten strukturierten Eintrag nur noch ihn, ältere Läufe weiter aus~/mc2-notify.log. Den Hermes-Auftrag des Sonntags-Laufs startetautoupdate.shmit?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),visionundcoder-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 nachttlwieder 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 ancoder-bild, alles andere anvision). Ältere Bilder beschreibtvisioneinmal als Text (Cache je Bild), damit der Rest beim schnellen Modell mit Draft bleibt. model: autoist die Chat-Spur: normalfast, bei langen oder schweren Anfragenheavy(Schwellen in der Routing-Policymc2-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, sagtMC_WARMSETim Drop-inmc2-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 |
~/.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, POST /system/backup, GET /system/backups, POST /system/restart, GET /system/token-stats |
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.
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, Code seit 24.09.; Einrichtung wartet auf das User-OK)
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 (KopfzeileX-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,host_update,host_neustart) und prüft selbst, ob ein Gast das Etikettcommunity-scriptoderwatcherträgt und nichtwatcher-aus— dem Server vertraut er dabei 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/updateim 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. - Homelab-Teil
backend/services/homelab/:kanal.py(Geheimnis, Auftragsliste, Bericht),apps.py(App- Katalog: Name, GitHub-Quelle, Weboberfläche),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“),updates.py(„Jetzt updaten“). Schnittstellenrouters/homelab.pyunter/api/homelab/…. - „Jetzt updaten“ (nur per Knopf): Snapshot (wo möglich) →
updatedes Community-Scripts (PHS_SILENT=1) bzw. Pakete → 20 s warten → frischer Bericht → Prüfung (Gast läuft, Weboberfläche antwortet, App-Version neu). Rot und Snapshot da → automatisch zurück + dringende Meldung; grün → älteremc2--Snapshots weg + Meldung. Läufe in/var/lib/mc2/homelab-laeufe.jsonund im strukturierten Update-Verlauf. Host: Pakete per Knopf mit Warnung, Neustart als eigener Knopf. - 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 (MC_ARCANE_KEY, KopfzeileX-API-Key). Docker-Updates laufen zuerst nur als Probelauf (dryRun); echt erst mitMC_ARCANE_ECHT=1. Die Arcane-VM braucht dafür kein Etikett, weil der Ausführer nicht beteiligt ist. - Wächter in der Rolle
homelab: Platte, Partner (die KI-Box), Ausführer (kein Bericht seit 30 min = rot) und jede Weboberfläche der freigegebenen Gäste. - Oberfläche: Die Seite „Homelab“ zeigt alle Geräte als Karten — die KI-Box (
/api/ziele) und alles aus/api/homelab/ziele— mit Stand je Baustein, Rückweg und Knopf samt Rückfrage. - Meldungen ohne Hermes:
notify.shim 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.