Files
craftvia/docs/_certvia-archiv/KONZEPT-garage-migration.md
T
msolarczekandClaude Opus 5 cadaedc6cc L10b Betrieb & Aufräumen: Deploy – craftvia-worker, CI-Testjob, DEPLOY.md, Certvia-Doku archiviert
- docker-compose.coolify(.prebuilt).yml: Service craftvia-worker (Target worker, Chromium,
  shm_size 1gb, gleiche Härtung), Craftvia-Variablen für app und worker.
- Dockerfile: worker-Stage mit HOME=/home/app (Chromium-Profil für non-root); lokaler
  docker build der Targets runner und worker erfolgreich, PDF-Erzeugung im Image geprüft.
- .env.example/.env.prod.example/.env.coolify.example: alle Craftvia-Variablen inkl. RLS,
  KI-Provider, PDF_CHROMIUM_PATH, OFFLINE_MAX_DAYS, API_RATE_LIMIT_*, AI_GENERATION_RETENTION_DAYS,
  AI_MONTHLY_TOKEN_LIMIT.
- CI (.github, .gitea): Job gate mit Postgres (pgvector) und Redis als Service: migrate deploy,
  seed, Passwort für craftvia_app, tsc, lint, build, npm run test.
- docs/craftvia/DEPLOY.md (aus den Certvia-Deploy-Docs abgeleitet): Architektur, Domains, Secrets,
  Worker, Migrationen, RLS-Aktivierung, Backup/Restore, KI, Rate Limits, Aufbewahrung, Smoke,
  Update/Rollback. build-and-push-images.sh baut craftvia-worker.
- Certvia-/ISMS-Dokumente aus docs/ nach docs/_certvia-archiv/ (mit README); Verweise in README.md
  und Skript-Kommentaren angepasst.

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

16 KiB
Raw Blame History

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.)