- ARCHITEKTUR.md (neu): Prozesse und Units, Rollen box/homelab und Partner-Instanz aus
Phase 2a (kern/einstellungen.py, kern/zeit.py, kern/partner.py, /api/partner), Modell-Rollen,
Bild-Weiche, wer welche Datei schreibt (inkl. mc2-quittiert.json), Meldeweg mit
Telegram-Zweitweg, alle 62 /api-Routen, Herkunftspruefung, kurzer Ausblick Homelab-Teil.
- BETRIEB.md (neu, loest RUNBOOK.md und BACKUP.md ab): Handgriffe, Meldungen und Betreffzeilen,
Waechter-Regeln (Partner nach 5 Takten, Spracherkennung nur wenn wach, Ausblenden-Knopf),
Dienste schlafen/wecken, zweistufiger Deploy mit Prueftor, Sicherung und Zurueckspielen,
Notfall, Pfade auf der Box.
- UPDATES.md (neu): Sonntags-Kette per Timer, Postchecks, Festhalten und Freigeben, Handbetrieb,
Update-Verlauf, Fallen.
- RADAR.md (neu): Modell-Radar (Leitplanken, Nachtablauf, Pruefstand, Uebernehmen/Verwerfen,
Merkliste) und Stack-Radar.
- WIEDERAUFBAU.md (neu, loest DISASTER_RECOVERY.md ab): entschlackte Schrittfolge, Anhang A
(llama-swap-Unit und Drop-ins) behalten.
Stand der Aussagen: Code in main 75611be.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14 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; Update- und Download-Jobs als Kindprozesse (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.
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-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; der Wortlaut „QUEUED für Morgen-Digest" muss bleiben |
~/.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.
Ausblick: der Homelab-Teil (Phasen 3 und 4)
Stand der Recherche vom 24.09.2026: Community-Script-Container aktualisiert man im Container mit update, ohne
Rückfragen mit PHS_SILENT=1; die installierte Version steht in /root/.<app>. Die Proxmox-API kann keine Befehle
in Containern ausführen und gibt die Liste der Host-Updates nur mit Schreibrecht heraus. Deshalb: ein Lese-Schlüssel,
ein Proxmox-Webhook für Host-Pakete und ein kleiner Ausführer auf dem Host mit festen Aktionen. Container mit
Bind-Mount (z. B. PBS) lassen sich nicht snapshotten, dort gilt „Backup statt Snapshot". Arcane meldet Image-Updates
über eine eigene API mit Schlüssel. Ein Exit-Code 0 beweist kein gelungenes Update; geprüft wird unabhängig
(HTTP-Probe, Dienststatus, Version). Fahrplan: wissen/OFFENE-FAEDEN.md.