570 lines
26 KiB
Markdown
570 lines
26 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`.
|
|
|
|
Munin läuft entweder mit **SQLite** (Standard, eine Datei je Workspace) oder
|
|
mit **PostgreSQL** (`DB_DRIVER=postgres`, ein Schema je Workspace). Wo sich
|
|
die Schritte unterscheiden, steht beides da. Welches gilt, zeigt die Zeile
|
|
`Speicher:` beim Start.
|
|
|
|
Für die PostgreSQL-Befehle: `$DATABASE_URL` ist dieselbe Adresse wie beim
|
|
Server, das Passwort kommt aus `PGPASSWORD` oder `~/.pgpass`.
|
|
|
|
---
|
|
|
|
## 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 /live` antwortet `ok`, solange der Prozess läuft (`/healthz` ist der alte Name) |
|
|
| Bereitschaft | `GET /ready` antwortet `ok`, wenn die Datenbank antwortet, sonst `503 nicht bereit`. Der Grund steht im Log (`Bereitschaft: …`) |
|
|
|
|
Mit PostgreSQL:
|
|
|
|
| Was | Wo |
|
|
|---|---|
|
|
| Kontroll-Datenbank | Schema `munin_control` in der Datenbank aus `DATABASE_URL` |
|
|
| Ein Workspace | Schema `munin_ws_<id>` (Bindestrich der ID als `_`). Der Name steht als Kommentar daran und unter *Workspaces* in der Weboberfläche |
|
|
| Sicherungen | `BACKUP_DIR/<zeitstempel>/control.dump` und `…/workspaces/munin_ws_<id>.dump` |
|
|
|
|
Welcher Workspace hinter einer ID steckt:
|
|
|
|
```sh
|
|
# SQLite
|
|
sqlite3 /data/control.db "SELECT id, name FROM workspaces;"
|
|
|
|
# PostgreSQL: Schema und Name
|
|
psql "$DATABASE_URL" -c "SELECT nspname, obj_description(oid, 'pg_namespace') FROM pg_namespace WHERE nspname LIKE 'munin%' ORDER BY 1;"
|
|
```
|
|
|
|
---
|
|
|
|
## Läuft er?
|
|
|
|
```sh
|
|
curl -s https://<server>/live # Prozess läuft? erwartet: ok
|
|
curl -s https://<server>/ready # Datenbank erreichbar? 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)
|
|
Speicher: SQLite — Workspaces: /data/workspaces, Kontrolle: /data/control.db
|
|
Schreibbudget: 1000 Aufrufe je Projekt und Stunde (Vorgabe)
|
|
```
|
|
|
|
Mit PostgreSQL lautet die zweite Zeile etwa
|
|
`Speicher: PostgreSQL — db.intern:5432/munin (Schemas munin_control, munin_ws_…)`.
|
|
|
|
`/live` prüft nur, ob der Prozess antwortet. `/ready` prüft zusätzlich die
|
|
Kontroll-Datenbank (unter PostgreSQL also auch, ob der Datenbankserver
|
|
erreichbar ist), aber nicht jede einzelne Workspace-Datei. Siehe dazu
|
|
[Datenbank beschädigt](#datenbank-beschädigt) und
|
|
[PostgreSQL ist weg](#postgresql-ist-weg).
|
|
|
|
Mehr Einzelheiten zeigt dem Plattform-Admin die Seite **System**
|
|
(`/admin/system`). Ganz oben steht der Kasten **Zustand** mit allem, was zu
|
|
tun ist: Verbindungen knapp, WAL-Datei zu groß, Export fehlgeschlagen oder
|
|
überfällig, Sortierung der PostgreSQL-Datenbank falsch, Keys laufen ab oder
|
|
sind ungenutzt. Darunter die Aktivität der letzten 30 Tage, Bestand und Platz
|
|
je Workspace, Datenbank, Bereitschaft, offene Verbindungen je Workspace gegen
|
|
das Budget (mit PostgreSQL), wie oft auf Verbindungen gewartet wurde, Fassung,
|
|
Schema-Stand und Laufzeit.
|
|
|
|
Der Container-Healthcheck im Dockerfile hängt an `/live`: Fehlt nur die
|
|
Datenbank, soll der Container nicht neu starten, das hülfe nicht. Ein
|
|
Load-Balancer oder Monitoring gehört an `/ready`.
|
|
|
|
---
|
|
|
|
## Server startet nicht
|
|
|
|
**Man sieht:** Der Container beendet sich sofort, `/live` 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 32 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) |
|
|
|
|
Mit PostgreSQL kommen diese hinzu:
|
|
|
|
| Meldung | Ursache | Abhilfe |
|
|
|---|---|---|
|
|
| `DB_DRIVER=postgres, aber DATABASE_URL ist nicht gesetzt` | Umgebungsvariable fehlt | setzen, siehe README „Mit PostgreSQL“ |
|
|
| `PostgreSQL ist noch nicht erreichbar (Versuch …)` und am Ende `PostgreSQL ist nicht erreichbar` | Datenbank läuft nicht, falscher Host/Port, Firewall, falsches Passwort | Die Meldung nennt den Grund. Mit `psql "$DATABASE_URL" -c 'SELECT 1'` vom selben Rechner gegenprüfen |
|
|
| `zu wenige PostgreSQL-Verbindungen: … max_connections auf mindestens N erhöhen` | `max_connections` reicht nicht für alle Workspaces | Beim Datenbank-Admin `max_connections` auf mindestens N setzen lassen und PostgreSQL neu starten. Munin danach neu starten |
|
|
| `Schema … konnte nicht angelegt werden: permission denied` | Der Benutzer aus `DATABASE_URL` darf keine Schemas anlegen | `GRANT CONNECT, CREATE ON DATABASE munin TO munin;` (oder ihn zum Eigentümer machen), siehe [Rechte des Datenbankbenutzers](#rechte-des-datenbankbenutzers) |
|
|
| `Schema … konnte nicht gelöscht werden: must be owner of schema` | Das Schema gehört einem anderen Benutzer, etwa nach einem Zurückspielen als anderer Benutzer | Eigentum übertragen: `ALTER SCHEMA munin_ws_<id> OWNER TO munin;` (für alles auf einmal `REASSIGN OWNED BY <alter> TO munin;`, als Superuser) |
|
|
| `Warnung: Die Datenbank sortiert nach "…" statt "C"` | Datenbank ohne `LOCALE 'C'` angelegt | Läuft trotzdem, Listen sind nur anders sortiert. Beheben lässt es sich nur durch eine neue Datenbank mit `LOCALE 'C'` und Zurückspielen einer Sicherung |
|
|
|
|
---
|
|
|
|
## 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.
|
|
|
|
Mit PostgreSQL betrifft das die Platte des Datenbankservers. PostgreSQL rollt
|
|
einen Schreibvorgang, der nicht mehr passt, ebenso vollständig zurück. Platz
|
|
schaffen ist dort Sache des Datenbank-Admins; auf Munins Seite wachsen dann
|
|
nur noch Sicherungen und Export.
|
|
|
|
---
|
|
|
|
## Rechte des Datenbankbenutzers
|
|
|
|
Munin arbeitet mit **einem** Datenbankbenutzer (`DATABASE_URL`) für alles. Er
|
|
braucht genau zwei Rechte auf der Datenbank, oder er ist ihr Eigentümer:
|
|
|
|
```sql
|
|
GRANT CONNECT, CREATE ON DATABASE munin TO munin;
|
|
```
|
|
|
|
| Vorgang | Was in PostgreSQL passiert | Nötiges Recht |
|
|
|---|---|---|
|
|
| Verbinden | Anmeldung an der Datenbank | `CONNECT` auf der Datenbank |
|
|
| Erster Start, Workspace anlegen | `CREATE SCHEMA munin_control` bzw. `munin_ws_<id>` | `CREATE` auf der Datenbank |
|
|
| Workspace öffnen (Migrationen) | `CREATE TABLE`, `CREATE INDEX` im Schema | Eigentum am Schema — hat er, weil er es angelegt hat |
|
|
| Normaler Betrieb | `SELECT`, `INSERT`, `UPDATE`, `DELETE` | Eigentum an den Tabellen — ebenso |
|
|
| Workspace umbenennen | `COMMENT ON SCHEMA …` | Eigentum am Schema |
|
|
| Workspace löschen | `DROP SCHEMA munin_ws_<id> CASCADE` | **nur** Eigentum am Schema (oder Superuser). Ein eigenes Löschrecht gibt es in PostgreSQL nicht; auch alle Rechte an allen Tabellen reichen nicht |
|
|
| Verbindungsbudget prüfen | `SHOW max_connections` | keins |
|
|
|
|
Nicht nötig: Superuser, `CREATEDB`, `CREATEROLE`, Rechte am Schema `public`.
|
|
|
|
**Erprobt am 2026-10-16:** Munin mit `CONNECT, CREATE` auf einer Datenbank, die
|
|
einem anderen Benutzer gehört — Start, Workspace anlegen, `/ready` ok, die
|
|
Schemas gehören danach Munins Benutzer. Ein Benutzer mit allen Tabellenrechten,
|
|
aber ohne Eigentum, kann das Schema nicht löschen (`must be owner of schema`).
|
|
|
|
**Sicherung und Wiederherstellung:** `pg_dump` braucht nur Lesezugriff;
|
|
`pg_restore` und das Umbenennen eines Schemas (`ALTER SCHEMA … RENAME`) laufen
|
|
am besten als derselbe Benutzer wie Munin. Dann gehört das zurückgespielte
|
|
Schema wieder ihm, und er kann es später auch löschen. Wurde als ein anderer
|
|
Benutzer zurückgespielt: `ALTER SCHEMA munin_ws_<id> OWNER TO munin;` und
|
|
`ALTER TABLE … OWNER TO munin;`, einfacher als Superuser
|
|
`REASSIGN OWNED BY <anderer> TO munin;`.
|
|
|
|
**Bewusst kein zweiter Benutzer** für die Verwaltung: siehe AUSBAUSTUFEN.md
|
|
4.10, „Entscheidung: ein Datenbankbenutzer“.
|
|
|
|
---
|
|
|
|
## PostgreSQL ist weg
|
|
|
|
**Man sieht:** `/live` antwortet `ok`, `/ready` aber `503 nicht bereit`. Seiten
|
|
und MCP-Aufrufe enden mit Fehlern, im Log steht `Bereitschaft: Datenbank
|
|
antwortet nicht …` (einmal, beim Wechsel) und Meldungen wie `connection refused`
|
|
oder `the database system is shutting down`.
|
|
|
|
1. Läuft die Datenbank? `pg_isready -d "$DATABASE_URL"` oder
|
|
`psql "$DATABASE_URL" -c 'SELECT 1'`.
|
|
2. Ist sie wieder da, sollten Anfragen von selbst wieder gehen: Go verwirft
|
|
abgebrochene Verbindungen und baut neue auf. Im Log steht dann
|
|
`Bereitschaft: Datenbank antwortet wieder`, und `/ready` meldet `ok`.
|
|
**Noch nicht erprobt.** Gehen sie nach einer Minute nicht, Munin neu
|
|
starten (`docker restart munin`).
|
|
Dabei geht nichts verloren, der Suchindex baut sich beim ersten Zugriff neu
|
|
auf.
|
|
|
|
Schreibvorgänge, die während des Ausfalls scheiterten, haben nichts
|
|
geschrieben. Agenten bekommen einen Fehler und versuchen es erneut.
|
|
|
|
---
|
|
|
|
## 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** oben bei *Wer* den Namen des Keys wählen, bei Bedarf
|
|
den Zeitraum (*von*, *bis*) eingrenzen: Was wurde damit getan, von welchen
|
|
Adressen? Unbekannte Adressen deuten auf Missbrauch hin. Die gefilterte
|
|
Ansicht hat eine eigene Adresse und lässt sich weitergeben.
|
|
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>/ready
|
|
```
|
|
|
|
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.
|
|
|
|
### Einen Workspace zurückholen (PostgreSQL)
|
|
|
|
Am 2026-10-16 durchgespielt (Sicherung, Zurückspielen über einen bestehenden
|
|
Workspace, Start auf einer zurückgespielten Datenbank).
|
|
|
|
Das alte Schema wird nicht gelöscht, sondern umbenannt. Der neue Name beginnt
|
|
nicht mit `munin_ws_`, damit weder Munin noch `scripts/backup.sh` ihn für
|
|
einen Workspace halten.
|
|
|
|
```sh
|
|
# 1. Welches Schema? (Schema ↔ Name)
|
|
psql "$DATABASE_URL" -c "SELECT nspname, obj_description(oid, 'pg_namespace') FROM pg_namespace WHERE nspname LIKE 'munin_ws_%';"
|
|
|
|
# 2. Sicherung prüfen: Lässt sie sich lesen?
|
|
pg_restore --list <BACKUP_DIR>/<zeitstempel>/workspaces/munin_ws_<id>.dump > /dev/null && echo lesbar
|
|
|
|
# 3. Server stoppen
|
|
docker stop munin
|
|
|
|
# 4. Das jetzige Schema zur Seite legen, nicht löschen
|
|
psql "$DATABASE_URL" -c 'ALTER SCHEMA munin_ws_<id> RENAME TO munin_vorher_ws_<id>;'
|
|
|
|
# 5. Zurückspielen. Legt das Schema samt Namenskommentar wieder an.
|
|
pg_restore --dbname="$DATABASE_URL" --no-owner <BACKUP_DIR>/<zeitstempel>/workspaces/munin_ws_<id>.dump
|
|
|
|
# 6. Starten und prüfen
|
|
docker start munin
|
|
curl -s https://<server>/ready
|
|
```
|
|
|
|
Danach wie oben **zurückgezogene Keys erneut zurückziehen**. Läuft alles, kann
|
|
das alte Schema weg: `DROP SCHEMA munin_vorher_ws_<id> CASCADE;`
|
|
|
|
Fehlen `pg_restore` oder `psql` auf dem Rechner oder sind sie älter als der
|
|
Server, gehen dieselben Befehle über einen PostgreSQL-Container, z.B.
|
|
`docker exec -i <container> pg_restore … < datei.dump`.
|
|
|
|
### Alles zurückholen (PostgreSQL)
|
|
|
|
Am sichersten in eine **neue, leere Datenbank** und dann `DATABASE_URL`
|
|
darauf umstellen. Die alte bleibt so unberührt, bis klar ist, dass alles
|
|
stimmt:
|
|
|
|
```sql
|
|
CREATE DATABASE munin_neu OWNER munin TEMPLATE template0 LOCALE 'C' ENCODING 'UTF8';
|
|
```
|
|
|
|
```sh
|
|
for f in <BACKUP_DIR>/<zeitstempel>/control.dump <BACKUP_DIR>/<zeitstempel>/workspaces/*.dump; do
|
|
pg_restore --dbname="postgres://munin@<host>:5432/munin_neu" --no-owner "$f"
|
|
done
|
|
```
|
|
|
|
Dann `DATABASE_URL` auf `munin_neu` ändern und Munin neu starten. Auch hier
|
|
gelten danach zurückgezogene Keys und Workspace-Admins wieder.
|
|
|
|
### 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.
|
|
|
|
Mit PostgreSQL gilt dasselbe: Schema **und** `munin_control` müssten zurück.
|
|
Der bessere Weg: die ganze Sicherung in eine eigene Datenbank zurückspielen
|
|
(siehe oben), einen zweiten Munin mit `DATABASE_URL` darauf und anderem `PORT`
|
|
starten, dort den Workspace als ZIP herunterladen und im echten Server in
|
|
einen neuen Workspace importieren.
|
|
|
|
---
|
|
|
|
## 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.
|
|
|
|
Mit PostgreSQL ist eine beschädigte Datenbank Sache des Datenbank-Admins.
|
|
Betrifft es nur einen Workspace, lässt er sich wie oben einzeln zurückholen.
|
|
|
|
---
|
|
|
|
## 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, `/ready`, 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`. |
|
|
| wöchentlich | Ein Blick auf *System* → *Zustand*. Steht dort „Alles in Ordnung“, ist nichts zu tun. |
|
|
| monatlich | Marke *ungenutzt* bei Keys und Workspace-Admins (Übersicht unter *Workspaces*, Summe unter *System* → *Zustand*). Vergessene Zugänge zurückziehen. Keys mit *läuft bald ab*: noch gebraucht? Dann rechtzeitig ersetzen. Unter *System* → *Aktivität*: Projekte ganz ohne Aktivität mit dem Team klären. |
|
|
| vierteljährlich | **Probe der Sicherung:** `scripts/sicherung-pruefen.sh <BACKUP_DIR>/<zeitstempel>` (siehe unten). Endet es mit `PROBE BESTANDEN`, ist die Sicherung zurückspielbar. Eine Sicherung, die nie zurückgespielt wurde, ist nur eine Hoffnung. |
|
|
| vor jedem Update | Sicherung von Hand, siehe oben |
|
|
|
|
### Probe der Sicherung
|
|
|
|
Ein Befehl, für beide Datenbanken. Das Skript spielt die Sicherung zurück
|
|
(SQLite: in ein Arbeitsverzeichnis samt `PRAGMA integrity_check`; PostgreSQL:
|
|
in eine leere Probe-Datenbank), startet Munin darauf mit eigenem Port und
|
|
eigenen Geheimnissen, wartet auf `/ready`, vergleicht die Zahl der Workspaces
|
|
mit der Sicherung und räumt auf. Der echte Server wird nicht berührt.
|
|
|
|
```sh
|
|
# SQLite (braucht sqlite3 und Go, oder MUNIN_BIN=/pfad/zu/munin)
|
|
scripts/sicherung-pruefen.sh <BACKUP_DIR>/<zeitstempel>
|
|
|
|
# PostgreSQL: einmalig eine leere Probe-Datenbank anlegen …
|
|
psql "$DATABASE_URL" -c "CREATE DATABASE munin_probe OWNER munin TEMPLATE template0 LOCALE 'C' ENCODING 'UTF8';"
|
|
# … dann bei jeder Probe
|
|
DB_DRIVER=postgres PROBE_DATABASE_URL="postgres://munin@<host>:5432/munin_probe" \
|
|
scripts/sicherung-pruefen.sh <BACKUP_DIR>/<zeitstempel>
|
|
```
|
|
|
|
Ist die Probe-Datenbank nicht leer, bricht das Skript ab, statt etwas zu
|
|
überschreiben. Das schützt davor, versehentlich die echte Datenbank anzugeben.
|
|
Am 2026-10-16 ausprobiert: SQLite und PostgreSQL bestanden, eine absichtlich
|
|
beschädigte SQLite-Sicherung durchgefallen, die echte Datenbank als Probe
|
|
abgelehnt.
|
|
|
|
Von Hand, falls das Skript nicht zur Hand ist (SQLite, 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
|
|
```
|
|
|
|
Mit PostgreSQL, in eine eigene Probe-Datenbank auf demselben Server:
|
|
|
|
```sh
|
|
psql "$DATABASE_URL" -c "CREATE DATABASE munin_probe OWNER munin TEMPLATE template0 LOCALE 'C' ENCODING 'UTF8';"
|
|
probe_url="postgres://munin@<host>:5432/munin_probe"
|
|
for f in <BACKUP_DIR>/<zeitstempel>/control.dump <BACKUP_DIR>/<zeitstempel>/workspaces/*.dump; do
|
|
pg_restore --dbname="$probe_url" --no-owner "$f"
|
|
done
|
|
DB_DRIVER=postgres DATABASE_URL="$probe_url" PORT=18080 SESSION_SECRET=$(openssl rand -hex 32) \
|
|
ADMIN_TOKEN=probe-probe-probe-1 go run .
|
|
# danach aufräumen:
|
|
psql "$DATABASE_URL" -c "DROP DATABASE munin_probe;"
|
|
```
|
|
|
|
Die Probe darf mit eigenen Geheimnissen laufen. Sie braucht weder die echten
|
|
noch erreicht sie den echten Server.
|