Files
craftvia/docs/KONZEPT-garage-migration.md
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

218 lines
16 KiB
Markdown
Raw 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.
# KONZEPT: Objektspeicher-Migration MinIO → Garage
**Stand:** 2026-08-20 · **Zielgruppe:** mehrköpfiges Entwicklerteam + PM · **Status:** Entwurf zur Abnahme
> **Randbedingung (2026-08-20):** Es existieren **nur Test-Instanzen** — **keine produktiven Daten**. Daher **keine Datenmigration** (kein rclone-Sync), sondern ein **kompletter Neu-Deploy** mit frischer Garage. Das eliminiert die frühere Migrations-Lane und das Wartungsfenster; Cutover = MinIO-Service durch Garage ersetzen, provisionieren, neu deployen (optional DB-Reset wie gehabt).
---
## 1. Ausgangslage & Motivation
certvia nutzt aktuell **MinIO** als S3-kompatiblen Objektspeicher (Dokument-Uploads + Backup-Artefakte). Anlass für die Prüfung: der Hinweis, MinIO werde „nicht mehr weiterentwickelt".
**Faktenlage (verifiziert 2026-08-20):**
- **Mai 2025** — MinIO entfernt die Admin-Konsole/Verwaltungs-GUI aus der Community Edition (nur noch rudimentärer Object-Browser; Bucket-/User-/Policy-Verwaltung nur noch im kommerziellen AIStor).
- **Okt 2025** — MinIO publiziert keine Container-Images mehr auf Docker Hub/Quay (auch nicht für einen kritischen CVE-Fix).
- **Dez 2025** — Community-Repo im **Maintenance-Mode**: keine neuen Features, keine PRs, Security-Fixes nur „case-by-case"; Repo als **„no longer maintained"** markiert. Fokus liegt auf **AIStor** (Abo-Produkt). Der Code bleibt AGPLv3 verfügbar.
**Bewertung:** MinIO ist nicht „von heute auf morgen tot", aber die **Community Edition ist faktisch im End-of-Life-/Wartungsmodus**. Für ein selbstgehostetes ISMS-/Compliance-Produkt (das genau Betriebs-, Wartungs- und Supply-Chain-Sicherheit demonstrieren soll) ist der proaktive Wechsel auf einen aktiv gepflegten Store gerechtfertigt und gut begründbar (u. a. relevant für die eigene Lieferanten-/Komponentenbewertung).
**Warum Garage:** aktiv entwickelt (Deuxfleurs, AGPLv3, Rust), bewusst **minimalistisch & leichtgewichtig**, S3-kompatibel, für Selbsthosting/kleine bis mittlere Deployments und geo-verteilte Replikation ausgelegt. Passt zum internen Coolify-Testserver **und** zum Contabo-Prod-VPS.
---
## 2. Zielbild
Ein **API-kompatibler Austausch** des Storage-Backends: Der Anwendungscode spricht weiterhin S3 (AWS SDK v3, `forcePathStyle`), lediglich der Container-Dienst, die Provisionierung von Bucket/Key und die Daten werden migriert. **Keine Änderung an Fachlogik, UI oder Datenmodell.**
Was gleich bleibt:
- S3-Protokoll, AWS SDK v3, `forcePathStyle: true`, die Env-Kontrakte `S3_ENDPOINT / S3_ACCESS_KEY / S3_SECRET_KEY / S3_BUCKET / S3_REGION`.
- Die Storage-Abstraktionen `src/server/storage/adapter.ts` (Uploads) und `src/server/storage/backup-store.ts` (Backups) inkl. Local-/Stub-Fallback.
- Key-Schema (`<tenantId>/uploads/<uuid>-<name>`), Mandanten-Isolation über Key-Präfix.
---
## 3. Ist-Analyse certvia (Code-Stand dev)
Zwei S3-Konsumenten, beide identischer S3-Dialekt:
| Konsument | Datei | Zweck | Ops |
|-----------|-------|-------|-----|
| Upload-Storage | `src/server/storage/adapter.ts` | hochgeladene Richtlinien + Audit-Nachweise | Put/Get, HeadBucket→CreateBucket |
| Backup-Store | `src/server/storage/backup-store.ts` | verschlüsselte Backup-Artefakte (`.cvb`), DSGVO-ZIP | Put/Get/List/Delete, HeadBucket→CreateBucket |
Gemeinsam:
- `new S3Client({ endpoint, region, forcePathStyle: true, credentials })` — **path-style**, exakt was Garage erwartet.
- Verwendete S3-Operationen: `PutObject`, `GetObject`, `HeadBucket`, `CreateBucket`, `ListObjectsV2`, `DeleteObjects`. **Alle außer `CreateBucket` sind Garage-Standard.**
- Region-Default `us-east-1`.
- Backend-Auswahl rein über Env → sauberer Graceful-Fallback (Stub/Local), kein Hardcoding auf MinIO.
**Zu provisionierende Buckets (Neu-Deploy, keine Altbestände zu übernehmen):**
- `S3_BUCKET` = `isms-documents` (Uploads).
- Backup-Bucket: nur falls der Backup-Store auf S3 statt lokal (`BACKUP_LOCAL_DIR`) betrieben wird — dann eigenen Bucket provisionieren.
### Der einzige echte Reibungspunkt: Bucket-/Key-Anlage
Garage verwaltet **Buckets und Access-Keys sowie deren Rechte** über die **Garage-Admin-API / `garage`-CLI**, **nicht** über die S3-Operation `CreateBucket`. Konsequenz:
- `HeadBucket`, `PutObject`, `GetObject`, `ListObjectsV2`, `DeleteObjects` → funktionieren gegen Garage unverändert.
- `CreateBucket` (unsere Selbstheilung „Bucket fehlt → anlegen") → **wird von Garage über die S3-API nicht bedient**. Buckets/Keys müssen **vorab out-of-band** provisioniert werden.
> Hinweis: `backup-store.ts` **schluckt** einen `CreateBucket`-Fehler bereits (Bucket gilt als „vorhanden angenommen"), `adapter.ts` **wirft** dagegen bei nicht-„already exists"-Fehlern. Nach Vorab-Provisionierung greift ohnehin nur der `HeadBucket`-Erfolgspfad — trotzdem soll `ensureBucket()` explizit Garage-tauglich gemacht werden (siehe Lane C), damit wir uns nicht auf verschlucktes Fehlerverhalten verlassen.
---
## 4. Entscheidungen (D) — vom Team/PO zu bestätigen
| # | Entscheidung | Empfehlung | Begründung |
|---|--------------|-----------|------------|
| **D1** | Zielspeicher | **Garage, self-hosted — ENTSCHIEDEN 2026-08-20** | Aktiv gepflegt, S3-kompatibel, leichtgewichtig, DSGVO/Datenresidenz in eigener Hand. Alternativen (SeaweedFS, Ceph/RGW, extern R2/Hetzner) in §12 abgewogen. |
| **D2** | Region-Handhabung | Garage-`s3_region` = **`us-east-1`** setzen | App-Env bleibt unverändert (Default `us-east-1`) → keine Code-/Config-Drift. |
| **D3** | `ensureBucket()` | **Vorab-Provisionierung** + `ensureBucket` prüft nur (HeadBucket), kein S3-CreateBucket | Deterministisch; klare Fehlermeldung „Bucket nicht provisioniert" statt stiller Selbstheilung. |
| **D4** | Cutover-Strategie | **Neu-Deploy, kein Wartungsfenster** (MinIO→Garage im Compose ersetzen, provisionieren, deployen; optional DB-Reset) | **Keine produktiven Daten** → keine rclone-Migration nötig. |
| **D5** | Topologie | **Single-Node** Garage pro Environment | Passt zur aktuellen 1-Host-Topologie; Multi-Node/Replikation als spätere Ausbaustufe dokumentieren. |
| **D6** | Deployment | Garage als **Compose-Service** in `docker-compose.coolify.yml` (ersetzt `minio`) | Gleiches Betriebsmodell wie bisher (Coolify/Traefik). |
| **D7** | Reihenfolge | Aktuell nur **Test-Instanz(en)** — dort umsetzen; Prod später nach gleichem Muster | Es gibt derzeit keine Prod-Instanz; Runbook bleibt für spätere Prod gültig. |
Strategische Vorfrage (self-hosted vs. externer managed S3): **entschieden am 2026-08-20 zugunsten self-hosted Garage** — Datenresidenz + Betriebshoheit für ein DSGVO-/ISMS-Produkt. Externe Optionen (Hetzner OS/Cloudflare R2) bleiben nur als dokumentierte Alternative in §12.
---
## 5. Zielarchitektur
```
┌───────────────────────── Coolify-Stack (pro Environment) ─────────────────────────┐
app ──S3──► │ garage (Container) │
worker ─S3► │ ├─ garage.toml (rpc_secret, s3_api.s3_region=us-east-1, api_bind_addr :3900, │
backup- ─S3► │ │ admin_token, metadata_dir, data_dir) │
worker │ ├─ Volume: garage_meta → /var/lib/garage/meta (KRITISCH: Metadaten/Layout) │
│ └─ Volume: garage_data → /var/lib/garage/data (Objekt-Bytes) │
│ garage-provision (Init-Job, restart:no): Layout + Bucket + Key + Rechte via CLI │
└────────────────────────────────────────────────────────────────────────────────────┘
```
- **Endpoint:** `S3_ENDPOINT=http://garage:3900` (S3-API-Port; Standard 3900). Admin-API auf 3903 (nur intern).
- **Zwei Ports beachten:** 3900 = S3, 3902 = Web (optional), 3903 = Admin. Nur S3 wird von der App genutzt; Admin bleibt clusterintern.
- **Secrets (pro Environment via Coolify-Env, echte Werte NIE im Chat/Repo):** `GARAGE_RPC_SECRET` (32-byte hex), `GARAGE_ADMIN_TOKEN`, plus der erzeugte Access-Key/Secret, die als `S3_ACCESS_KEY/S3_SECRET_KEY` in die App gehen.
- **Backup der Garage-Metadaten:** `garage_meta` enthält Bucket-/Key-/Layout-Definitionen — muss in die Host-Backup-Strategie (analog `pgdata`). Ohne Meta sind die Daten nicht adressierbar.
---
## 6. Workstreams / Lanes für das Team
Vier parallelisierbare Lanes + PM-Koordination (die frühere rclone-Migrations-Lane entfällt, da Neu-Deploy). Abhängigkeiten in Klammern.
### Lane A — Infra & Deployment (Owner: DevOps)
- Garage-Service in `docker-compose.coolify.yml` ergänzen (Image pinnen, Ports, 2 Volumes, `security_opt`/`cap_drop` analog bestehender Services, Healthcheck auf Admin-API `/health`).
- `garage.toml` als Config (über Env/Coolify-Mount): `rpc_secret`, `s3_region=us-east-1`, `metadata_dir`, `data_dir`, `admin_token`.
- Single-Node-Layout initial (Node einer Zone mit Kapazität zuweisen — ohne Layout kein Schreibzugriff).
- `minio`-Service erst **nach** abgenommenem Garage-Betrieb entfernen (bis dahin als Rollback-Sicherheitsnetz stehen lassen).
- Garage-`meta`-Volume in Host-Backup aufnehmen.
- **Liefergegenstand:** lauffähiger Garage-Container auf der Test-Instanz, Admin-API erreichbar, Layout „ready".
### Lane B — Provisioning-Automatisierung (Owner: DevOps/Backend) — *(braucht A)*
- Init-Job `garage-provision` (restart:no, analog `migrate`): idempotent
1. Layout anwenden (falls noch nicht),
2. Bucket(s) anlegen (`isms-documents`, ggf. Backup-Bucket),
3. Access-Key erzeugen **oder** vorhandenen importieren,
4. Key→Bucket-Rechte (read/write/owner) setzen.
- Umsetzung über `garage`-CLI **oder** Admin-API (HTTP) — Entscheidung dokumentieren; idempotent (mehrfach ausführbar ohne Fehler).
- Ausgabe des Access-Key/Secret **nur** in Coolify-Secrets, nicht in Logs.
- **Liefergegenstand:** ein Skript/Job, der aus „leerer Garage" reproduzierbar den betriebsbereiten Zustand herstellt (dokumentiert in `docs/DEPLOY-*`).
### Lane C — App-Code-Anpassung (Owner: Backend) — *(unabhängig, klein)*
- `ensureBucket()` in **beiden** Stores Garage-tauglich: `HeadBucket` zur Verifikation; bei „missing" **kein** S3-`CreateBucket`, sondern klarer Konfigurationsfehler „Bucket nicht provisioniert — Provisioning-Job ausführen". (Provisionierung liegt bei Lane B.)
- Optional: gemeinsame S3-Client-Factory extrahieren (DRY über `adapter.ts`/`backup-store.ts`) — nur wenn ohne Risiko.
- Region/Endpoint-Doku in `.env.coolify.example` + `.env.example` aktualisieren (MinIO-Kommentare → Garage; `forcePathStyle`-Begründung bleibt gültig).
- **Tests:** bestehende `scripts/test-backup-*.ts` + Upload/Download-Pfad gegen einen lokalen Garage-Container grün; neuer Smoke-Test „Bucket fehlt → sprechender Fehler".
- **Liefergegenstand:** PR mit Code + aktualisierten Tests, `tsc/lint/build` grün.
### Lane D — Test, Cutover & Abnahme (Owner: QA/DevOps + PM) — *(integriert A–C)*
- End-to-End-Durchstich auf der **Test-Instanz** (Neu-Deploy): Upload, Download, Backup-Export (`.cvb`), DSGVO-ZIP, Restore, Mandanten-Isolation.
- **Cutover-Runbook** (siehe §7) an der Test-Instanz **einmal durchspielen** (MinIO raus, Garage rein, provisionieren, deployen, ggf. DB-Reset).
- **Rollback-Runbook** (siehe §8).
- Abnahmekriterien (§9) abhaken.
- **Liefergegenstand:** abgenommene Test-Instanz auf Garage + freigegebenes Runbook (auch für spätere Prod).
**PM:** Reihenfolge/Abhängigkeiten (A→B, C parallel, D integriert), Abnahme je Lane, Freigabe des Runbooks für spätere Prod (D7).
---
## 7. Cutover-Runbook (Neu-Deploy, kein Wartungsfenster nötig)
1. Im Compose `minio`-Service durch `garage` + `garage-provision` (Init-Job) ersetzen; Volumes `garage_meta`/`garage_data` anlegen.
2. Coolify-Env setzen: `S3_ENDPOINT=http://garage:3900`, `S3_REGION=us-east-1`, `GARAGE_RPC_SECRET`, `GARAGE_ADMIN_TOKEN` (literal, echte Werte NICHT aus dem Chat). `S3_ACCESS_KEY/S3_SECRET_KEY` = der beim Provisioning erzeugte Garage-Key.
3. Deploy: Garage startet, Layout „ready", `garage-provision` legt Bucket(s) + Key + Rechte an.
4. Optional **DB-Reset** (wie in bisherigen Deploys), falls alte Objekt-Referenzen im Datenbestand stören — bei frischer/geseedeter Test-Instanz meist unnötig.
5. **Smoke-Tests** (§9) aktiv durchführen.
6. `minio`-Service + Volumes entfernen; Garage-`meta`-Volume in Host-Backup bestätigen.
## 8. Rollback
- Da es **keine produktiven Daten** gibt, ist Rollback unkritisch: Compose zurück auf `minio` (oder frischer Re-Deploy). Optional den alten `minio`-Service während der ersten Testphase noch nicht löschen (Schritt 6 verzögern), bis Garage abgenommen ist.
---
## 9. Validierung / Abnahmekriterien
Gegen Garage müssen grün sein:
- **Upload:** Richtlinie hochladen → Objekt in Garage, `<tenantId>/uploads/...`-Präfix korrekt.
- **Download:** Datei abrufen (`/files/[...key]`), Content-Disposition/Filename-Metadatum stimmt.
- **Backup-Export:** `.cvb`-Artefakt erzeugt (CVB1-Header), Persistenz + Download.
- **DSGVO-ZIP:** erzeugt und lesbar.
- **Restore:** Backup → Wiederherstellung, Datenintegrität, **Mandanten-Isolation** gewahrt.
- **List/Delete:** Backup-Historie listet, Aufräumen entfernt Prefix.
- **Fehlerfall:** fehlender Bucket → sprechender Konfigfehler (kein stiller CreateBucket-Versuch).
- **Automatisiert:** `scripts/test-backup-*.ts` grün gegen Garage; `tsc/lint/build` grün.
---
## 10. Risiken & Gegenmaßnahmen
| Risiko | Gegenmaßnahme |
|--------|---------------|
| `CreateBucket` (S3) gegen Garage nicht verfügbar | Vorab-Provisionierung (Lane B) + `ensureBucket` nur prüfend (Lane C). |
| Garage-`meta`-Volume nicht gesichert → Buckets/Keys „weg" | Meta-Volume in Host-Backup; im Runbook explizit verifiziert. |
| Region-/Endpoint-Mismatch (Coolify-Env vs. `garage.toml`) | D2 fixiert `us-east-1` beidseitig; in Abnahme geprüft. |
| Access-Key/Secret landet in Logs | Provisioning schreibt nur in Coolify-Secrets; Log-Redaction. |
| Layout nicht angewendet → Schreibfehler „no capacity" | Provisioning-Job setzt Layout idempotent; Healthcheck. |
| Coolify-Interpolations-/„managed"-Fallen (bekannt) | Neue Env (`GARAGE_*`) literal setzen, nicht über `${…}` referenzieren; im Container-Env verifizieren. |
| Presigned URLs / spezielle S3-Features | certvia nutzt aktuell keine Presigned-URLs (Downloads laufen serverseitig) — vor Ausbau prüfen. |
---
## 11. Grobe Aufwandsschätzung
| Lane | Aufwand (Personentage, grob) |
|------|------------------------------|
| A Infra/Deployment | 2–3 |
| B Provisioning | 2–3 |
| C App-Code | 1–2 |
| D Test/Cutover/Abnahme | 1–2 |
| PM/Koordination | durchgehend |
| **Summe** | **~6–10 PT** (ohne Datenmigration), gut parallelisierbar |
---
## 12. Alternativen (zur Vollständigkeit für D1)
| Option | Pro | Contra |
|--------|-----|--------|
| **Garage** (empfohlen) | aktiv, leicht, S3, self-hosted, DSGVO in eigener Hand | Bucket/Key via Admin-API (einmaliger Provisioning-Aufwand); kein Erasure-Coding (Replikation) |
| SeaweedFS | performant, S3-Gateway, aktiv | größerer Funktionsumfang/komplexer als nötig |
| Ceph/RGW | Enterprise-Standard, sehr robust | schwergewichtig, hoher Betriebsaufwand — für 1-Host überdimensioniert |
| Externer managed S3 (Hetzner OS / Cloudflare R2) | kein Storage-Ops, hohe Verfügbarkeit | Datenresidenz/Abhängigkeit extern; Kosten; DSGVO-AV nötig |
---
## 13. Quellen (MinIO-Status / Garage-Provisioning)
- MinIO users complain after admin UI removed from Community Edition — blocksandfiles.com (2025-06)
- MinIO Faces Fallout for Stripping Functions from Open Source Version — futuriom.com (2025-06)
- MinIO Ends Community Development, Positions AIStor as the Future — faun.dev / devopslinks
- MinIO in Maintenance Mode: Open Source Alternatives — bizety.com (2025-12)
- minio/minio Discussion #21326 „It's not a feature issue, it's a trust one" — github.com
- Garage — Administration API / Features / Bucket & Key Operations — garagehq.deuxfleurs.fr, deepwiki.com
*(Quellen-Status zeitkritisch; vor Projektstart kurz gegenprüfen.)*