347 lines
14 KiB
Markdown
347 lines
14 KiB
Markdown
# Betriebshandbuch
|
|
|
|
Was tun, wenn etwas schiefgeht? Diese Seite ist für den Ernstfall geschrieben,
|
|
auch für jemanden, der Munin nicht selbst gebaut hat. Jeder Abschnitt beginnt
|
|
mit dem, was man sieht, und endet mit dem, was man tut.
|
|
|
|
Die Befehle gehen vom Docker-Aufbau aus dem README aus: Container `munin`,
|
|
Volume unter `/data`. Ohne Docker gilt dasselbe mit dem eigenen `DATA_DIR`
|
|
statt `/data`, und die Ausgabe des Prozesses statt `docker logs`.
|
|
|
|
---
|
|
|
|
## Auf einen Blick
|
|
|
|
| Was | Wo |
|
|
|---|---|
|
|
| Kontroll-Datenbank | `DATA_DIR/control.db`: Workspaces, welcher Key zu welchem Workspace gehört, Workspace-Admins |
|
|
| Ein Workspace | `DATA_DIR/workspaces/<workspace-id>.db`: Projekte, Einträge, Keys, Protokoll und Suchprotokoll dieses Workspace |
|
|
| Zugehörige Hilfsdateien | `…db-wal` und `…db-shm` neben jeder Datenbank. Sie gehören im laufenden Betrieb dazu, **nie einzeln löschen, solange der Server läuft** |
|
|
| Logs | Standardausgabe des Prozesses: `docker logs munin` |
|
|
| Sicherungen | `BACKUP_DIR/<zeitstempel>/` von `scripts/backup.sh`, siehe README „Datensicherung“ |
|
|
| Markdown-Export | `EXPORT_DIR`, falls gesetzt. Lesbar, aber **keine** vollständige Sicherung (keine Keys, keine Historie) |
|
|
| Geheimnisse | `SESSION_SECRET`, `ADMIN_TOKEN` als Umgebungsvariablen, siehe README „Konfiguration“ |
|
|
| Lebenszeichen | `GET /healthz` antwortet `ok` |
|
|
|
|
Welcher Workspace hinter einer ID steckt:
|
|
|
|
```sh
|
|
sqlite3 /data/control.db "SELECT id, name FROM workspaces;"
|
|
```
|
|
|
|
---
|
|
|
|
## Läuft er?
|
|
|
|
```sh
|
|
curl -s https://<server>/healthz # erwartet: ok
|
|
docker logs --tail 50 munin
|
|
```
|
|
|
|
Beim Start schreibt Munin diese Zeilen ins Log. Fehlen sie, ist der Start
|
|
gescheitert:
|
|
|
|
```
|
|
Munin läuft auf http://localhost:8080 (3 Workspaces)
|
|
Workspaces: /data/workspaces (Kontrolle: /data/control.db)
|
|
Schreibbudget: 1000 Aufrufe je Projekt und Stunde (Vorgabe)
|
|
```
|
|
|
|
`/healthz` prüft nur, ob der Prozess antwortet, nicht die Datenbanken. Ein
|
|
`ok` heißt also nicht, dass jeder Workspace lesbar ist. Siehe dazu
|
|
[Datenbank beschädigt](#datenbank-beschädigt).
|
|
|
|
---
|
|
|
|
## Server startet nicht
|
|
|
|
**Man sieht:** Der Container beendet sich sofort, `/healthz` antwortet nicht.
|
|
|
|
`docker logs munin` zeigt die Ursache in der letzten Zeile:
|
|
|
|
| Meldung | Ursache | Abhilfe |
|
|
|---|---|---|
|
|
| `SESSION_SECRET ist nicht gesetzt` / `zu kurz` | Umgebungsvariable fehlt oder hat weniger als 32 Zeichen | Aus dem Passwortspeicher setzen. **Nicht** neu erzeugen, wenn es ein altes gibt, sonst sind alle Anmeldungen weg |
|
|
| `ADMIN_TOKEN ist nicht gesetzt` / `zu kurz` | dasselbe, mindestens 16 Zeichen | dasselbe |
|
|
| `Datenverzeichnis konnte nicht angelegt werden` | Volume fehlt oder gehört nicht dem Benutzer im Container (UID 10001) | `chown -R 10001:10001` auf das Volume, oder richtig einhängen |
|
|
| `unable to open database file`, `permission denied` | dasselbe für eine einzelne Datei, oft nach einem Zurückkopieren als root | Besitzer der Datei korrigieren |
|
|
| `address already in use` | Ein anderer Prozess belegt den Port | anderen Prozess beenden oder `PORT` ändern |
|
|
| `database disk image is malformed` | beschädigte Datenbank | [Datenbank beschädigt](#datenbank-beschädigt) |
|
|
|
|
---
|
|
|
|
## Platte voll
|
|
|
|
**Man sieht:** Lesen geht noch, Schreiben schlägt fehl. Im Log steht
|
|
`database or disk is full`, Agenten bekommen Fehler bei `remember`.
|
|
|
|
SQLite bleibt dabei heil. Ein Schreibvorgang, der nicht mehr passt, wird
|
|
vollständig zurückgerollt, halbe Einträge gibt es nicht.
|
|
|
|
1. Nachsehen, was wächst: `du -sh /data/* /data/workspaces/* <BACKUP_DIR>/* <EXPORT_DIR>`
|
|
2. Platz schaffen, in dieser Reihenfolge:
|
|
- alte Sicherungen löschen und `RETENTION_DAYS` im Cronjob setzen, falls es fehlt
|
|
- Docker-Logs begrenzen (`--log-opt max-size=50m`)
|
|
3. **Nicht** die `-wal`- oder `-shm`-Dateien löschen. Darin stehen Änderungen,
|
|
die noch nicht in der Hauptdatei sind.
|
|
|
|
Sobald wieder Platz ist, schreibt Munin ohne Neustart weiter.
|
|
|
|
---
|
|
|
|
## Ein Key ist in falsche Hände geraten
|
|
|
|
Zum Beispiel in ein öffentliches Repository eingecheckt, in einem Chat
|
|
gepostet, oder der Laptop ist weg.
|
|
|
|
1. Mit Admin-Rechten anmelden, ins Projekt wechseln, **Zugänge** → beim
|
|
betroffenen Key **Zurückziehen**. Das wirkt sofort, für MCP-Aufrufe und für
|
|
Sitzungen in der Weboberfläche. Jede Anfrage prüft den Key neu in der
|
|
Datenbank.
|
|
2. Unter **Protokoll** in der Spalte *Wer* nach dem Namen des Keys suchen (ein
|
|
Filter fehlt bisher, die Suche des Browsers hilft): Was wurde damit getan,
|
|
von welchen Adressen? Unbekannte Adressen deuten auf Missbrauch hin.
|
|
3. Hat der Key geschrieben, was er nicht sollte: Einträge über ihre
|
|
**Historie** auf eine frühere Fassung zurücksetzen, Gelöschtes aus dem
|
|
**Papierkorb** holen.
|
|
4. Dem Team einen neuen Key ausstellen.
|
|
|
|
Wer den Key nicht kennt, findet ihn über den Workspace: Der Plattform-Admin
|
|
sieht alle Workspaces, ein Workspace-Admin alle Projekte seines Workspace.
|
|
|
|
---
|
|
|
|
## Ein Workspace-Admin-Token (`wa_…`) ist in falsche Hände geraten
|
|
|
|
Ein solches Token öffnet den ganzen Workspace.
|
|
|
|
1. Als Plattform-Admin **Workspaces** → **Bearbeiten** → Abschnitt
|
|
*Workspace-Admins* → **Zurückziehen**. Das wirkt sofort, auch für eine
|
|
laufende Sitzung.
|
|
2. **Protokoll (alle)** des Workspace prüfen: angelegte oder gelöschte
|
|
Projekte, neue Keys. Keys, die in der fraglichen Zeit angelegt wurden,
|
|
zurückziehen.
|
|
3. Neues Token für die Person ausstellen.
|
|
|
|
---
|
|
|
|
## `ADMIN_TOKEN` ist in falsche Hände geraten
|
|
|
|
Mit dem `ADMIN_TOKEN` kann sich jeder als Plattform-Admin anmelden und kommt
|
|
an jeden Workspace.
|
|
|
|
```sh
|
|
openssl rand -hex 24 # neues ADMIN_TOKEN
|
|
```
|
|
|
|
1. Neuen Wert im Passwortspeicher ablegen und in der Konfiguration des
|
|
Containers setzen.
|
|
2. Container neu starten. **Alle Sitzungen des Plattform-Admins enden damit
|
|
sofort**, auch die eines Angreifers, der sich schon angemeldet hat. Jede
|
|
Admin-Sitzung trägt einen Fingerabdruck des Tokens, mit dem sie entstanden
|
|
ist, und der Server vergleicht ihn bei jeder Anfrage (seit 2026-10-06,
|
|
siehe `auth.PlatformSubject`). Teams und Workspace-Admins bleiben
|
|
angemeldet, MCP-Agenten merken nichts.
|
|
3. [Prüfen, was in der fraglichen Zeit geschah](#was-ist-in-der-fraglichen-zeit-geschehen).
|
|
|
|
## `SESSION_SECRET` ist in falsche Hände geraten
|
|
|
|
Mit dem `SESSION_SECRET` kann jeder gültige Anmelde-Cookies selbst ausstellen,
|
|
für jeden Zugang. Da der Fingerabdruck des `ADMIN_TOKEN` ebenfalls aus dem
|
|
`SESSION_SECRET` berechnet wird, schließt das den Plattform-Admin ein, ohne
|
|
dass das Token je bekannt war. Das ist der schwerste Fall.
|
|
|
|
```sh
|
|
openssl rand -hex 32 # neues SESSION_SECRET
|
|
openssl rand -hex 24 # neues ADMIN_TOKEN, zur Sicherheit gleich mit
|
|
```
|
|
|
|
1. Beide Werte im Passwortspeicher ersetzen und in der Konfiguration des
|
|
Containers setzen.
|
|
2. Container neu starten. Alle sind danach aus der Weboberfläche abgemeldet und
|
|
melden sich mit ihrem Key neu an. **MCP-Agenten merken nichts davon**, sie
|
|
nutzen ihre Keys direkt, nicht die Cookies.
|
|
3. [Prüfen, was in der fraglichen Zeit geschah](#was-ist-in-der-fraglichen-zeit-geschehen).
|
|
|
|
### Was ist in der fraglichen Zeit geschehen?
|
|
|
|
- Anmeldungen des Plattform-Admins stehen **nur im Server-Log**, nicht im
|
|
Protokoll: `docker logs munin 2>&1 | grep "Anmeldung Plattform-Admin"`.
|
|
Unbekannte Adressen sind ein Warnsignal. Ein selbst ausgestelltes Cookie
|
|
(geleaktes `SESSION_SECRET`) hinterlässt diese Zeile allerdings nicht.
|
|
- In jedem Workspace das **Protokoll (alle)**: neue Projekte, Keys und
|
|
Workspace-Admins. Die Liste der Workspaces auf unbekannte Einträge prüfen.
|
|
Was nicht von dir stammt, zurückziehen oder löschen.
|
|
|
|
---
|
|
|
|
## Ein Agent dreht durch
|
|
|
|
**Man sieht:** Ein Projekt füllt sich mit Unsinn, oder Agenten melden, dass das
|
|
Schreibbudget erschöpft ist.
|
|
|
|
Das Schreibbudget (Vorgabe 1.000 Aufrufe je Projekt und Stunde) begrenzt den
|
|
Schaden bereits. Ein Agent, der in einer Schleife hängt, wird nach spätestens
|
|
einer Stunde gebremst.
|
|
|
|
1. **Nutzung** im Projekt zeigt, welcher Key wie viel geschrieben hat.
|
|
2. Den Key des Agenten **zurückziehen** (siehe oben) oder das Team bitten, den
|
|
Agenten zu stoppen.
|
|
3. Aufräumen über **Historie** und **Papierkorb**.
|
|
4. Braucht ein Projekt dauerhaft mehr: Der Plattform-Admin setzt unter
|
|
*Projekte → Bearbeiten* eine eigene Obergrenze.
|
|
|
|
---
|
|
|
|
## Einträge versehentlich gelöscht oder überschrieben
|
|
|
|
Fast immer ohne Sicherung lösbar:
|
|
|
|
- **Überschrieben:** Eintrag öffnen → **Historie** → frühere Fassung
|
|
wiederherstellen. Jede Fassung bleibt erhalten.
|
|
- **Gelöscht:** **Papierkorb** im Projekt, 30 Tage lang.
|
|
- **Endgültig gelöscht oder älter als 30 Tage im Papierkorb:** nur noch aus einer
|
|
Sicherung. Für einzelne Einträge reicht oft der Markdown-Export: Datei
|
|
heraussuchen, als ZIP packen und über **Import** im Projekt wieder einlesen.
|
|
- **Ganzes Projekt gelöscht:** nur aus einer Sicherung, siehe unten.
|
|
|
|
---
|
|
|
|
## Aus einer Sicherung wiederherstellen
|
|
|
|
Der Ablauf ist am 2026-10-06 einmal vollständig durchgespielt worden (Sicherung
|
|
im laufenden Betrieb, Zurückkopieren, Start).
|
|
|
|
### Vorher bedenken
|
|
|
|
Eine Sicherung setzt **alles** in der zurückgespielten Datei auf ihren Stand
|
|
zurück, nicht nur das, was man retten will:
|
|
|
|
- Einträge und Fassungen nach der Sicherung sind weg.
|
|
- Keys, die nach der Sicherung **angelegt** wurden, gelten nicht mehr.
|
|
- Keys, die nach der Sicherung **zurückgezogen** wurden, **gelten wieder.**
|
|
Nach dem Zurückspielen deshalb unter *Zugänge* nachsehen und erneut
|
|
zurückziehen. Das ist der gefährlichste Nebeneffekt.
|
|
|
|
Wer nur ein paar Einträge braucht, spielt die Sicherung besser in ein
|
|
**getrenntes Verzeichnis** zurück, startet dort einen zweiten Server auf einem
|
|
anderen Port, exportiert die Einträge und importiert sie im echten Server.
|
|
Dann bleibt alles andere unberührt.
|
|
|
|
### Einen Workspace zurückholen
|
|
|
|
Die Kontroll-Datenbank bleibt dabei, wie sie ist. Die anderen Workspaces merken
|
|
nichts davon.
|
|
|
|
```sh
|
|
# 1. Welche Datei? (ID ↔ Name)
|
|
sqlite3 /data/control.db "SELECT id, name FROM workspaces;"
|
|
|
|
# 2. Sicherung prüfen, bevor man sich auf sie verlässt
|
|
sqlite3 <BACKUP_DIR>/<zeitstempel>/workspaces/<id>.db "PRAGMA integrity_check;" # erwartet: ok
|
|
|
|
# 3. Server stoppen
|
|
docker stop munin
|
|
|
|
# 4. Die Hilfsdateien der alten Fassung entfernen. Nach einem Absturz liegen
|
|
# sie noch da, und SQLite würde sonst ihren Inhalt in die zurückgespielte
|
|
# Datei übernehmen.
|
|
rm -f /data/workspaces/<id>.db-wal /data/workspaces/<id>.db-shm
|
|
|
|
# 5. Die kaputte oder falsche Datei zur Seite legen, nicht löschen
|
|
mv /data/workspaces/<id>.db /data/workspaces/<id>.db.vorher
|
|
|
|
# 6. Zurückkopieren, Besitzer richtig setzen
|
|
cp <BACKUP_DIR>/<zeitstempel>/workspaces/<id>.db /data/workspaces/
|
|
chown 10001:10001 /data/workspaces/<id>.db
|
|
|
|
# 7. Starten und prüfen
|
|
docker start munin
|
|
curl -s https://<server>/healthz
|
|
```
|
|
|
|
Danach in der Weboberfläche den Workspace öffnen und **zurückgezogene Keys
|
|
erneut zurückziehen** (siehe oben). Läuft alles, kann `<id>.db.vorher` weg.
|
|
|
|
### Alles zurückholen
|
|
|
|
Wie oben, aber mit `control.db` und **allen** Dateien unter `workspaces/`, und
|
|
mit allen `-wal`/`-shm`-Dateien. Zusätzlich zu den Keys gelten dann auch
|
|
Workspace-Admins wieder, die nach der Sicherung zurückgezogen wurden.
|
|
|
|
### Ein gelöschter Workspace
|
|
|
|
Beim Löschen verschwinden die Datei **und** sein Eintrag in der
|
|
Kontroll-Datenbank. Nur die Datei zurückzukopieren reicht deshalb nicht, der
|
|
Server kennt den Workspace nicht mehr. Es braucht auch die `control.db` aus
|
|
derselben Sicherung, und die setzt alles Zentrale zurück: andere Workspaces,
|
|
Workspace-Admins, die Zuordnung neuer Keys. Das ist eine bewusste Entscheidung.
|
|
Meist ist der Weg über einen getrennten zweiten Server mit Export und Import
|
|
der bessere.
|
|
|
|
---
|
|
|
|
## Datenbank beschädigt
|
|
|
|
**Man sieht:** `database disk image is malformed` im Log, oder ein Workspace
|
|
lässt sich nicht öffnen, während andere gehen. Workspace-Dateien werden erst
|
|
beim ersten Zugriff geöffnet, deshalb kann der Fehler auch erst Stunden nach
|
|
dem Start auftauchen.
|
|
|
|
```sh
|
|
docker stop munin
|
|
sqlite3 /data/workspaces/<id>.db "PRAGMA integrity_check;"
|
|
```
|
|
|
|
Alles außer `ok` heißt: [aus der Sicherung zurückholen](#einen-workspace-zurückholen).
|
|
Nicht versuchen, die Datei zu reparieren. Mit der letzten guten Sicherung ist
|
|
man schneller und sicherer am Ziel.
|
|
|
|
Häufigste Ursache ist eine Datei, die im laufenden Betrieb mit `cp` statt mit
|
|
`scripts/backup.sh` kopiert und dann zurückgespielt wurde.
|
|
|
|
---
|
|
|
|
## Eine neue Fassung einspielen
|
|
|
|
Das Schema der Datenbanken wird beim Start automatisch angepasst
|
|
(`internal/store/migrations*`). Der Weg zurück auf eine ältere Fassung ist
|
|
danach nicht garantiert. Deshalb:
|
|
|
|
1. `scripts/backup.sh` von Hand laufen lassen.
|
|
2. Neues Abbild bauen, Container ersetzen.
|
|
3. Log auf die Startzeilen prüfen, `/healthz`, einmal in der Weboberfläche
|
|
suchen und ein MCP-`recall` absetzen.
|
|
4. Geht etwas schief: altes Abbild starten **und** die Sicherung aus Schritt 1
|
|
zurückspielen.
|
|
|
|
Ein Neustart beendet laufende Anfragen sauber (bis zu 10 Sekunden). Agenten
|
|
verbinden sich danach von selbst neu.
|
|
|
|
---
|
|
|
|
## Regelmäßig
|
|
|
|
| Wann | Was |
|
|
|---|---|
|
|
| wöchentlich | Läuft die Sicherung? Ein Blick ins Log des Cronjobs und auf den neuesten Ordner in `BACKUP_DIR`. |
|
|
| monatlich | Marke *ungenutzt* bei Keys und Workspace-Admins (Übersicht unter *Workspaces*). Vergessene Zugänge zurückziehen. |
|
|
| vierteljährlich | **Probe der Sicherung:** die neueste Sicherung in ein leeres Verzeichnis kopieren, `PRAGMA integrity_check` für jede Datei, einen zweiten Server mit `DATA_DIR` darauf und anderem `PORT` starten, einmal anmelden und suchen. Eine Sicherung, die nie zurückgespielt wurde, ist nur eine Hoffnung. |
|
|
| vor jedem Update | Sicherung von Hand, siehe oben |
|
|
|
|
Die Probe der Sicherung ohne Docker, auf einem beliebigen Rechner mit Go und
|
|
sqlite3:
|
|
|
|
```sh
|
|
probe=$(mktemp -d)
|
|
cp -r <BACKUP_DIR>/<zeitstempel>/. "$probe/"
|
|
for f in "$probe"/control.db "$probe"/workspaces/*.db; do
|
|
echo "$f: $(sqlite3 "$f" 'PRAGMA integrity_check;')"
|
|
done
|
|
DATA_DIR="$probe" PORT=18080 SESSION_SECRET=$(openssl rand -hex 32) \
|
|
ADMIN_TOKEN=probe-probe-probe-1 go run .
|
|
# im Browser: http://localhost:18080, Anmeldung mit probe-probe-probe-1
|
|
```
|
|
|
|
Die Probe darf mit eigenen Geheimnissen laufen. Sie braucht weder die echten
|
|
noch erreicht sie den echten Server.
|