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>
16 KiB
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-KontrakteS3_ENDPOINT / S3_ACCESS_KEY / S3_SECRET_KEY / S3_BUCKET / S3_REGION. - Die Storage-Abstraktionen
src/server/storage/adapter.ts(Uploads) undsrc/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ßerCreateBucketsind 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.tsschluckt einenCreateBucket-Fehler bereits (Bucket gilt als „vorhanden angenommen"),adapter.tswirft dagegen bei nicht-„already exists"-Fehlern. Nach Vorab-Provisionierung greift ohnehin nur derHeadBucket-Erfolgspfad — trotzdem sollensureBucket()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 alsS3_ACCESS_KEY/S3_SECRET_KEYin die App gehen. - Backup der Garage-Metadaten:
garage_metaenthält Bucket-/Key-/Layout-Definitionen — muss in die Host-Backup-Strategie (analogpgdata). 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.ymlergänzen (Image pinnen, Ports, 2 Volumes,security_opt/cap_dropanalog bestehender Services, Healthcheck auf Admin-API/health). garage.tomlals 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, analogmigrate): idempotent- Layout anwenden (falls noch nicht),
- Bucket(s) anlegen (
isms-documents, ggf. Backup-Bucket), - Access-Key erzeugen oder vorhandenen importieren,
- 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:HeadBucketzur 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.exampleaktualisieren (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/buildgrü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)
- Im Compose
minio-Service durchgarage+garage-provision(Init-Job) ersetzen; Volumesgarage_meta/garage_dataanlegen. - 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. - Deploy: Garage startet, Layout „ready",
garage-provisionlegt Bucket(s) + Key + Rechte an. - Optional DB-Reset (wie in bisherigen Deploys), falls alte Objekt-Referenzen im Datenbestand stören — bei frischer/geseedeter Test-Instanz meist unnötig.
- Smoke-Tests (§9) aktiv durchführen.
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 altenminio-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-*.tsgrün gegen Garage;tsc/lint/buildgrü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.)