Objektspeicher (S3/MinIO)#
Vorgangsanhänge und Benutzer-Avatare werden nicht in MongoDB gespeichert — sie leben in S3-kompatiblem Objektspeicher. Der Standard-Docker-Stack liefert MinIO aus, aber der Server kommuniziert mit dem Speicher über die Standard-S3-API, sodass du ihn ohne Codeänderung auf AWS S3 oder jeden anderen S3-kompatiblen Anbieter richten kannst. Diese Seite behandelt die Standardeinrichtung, wie Downloads sicher bleiben und wie du einen externen Bucket einbindest.
MinIO im Standard-Stack#
Die produktive docker-compose.yml betreibt einen MinIO-Container neben dem Server:
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?set in .env}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set in .env}
volumes:
- minio-data:/data
Der Server verbindet sich über das interne Docker-Netzwerk mit ihm und verwendet die MinIO-Root-Zugangsdaten als seine S3-Access-/Secret-Keys wieder:
HINATA_S3_ENDPOINT: http://minio:9000
HINATA_S3_ACCESS_KEY: ${MINIO_ROOT_USER}
HINATA_S3_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
HINATA_S3_BUCKET: ${HINATA_S3_BUCKET:-hinata}
Im Standard-Stack setzt du also nur drei Dinge in .env:
MINIO_ROOT_USER=hinata
MINIO_ROOT_PASSWORD=change-me-to-a-long-random-value
HINATA_S3_BUCKET=hinata
Die MinIO-Weboberfläche ist auf Port 9001 verfügbar und die S3-API auf 9000.
In der lokalen Entwicklung (docker-compose.dev.yml) werden beide auf Loopback veröffentlicht —
http://localhost:9001 (Konsole) und http://localhost:9000 (API) — mit den Dev-Schlüsseln
hinata / hinata-dev-secret.
Ändere das MinIO-Passwort vor der Produktion
hinata-dev-secret ist ein Entwicklungsstandard. Setze ein langes, zufälliges
MINIO_ROOT_PASSWORD (z. B. aus ./deploy/generate-secrets.sh) für jedes reale
Deployment und veröffentliche die MinIO-Ports niemals im öffentlichen Internet — nur der
Server muss sie erreichen.
Der Bucket wird für dich erstellt#
Du musst den Bucket nicht vorab anlegen. Beim ersten Upload prüft der Server, ob
HINATA_S3_BUCKET existiert, und ruft makeBucket auf, falls nicht. Der Bucket bleibt
privat — nichts wird jemals öffentlich lesbar gemacht. Jeder Download wird vom Server
vermittelt (siehe unten), sodass Objekte nie direkt aus dem Bucket ausgeliefert werden.
Tip
Wenn du den Bucket lieber selbst anlegst (zum Beispiel, um vorab eine Lifecycle-Regel
oder eine Bucket-Policy zu setzen), tue dies mit dem Standardnamen hinata oder setze
HINATA_S3_BUCKET auf den von dir erstellten Namen. Der Server verwendet einen
vorhandenen Bucket gerne wieder.
Presigned Downloads und zufällig erzeugte Schlüssel#
Zwei Design-Entscheidungen halten den Objektspeicher standardmäßig sicher:
- Zufällig erzeugte Objektschlüssel. Ein vom Nutzer angegebener Dateiname wird nie zum
Objektschlüssel. Der Server speichert jeden Anhang unter einer zufälligen UUID (optional
hinter einem Präfix wie
media/oderavatars/), sodass ein Bucket-Schlüssel nicht erraten werden kann und der ursprüngliche Dateiname nie das Bucket-Layout berührt. - Presigned, kurzlebige Downloads. Wenn ein Client einen Anhang anfordert, gibt der
Server eine presigned GET-URL zurück, die 10 Minuten gültig ist und einen
Content-Disposition: attachment-Header trägt (sodass Dateien heruntergeladen statt inline gerendert werden). Der Bucket selbst muss nie öffentlich sein.
Das bedeutet, dass die S3-Zugangsdaten vollständig serverseitig bleiben; Clients sehen immer nur zeitlich begrenzte URLs.
Live-Anhang-Ereignisse (SSE)#
Anhang-Änderungen werden allen, die einen Vorgang betrachten, in Echtzeit über Server-Sent Events gepusht:
GET /api/v1/issues/{issueId}/attachments/stream
Wenn jemand eine Datei hochlädt oder entfernt — oder mehrere auf einmal ablegt — sieht jeder offene Betrachter das Raster live aktualisiert, ohne Polling. Der Stream läuft in-process pro Server-Instanz; für ein geclustertes Deployment würdest du ihn mit einem gemeinsamen Broker vorschalten.
Größen- und Content-Type-Limits#
Uploads werden auf mehreren Achsen validiert. Die Standardwerte:
| Einstellung | Env / Property | Standard |
|---|---|---|
| Maximale Größe einer einzelnen Datei | HINATA_STORAGE_MAX_UPLOAD_MB |
25 MB |
| Maximale Anzahl Dateien pro Anfrage | hinata.storage.max-files-per-request |
10 |
| Maximale Gesamtgröße einer Anfrage | HINATA_STORAGE_MAX_REQUEST_MB |
100 MB |
Erlaubte Content-Types sind eine explizite Allow-List — PNG, JPEG, GIF, WebP, PDF, reiner Text, CSV, ZIP, JSON und die OOXML-Word-/Excel-Dokumente. Ein paar wichtige Schutzmaßnahmen:
image/svg+xmlist absichtlich ausgeschlossen, weil SVG JavaScript einbetten kann (ein Stored-XSS-Risiko).- Magic-Byte-Verifizierung. Bei Binärtypen prüft der Server die führenden Bytes der Datei gegen den deklarierten Content-Type, sodass sich eine Datei nicht etwa als PNG ausgeben kann.
- Ein abgelehnter Upload gibt einen lokalisierten, stabilen Fehler zurück
(
error.storage.fileTooLarge,error.storage.fileTypeNotAllowed,error.storage.contentMismatch).
Zwei Größenobergrenzen arbeiten zusammen
Springs Multipart-Limits (max-file-size / max-request-size, gesteuert von denselben
MB-Werten) sind die äußere Schutzschicht; die App erzwingt dann obendrauf die Dateianzahl
und die aggregierte Größe. Erhöhe sie alle gemeinsam, wenn du größere Uploads brauchst.
Externes / AWS S3 statt MinIO verwenden#
Um AWS S3 oder einen anderen S3-kompatiblen Anbieter (Cloudflare R2, Backblaze B2, Wasabi, Ceph, ein verwaltetes MinIO, …) zu verwenden, richte dieselben vier Variablen darauf und lasse den mitgelieferten MinIO-Container weg:
HINATA_S3_ENDPOINT=https://s3.eu-central-1.amazonaws.com
HINATA_S3_ACCESS_KEY=AKIA...
HINATA_S3_SECRET_KEY=your-secret-access-key
HINATA_S3_BUCKET=my-hinata-bucket
HINATA_S3_REGION=eu-central-1
Hinweise:
HINATA_S3_REGIONist standardmäßigus-east-1; setze es bei AWS und Anbietern, denen es wichtig ist, auf die Region deines Buckets.- Für einen Nicht-AWS-Anbieter ist
HINATA_S3_ENDPOINTdessen S3-Endpunkt-URL (z. B. dein R2- oder Backblaze-Endpunkt). Verwende HTTPS für alles, was das Netzwerk überquert. - Die Zugangsdaten brauchen Berechtigung für
PutObject,GetObject,DeleteObject,ListBucketund (sofern du den Bucket nicht vorab anlegst)CreateBucket. - Wenn der Speicher unkonfiguriert bleibt (leerer Access-Key), antworten die Anhang- und
Avatar-Endpunkte mit
error.storage.notConfigured— der Rest von Hinata funktioniert weiterhin, du kannst nur keine Dateien hochladen.
Halte Buckets privat
Welchen Anbieter du auch verwendest, halte den Bucket privat. Hinata braucht nie öffentlichen Lesezugriff: Es gibt immer kurzlebige presigned URLs aus, sodass öffentlicher Zugriff nur deine Angriffsfläche vergrößern würde.
Siehe die Konfigurationsreferenz für die vollständige Variablenliste und MongoDB & X.509 für die Datenbankseite des Stacks.