Audit-Log
Protokolliert Änderungen — wer, wann, vorher/nachher.
Wer hat was geändert — und wie kommst du zurück? Drei Ebenen: Das Audit-Log protokolliert Änderungen, Snapshots frieren Datenstände ein, Backups sichern alles für den Ernstfall. Suchbegriffe: Änderungshistorie, Verlauf, Wiederherstellen, Snapshot, Datensicherung.
Audit-Log
Protokolliert Änderungen — wer, wann, vorher/nachher.
Snapshots
Frieren Datenstände im Ordner snapshotPath ein.
Backups
Voll-Export als ZIP für Umzug und Ernstfall.
Audit ist zweistufig: Es läuft nur, wenn Server und Projekt es erlauben.
audit: enabled: true| Ebene | Schalter | Standard |
|---|---|---|
| Server | audit.enabled, TTL audit.defaultTTL: 720h (30 Tage, "0" = ewig) |
Betreiber-Sache |
| Projekt | audit.enabled: true |
Starter: an |
| Sammlung | audit: { enabled, actions: [create, update, delete] } |
Standard-Aktionen: anlegen, ändern, löschen |
| Action | audit: { enabled, actions: [get, post] } |
Nur was du einschaltest |
PUT).GET /api/v1/audit (Recht audit.read nötig).Du musst nicht per API lesen: Der Admin zeigt den Verlauf pro Eintrag (letzte 50 Änderungen mit Vorher/Nachher-Vergleich und Wiederherstellen einzelner Felder) sowie global als Audit-Log. Details zur Bedienung — Verlaufspanel, Diff-Ansicht, Wiederherstellen-Dialog, Seitenleiste — stehen unter Eintrags-Editor.
Snapshots sind eingefrorene Datenstände im Ordner snapshotPath (../snapshots im Starter):
snapshotPath: ../snapshotssnapshots: - name: daily # 12:00 Uhr, redaktionelle Daten + Medien, 14 Stück schedule: "0 12 * * *" collections: [content, navigation, tags, medialib, ssr] includeMedia: true retention: { maxCount: 14 } - name: full-daily schedule: "0 3 * * *" # 03:00 Uhr, alles + Medien, 14 Stück includeMedia: true retention: { maxCount: 14 } - name: full-weekly # sonntags 05:00 Uhr, 12 Stück schedule: "0 5 * * 0" collections: [] # leer = alle Sammlungen includeMedia: true retention: { maxCount: 12 } - name: full-monthly # monatlich, Langzeitarchiv, 12 Stück schedule: "0 6 1 * *" collections: [] includeMedia: true retention: { maxCount: 12 }| Regel | Bedeutung |
|---|---|
collections: [] (leer) |
Alle Sammlungen sichern |
| Liste mit Namen | Nur diese sichern (Medien nur mit includeMedia: true, per Hardlink) |
retention.maxCount |
Älteste fliegen automatisch raus |
| Speicherung | json (BSON-Extended-JSON auf Platte, Standard); db/both sind Zukunft |
Alle Felder aus snapshot_config.go:11-58 — nur diese Namen verwenden:
| Feld | YAML-Name | Bedeutung |
|---|---|---|
| Name | name |
Eindeutige Profil-Kennung (Pflicht, sonst snapshot config: name is required) |
| Beschreibung | description |
Freitext, optional |
| Sammlungen | collections |
[] = alle; String-Kurzform "content" = include; Objektform {name, mode} (include oder exclude) — gemischt include + exclude ist ein Validierungsfehler |
| Medien | includeMedia |
Datei-Felder per Hardlink sichern (Standard: true) |
| Deduplizierung | collectionDedup |
SHA256-Dedup identischer Collection-Exporte über .store/collections/ per Hardlink — nur für große, selten geänderte Sammlungen |
| Modus | mode |
""/json = Platte (Standard, implementiert); db/both = MongoDB-$out-Kopie (Zukunft) — Medien immer Hardlink, egal welcher Modus |
| Aufbewahrung | retention: { maxCount, maxAge, maxSizeBytes } |
Auto-Prune-Regeln, alle optional |
| Zeitplan | schedule |
Cron-Ausdruck; leer = nur manuell per Trigger-API |
| Team-Rechte | permissions |
Liste reiner Berechtigungsnamen — Phase 2, aktuell gilt AllowSnapshots |
| An/Aus | enabled |
false = Scheduler überspringt das Profil; manueller Trigger geht trotzdem |
snapshots: - name: redaktionell description: "Täglich, ohne Systemdaten" collections: - content # Kurzform = include - name: ssr # Objektform mode: exclude includeMedia: true collectionDedup: false mode: json retention: { maxCount: 14, maxAge: "336h", maxSizeBytes: 10737418240 } schedule: "0 12 * * *" permissions: [] enabled: trueAlle Routen aus setup.go:436-495 (GET liest, POST löst aus, DELETE löscht):
| Methode | Pfad (hinter /api/v1/_/:project/_/admin) |
Zweck |
|---|---|---|
GET |
/snapshots/configs |
Alle Profile aus der Projekt-YAML lesen (AdminSnapshotConfigs) |
POST |
/snapshots/configs/:name/trigger |
Profil manuell auslösen — geht auch bei enabled: false |
GET |
/snapshots |
Alle Einträge auflisten (Scannt das entries-Verzeichnis) |
GET |
/snapshots/download/:file |
Datei-Download (vor :id registriert, damit download kein :id matcht) |
GET |
/snapshots/:id |
Einzelnen Eintrag ansehen |
DELETE |
/snapshots/:id |
Eintrag löschen |
POST |
/snapshots/:id/restore?merge=1&collections=a,b |
Zurückspielen (merge=1 = zusammenführen; collections = Auswahl) |
GET |
/snapshots/:id/download |
Eintrag als ZIP laden |
GET |
/snapshots/stats |
Belegung prüfen |
POST |
/snapshots/prune |
Retention-Aufräumen manuell anstoßen |
Im tibi-admin (/projects/:id/snapshots, drei Tabs):
| Tab | Was du tust |
|---|---|
| Configs | Profile ansehen |
| Entries | Auslösen (mit Fortschritt), herunterladen (ZIP), zurückspielen, löschen, aufräumen |
| Stats | Belegung prüfen |
Zum Zurückspielen braucht dein Token allowSnapshots: true.
Übe das Zurückspielen nur mit Testdaten — es überschreibt den Live-Stand.
Alle Routen aus setup.go:398-432:
| Methode | Pfad (hinter /api/v1/_/:project/_/admin) |
Zweck |
|---|---|---|
GET |
/backup/analyze-download?collections=a,b |
Schätzung (Dokument-/Medien-Zahlen) ohne Voll-Export — vor dem Download prüfen |
GET |
/backup |
Voll-ZIP erzeugen und laden (?collections= schränkt ein, ?sse=1 streamt) |
GET |
/backup/download/:file |
Fertiges Temp-ZIP aus dem SSE-Modus laden (One-Time-Use: zweiter Request = 404) |
POST |
/backup?sse=1 |
ZIP hochladen und analysieren → antwortet mit restoreToken |
POST |
/backup/:restoreToken?collections=…&merge=true&sse=1 |
Restore aus dem analysierten Temp-Verzeichnis ausführen |
DELETE |
/backup/:restoreToken |
Temp-Daten zum Token manuell aufräumen |
POST /backup erzeugt Token plus Temp-Verzeichnis (admin_backup.go:204-234), POST /backup/:restoreToken verbraucht es, DELETE räumt auf.
POST /backup hochladen — Antwort enthält restoreToken, sourceNamespace, createdAt, expiresAt (admin_backup.go:378-384). Mit ?sse=1 kommt der Fortschritt als Stream (init → done, jeweils mit restoreToken).collections-Analyse in der Antwort lesen (Quelle gegen Ziel abgleichen, ggf. ?collections= für Teil-Restore vormerken).POST /backup/:restoreToken ausführen — bei Erfolg landet restoredCollections/totalDocuments/mediaFiles in der Antwort, alles wird als Audit-Action backup/restore protokolliert. Danach ist das Token verbraucht (Eintrag wird vor dem Restore aus dem Store entfernt).sse, löscht der Server das Temp-Verzeichnis selbst (os.RemoveAll). Bei Abbruch oder abgelaufenem Token per DELETE /backup/:restoreToken aufräumen — Antwort 404 restore token not found or already cleaned up heißt: schon weg. Abgelaufene Tokens räumt zusätzlich ein Hintergrund-Job weg.| Ebene | Schalter | Beleg |
|---|---|---|
| Server-Token | Scope project.backup / project.snapshot (scope.go:53-55) |
X-Admin-Token oder Authorization: Bearer |
| Projekt-Token | allowBackup / allowSnapshots in der Projekt-Config (project.go:118-120) |
Nur mit passendem Flag |
| JWT-Login | Rolle AdminRole plus Scope-Check (EnforceScope) |
Persönliche Tokens bleiben durch ihre Scopes begrenzt |
Der Check läuft in admin_auth.go:20-74 immer in dieser Reihenfolge:
config.GetAdminTokenInfo).AllowBackup / AllowSnapshots per ConstantTimeCompare).403 permission denied.Backups sind der Voll-Export für Umzug und Desaster-Fall — im Admin unter /projects/:id/backup:
backup/analyze-download), dann neu laden.| Unterschied | Snapshot | Backup |
|---|---|---|
| Wofür | Schneller Rücksprung im Alltag | Umzug, Desaster, Weitergabe |
| Wo | snapshotPath auf dem Server |
ZIP-Datei bei dir |
| Recht | allowSnapshots |
Projekt-Admin plus AllowBackup |
| Fortschritt | Ja (Stream) | Ja (SSE-Stream) |
| Fehlerbild | Ursache | Lösung |
|---|---|---|
| Audit bleibt leer | audit.enabled im Projekt fehlt oder Server-Flag aus |
Beide Schalter prüfen, Änderung erneut auslösen |
| Alte Einträge verschwinden | TTL (720h) abgelaufen |
Wichtige Stände als Snapshot sichern, TTL ggf. auf "0" |
| Snapshot-Button fehlt | Token ohne allowSnapshots |
Flag allowSnapshots am Projekt-Token setzen (siehe Rechte und Auth-Reihenfolge) |
| Backup-Button fehlt | Kein Projekt-Admin oder AllowBackup fehlt |
Projekt-Admin-Rolle bzw. Flag AllowBackup am Projekt-Token prüfen (siehe Rechte und Auth-Reihenfolge) |
400 missing restoreToken |
Restore ohne Token aufgerufen | Erst POST /backup (Analyse), Token aus der Antwort nehmen |
404 restore token not found or expired |
Token verbraucht, aufgeräumt oder TTL abgelaufen | Analyse erneut hochladen, neues Token holen |
403 invalid entry id / 404 snapshot not found |
Snapshot-Restore mit falscher/gelöschter ID | ID aus GET /snapshots neu lesen |
snapshot config: name is required |
Profil ohne name |
name in der YAML ergänzen |
| Restore zeigt keine Wirkung | Cache alt | Reload plus ?clear=1 (siehe Veröffentlichen) |
Wie ein Redakteur eine Sicherung einspielt: Backup einspielen.
Verifiziert am e3f92eb 2026-10-17 (tibi-svelte-starter, config.yml audit- und snapshots-Blöcke), am 68b4278 2026-10-17 (tibi-server, audit-, backup- und snapshot-docs), am 2b091d8 2026-10-17 (tibi-admin-nova, test-pläne 43 und 44, AuditView, AuditHistoryPanel, AuditDiff, AuditRestoreDialog, AuditSidebarHistory)