Ka-Note/docs/feature-backup.md

180 lines
6.1 KiB
Markdown
Raw Permalink 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.

# Feature: Backup
Ka-Note hat zwei unabhängige Backup-Systeme:
1. **Scheduled Backup** — automatische serverseitige ZIP-Exporte
2. **AI Workspace Export** — lock-basierter Export/Import für KI-Agenten
---
## 1. Scheduled Backup
### Konfiguration (Umgebungsvariablen)
| Variable | Default | Beschreibung |
|---|---|---|
| `BACKUP_ENABLED` | `false` | Feature aktivieren |
| `BACKUP_DIR` | `/data/backups` | Speicherort |
| `BACKUP_RETENTION_COUNT` | `14` | Letzte N Backups behalten |
| `BACKUP_CRON_HOUR` | `3` | UTC-Stunde für automatischen Lauf |
### Ablauf
- Server startet Scheduler beim Hochfahren (`msUntilNextRun()`)
- Täglich zur konfigurierten UTC-Stunde: ZIP pro User → `backup-{userId}-{timestamp}.zip`
- Alte Backups werden automatisch gelöscht (Retention Count)
### API Endpoints
| Method | Path | Beschreibung |
|---|---|---|
| `GET` | `/api/backup/status` | Konfiguration & Schedule-Status |
| `GET` | `/api/backup/export` | On-demand Export (kein Lock) |
| `POST` | `/api/backup/run` | Geplanten Backup manuell auslösen |
| `GET` | `/api/backup/list` | Gespeicherte Backups auflisten |
| `GET` | `/api/backup/download/:filename` | Backup herunterladen |
| `DELETE` | `/api/backup/:filename` | Backup löschen |
| `PATCH` | `/api/backup/config` | Retention Count ändern |
### Client UI (`/backup`)
- "Jetzt herunterladen" → `GET /api/backup/export`
- Auto-Backup-Statusanzeige (nur wenn `BACKUP_ENABLED=true`): nächster Lauf, letzter Lauf, Retention-Editor
- "Jetzt ausführen" → `POST /api/backup/run`
- Tabelle gespeicherter Backups (neueste zuerst) mit Download/Löschen
### Relevante Dateien
- `ka-note/server/src/lib/backup-service.ts`
- `ka-note/server/src/routes/backup.ts`
- `ka-note/client/src/routes/backup/+page.svelte`
---
## 2. AI Workspace Export
Lock-basierter Export/Import für KI-Agenten. Verhindert Sync-Konflikte während KI-Edits.
### Workflow
```
download.ps1 → [KI bearbeitet work/] → upload.ps1
| |
Lock acquired Lock released
```
Bei Unterbrechung: `unlock.ps1` gibt Lock frei ohne Upload.
### Lock-Mechanismus
- Lock-Ablauf: `AI_LOCK_EXPIRY_HOURS` (Standard 24h, Projekt: `168` = 7 Tage)
- Solange Lock aktiv: normaler Sync pausiert
- Token im `manifest.json` des ZIPs → wird beim Upload validiert
### API Endpoints
| Method | Path | Beschreibung |
|---|---|---|
| `GET` | `/api/ai/status` | Lock-Status prüfen |
| `GET` | `/api/ai/download` | Export + Lock acquiren |
| `POST` | `/api/ai/upload` | Import + Lock freigeben |
| `POST` | `/api/ai/unlock` | Lock ohne Upload freigeben |
Upload-Parameter: `?force=true` überschreibt Version-Konflikte.
### ZIP-Struktur
```
manifest.json # userId, exportedAt, lockToken, expiresAt, exportVersion
contexts.json # alle Contexts (inkl. soft-deleted)
topics.json # alle Topics
ratings.json # alle Ratings
history/{id}.meta.json # History-Entry-Metadaten
history/{id}.md # History-Entry-Text
wiki/{id}.meta.json # Wiki-Seiten-Metadaten
wiki/{id}.md # Wiki-Seiten-Text
images/{id}.{ext} # Binäre Bildblobs
notebooks.json
page_notebooks.json
```
**manifest.json-Felder:** `userId, exportedAt, lockToken, expiresAt, exportVersion`
### Pflichtfelder pro Entity
**Context:** `id, name, type, sortOrder, meta, archivedAt, isFavorite, updatedAt, deletedAt, purgedAt, version`
**Topic:** `id, contextId, title, status, snoozeUntil, sortOrder, isNew, updatedAt, deletedAt, purgedAt, version`
**History (meta):** `id, topicId, date (YYYY-MM-DD), sortOrder, linkedContextId, doneAt, wiedervorlageDate, wiedervorlageResolvedAt, updatedAt, deletedAt, purgedAt, version`
→ Kein `contextId`, kein `title` — History ist topic-scoped!
**Rating:** `id, topicId, historyEntryId, personName, value (14), comment, updatedAt, deletedAt, purgedAt, version`
### Versions-Konfliktauflösung
| Bedingung | Aktion |
|---|---|
| Client-Version > Server-Version | Update |
| Client-Version = Server-Version | Skip (keine Änderung) |
| Client-Version < Server-Version | **Conflict** |
| Conflicts ohne `force=true` | 409-Response, keine Änderungen |
| Mit `-Force` (PS-Script) | Retry mit `?force=true` |
### PowerShell Scripts (`ka-note/scripts/`)
| Script | Beschreibung |
|---|---|
| `download.ps1` | `work/` leeren, ZIP holen, entpacken, Lock-Info anzeigen |
| `upload.ps1` | ZIP mit .NET ZipArchive erstellen (Forward-Slash-Fix!), hochladen |
| `unlock.ps1` | Lock ohne Upload freigeben |
| `show-bundle.ps1` | Bundle-Inhalt (Lock-Token, Ablauf, Counts) anzeigen |
| `get-token.ps1` | Azure AD Bearer-Token holen/cachen |
| `set-token.ps1` | Token aus Clipboard/Argument speichern |
**Wichtig:** `download.ps1` löscht `work/` beim Start Token nie dort ablegen!
**Windows-Pfad-Fix:** `upload.ps1` verwendet .NET `ZipArchive` statt `Compress-Archive`,
da `Compress-Archive` Backslash-Pfade erzeugt, der Server aber Forward-Slashes erwartet.
### Token-Workflow
```powershell
# Token aus Browser kopieren, dann:
.\set-token.ps1 # liest Clipboard → speichert nach ~\.ka-note\token.txt
.\get-token.ps1 # prüft JWT-Ablauf, gibt Token zurück
```
### Import-Helpers (`import-helpers.ps1`)
Dot-sourcen für eigene Import-Scripte:
```powershell
. .\import-helpers.ps1
Upsert-Context -Id "..." -Name "..." -Type "meeting"
Add-Topic -ContextId "..." -Title "..."
Add-HistoryEntry -TopicId "..." -Date "2026-02-24" -Body "..."
```
Vorhandene Import-Scripte: `import-jf-sysadmins.ps1`, `import-jf-landata.ps1`
### Relevante Dateien
- `ka-note/server/src/lib/ai-export-service.ts`
- `ka-note/server/src/lib/ai-agent-readme.ts`
- `ka-note/server/src/routes/ai-export.ts`
- `ka-note/client/src/lib/stores/aiLock.ts`
- `ka-note/scripts/download.ps1`
- `ka-note/scripts/upload.ps1`
- `ka-note/scripts/unlock.ps1`
---
## Gemeinsames ZIP-Format
Beide Systeme erzeugen strukturell identische ZIPs, unterscheiden sich nur im `manifest.json`:
- Scheduled Backup: `type: "backup"`
- AI Export: enthält `lockToken` + `expiresAt`
Soft-deleted Entities werden **immer exportiert** (mit gesetztem `deletedAt`).
Hard-delete existiert nicht.