Files
mission-control-v2/docs/BETRIEB.md
T
HitonabiandClaude Opus 5.5 c31ef827fc meldungen: Lucy spricht – jede Telegram-Meldung in ihrem Ton
User 25.09.: „Hermes' Persona ist Lucy – das muss sich in all ihren Meldungen
widerspiegeln“; das angehängte „(Diese Meldung kam auch an Lucy.)“ tat so, als
wäre sie jemand anderes als der Absender.
- notify.sh: Kopfzeile aus dem Betreff in Lucys Ton (SOUL.md: „Commander“, knapp,
  sachlich), bei Dringendem „Commander, das ist dringend.“; keine zweite Anrede,
  wenn der Text schon mit ihr beginnt. Morgenmeldung mit kurzen Marken statt
  Betreffen in Klammern; die des Homelabs sagt, woher sie kommt.
- Wächter: Zusatz „kam auch an Lucy“ entfernt (der Briefkasten bekommt die
  Meldung weiter).
- Melde-Log und Auswertungen unverändert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:07:42 +02:00

474 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Betrieb — Handgriffe, Meldungen, Wächter, Deploy, Sicherung, Notfall
_Stand 24.09.2026, Code in `main` = `75611be`. Für Techniker und Agenten. Gilt für die Instanz auf der KI-Box
(Rolle `box`); die Homelab-Instanz ist noch nicht eingerichtet. Die Bedienung für den User steht in
[BEDIENUNG.md](BEDIENUNG.md), die Update-Kette in [UPDATES.md](UPDATES.md), das Modell-Radar in [RADAR.md](RADAR.md)._
## Handgriffe (SSH auf die Box)
```bash
ssh hitonabi@192.168.178.151
export XDG_RUNTIME_DIR=/run/user/$(id -u) # sonst sieht systemctl --user nichts
systemctl --user status mission-control-2 mc2-gateway mc2-steward
systemctl --user list-timers # Radar, Sicherung, Updates, Morgenmeldung, projekte-sync
journalctl --user -u mc2-steward -n 100 # Wächter; ebenso mc2-radar, mc2-autoupdate, mc2-backup
curl -s http://127.0.0.1:9001/api/health # Gesamtzustand, Rolle und Instanz
curl -s http://127.0.0.1:9001/api/partner # Lebenszeichen der anderen Instanz (falls eingerichtet)
curl -s http://127.0.0.1:9010/gw/health # Gateway und Motor
bash -lc 'hermes cron list' # Hermes-Jobs (hermes ist über SSH nicht im PATH)
tail -n 50 ~/mc2-notify.log # was zuletzt gemeldet wurde
tail -n 5 /srv/models/mc2-deploy.log # welche Stände wann live gingen
cat /srv/models/mc2-stand.json # welcher Stand gerade live ist (seit 24.09.)
cat /srv/models/mc2-einstellungen.json # Update-Zeitfenster und Radar-Schalter der Oberfläche
sudo journalctl -u llama-swap -n 100 # Motor (System-Dienst)
```
## Meldungen
- **Ein Weg:** `bash ~/mission-control-v2/deploy/notify.sh [-d] [-s "[Betreff]"] "Text"` legt die Meldung in Lucys
Briefkasten und schickt sie an Telegram. Protokoll: `~/mc2-notify.log`, je Meldung eine Zeile mit `OK telegram`,
`OK telegram direkt`, `QUEUED für Morgen-Digest` oder `FALLBACK (…)`. Den Wortlaut nicht ändern:
`backend/services/update_verlauf.py` liest ihn.
- **Zwei Wege zu Telegram (seit 24.09.):** zuerst `hermes send`. Scheitert das oder fehlt Hermes, geht die Meldung
direkt an die Telegram-Bot-API; die Zugangsdaten (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_HOME_CHANNEL`, optional
`TELEGRAM_HOME_CHANNEL_THREAD_ID`) liest `notify.sh` aus der Datei in `MC_TELEGRAM_ENV` (Standard `~/.hermes/.env`).
Das Token steht dabei nicht in der Prozessliste. Live bewiesen am 24.09. um 15:41.
- **Nachtruhe:** 00:00–06:59 landet alles Nicht-Dringende in `~/.hermes/night-queue.txt`. Dringend ist `-d`, ein
Betreff mit „Alarm" oder „Notfall" oder ein Text, der mit „KRITISCH" beginnt.
- **Morgenmeldung:** `mc2-morgenmeldung.timer` (07:00) startet `deploy/morgenmeldung.sh`. Es schickt eine Nachricht
„Guten Morgen, Commander. Heute Nacht gab es N Meldungen" mit höchstens 3500 Zeichen. Drei Versuche im Abstand von
60 s; scheitert Telegram, bleibt der Stapel als `night-queue.txt.senden` liegen und kommt mit der nächsten
Morgenmeldung. Zuletzt Gesendetes: `night-queue.txt.zuletzt-gesendet`. Das Skript kürzt außerdem
`~/mc2-notify.log` ab 3 MB auf die letzten 2 MB.
- **Scheitern beide Wege,** steht die Meldung mit `FALLBACK` im Protokoll und geht per `wall` an die
Box-Konsolen (`MC_NOTIFY_OHNE_WALL=1` schaltet `wall` ab). Seit 24.09. steht im `FALLBACK`-Eintrag auch, warum der
Zweitweg scheiterte: `FALLBACK (telegram fehlgeschlagen: <Ausgabe von hermes send>; Zweitweg: <Grund>): <Text>`,
etwa „Zugangsdatei … fehlt oder ist nicht lesbar“ oder „Telegram lehnt ab, HTTP-Fehler“ (ohne Klammern, damit
`update_verlauf.py` den Eintrag weiter trennen kann).
- **Testen:** Einstellungen → Meldungen → „Test senden“ (je Instanz; `POST /api/einstellungen/telegram-test` bzw.
`/api/homelab/einstellungen/telegram-test`). Der Knopf ruft `notify.sh -d -s "[Test]" …` mit
`MC_NOTIFY_NO_ANNOUNCE=1` (Lucy spricht den Test nicht) und liest das Ergebnis samt Grund über eine Kennung aus dem
Melde-Log (`services/einstellungen.py`). Höchstens ein Test alle 20 s. Von Hand:
`bash ~/mission-control-v2/deploy/notify.sh -s "[Test]" "Testmeldung"`; nachts landet sie in der Warteschlange,
mit `-d` kommt sie sofort.
**Lucys Stimme (seit 25.09.2026, User-Wunsch: „Hermes' Persona ist Lucy“).** Was über Telegram rausgeht, kommt von
Lucys Bot und spricht in ihrem Ton (`~/.hermes/SOUL.md`: Anrede „Commander“, knapp, sachlich, keine Emojis).
`notify.sh` macht aus dem Betreff ihre Kopfzeile (`lucy_kopf`): „Commander, im Homelab stimmt etwas nicht.“,
„Commander, Entwarnung auf der Box.“, „Commander, Bericht aus dem Homelab.“, bei Dringendem „Commander, das ist
dringend.“, ohne Betreff „Commander, kurz gesagt:“. Beginnt der Text schon mit ihrer Anrede (Morgenmeldung, von Lucy
selbst formulierte Berichte), kommt keine zweite dazu. In der Morgenmeldung stehen kurze Marken („Homelab, Störung:
…“) statt der Betreffe in Klammern. Melde-Log, Update-Verlauf und Protokoll behalten Betreff und Wortlaut wie in der
Tabelle — die Betreffe unten sind also die internen Kennungen. Das frühere „(Diese Meldung kam auch an Lucy.)“ am
Ende der Wächter-Meldungen ist weg; in ihrem Briefkasten liegt jede Meldung trotzdem.
| Betreff | Absender | Inhalt |
|---|---|---|
| `[Box-Update]` | `autoupdate.sh`, `jobs/sonntags-update.sh` | Ergebnis je Baustein, Wochenbericht, Neustart-Ankündigung; beginnt der Text mit „KRITISCH", ist es dringend |
| `[Box-Problem]` | Wächter | neuer roter Hinweis; nach 6 h erneut mit „Immer noch:" |
| `[Box wieder ok]` | Wächter | ein gemeldeter roter Hinweis ist erledigt |
| `[Homelab-Problem]` / `[Homelab wieder ok]` | Wächter der Homelab-Instanz | wie oben, sobald diese Instanz läuft |
| `[Homelab-Update]` / `[Alarm] Homelab-Update` | „Jetzt updaten“ und „Alle aktualisieren“ im Homelab | Ergebnis je Lauf, dringend bei Rot; bei „Alle aktualisieren“ (seit 24.09.) nur Fehler einzeln, am Ende eine Sammelmeldung, dringend bei Abbruch |
| `[Modell-Radar]` | `backend/radar_lauf.py` | ein Kandidat hat den Nachttest bestanden |
| `[Sicherung]` | `backend/services/probe_wiederherstellung.py` | die monatliche Probe-Wiederherstellung war rot (normale Dringlichkeit) |
| `[Stack-Radar]` | `jobs/stack-radar.sh` | Samstagsbericht |
| `[Morgenmeldung]` | `morgenmeldung.sh` | Sammelmeldung der Nacht, 07:00 |
| `[Alarm] Deploy` | `deploy.sh` | Deploy gescheitert, der alte Stand läuft wieder (dringend) |
| ohne Betreff | `jobs/news-melden.sh` | Daily News Report, danach die Sprachnachricht |
## Wächter (`backend/services/waechter.py`, im `mc2-steward`)
Takt jede Minute, der erste 60 s nach dem Start. Ein Befund wird nach 2 Takten zum Hinweis; Timer-, Job-,
Platten- und Pin-Befunde sofort, die Partner-Instanz erst nach 5 Takten (`MC_WAECHTER_PARTNER_TAKTE`, ein Neustart
soll kein Alarm sein).
| Prüfung | Rot | Gelb |
|---|---|---|
| Dienste (systemd) | `llama-swap`, `hermes-gateway`, `mc2-gateway`, `mission-control-2` | `voice-service`, `lucy-stimme`, `hermes-builtin-ui` |
| Timer-Läufe | `mc2-backup`, `mc2-morgenmeldung`, `mc2-autoupdate` | `projekte-sync`, `mc2-radar` |
| Hermes-Jobs | fehlgeschlagener Job mit „update" im Namen | andere fehlgeschlagene Jobs; Werkzeugfehler im letzten Lauf |
| Kern (HTTP) | Motor, Hirn, Hermes, MC2, Gateway antworten nicht; die Spracherkennung nur, wenn sie nicht schläft | – |
| Platte (`MC_DATEN_DIR`, auf der Box `/srv/models`) | ab 90 % | ab 80 % |
| Festgehaltene Updates | – | jeder Pin |
| Partner-Instanz (nur mit `MC_PARTNER_URL`) | „<Name> antwortet nicht" | – |
| Sicherung (seit 24.09.) | – | `pbs-backup` scheitert; Probe-Wiederherstellung rot, älter als 40 Tage oder noch nie gelaufen (sobald ihr Timer eingerichtet ist) |
| Proxmox-Host (nur Rolle `homelab`, seit 24.09.) | – | 10 Minuten lang über 95 % RAM oder über 90 °C CPU (aus den Messwerten, mindestens 8 Minutenpunkte) |
| Abgestürzte Prüfung | – | „Eine Prüfung des Wächters lief nicht" |
In der Rolle `homelab` prüft der Wächter Platte, Partner, den Ausführer, die Weboberflächen der freigegebenen
Gäste (einen Gast mitten in seinem Update-Lauf nicht: Das Ergebnis meldet der Lauf selbst) und seit 24.09. RAM und
CPU-Temperatur des Proxmox-Hosts (gelb, Schwellen über `MC_WAECHTER_HOST_RAM` und `MC_WAECHTER_HOST_TEMP` in
`/etc/mc2/homelab.env`) und meldet mit „[Homelab-Problem]". Im selben Takt stößt er das wöchentliche Suchen an (siehe „Homelab-Teil im Betrieb“); scheitert
es für einen Gast zweimal hintereinander, gibt es einen gelben Hinweis „<App>: Die Suche nach Updates scheitert“.
- **Selbstreparatur:** Nur ein abgestürzter Dienst (`ActiveState=failed`) wird neu gestartet, höchstens 2× je Stunde
und Hinweis. Ein gestoppter Dienst (`inactive`) wird nur gemeldet. Ein schlafender Dienst (`inactive` und
`disabled`) ist kein Befund. Bei Timer-Läufen schläft der Timer, nicht der Dienst dahinter (der ist `static`):
Ist `mc2-radar.timer` abgeschaltet (Einstellungen → Radar aus), ist ein alter Fehlschlag von `mc2-radar` kein
Befund mehr.
- **Während eines Updates** (ein Prozess `autoupdate.sh`, `update-engine.sh`, `update-swap.sh` oder `hermes … update`
läuft) prüft er Dienste und Kern nicht und repariert nichts. Die Oberfläche zeigt dann „Update läuft".
- **Meldungen:** Rote Hinweise gehen an Telegram und in den Briefkasten, bei Fortbestand alle 6 h erneut; nachts gilt
die Nachtruhe. Gelbe Hinweise stehen nur in der Oberfläche.
- **Knöpfe am Hinweis:** „Neu starten" bzw. „Starten", „Protokoll", „Jetzt erneut laufen lassen" (Timer),
„Erneut ausführen" (`hermes cron run`), „Freigeben" (Pin), „Ausblenden bis zum nächsten Lauf" (Werkzeugfehler
eines Jobs; MC2 merkt sich den Lauf in `/srv/models/mc2-quittiert.json`, hat der nächste Lauf wieder Fehler,
erscheint der Hinweis erneut).
- Der Wächter ist der einzige Schreiber von `/srv/models/mc2-waechter.json`. Ist sein letzter Takt älter als
5 Minuten, zeigt die Startseite „Wächter schweigt".
- **Hirn (seit 24.09.):** Geprüft wird gezielt das Modell mit der Rolle `hermes` und sein Zustand in llama-swap
(`/running`), nicht mehr „irgendein Modell läuft". Lädt es gerade (`starting`), ist das kein Befund — höchstens
10 Minuten lang (`MC_WAECHTER_HIRN_LADEN_S`), danach heißt der Hinweis „Lucys Hirn lädt nicht fertig".
## Einstellungen der Oberfläche (Zeitfenster, Radar-Schalter)
Seit 24.09.2026, Logik in `backend/services/einstellungen.py`, Schnittstellen unter `/api/einstellungen`.
- **Eine Datei:** `/srv/models/mc2-einstellungen.json` (`update_fenster: {tag: 1–7, uhrzeit: "HH:MM"}`,
`radar: {an}`). Nur MC2 schreibt sie; `deploy.sh` und `autoupdate.sh` lesen sie. Fehlt sie, gilt So 04:30 und
Radar an. Sie steckt in jeder Sicherung (`mc2-*.json`).
- **Update-Zeitfenster:** Speichern schreibt das Drop-in
`~/.config/systemd/user/mc2-autoupdate.timer.d/zeitfenster.conf` (`OnCalendar=` leer, dann der neue Plan). Ablauf:
Timer stoppen → Drop-in → `daemon-reload` → Stempel `~/.local/share/systemd/timers/stamp-mc2-autoupdate.timer`
auf jetzt → Timer starten. Grund: Der Timer hat `Persistent=true`. Ein laufender Timer rechnet beim Reload mit
seinem letzten Lauf und dem neuen Plan, ein frisch gestarteter mit dem Stempel. Beides hätte einen schon vorbei
gewesenen Termin sofort nachgeholt (so am 24.09. mittags passiert). Scheitert ein Schritt, kommt das alte Drop-in
zurück, und der Timer läuft wieder. Während `mc2-autoupdate.service` läuft, lehnt MC2 das Ändern ab.
- **Neustart-Fenster:** `deploy/wartungsfenster.sh` (von `autoupdate.sh` eingebunden) erlaubt den Neustart nur in
den drei Stunden ab der vollen Stunde des Beginns: So 04:30 → So 04:00–06:59, Sa 23:30 → Sa 23:00 – So 01:59.
Es liest das Fenster mit `jq` aus der Einstellungsdatei; `MC_WARTUNGSFENSTER="<Tag> <HH:MM>"` und
`MC_FENSTER_JETZT` sind Test-Schalter. `MC_AUTOUPDATE_REBOOT_JETZT=1` erzwingt den Neustart wie bisher.
- **Radar-Schalter:** Aus = `systemctl --user disable --now mc2-radar.timer` (ein laufender Nachttest endet noch).
An = `enable`, Stempel `stamp-mc2-radar.timer` auf jetzt, `start` — kein Nachholen, also tagsüber kein Laden.
- **Deploy:** Das Drop-in fasst `deploy.sh` nie an. Steht das Radar auf aus, lässt der Deploy den Timer aus
(`disable --now`) statt ihn wie früher einzuschalten; steht es auf an und läuft der Timer nicht, startet ihn der
Deploy mit Stempel, also ohne Nachholen.
- **Wächter:** Ein ausgeschaltetes Radar schläft (Timer `inactive` und `disabled`) und ist kein Befund; die
Oberfläche zeigt unter Einstellungen „Schläft“.
- **Prüfen auf der Box:**
`systemctl --user cat mc2-autoupdate.timer`, `systemctl --user list-timers mc2-autoupdate.timer mc2-radar.timer`,
`systemctl --user is-enabled mc2-radar.timer`.
## Software-Stand
- **Stand-Datei:** Nach dem Umschalten schreibt `deploy.sh` (nach der Nachprüfung) `/srv/models/mc2-stand.json`:
`commit` (kurz), `betreff`, `commit_datum`, `deploy_zeit`. `deploy/homelab/ausrollen.sh` schreibt dasselbe nach
erfolgreichem `einrichten.sh` in den Container (`/var/lib/mc2/mc2-stand.json`, Eigentümer `mc2`). Beide nutzen
`deploy/stand-schreiben.py` (nur Standardbibliothek, atomar). Scheitert nur das Schreiben, gilt der Deploy trotzdem.
- **Anzeige:** `/api/instanz` liefert `stand` mit, `/api/partner/instanz` den der anderen Instanz; Einstellungen →
Software-Stand zeigt beide und einen Hinweis, wenn sie abweichen. Ohne Stand-Datei liest MC2 den Stand aus git
(`quelle: "git"`, ohne Deploy-Zeit), sonst `unbekannt`.
## Dienste schlafen legen und wecken
- Schlafen legen: `systemctl --user disable --now <unit>` (seit 24.09.: `box-console`, `voice-service`).
- Wecken bis zum nächsten Neustart der Box: Knopf „Wecken" unter „Dienste und Protokolle" oder
`systemctl --user start <unit>`.
- Dauerhaft wecken: `systemctl --user enable --now <unit>`; für einen Neuaufbau die Unit zusätzlich in `AKTIV` in
`deploy/deploy.sh` eintragen.
## Deploy
Voraussetzung: Der Stand liegt in `main` auf Gitea (Branch, Prüftor grün, Merge). Dann auf der Box:
```bash
bash ~/mission-control-v2/deploy/deploy.sh
```
**Stufe 1** (die bisherige Fassung des Skripts): Sperre gegen einen zweiten Deploy (`flock` auf
`$XDG_RUNTIME_DIR/mc2-deploy.lock`). Läuft ein Update-Auftrag (Gruppe `maintenance` in `/api/jobs`), bricht der
Deploy ab — das Update führt Skripte aus diesem Checkout aus. Downloads laufen weiter: Aufträge sind eigene
systemd-Einheiten und überleben den Neustart von MC2. Dann den alten Stand merken, `git fetch` und
`git merge --ff-only origin/main` und die neue Fassung als Stufe 2 starten.
**Stufe 2** (die neue Fassung):
1. `pip install` aus `backend/requirements.txt` und `backend/requirements-dev.txt`.
2. Prüftor `deploy/pruefen.sh`.
3. llama-swap-Config nur, wenn sich `deploy/llama-swap.config.yaml` in diesem Deploy geändert hat: Sicherung
`/etc/llama-swap/config.yaml.bak-<Zeit>` (die letzten 5 bleiben), dann ersetzen. llama-swap lädt dank
`--watch-config` selbst neu. Weicht die lebende Datei nur ab, gibt es einen Hinweis und sonst nichts.
4. Alle Repo-Units nach `~/.config/systemd/user/`, dazu die Drop-ins `mc2-steward.service.d/warmset.conf` und
`mission-control-2.service.d/override.conf`; `daemon-reload`; `enable` für die Units in `AKTIV`; Timer starten
(den Update-Timer nur, wenn er noch nicht läuft). Das Drop-in `mc2-autoupdate.timer.d/zeitfenster.conf` gehört
der Oberfläche und bleibt unangetastet. Den Radar-Timer schaltet der Deploy nach dem Schalter in
`mc2-einstellungen.json`: aus = `disable --now`, an = `enable` und, falls er nicht läuft, Start mit Stempel.
5. Cron-Skripte `deploy/jobs/*.sh` und `*.py` nach `~/.hermes/scripts/`.
6. Skills `deploy/skills/*` nach `~/.hermes/skills/<name_mit_unterstrich>/`, abgelöste Skills nach
`~/.hermes/skills-archiv/`. Plugins `deploy/hermes-plugins/*` nach `~/.hermes/plugins/`; aktiviert werden sie
einmalig von Hand, Hermes startet der Deploy nicht neu.
7. Neustart von `mc2-gateway`, `mission-control-2` und `mc2-steward`, dann bis zu 60 s Nachprüfung: `/api/health`,
`/gw/health`, Steward aktiv.
Erfolg: eine Zeile in `/srv/models/mc2-deploy.log`, die Stand-Datei `/srv/models/mc2-stand.json` und „Deploy <commit>
ist live". Danach (Schritt 8) zieht der Deploy den
Homelab-Teil mit, sobald `box-partner.sh` einen Partner eingerichtet hat: `deploy/homelab/ausrollen.sh` mit der IP aus
`partner.conf`. Scheitert das, bleibt die Box live und es kommt „[Alarm] Deploy"; `MC_DEPLOY_OHNE_HOMELAB=1` lässt
den Schritt aus. Scheitert ein Schritt:
`git reset --hard` auf den alten Stand, llama-swap-Config zurück (falls ersetzt), Dienste neu, Zeile `GESCHEITERT` im
Log und die dringende Meldung „[Alarm] Deploy".
Schalter: `MC_DEPLOY_TROTZDEM=1` (trotz laufendem Update-Auftrag), `MC_DEPLOY_OHNE_TESTS=1` (Prüftor überspringen, nur im
Notfall), `MC_DEPLOY_SKIP_SWAP_CONFIG=1` (llama-swap-Config nie anfassen).
Läuft im Homelab ein Update oder „Alle aktualisieren“ (`/api/partner/homelab/laeufe` bzw. `…/sammellauf`, Status
`laeuft`), wird der Deploy verschoben (seit 25.09.) — Schritt 8 startet den Homelab-Teil neu und bräche den Lauf ab.
Beginnt ein Lauf erst während der Prüfungen, bleibt die Box live und nur Schritt 8 entfällt (Meldung „[Deploy]“).
`ausfuehrer-einrichten.sh` wartet genauso (`MC_TROTZDEM=1` erzwingt): Ein Neustart des Ausführers bräche einen
laufenden Auftrag ab, etwa apt in einem Gast.
Nicht Teil des Deploys: das Frontend bauen (`frontend/dist` kommt fertig aus Git), die Root-Kopie von `warmup.sh`,
Neustarts von llama-swap und Hermes.
**Prüftor** `deploy/pruefen.sh` (am PC vor dem Push, auf der Box im Deploy): Shell-Syntax (`bash -n` für
`deploy/*.sh` und `deploy/jobs/*.sh`), Python-Syntax aller versionierten `.py` außerhalb von `frontend/`,
`ruff check .` (nur wo ruff installiert ist; `requirements-dev.txt` bringt nur pytest mit), Import von `app`,
`gateway_app`, `steward` und `radar_lauf`, dann `pytest backend/tests`. Die Frontend-Prüfungen (`npm run lint`,
`npm test`, `npm run build`) laufen nur am PC.
**Probelauf auf der Box** `deploy/probelauf-box.sh` (am PC, vor dem Merge nach `main`): schickt den lokalen HEAD
als Abzug nach `/tmp` auf der Box und lässt dort das Prüftor mit dem Python des Box-Checkouts laufen. Ändert auf
der Box sonst nichts. Grund: Unter Linux zeigen sich Wettläufe, die am Windows-PC durchgehen (24.09.).
## Homelab-Teil einrichten (Phase 3/4)
Jeder Schritt braucht das OK des Users (Container anlegen, Ausführer als root auf dem Host, Telegram-Zugang in
den Container). Alles läuft am PC in Git-Bash, im Repo; SSH-Zugang zu `pve` (Schlüssel `id_lucy_infra`).
1. `bash deploy/homelab/container-anlegen.sh` — unprivilegierter Debian-13-Container (nächste freie ID, 1 Kern,
1 GB RAM, 4 GB, DHCP, Autostart, Etikett `mc2`), SSH-Schlüssel wie bei allen Gästen. Gibt die IP aus. Diese
Adresse danach in der Fritzbox fest zuordnen (Container 107: 192.168.178.31, fest seit 25.09.): Partner-Adresse
der Box, Ausführer-Konfiguration und Deploy-Schritt 8 hängen an ihr.
2. `bash deploy/homelab/ausrollen.sh <ip>` — Code von HEAD in `/opt/mc2`, `einrichten.sh` (Nutzer `mc2`, Python-
Umgebung, Units `mc2-homelab`, `mc2-homelab-steward`, `mc2-homelab-morgenmeldung.timer`). Auch jedes spätere
Update des Homelab-Teils geht so (erst Prüftor und Probelauf auf der Box).
3. Telegram-Zugang (nur mit OK): `TELEGRAM_BOT_TOKEN` und `TELEGRAM_HOME_CHANNEL` in `/etc/mc2/telegram.env`
(Eigentümer `root:mc2`, 0640).
4. `bash deploy/homelab/ausfuehrer-einrichten.sh <ip>` — Ausführer, Unit und Konfiguration (0600) auf den Host.
5. `bash deploy/homelab/box-partner.sh <ip>` — Drop-ins `partner.conf` für `mission-control-2` und `mc2-steward`
auf der Box; ab dann prüfen sich beide Instanzen gegenseitig.
Prüfen: `curl http://<ip>:9001/api/homelab/ziele` (nach etwa einer Minute stehen die Geräte darin),
`systemctl status mc2-ausfuehrer` auf dem Host, Seite „Homelab“ der Oberfläche.
Ein neuer Stand des Ausführers (`deploy/homelab/ausfuehrer.py`) kommt nicht mit dem Deploy; dafür Schritt 4 erneut
laufen lassen. Achtung: `ausfuehrer-einrichten.sh` schreibt `/etc/mc2-ausfuehrer.json` neu — ein von Hand gesetztes
`sicherung_speicher` danach wieder eintragen und `systemctl restart mc2-ausfuehrer`.
## Homelab-Teil im Betrieb
- **Sicherung statt Snapshot** (Gäste, bei denen kein Snapshot geht; heute der PBS, CT 105, wegen seines
Bind-Mounts): Vor dem Update sichert der Ausführer den Container per `vzdump` auf den ersten lokalen Speicher, der
Sicherungen annimmt — auf dem Proxmox-PC `local` (`/var/lib/vz/dump`, liegt auf der Systemplatte des Hosts). Nie
auf `pbs-qnap`. Anderer Speicher: `"sicherung_speicher": "<Name>"` in `/etc/mc2-ausfuehrer.json`, dann
`systemctl restart mc2-ausfuehrer`. Nimmt kein lokaler Speicher Sicherungen an, sagt es die Karte („Rückweg“); die
Lösung ist Sache des Users (Proxmox → Rechenzentrum → Speicher → `local` → Inhalt „VZDump-Sicherung“). Vorher prüft
er den Platz (frei > belegt × 1,2), sonst beginnt das Update nicht.
- **Eigene Sicherungen erkennen:** Notiz `mc2-sicherung: vor einem Update durch den Homelab Orchestrator (<Gast>)`.
Nach einem grünen Lauf bleibt nur die neueste je Gast. Ansehen auf dem Host:
`pvesh get /nodes/pve/storage/local/content --content backup --output-format json-pretty`. Eine Sicherung von Hand
zurückspielen: `pct shutdown <vmid> && pct restore <vmid> <volid> --force 1 --storage local-lvm && pct start <vmid>`
(der Bind-Mount und seine Daten bleiben, wie sie sind).
- **Wartezeit nach Skriptänderung:** Ist `ct/<app>.sh` bei community-scripts jünger als `MC_HOMELAB_KARENZ_H` Stunden
(Standard 48), zeigt die Karte „Update bereit“ ohne Knopf und nennt, ab wann es geht. Ändern oder abschalten (`0`):
`MC_HOMELAB_KARENZ_H=…` in `/etc/mc2/homelab.env` im Container, dann `systemctl restart mc2-homelab`.
- **Arcane-Schlüssel (seit 24.09. über die Oberfläche):** in Arcane unter Einstellungen → API-Schlüssel anlegen, dann in
der Oberfläche unter Einstellungen (Karte Homelab) eintragen. Der Homelab-Teil fragt Arcane erst, ob der Schlüssel
gilt (Umgebungen und Images lesen); nur dann speichert er ihn in `/var/lib/mc2/arcane.key` (0600, Eigentümer `mc2`),
und die Übersicht sieht die Docker-Images ohne Neustart. Lehnt Arcane ab (etwa „HTTP 401“) oder antwortet nicht,
wird nichts gespeichert. Der Schlüssel erscheint danach nirgends mehr (Antwort, Protokoll, Journal). Löschen:
derselbe Knopf in der Oberfläche oder `rm /var/lib/mc2/arcane.key`. `MC_ARCANE_KEY` in `/etc/mc2/homelab.env` geht
weiter vor; solange es gesetzt ist, lehnt die Oberfläche das Eintragen ab und sagt warum.
- **Docker-Updates echt statt Probelauf:** Schalter in den Einstellungen, gespeichert in
`/var/lib/mc2/homelab-einstellungen.json` (`{"arcane": {"echt": true}}`). Setzt `/etc/mc2/homelab.env`
`MC_ARCANE_ECHT`, gewinnt die Umgebung (`1` = echt, sonst Probelauf), und der Schalter sagt es.
- **„Alle aktualisieren“ (seit 24.09.):** spielt alle Updates mit Knopf nacheinander ein — die Gäste nach VMID, dann
NPMplus, dann AdGuard, zuletzt die Pakete des Proxmox-Hosts (ohne Neustart). Ist ein Schritt rot (zurückgerollt oder
gescheitert), bleibt der Rest stehen, und es kommt eine dringende Meldung; sonst am Ende eine Sammelmeldung.
Solange er läuft, lehnen die einzelnen Knöpfe „Jetzt updaten“ ab. Stand:
`curl -s http://192.168.178.31:9001/api/homelab/sammellauf` bzw. `/var/lib/mc2/homelab-sammellauf.json`. Startet
`mc2-homelab` mitten im Lauf neu (auch durch einen Deploy), gilt er als abgebrochen („unterbrochen (Neustart)“, mit
dringender Meldung); der Schritt, der gerade lief, steht auf „fehler“ — den Stand des Geräts in der Übersicht prüfen.
- **Protokoll (seit 24.09.):** `curl -s 'http://192.168.178.31:9001/api/homelab/protokoll?grenze=50' | jq` zeigt
Aufträge, Läufe, Sammelläufe, Hinweise, Meldungen und das wöchentliche Suchen, neueste zuerst; `&berichte=1` auch die
Berichte des Ausführers. Die Quellen bleiben die Dateien in `/var/lib/mc2` (`ausfuehrer-auftraege.json` hebt jetzt
die letzten 500 erledigten Aufträge auf, `ausfuehrer-berichte.json` die Eingänge der letzten 150 Berichte) und das
Melde-Log `/var/lib/mc2/notify.log`.
- **Wöchentliches Suchen:** Der Steward legt für jeden freigegebenen, laufenden Container, dessen Paketlisten älter als
7 Tage sind, den Auftrag `suchen` an (nur `apt-get update` bzw. `apk update` im Gast, es wird nichts installiert):
nachts 02:00–05:00, höchstens einmal je Gast und Tag, nicht während eines Updates; war die Instanz nachts aus, gleich
nach dem Start. Stand in `/var/lib/mc2/homelab-pflege.json` (Nacht, je Gast: letzter Tag, Fehlschläge, letzter
Fehler). Schreibt nur der Steward; zum Zurücksetzen die Datei löschen und `systemctl restart mc2-homelab-steward`.
## Messwerte (Monitoring, seit 24.09.)
Jede Minute ein Punkt je Gerät, gelesen als letzte Stunde, letzter Tag oder letzte Woche (Aufbau in
[ARCHITEKTUR.md](ARCHITEKTUR.md), Abschnitt „Monitoring“).
- **Wo:** Box `/srv/models/mc2-messwerte/box-JJJJ-MM-TT.jsonl` (schreibt `mc2-steward`). Homelab
`/var/lib/mc2/mc2-messwerte/pve-…`, `ct-<vmid>-…`, `vm-<vmid>-…` (schreibt `mc2-homelab`, was der Ausführer jede Minute
schickt; Eigentümer `mc2`). Je Tag (Berliner Zeit) eine Datei, eine JSON-Zeile je Minute.
- **Wie groß:** eine Zeile hat rund 140 Bytes (Box, Gäste) bzw. 175 Bytes (Proxmox-Host), am Tag 1440 Zeilen. Box:
rund 0,2 MB je Tag, höchstens rund 2 MB. Homelab mit Host und 7 Gästen: rund 1,7 MB je Tag, höchstens rund 15 MB;
jeder weitere Gast rund 0,2 MB je Tag.
- **Wie lange:** Das erste Schreiben eines neuen Tages löscht die Tagesdateien, deren Tag mehr als 8 Tage zurückliegt;
es bleiben höchstens 9 je Gerät (heute und die 8 Tage davor). Nicht in der Sicherung (`backup.sh` nimmt nur
`mc2-*.json`); das Aufräumen der Modell-Platte bietet den Ordner nie zum Löschen an. Den Ordner von Hand zu löschen
ist harmlos: Er entsteht mit dem nächsten Punkt neu, der Verlauf beginnt von vorn.
- **Lücken:** Läuft der Steward, die Box oder der Ausführer nicht, bleiben die Minuten leer (`null`) und werden nicht
aufgefüllt. Nach einem Neustart des Homelab-Teils fehlt für eine Minute die Netz-Rate; nach dem Neustart eines Gasts
oder des Hosts ebenso (die Zähler beginnen von vorn).
- **Ansehen:**
```bash
tail -n 3 /srv/models/mc2-messwerte/box-$(date +%F).jsonl # Box: die letzten Minuten
curl -s 'http://127.0.0.1:9001/api/messwerte?zeitraum=1h' | jq '.aktuell' # Box: letzter Punkt
curl -s 'http://192.168.178.31:9001/api/homelab/messwerte?zeitraum=1h' | jq '.geraete[] | {id, aktuell}'
python3 /usr/local/lib/mc2/ausfuehrer.py --messwerte # auf pve: ein Punkt, nur lesend (CPU über eine Sekunde)
journalctl -u mc2-ausfuehrer -n 50 | grep Messwerte # auf pve: steht hier etwas, gingen Punkte nicht durch
```
- **Ausführer:** Ein neuer Stand kommt nicht mit dem Deploy, sondern mit `bash deploy/homelab/ausfuehrer-einrichten.sh
<ip>` (danach ein von Hand gesetztes `sicherung_speicher` wieder eintragen, siehe oben). Ein alter Ausführer schickt
keine Messwerte; dann bleibt der Homelab-Verlauf leer, sonst ändert sich nichts.
- **Schalter:** `MC_MESSWERTE_ENABLED=0` im Steward der Box schaltet das Schreiben ab; `MC_MESSWERTE_PROBE_S` (Standard
5) ist der Abstand der Proben für GPU-Last und Temperaturen. Ein Trocken- oder Probelauf (`MC_WAECHTER_TROCKEN=1`,
`MC_PROBELAUF=1`) schreibt nie mit.
## Sicherung
- **Wann:** `mc2-backup.timer` täglich 03:30 (+≤5 min); außerdem vor jedem Hermes-Update, vor jedem Zurückspielen und
per Knopf „Jetzt sichern". Von Hand: `bash ~/mission-control-v2/deploy/backup.sh`.
- **Wohin:** `/srv/models/mc2-backups/mc2-state-<Zeit>.tar.gz`, Rechte 600 (enthält `~/.hermes/.env`), die letzten 14
bleiben. Danach Spiegel per `rsync --delete` auf den Proxmox-Host (`/var/lib/vz/mc2-backups`, eigener Schlüssel
`~/.ssh/mc2_offsite`; Ziel und Schlüssel über `MC_BACKUP_OFFSITE` und `MC_BACKUP_OFFSITE_KEY`). Scheitert der
Spiegel, gilt die lokale Sicherung trotzdem.
- **Inhalt:**
- aus `~/.hermes`: `config.yaml`, `.env`, `plugins/`, `cron/`, `state/`; seit 24.09. auch `memories/` (Lucys
Gedächtnis), `SOUL.md`, `skills/` und `scripts/`; dazu Reste der früheren Desktop-Anbindung (`agent-hooks/`,
`shell-hooks-allowlist.json`, `desktop-gateway-token`, `pc-paths.yaml`, Drop-in `session-token.conf`);
- `/etc/llama-swap/config.yaml`;
- der Box-Wart-Zustand `/srv/models/mc2-*.json`;
- die Units aus `~/.config/systemd/user/` und `/etc/systemd/system/llama-swap.service.d/` (nur zum Nachschlagen,
`restore.sh` spielt sie nicht zurück);
- Lucys Stimmreferenz `~/.lucy-stimme/app/ref.wav`;
- `known-good/`: `pip freeze` je venv, Versionen (OS, Kernel, MC2- und Hermes-Commit, llama.cpp, llama-swap) und
die Liste der Modelldateien.
- Eine Sicherung ist seit 24.09. rund 12 MB groß.
- **Nicht enthalten:** Modelle (neu ladbar), Code (Git), venvs, `~/.ssh` (auch nicht der Spiegel-Schlüssel).
- **Zweite Schicht: PBS** (`pbs-backup.timer`, täglich 03:50; Units und Skript seit 24.09. im Repo):
`deploy/pbs-backup.sh` sichert `~/.hermes` ganz (auch Sitzungen und Protokolle, die der Tarball weglässt),
`~/wissens-vault` (optional) und `/etc/llama-swap` als pxar-Archive auf den Proxmox Backup Server
(`192.168.178.156:8007`, Container 105, Datastore `qnap` = NFS-Freigabe auf dem QNAP, Backup-ID `aibox`). Der PBS
dedupliziert (ein Lauf dauert inkrementell rund 30 s) und hebt 7 Tage, 4 Wochen und 3 Monate auf (Prune-Job auf dem
PBS). Zugang `~/.config/pbs-backup/env` (Benutzer `aibox@pbs`, Rolle DatastoreBackup, Rechte 600) und der Client
`~/bin/proxmox-backup-client` liegen nur auf der Box, nie im Repo. Die Sicherung lief vom 09.07. bis 20.08. täglich
grün; am 21.08. brach sie ab (ihr erster Quellordner `/srv/models/mem0`, das abgelöste Gedächtnis, fehlte) und wurde
im KISS-Umbau abgeschaltet. Das neue Skript kennt mem0 nicht mehr. `deploy.sh` spielt die Units aus, schaltet sie
aber weder ein noch aus. Scheitert ein Lauf, zeigt der Wächter gelb.
- **Außerdem:** Der PBS sichert die Proxmox-Container, das QNAP macht Snapshots; beides läuft auf den anderen Geräten
und ist hier nicht geprüft.
**PBS-Sicherung** — an seit 24.09. (User-Ja; erster Lauf 21:20, 116 s, 65 % wiederverwendet). Der Deploy hält
den Timer an (`AKTIV`). Von Hand prüfen oder neu einrichten:
```bash
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user cat pbs-backup.service | grep ExecStart # muss auf deploy/pbs-backup.sh zeigen
systemctl --user start pbs-backup.service # Probelauf; kehrt nach dem Lauf zurück (~30 s)
journalctl --user -u pbs-backup.service -n 20 --no-pager # erwartet: „Duration: …“ und „End Time: …“, kein „Error“
touch ~/.local/share/systemd/timers/stamp-pbs-backup.timer # sonst holt der erste Start einen alten Lauf gleich nach
systemctl --user enable --now pbs-backup.timer
systemctl --user list-timers pbs-backup.timer # nächster Lauf 03:50
```
Ausschalten: `systemctl --user disable --now pbs-backup.timer`. Die alte Fassung `~/bin/pbs-backup.sh` wird danach
nicht mehr gebraucht; der Client daneben schon.
## Probe-Wiederherstellung (monatlich, seit 24.09.)
- **Wann:** `mc2-probe-wiederherstellung.timer`, am ersten Montag im Monat um 05:15 — nie sonntags (Updates ab 04:30),
lange nach der Sicherung um 03:30. `Persistent=true` holt einen verpassten Lauf nach. Von Hand:
`systemctl --user start mc2-probe-wiederherstellung.service` oder Knopf „Jetzt prüfen" am Hinweis.
- **Was:** `backend/services/probe_wiederherstellung.py` packt die jüngste Sicherung aus `/srv/models/mc2-backups` in
einen eigenen Tmp-Ordner `/var/tmp/mc2-probe-*` aus, prüft und löscht ihn wieder. Lebende Dateien fasst sie nie
an. `.env` und die Token-Dateien packt sie nicht aus, sie prüft nur, dass sie in der Sicherung stehen.
- **Geprüft:** Die jüngste Sicherung ist höchstens 48 Stunden alt und lässt sich vollständig lesen. Lucys Gedächtnis
(`memories/`) und `SOUL.md` sind da und stimmen mit dem lebenden Stand überein, soweit er vor der Sicherung schon so
dastand. Skills, Cron-Skripte und Hermes-Cron-Jobs sind da, `config.yaml` ist lesbar, `.env` vorhanden, die
llama-swap-Config hat Modelle, jede `mc2-*.json` des Box-Wart-Zustands ist lesbar, die systemd-Units samt
llama-swap-Drop-ins sind drin.
- **Ergebnis:** `/srv/models/mc2-probe-wiederherstellung.json` (Zeit, `gruen`/`rot`, Sicherung, Geprüftes, Fehler)
und `journalctl --user -u mc2-probe-wiederherstellung`. Bei Rot: Meldung „[Sicherung]" in normaler Dringlichkeit
(nachts in die Morgenmeldung) und ein gelber Hinweis des Wächters. Gelb auch, wenn die letzte Probe älter als
40 Tage ist.
## Zurückspielen
- **Per Oberfläche:** Updates → Sicherungen → „Zurückspielen". MC2 startet `restore.sh --yes <Datei>` als eigene
systemd-Unit (`mc2-restore-<Zeit>`), weil `restore.sh` MC2 selbst stoppt.
- **Per SSH:**
```bash
cd ~/mission-control-v2
bash deploy/restore.sh --list # lokal und auf dem Proxmox-Host
bash deploy/restore.sh --dry-run latest # zeigt, was passieren würde
bash deploy/restore.sh latest # mit Rückfrage; --yes ohne
bash deploy/restore.sh --mit-gedaechtnis latest # Gedächtnis und Zustand auch überschreiben
bash deploy/restore.sh --pull-offsite # alle Sicherungen vom Proxmox-Host holen
```
Ablauf: Sicherheits-Sicherung des jetzigen Stands → Dienste `mission-control-2`, `mc2-gateway`, `mc2-steward`,
`hermes-gateway` stoppen → Hermes-Config, `.env`, Plugins, `cron/`, `state/`, llama-swap-Config und die Desktop-Reste
zurück → Gedächtnis, `SOUL.md`, Skills, Cron-Skripte und `mc2-*.json` nur, wenn sie fehlen oder mit
`--mit-gedaechtnis` → Dienste starten → `/api/health`. Fehlt eine Sicherung lokal, holt `restore.sh` sie vom
Proxmox-Host. Sicherungen von vor dem 27.08. enthalten noch das Verzeichnis des abgelösten Gedächtnis-Dienstes; es wird
nicht zurückgespielt.
## Notfall
**Box tot oder Oberfläche weg:**
1. Strom und Netz prüfen, die Box einmal per Knopf neu starten. Alles startet von selbst (systemd, Linger). Nach 2–3
Minuten `http://192.168.178.151:9001` öffnen.
2. Oberfläche immer noch weg: per SSH `systemctl --user status mission-control-2` und
`journalctl --user -u mission-control-2 -n 100`. Hilft ein Neustart der Dienste nicht:
`bash ~/mission-control-v2/deploy/restore.sh latest`.
3. Totalschaden (neue Platte, neue Hardware): [WIEDERAUFBAU.md](WIEDERAUFBAU.md).
**Deploy kaputt:** `deploy.sh` rollt selbst zurück. Von Hand: den vorigen Commit aus `/srv/models/mc2-deploy.log`
nehmen, `git -C ~/mission-control-v2 reset --hard <commit>` und
`systemctl --user restart mc2-gateway mission-control-2 mc2-steward`.
**llama-swap-Config kaputt:** `ls -t /etc/llama-swap/config.yaml.bak-*`, die passende Sicherung nach
`/etc/llama-swap/config.yaml` kopieren; llama-swap lädt selbst neu. Ältere Stände stecken in jeder Sicherung.
**Motor startet keine Modelle:** `sudo journalctl -u llama-swap -n 100`. Nach einem Engine-Sprung ist die typische
Ursache ein gestrichenes Flag ([wissen/FALLEN.md](wissen/FALLEN.md)). Den vorigen Build hebt `update-engine.sh` in
`/opt/llamacpp-vulkan.bak` auf, die vorige llama-swap-Version `update-swap.sh` in `/usr/local/bin/llama-swap.bak`.
**Hermes kaputt:** `journalctl --user -u hermes-gateway -n 100`, dann `bash ~/mission-control-v2/deploy/hermes-postcheck.sh`.
Rückweg wie in [UPDATES.md](UPDATES.md): alter Commit in `~/.hermes/hermes-agent`, Config aus der letzten Sicherung,
`systemctl --user restart hermes-gateway`.
**Festgehaltener Baustein:** Knopf „Freigeben" (Startseite oder Updates-Seite); der nächste Sonntagslauf versucht das
Update erneut.
## Wo was liegt (Box)
| Pfad | Inhalt |
|---|---|
| `~/mission-control-v2` | Checkout von `main` (nur über `deploy.sh` ändern) |
| `~/mission-control-v2/backend/.venv` | Python-Umgebung des Box-Warts (Python 3.14) |
| `~/.config/systemd/user/` | User-Units und Drop-ins |
| `/srv/models/` | Modelle, Box-Wart-Zustand (`mc2-*.json`, darunter `mc2-einstellungen.json` und `mc2-stand.json`), Sicherungen (`mc2-backups/`), Radar-Downloads (`radar/`), Messwerte (`mc2-messwerte/`) |
| `~/.config/systemd/user/mc2-autoupdate.timer.d/zeitfenster.conf` | Update-Zeitfenster aus der Oberfläche (nur MC2 schreibt es) |
| `/etc/llama-swap/config.yaml` | lebende llama-swap-Config (+ `.bak-*`) |
| `/opt/llamacpp-vulkan/`, `/usr/local/bin/llama-server` | Motor (llama.cpp Vulkan) |
| `/usr/local/bin/llama-swap`, `/usr/local/bin/llama-swap-warmup.sh` | Router und Vorwärm-Skript (Root-Kopie) |
| `~/.hermes/` | Hermes: Code (`hermes-agent/`), `config.yaml`, `.env`, `SOUL.md`, `memories/`, `skills/`, `scripts/`, `cron/`, `logs/` |
| `~/.lucy-stimme/` | Lucys Stimme (pocket-tts), Referenz `app/ref.wav` |
| `~/.voice/` | Umgebung der Spracherkennung (`voice_service/install.sh`) |
| `~/projekte/` | Spiegel der Gitea-Repos (`projekte-sync`) |
| `~/mc2-notify.log`, `~/.hermes/night-queue.txt` | Meldeprotokoll, Nacht-Warteschlange |