#229: operations manual (install, update, backup/restore, deletion, roles)
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 7m1s
CI / Build container images (pull_request) Successful in 1m21s
CI / Auth e2e pack (pull_request) Successful in 8m47s
CI / Import/export fidelity gate (pull_request) Successful in 58s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 7m1s
CI / Build container images (pull_request) Successful in 1m21s
CI / Auth e2e pack (pull_request) Successful in 8m47s
CI / Import/export fidelity gate (pull_request) Successful in 58s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
docs/vs-nfd/70-betriebshandbuch.md: installation as run on the real stages (airgap variant explicitly pending #218-#221 with what already exists as groundwork), update/rollback incl. the no-down-migrations caveat, backup/restore with the ADR-0026 target allowlist and the rehearsed monthly restore drill (evidence: logs on #98), the full scheduler-job table (cadences verified against code), the deletion-and- destruction chapter built on the #228 copy list (per content type: what deletion reaches, what remains, immediate-destruction path, decommissioning), and role separation incl. the deliberate limits of a Site Admin and the honest note that Site Admin read-bypass makes the content/platform split non-absolute app-side. Every procedure carries its evidence level (erprobt / nicht geprobt / offen) — nothing claimed above what was actually executed. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f0c6af4412
commit
040f3fbeae
@ -189,8 +189,9 @@ nach sich zieht.
|
||||
Backup nur lokal · 3 AT · #227
|
||||
- [x] **Sicherheitsdokumentation**: Architektur, Datenflüsse, Netzplan,
|
||||
Ports/Dienste, Vertrauensgrenzen · 4 AT · #228 → `60-sicherheitsdokumentation.md`
|
||||
- [ ] **Betriebshandbuch**: Installation (inkl. Airgap), Update, Backup/Restore,
|
||||
Löschung und Vernichtung, Rollentrennung · 4–5 AT · #229
|
||||
- [x] **Betriebshandbuch**: Installation (inkl. Airgap), Update, Backup/Restore,
|
||||
Löschung und Vernichtung, Rollentrennung · 4–5 AT · #229 → `70-betriebshandbuch.md`
|
||||
(Airgap-Kapitel verweist auf offene #218–#221)
|
||||
- [ ] **Zuarbeit IT-Grundschutz** APP.3.1 und CON.11.1, je Anforderung
|
||||
„Produkt / Betreiber / nicht anwendbar" · 3–4 AT · #230
|
||||
- [ ] **Restrisikoliste** mit bewusst offenen Punkten · 1 AT · #231
|
||||
|
||||
210
docs/vs-nfd/70-betriebshandbuch.md
Normal file
210
docs/vs-nfd/70-betriebshandbuch.md
Normal file
@ -0,0 +1,210 @@
|
||||
# Betriebshandbuch (VS-NfD, Issue #229)
|
||||
|
||||
Zweck: der Betreiber führt die Instanz ohne uns — auch an dem Tag, an
|
||||
dem etwas ausfällt. Dieses Handbuch bündelt die betrieblichen
|
||||
Prozeduren und verweist für Schrittfolgen auf die maßgeblichen
|
||||
Runbooks im Repository, statt sie zu duplizieren. Das Kapitel mit dem
|
||||
größten VS-Gewicht ist **§5 Löschung und Vernichtung**; es baut auf der
|
||||
vollständigen Kopienliste der Sicherheitsdokumentation auf
|
||||
(`60-sicherheitsdokumentation.md` §5).
|
||||
|
||||
**Belegstufen.** Jede Prozedur nennt am Ende ihren Erprobungsstand:
|
||||
|
||||
- ✅ _erprobt_ — vom Autor bzw. automatisiert mindestens einmal real
|
||||
ausgeführt, mit Beleg.
|
||||
- ⚠️ _Mechanik vorhanden, nicht geprobt_ — implementiert und getestet,
|
||||
aber noch nie im Ernstfall/als Übung durchgespielt.
|
||||
- ⏳ _offen_ — kommt mit dem genannten Issue.
|
||||
|
||||
---
|
||||
|
||||
## 1 Installation
|
||||
|
||||
Referenz: `deploy/stages.md` (maßgebliche Schrittfolge),
|
||||
`docs/self-hosting/README.md`.
|
||||
|
||||
1. Host-Voraussetzungen: Docker + Compose, ein Reverse Proxy mit TLS
|
||||
(oder das mitgelieferte `caddy`-Compose-Profil).
|
||||
2. `deploy/compose/docker-compose.yml` + `.env` (Vorlage
|
||||
`.env.example`; **niemals** eine echte `.env` ins Repo — CI-Zaun
|
||||
#198) auf den Host bringen; `COMPOSE_PROJECT_NAME`, Ports
|
||||
(`WEB_PORT`/`API_PORT`/`COLLAB_PORT`), `POSTGRES_PASSWORD`, Secrets
|
||||
setzen.
|
||||
3. `docker compose pull && docker compose up -d` — die api wendet
|
||||
Migrationen beim Start selbst an (`MIGRATE_ON_START`); es gibt
|
||||
keinen separaten Migrationsschritt.
|
||||
4. Erststart: der Setup-Wizard (Issue #80) legt das Admin-Konto an und
|
||||
verriegelt sich danach dauerhaft; bis zum Abschluss ist nur
|
||||
`/setup/*` erreichbar.
|
||||
5. Reverse Proxy: `/` → web, `/api` → api, `/collab` → collab mit
|
||||
WebSocket-Upgrade (Portliste: Sicherheitsdokumentation §2).
|
||||
6. Für VS-NfD-Betrieb anschließend die Referenzkonfiguration aus dem
|
||||
Härtungsleitfaden (`50-haertungsleitfaden.md`) anwenden und mit
|
||||
dessen Checkliste verifizieren.
|
||||
|
||||
Belegstufe: ✅ erprobt — die Stages Test/Int/Prod auf ONE sind exakt
|
||||
nach dieser Prozedur aufgesetzt und laufen produktiv (`deploy/stages.md`
|
||||
dokumentiert die realen Instanzen).
|
||||
|
||||
**Airgap-/Offline-Variante:** ⏳ offen — Mirror-Verfahren (#218),
|
||||
netzloser Build (#219), Testlauf in isolierter Umgebung (#220),
|
||||
Offline-Update-Pfad (#221); Meilenstein M28. Bereits vorhanden als
|
||||
Grundlage: alle Dritt-Images digest-gepinnt (#203), ein authoritativer
|
||||
Node-Pin (#236), SBOMs je Release (#202).
|
||||
|
||||
## 2 Update und Rollback
|
||||
|
||||
Referenz: `docs/architecture/operations.md` §Update strategy,
|
||||
`deploy/stages.md`.
|
||||
|
||||
- **Stages:** Merge auf `main` → CD baut Images, deployt Test, führt
|
||||
Smoke-Tests aus, promotet Int. CD synct **keine** Compose-Dateien —
|
||||
Compose-Änderungen werden von Hand auf den Host übernommen, sonst
|
||||
räumt `--remove-orphans` manuell ergänzte Container ab.
|
||||
Belegstufe: ✅ erprobt (läuft bei jedem Merge; zuletzt CD-Lauf 568).
|
||||
- **Produktion:** ausschließlich getaggte Releases. `git tag vX.Y.Z`
|
||||
baut die Versions-Images und führt die Update-Simulation als Gate
|
||||
aus; `git tag prod-vX.Y.Z-initial` pinnt die Version in der
|
||||
Prod-`.env` und rollt aus (readyz-Poll). Migrationen laufen beim
|
||||
api-Start; **Rückwärtsmigrationen gibt es nicht** — Rollback setzt
|
||||
deshalb ein DB-kompatibles Vorgängerimage voraus (Migrations-Kaveat;
|
||||
im Zweifel Restore aus dem Backup-Set der Vorversion).
|
||||
Belegstufe: ✅ erprobt (alle Prod-Versionen bis v0.12.0 so
|
||||
ausgerollt).
|
||||
- **Rollback:** `git tag prod-v<prev>-rollback1` pinnt die Vorversion
|
||||
zurück. Belegstufe: ⚠️ Mechanik vorhanden, nicht geprobt — ein realer
|
||||
Rollback war bisher nie nötig; die Übung steht aus.
|
||||
- **Dritt-Image-Digest heben:** Prozedur `deploy/stages.md` §5a
|
||||
(imagetools inspect → Compose-Referenz ändern → CI bestätigt → Stage-
|
||||
Composes von Hand nachziehen → `docker inspect` verifiziert).
|
||||
Belegstufe: ✅ erprobt (Rollout #203 am 31.07.2026).
|
||||
- **Offline-Update:** ⏳ offen (#221, M28).
|
||||
|
||||
## 3 Backup und Restore
|
||||
|
||||
Referenz: `docs/operations/restore-runbook.md` (maßgeblich),
|
||||
`deploy/backup-basel.md`, ADR 0015/0026.
|
||||
|
||||
- Nächtlich erzeugt der backup-Sidecar ein konsistentes Restore-Set
|
||||
(`pg_dump -Fc` + Tar der Volumes `uploads`/`plugins`) auf dem
|
||||
`backups`-Volume; Retention `BACKUP_RETENTION_DAYS` (Default 30,
|
||||
Test/Int 7). Status in `status.json`, Fehler alarmieren per Mail
|
||||
direkt via SMTP (bewusst nicht über die api — sie könnte das kaputte
|
||||
Teil sein).
|
||||
- **Zielbeschränkung (#192, ADR 0026):** Fernziele (rsync-Spiegel,
|
||||
WebDAV) funktionieren nur gegen Hosts der **Deploy-Allowlist**
|
||||
`BACKUP_ALLOWED_TARGETS`; eine leere Allowlist schaltet alle
|
||||
Fernziele hart ab. Die Allowlist ist bewusst NICHT über die Admin-UI
|
||||
änderbar (Rollentrennung, §6).
|
||||
- **Restore:** `deploy/backup/restore.sh` fährt den Stack kontrolliert
|
||||
herunter, spielt Dump + Volumes zurück und startet neu; Details und
|
||||
Totalverlust-Szenario im Runbook.
|
||||
- **Geprobter Restore:** ✅ erprobt — ein automatischer monatlicher
|
||||
Restore-Drill (`drill-*`-Tag → `drill.yml`) stellt das jüngste
|
||||
Prod-Set in einer Wegwerf-Umgebung wieder her und prüft Inhalte; das
|
||||
Protokoll jedes Laufs steht als Kommentar an Issue #98.
|
||||
Backups sind unverschlüsselt by design — Datenträgerschutz ist
|
||||
Plattformsache (Abgrenzungserklärung).
|
||||
|
||||
## 4 Wartungsjobs (Scheduler)
|
||||
|
||||
Alle Jobs laufen in der api (in-app Scheduler, `jobs`-Tabelle), sind im
|
||||
Site-Admin-Systempanel sichtbar und dort manuell auslösbar (auditiert
|
||||
als `job.triggered`). Aktueller Bestand (9):
|
||||
|
||||
| Job | Rhythmus | Wirkung |
|
||||
| -------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `trash-purge` | täglich | endgültiges Löschen abgelaufener Papierkorb-Seiten **und** -Teiche (`trash.retentionDays`, #31/#193) |
|
||||
| `orphan-file-sweep` | täglich | verwaiste Uploads entfernen (#194); trägt nachts auch den SHA-256-Backfill (#199) |
|
||||
| `page-compaction` | stündlich | `page_updates`-Log in den Zustand mergen |
|
||||
| `version-thinning` | täglich | automatische Versions-Snapshots ausdünnen |
|
||||
| `audit-retention` | täglich | `audit_log` nach `audit.retentionDays` beschneiden (#196), Lücke selbst auditiert (`audit.pruned`) |
|
||||
| `conversion-payload-prune` | täglich | Import-/Export-Payloads fertiger Jobs nullen (#233) |
|
||||
| `mail-outbox-retention` | täglich | SENT/endgültig FAILED Outbox-Zeilen löschen (#234) |
|
||||
| `data-export-purge` | stündlich | abgelaufene DSGVO-Datenexporte entfernen (#68) |
|
||||
| `notification-digest` | alle 15 min (Versand nach Nutzer-Präferenz) | Benachrichtigungs-Digests versenden |
|
||||
|
||||
Der e2e-Zaun `apps/web/e2e/system.spec.ts` pinnt diese Zahl — ein
|
||||
neuer Job ohne Handbuch-/Zaun-Anpassung wird rot.
|
||||
|
||||
## 5 Löschung und Vernichtung
|
||||
|
||||
Grundsatz: „Gelöscht" heißt in Dorfteich erst dann gelöscht, wenn alle
|
||||
Kopien aus `60-sicherheitsdokumentation.md` §5 erreicht sind. Die
|
||||
Tabelle nennt je Inhaltstyp, was die Löschung tut, welche Kopien sie
|
||||
erreicht und was **stehen bleibt**.
|
||||
|
||||
| Vorgang | Wirkung | erreichte Kopien | Rückstände / Fristen |
|
||||
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Seite in den Papierkorb | Soft-Delete (`deleted_at`), aus Suchindex entfernt (#195) | Suchvektor sofort | Inhalt vollständig vorhanden, wiederherstellbar bis Purge |
|
||||
| Seiten-Purge (automatisch nach `trash.retentionDays`, Default 30, oder manuell) | löscht Zustand, Update-Log, Content-Cache, Versionen, Kommentare, Attachments (Bytes + Quota), Watches; Kinder rücken auf | DB-Zeilen + `uploads`-Bytes | `page_links.target_slug` bleibt bewusst (#235, Restrisiko I-24); Backups/Spiegel bis Ablauf ihrer Retention; Export-/Endgeräte-Kopien organisatorisch |
|
||||
| Teich-Purge (#193, automatisch/manuell, Site-Admin) | löscht alle Seiten samt Kaskade, Labels, Grants, Nutzungszähler, Plugin-Opt-ins, Conversion-Jobs, Files | wie oben, teichweit | wie oben |
|
||||
| Nutzer löschen / DSGVO | Konto löschen bzw. Autorschaft pseudonymisieren (`user.pseudonymized`) | Identitätsdaten | von ihm erstellte Inhalte gehören dem Teich |
|
||||
| Export-Payloads | `conversion-payload-prune` nullt `input`/`result` fertiger Jobs (Default 30 d) | DB | Job-Zeile bleibt für Status/Audit |
|
||||
| Mail-Kopien | `mail-outbox-retention` löscht SENT/endgültig FAILED (Default 30 d) | DB | zugestellte Mails beim Empfänger (Restrisiko I-23) |
|
||||
| Backup-Sets | Retention des Sidecars bzw. des Spiegels | `backups`-Volume, Spiegel | ein gelöschter Inhalt lebt maximal bis zum Ablauf der längsten Backup-Retention weiter — bei sofortigem Vernichtungsbedarf Sets manuell löschen (Host-Zugriff) und Spiegel bereinigen |
|
||||
| Audit-Trail | `audit-retention` (Default 365 d) | DB | Metadaten, nie Inhalt |
|
||||
|
||||
**Sofortige Vernichtung einzelner Inhalte** (über die Fristen hinaus):
|
||||
Seite manuell purgen (Papierkorb → endgültig löschen), danach auf dem
|
||||
Host die Backup-Sets der Aufbewahrungskette löschen bzw. den Spiegel
|
||||
bereinigen und Endgeräte-/Exportkopien organisatorisch einsammeln.
|
||||
Belegstufe Purge-Pfade: ✅ erprobt (laufen täglich produktiv; Purge-
|
||||
Semantik durch Tests gepinnt). Belegstufe „Backup-Kette manuell
|
||||
vernichten": ⚠️ nicht geprobt.
|
||||
|
||||
**Außerbetriebnahme einer Instanz:** `docker compose down -v` entfernt
|
||||
Container und **alle benannten Volumes** (`db-data`, `uploads`,
|
||||
`plugins`, `secrets`, `backups`, Caddy-Volumes); anschließend Spiegel-
|
||||
Bestände löschen und Host-Datenträger nach Betreiber-Vorgabe
|
||||
vernichten — die physische Vernichtung ist Plattformsache
|
||||
(Abgrenzungserklärung). Belegstufe: ✅ erprobt für den Stack-Teil (der
|
||||
stillgelegte Alt-VPS wurde so zurückgebaut; Datenträger dort noch als
|
||||
Rollback-Reserve vorhanden).
|
||||
|
||||
## 6 Rollentrennung
|
||||
|
||||
| Aufgabe | braucht |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
||||
| Instanz-Settings, Nutzer-/Quota-Verwaltung, Plugins, Wartungsjobs anstoßen, Teich-Purge, Audit-Panel | **Site-Admin** (App-Rolle) |
|
||||
| Deploy, `.env`/Compose ändern, Backup-Allowlist, Secrets-Volume, Restore, Backup-Sets vernichten, DB-Direktzugriff, Log-Collector | **Plattformzugriff** (Host) |
|
||||
| Release nach Prod | Plattformzugriff + Freigabeprozess (Tag) |
|
||||
|
||||
Was ein Site-Admin **nicht** kann (bewusste Grenzen):
|
||||
|
||||
- die Backup-Zielliste ändern oder Fernziele aktivieren
|
||||
(`BACKUP_ALLOWED_TARGETS` ist Deploy-Konfiguration, ADR 0026);
|
||||
- Secrets lesen oder setzen, die im Secret-Store/der `.env` liegen
|
||||
(die Settings-Tabelle trägt nie Secrets, security.md §Secrets);
|
||||
- Deploy-/Composeänderungen, Image-Versionen, Migrationen auslösen;
|
||||
- den Audit-Trail editieren (nur lesen; Retention läuft als Job und
|
||||
protokolliert sich selbst);
|
||||
- Backups herunterladen ist möglich (In-App-Restore-Pfad, #103), aber
|
||||
ihre Vernichtung auf Host/Spiegel nicht.
|
||||
|
||||
Zu beachten: ein Site-Admin **liest** per Rollen-Bypass jeden Inhalt
|
||||
der Instanz (permissions.md) — die Trennung „wer darf Inhalte sehen"
|
||||
von „wer betreibt die Plattform" ist damit App-seitig bewusst NICHT
|
||||
absolut; wo das nicht tragbar ist, ist es organisatorisch zu regeln
|
||||
(Vier-Augen-Prinzip bei Vergabe der Site-Admin-Rolle). Der Lesetrail
|
||||
für eingestufte Inhalte (M29, #222–#225) macht solche Zugriffe
|
||||
nachvollziehbar.
|
||||
|
||||
## 7 Monitoring und Störung
|
||||
|
||||
Referenz: `deploy/monitoring.md`, `docs/architecture/operations.md`
|
||||
§Health & monitoring.
|
||||
|
||||
- `GET /api/v1/readyz` prüft DB, Migrationen, Konverter, Renderer,
|
||||
Backup-Status; `healthz` je Container. Externe Überwachung der
|
||||
Referenzinstanzen: Uptime-Kuma mit Alarmierung.
|
||||
- Logs: alle Dienste loggen JSON auf stdout (Docker json-file mit
|
||||
Rotation); Audit-Ereignisse mit `msg`-Präfix `audit: ` und
|
||||
`severity`-Feld — Weiterleitung an SIEM/Syslog übernimmt der
|
||||
Collector des Betreibers (Katalog:
|
||||
`docs/architecture/audit-events.md`, v1.1, mit
|
||||
Kompatibilitätsversprechen).
|
||||
- Integritätsalarm: `file.integrity_failed` (critical) = Download-Hash
|
||||
≠ Upload-Hash → Objekt als manipuliert/korrupt behandeln, Datei aus
|
||||
Backup-Set wiederherstellen (Runbook), erneut laden; der
|
||||
Audit-Eintrag trägt beide Hashes.
|
||||
Loading…
Reference in New Issue
Block a user