doku: Betriebsdoku neu (ARCHITEKTUR, BETRIEB, UPDATES, RADAR, WIEDERAUFBAU)
- 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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
24e6dd81ba
commit
f4bbdad311
@@ -0,0 +1,177 @@
|
||||
# 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](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
|
||||
|
||||
```mermaid
|
||||
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` (`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 nur `/api/health` und `/api/partner`; die
|
||||
Homelab-Router folgen in Phase 3. `steward.py` betreibt Re-Warm und Config-Watch nur in der Rolle `box`; der
|
||||
Wächter prüft im Homelab nur Platte und Partner.
|
||||
- **`/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 „<Name> 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.
|
||||
|
||||
## 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-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](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](wissen/OFFENE-FAEDEN.md).
|
||||
Reference in New Issue
Block a user