Files
Aether/README.md
T
HoneyandClaude Opus 5 428fd2672c docs(env): Beispieldateien kürzen, Erklärungen ins README
Die beiden .env.example zusammen waren 251 Zeilen für 40 Schlüssel – gut
gemeint, aber niemand liest einen Absatz pro Variable. Jetzt eine Zeile je
Schlüssel (58 + 89), die Begründungen stehen im README unter
'Einstellungen', gebündelt als die sieben Dinge, die nicht beim Start
auffallen, sondern später.

Dabei durch einen echten Start aus den Beispieldateien gefunden:
STORAGE_PATH fehlte, Vorgabe ./data wird im Container zu /app/data, und
der Start endete in 'mkdir /app/data: permission denied'. Das Abbild setzt
STORAGE_PATH und TMP_PATH jetzt selbst – es kennt seine Verzeichnisse
besser als eine Beispieldatei. Eine .env darf weiter überschreiben.

Geprüft: jeder Schlüssel, den die Anwendung liest, steht weiterhin in
deploy/.env.example; eine frische Instanz startet allein aus den beiden
Beispieldateien.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 11:27:31 +02:00

23 KiB
Raw Blame History

Aether

Self-hosted cloud storage — schnelle, schlanke Alternative zu Nextcloud.

Stack: Go + chi · React + TS + Vite · PostgreSQL · Docker Compose


Reverse Proxy (Pflicht)

Aether lauscht intern auf reinem HTTP. TLS wird von deinem eigenen Reverse Proxy terminiert. nginx, Traefik, Cloudflare Tunnel – alles funktioniert. Dieses Projekt liefert bewusst keinen Proxy mit: TLS, Zertifikate und Domain gehören in deine Hand, nicht in ein Compose-File, das du ohnehin überschreiben müsstest.

Der Proxy muss folgende Headers setzen:

X-Forwarded-For:   <echte Client-IP>
X-Forwarded-Proto: https

Die IP deines Proxys muss in TRUSTED_PROXIES stehen – sonst ignoriert Aether diese Header (Sicherheitsmaßnahme gegen IP-Spoofing).

Der Proxy muss folgendes durchreichen (keine eigene Verarbeitung):

  • Alle HTTP-Methoden inkl. PROPFIND, MKCOL, MOVE, COPY (für WebDAV)
  • Große Uploads (Timeout und Body-Size-Limit großzügig setzen)
  • WebSocket-Verbindungen (für Office-Co-Editing, Phase 6)
  • HSTS-Header setzen (z. B. Strict-Transport-Security: max-age=31536000; includeSubDomains)

Schnellstart (lokal)

Voraussetzungen

  • Docker + Docker Compose
  • Go 1.25+ und Node 20+ nur, wenn du aus dem Quelltext bauen willst – zum Betreiben genügt das fertige Abbild (siehe Schritt 3)

Zwei Wege, je nachdem was du vorhast:

Betreiben docker-compose.example.yml + .env.example im Wurzelverzeichnis. Zieht das fertige Abbild, braucht keinen Quelltext.
Entwickeln deploy/docker-compose.yml + deploy/.env.example. Baut aus dem Quelltext, kennt alle Schalter.

1. Dateien holen

Zum Betreiben genügen zwei Dateien – kein Klonen nötig:

mkdir aether && cd aether
curl -O https://gitea.honeyy.duckdns.org/Docker/Aether/raw/branch/main/docker-compose.example.yml
curl -O https://gitea.honeyy.duckdns.org/Docker/Aether/raw/branch/main/.env.example
mv docker-compose.example.yml docker-compose.yml

Zum Entwickeln stattdessen das Repository:

git clone https://gitea.honeyy.duckdns.org/Docker/Aether.git aether
cd aether

2. .env anlegen

cp .env.example .env          # beim Betreiben
cp deploy/.env.example deploy/.env   # beim Entwickeln

Ausfüllen musst du mindestens:

PUBLIC_URL=https://cloud.example.com
SESSION_SECRET=            # openssl rand -hex 32
DB_PASSWORD=
DATA_PATH=/pfad/zu/aether-data
APP_UID=1000               # id -u
APP_GID=1000               # id -g
TRUSTED_PROXIES=<IP-deines-Proxys>
ADMIN_USERNAME=admin
ADMIN_PASSWORD=            # nach dem ersten Start wieder leeren

Die schlanke .env.example im Wurzelverzeichnis führt nur diese Pflichtwerte und Office. Alles Weitere – OIDC, OCR, Vorschaubilder, Kontingente – steht mit Erklärung in deploy/.env.example.

3. Starten

docker login gitea.honeyy.duckdns.org   # nur beim ersten Mal
docker compose up -d

Beim ersten Start:

  • Postgres-Migrations laufen automatisch
  • Der erste Admin-Account wird aus ADMIN_USERNAME / ADMIN_PASSWORD angelegt
  • Nach dem Start: ADMIN_PASSWORD aus .env entfernen oder leer lassen

Aus dem Quelltext bauen statt ziehen:

docker compose -f deploy/docker-compose.yml build app
docker compose -f deploy/docker-compose.yml up -d

Aktualisieren:

docker compose pull app && docker compose up -d app

4. Öffnen

Die App ist über deinen Reverse Proxy erreichbar (z. B. https://cloud.example.com). Direkt (ohne Proxy): http://localhost:8080/health


Einstellungen

Zwei Beispieldateien, je nach Zweck:

.env.example (Wurzel) das Nötigste zum Betreiben
deploy/.env.example alle Schalter, auch die optionalen

Beide sind knapp gehalten – was dahintersteckt, steht hier.

Was schiefgeht, wenn man es falsch setzt

Diese sieben fallen nicht beim Start auf, sondern später und an unerwarteter Stelle. Der Rest hat brauchbare Vorgaben.

DB_HOST=postgres – nicht localhost. Aus dem Container heraus ist localhost er selbst; die Voreinstellung führt zu migrations failed beim Start.

DB_PASSWORD darf jedes Zeichen enthalten, auch % @ : / und Leerzeichen. Es gibt keine Liste erlaubter Zeichen – wer die erst nachschlagen muss, wählt am Ende ein schwächeres Passwort.

PUBLIC_URL ist die Adresse, unter der der Browser die Cloud sieht, ohne Schrägstrich am Ende. Ein falscher Wert startet klaglos und fällt erst auf, wenn ein Freigabe-Link oder ein OIDC-Rücksprung ins Leere zeigt.

TRUSTED_PROXIES entscheidet, wem X-Forwarded-For geglaubt wird. Steht hier das Falsche, sieht die Anwendung überall die IP des Proxys – Rate-Limit und Audit-Log werden damit wertlos, ohne dass irgendetwas nach Fehler aussieht.

ADMIN_PASSWORD legt beim allerersten Start den Admin an. Danach leeren: Das Konto bleibt, das Passwort stünde sonst dauerhaft in einer Datei.

OIDC_LINK_BY_USERNAME verbindet ein bestehendes Konto mit derselben Kennung, statt ein zweites anzulegen. Vor der ersten Anmeldung setzen – danach existiert das zweite Konto (marc2, leer, ohne Adminrecht) schon, und das Verbinden kommt zu spät. Nur bei einem Anbieter sinnvoll, dessen Konten du selbst anlegst (PocketID, Authentik); bei freier Registrierung wäre es die Übernahme fremder Konten. Verbunden wird ohnehin nur, was noch keine Bindung hat.

DATA_PATH ist ein gewöhnliches Verzeichnis auf dem Wirt, kein Docker-Volume – deshalb kommst du per SFTP, rsync oder im Dateimanager daran. Nur lesend anfassen: Wer von Hand hineinschreibt, hat eine Datei, die die Datenbank nicht kennt; die Cloud sieht sie nicht, und der GC räumt sie irgendwann weg. Zum Hineinlegen ist WebDAV der Weg. Dazu APP_UID/APP_GID aus id -u und id -g, sonst gehören die Dateien einem Container-Nutzer.

Volltextsuche (OCR)

Standardmäßig an. Fehlen ocrmypdf/tesseract im Abbild, schaltet sich der Arbeiter beim Start selbst ab und die Cloud läuft ohne Volltext weiter.

OCR_MAX_PAGES=50 ist eine Bremse, keine Schikane: Erkennung kostet rund eine Minute je gescannter Seite, und ohne Grenze blockiert ein dickes Handbuch die Warteschlange für alles andere. OCR_LANGUAGES kennt deu und eng; weitere brauchen ein zusätzliches apk-Paket im Dockerfile.

Office

Die drei Adressen und ihre Fallstricke stehen weiter unten bei Office – dort, wo auch die Container-Schnipsel liegen.

WebDAV

Läuft unter /dav/, kein Schalter nötig. Dein Reverse Proxy muss PROPFIND, MKCOL, MOVE und COPY durchreichen – viele Vorlagen erlauben nur GET/POST, und dann scheitert das Einbinden mit einem 405.


Abbild bauen und veröffentlichen

# im Wurzelverzeichnis des Projekts, nicht in deploy/
IMG=gitea.honeyy.duckdns.org/docker/aether
docker build -f deploy/Dockerfile -t $IMG:$(date +%Y.%m.%d) -t $IMG:latest .
docker push $IMG:$(date +%Y.%m.%d)
docker push $IMG:latest

Zwei Tags, mit Absicht. latest ist der bewegliche Name zum Aufbauen, der datierte der feste zum Zurückrollen. Wer nur latest fährt, hat im Fehlerfall nichts, worauf er zurück kann – der Name zeigt dann schon auf die Fassung, die gerade Ärger macht.

Das Abbild wiegt rund 540 MB. Den Löwenanteil machen ocrmypdf, tesseract samt Sprachmodellen und poppler-utils aus – ohne sie wären es etwa 210 MB, aber auch keine Volltextsuche in Scans. Wer das nicht braucht, streicht die zweite apk-Zeile im Dockerfile und setzt OCR_ENABLED=false; der Server prüft beim Start selbst, ob die Programme da sind, und läuft sonst ohne Volltextsuche weiter.

Gebaut wird für die Architektur des Wirts. Für ein anderes Ziel (etwa ein ARM-Gerät) braucht es docker buildx build --platform linux/arm64.


Lokale Entwicklung

Backend (immer via Docker)

Das Backend wird ausschließlich über Docker Compose gestartet – nie mit go run. Postgres-Migrations und DB-Verbindung sind auf die Compose-Umgebung ausgelegt.

cd deploy
docker compose up -d --build       # Backend + Postgres starten
docker compose logs -f app         # Logs verfolgen
docker compose down                # stoppen

Code-Änderungen am Backend: docker compose up -d --build neu ausführen (Image neu bauen).

Compile-Check ohne lokales Go-Toolchain (der Host hat kein Go installiert):

docker run --rm -v "$PWD/../backend":/src -w /src golang:1.25-alpine go build ./...

Frontend

cd frontend
npm install
npm run dev      # http://localhost:5173 (Vite dev server, spricht Backend auf :8080 an)
npm run build    # Production-Build (wird vom Backend-Image mitgebaut)

Tests

# Backend (im Go-Image, da Host kein Go hat)
docker run --rm -v "$PWD/backend":/src -w /src golang:1.25-alpine go test ./...

# Frontend
cd frontend && npm test

Struktur

aether/
├── backend/
│   ├── cmd/aether/         # Einstiegspunkt
│   ├── internal/           # Domänen-Pakete (auth, storage, files, …)
│   ├── db/migrations/      # SQL-Migrations
│   └── pkg/                # Shared utils (config, middleware, response)
├── frontend/
│   └── src/
│       ├── api/            # Typed API-Client
│       ├── components/
│       ├── hooks/
│       └── pages/
├── deploy/
│   ├── docker-compose.yml  # app + postgres (kein Proxy-Container)
│   ├── Dockerfile
│   └── .env.example
└── CLAUDE.md               # Projektkonventionen

Wo die Dateien liegen

Nicht in einem Docker-Volume, sondern in einem gewöhnlichen Verzeichnis auf dem Wirt – DATA_PATH in der .env. Der Aufbau darin ist derselbe wie in der Oberfläche:

<DATA_PATH>/
├── users/<benutzername>/home/…      ← die eigenen Dateien, echte Namen
│                       /.trash/…    ← Papierkorb
│                       /.versions/… ← frühere Fassungen
├── groups/<gruppen-id>/home/…       ← Gruppenordner
├── thumbs/                          ← Vorschaubilder
├── branding/                        ← Logo und App-Symbol
└── tmp/                             ← Zwischendateien, wird selbst geräumt

Warum das so ist: Ein Archiv ist nur dann eines, wenn man ohne die Anwendung an seine Unterlagen kommt. Hier reicht dafür scp -r oder ein Blick in den Dateimanager – keine Datenbank, kein Export, kein laufender Container nötig. Bei einem benannten Volume läge alles unter /var/lib/docker/volumes/… und gehörte root.

Damit die Dateien dir gehören und nicht einem Container-Nutzer, läuft der Container unter deiner Kennung (APP_UID/APP_GID, siehe .env). Damit funktioniert SFTP direkt:

sftp dein-nutzer@dein-server
get -r /pfad/zu/aether-data/users/deinname/home

Nur lesen. Wer von Hand eine Datei hineinlegt, hat sie auf der Platte, aber nicht in der Datenbank: Die Cloud sieht sie nicht, und die Speicherbereinigung räumt sie irgendwann weg. Zum Hineinlegen ist WebDAV der Weg (siehe unten).

Von einem benannten Volume umsteigen

Ältere Installationen hatten ein Volume deploy_data. Umzug mit Prüfung:

cd deploy
docker compose stop app
mkdir -p /pfad/zu/aether-data
docker run --rm -v deploy_data:/src:ro -v /pfad/zu/aether-data:/dst alpine:3.20 \
  sh -c 'cp -a /src/. /dst/ && chown -R $(id -u):$(id -g) /dst'

# Prüfen, bevor umgeschaltet wird: beide Summen müssen gleich sein
docker run --rm -v deploy_data:/src:ro alpine:3.20 \
  sh -c 'cd /src && find . -type f -exec sha256sum {} \; | LC_ALL=C sort | sha256sum'
(cd /pfad/zu/aether-data && find . -type f -exec sha256sum {} \; | LC_ALL=C sort | sha256sum)

# erst dann DATA_PATH in die .env eintragen und starten
docker compose up -d app

Das alte Volume nicht sofort löschen. Es ist der Rückfall, solange nicht alles geprüft ist; docker volume rm deploy_data kann warten, bis du sicher bist.


WebDAV

Adresse: https://<deine-domain>/dav/

Angemeldet wird sich mit dem Benutzernamen und einem App-Passwort (Profil → App passwords). Das Kontopasswort wird an /dav/ abgelehnt – es ist zugleich der Admin-Zugang und hat auf Geräten nichts verloren. Ein App-Passwort gilt nur für WebDAV, nie fürs Web-Login, und lässt sich einzeln widerrufen, ohne die anderen Geräte auszusperren.

Der Aufbau entspricht dem der Weboberfläche: eigene Ordner und Gruppenordner liegen nebeneinander in der Wurzel, Freigaben von Personen unter /dav/Shared/<Person>/<Freigabe>/. Trägt ein Gruppenordner denselben Namen wie ein eigener, behält der eigene ihn und der Gruppenordner bekommt den Gruppennamen angehängt (Projekt (Buero)).

Nach zehn Fehlanmeldungen in 15 Minuten sperrt die IP für den Rest des Fensters (429). Normale Anfragen zählen nicht mit – Sync-Clients senden legitim hunderte pro Minute.


Office (Collabora / OnlyOffice / Euro-Office)

Aether ist WOPI-Host. Das ist die Schnittstelle, über die Office-Server sich eine Datei holen und wieder zurückschreiben – alle drei sprechen dieselbe. Deshalb gibt es einen Adapter und einen Schalter in der .env, statt derselben Sache dreimal.

Im Compose-Beispiel ist deshalb kein Office-Server enthalten: Jeder bringt eigene Variablen mit, und man will genau einen. Such dir unten den passenden Schnipsel und häng ihn an dein docker-compose.yml.

Was dazukommt

OFFICE_PROVIDER collabora, onlyoffice oder leer (aus) – Euro-Office zählt als onlyoffice
OFFICE_URL wo das Backend den Office-Server erreicht
OFFICE_PUBLIC_URL wo der Browser ihn erreicht (leer = wie oben)
OFFICE_WOPI_URL wo der Office-Server Aether erreicht (leer = PUBLIC_URL)
OFFICE_ALLOWED_HOSTS Ziele, die das Backend anfragen darf – gegen SSRF

Drei Adressen für drei Richtungen. Das wirkt übertrieben, bis man es einmal aufgebaut hat: Im Compose-Netz heißt der Office-Server collabora, im Browser muss dort ein auflösbarer Name stehen, und der Office-Server wiederum erreicht Aether unter app:8080 – „localhost" ist aus seinem Container heraus er selbst.

Drei Adressen für drei Richtungen. Das wirkt übertrieben, bis man es einmal aufgebaut hat – die häufigste Fehlerquelle beim Aufbau.

Alle Schnipsel unten legen den Dienst ins Profil office. Ohne das Profil startet er nicht, und wer kein Office will, gibt kein Gigabyte dafür ab:

docker compose --profile office up -d

Schnipsel 1: Collabora

  office:
    container_name: aether-office
    image: collabora/code:latest
    profiles: ["office"]
    restart: unless-stopped
    environment:
      # Wer seine Dateien hier öffnen darf. Punkte maskieren – Collabora liest
      # den Wert als regulären Ausdruck.
      aliasgroup1: http://app:8080
      # ssl.enable      – spricht Collabora selbst HTTPS?
      # ssl.termination – endet HTTPS davor, an deinem Reverse Proxy?
      # Steht termination auf true ohne HTTPS davor, verlangt Collabora eine
      # wss://-Verbindung, die es nicht gibt: Der Editor lädt, das Dokument
      # bleibt weiß. mount_jail_tree=false spart CAP_SYS_ADMIN.
      extra_params: >-
        --o:ssl.enable=false
        --o:ssl.termination=true
        --o:mount_jail_tree=false
      dictionaries: de_DE en_US
    cap_add: [MKNOD]
    networks: [internal]
    ports:
      - "127.0.0.1:9980:9980"
OFFICE_PROVIDER=collabora
OFFICE_URL=http://office:9980
OFFICE_PUBLIC_URL=https://office.example.com
OFFICE_WOPI_URL=http://app:8080
OFFICE_ALLOWED_HOSTS=office

Schnipsel 2: OnlyOffice Docs

  office:
    container_name: aether-office
    image: onlyoffice/documentserver:latest
    profiles: ["office"]
    restart: unless-stopped
    environment:
      WOPI_ENABLED: "true"      # ohne das gibt es kein /hosting/discovery
      JWT_ENABLED: "true"
      JWT_SECRET: ${OFFICE_JWT_SECRET}
    networks: [internal]
    ports:
      - "127.0.0.1:9980:80"     # OnlyOffice lauscht auf 80, nicht auf 9980
OFFICE_PROVIDER=onlyoffice
OFFICE_URL=http://office
OFFICE_PUBLIC_URL=https://office.example.com
OFFICE_WOPI_URL=http://app:8080
OFFICE_ALLOWED_HOSTS=office
OFFICE_JWT_SECRET=            # mindestens 32 Zeichen

Schnipsel 3: Euro-Office

Seit dem 9. Juni 2026 zu haben und in Nextcloud Hub 26 die Alternative zu Collabora. Es ist ein OnlyOffice-Fork, und man sieht es an den Variablen: Sie heißen gleich. Für uns ist es deshalb derselbe Anbieter – OFFICE_PROVIDER=onlyoffice, nur ein anderes Abbild.

  office:
    container_name: aether-office
    image: ghcr.io/euro-office/documentserver:latest
    profiles: ["office"]
    restart: unless-stopped
    environment:
      WOPI_ENABLED: "true"
      JWT_ENABLED: "true"
      JWT_SECRET: ${OFFICE_JWT_SECRET}
      EXAMPLE_ENABLED: "false"        # die Beispiel-App gehört nicht ins Netz
      ALLOW_PRIVATE_IP_ADDRESS: "true"  # es muss app:8080 erreichen
    networks: [internal]
    ports:
      - "127.0.0.1:9980:80"

Die .env ist dieselbe wie bei OnlyOffice. Rechne mit 5 GB allein für das Abbild.

Ungeprüft. Collabora ist hier gemessen worden, den Kreis von Tippen bis Speichern eingeschlossen. Die beiden anderen Schnipsel stehen nach ihrer Dokumentation und dem gemeinsamen WOPI-Weg, sind auf dieser Anlage aber nie gelaufen – für OnlyOffice fehlt schlicht der Arbeitsspeicher.

Welche Dateien, und wie gut

Bearbeitet wird nur, was auf der Positivliste steht: odt docx doc rtf txt · ods xlsx xls csv · odp pptx ppt · odg. PDF steht bewusst nicht darauf – es hat seinen eigenen Betrachter, und ein Office-Server würde beim Speichern ein anderes Dokument daraus machen.

Collabora Online ist LibreOffice, mit derselben Datei-Unterstützung. Für die Microsoft-Formate heißt das:

docx, xlsx, pptx öffnen und speichern zuverlässig, Format bleibt erhalten
Alte doc, xls, ppt gehen, sind aber Rückwärtskompatibilität – kein Ziel der Entwicklung
Komplexes Layout Seitenumbrüche und Schriftabstände können um Kleinigkeiten abweichen
Sehr große Tabellen, Pivot, seltene Formeln die häufigste Stelle für echte Abweichungen
Makros (VBA) laufen nicht – und das ist Absicht, nicht Mangel

Fehlende Schriften sind der häufigste Grund für „sieht anders aus": Der Container bringt Calibri und Cambria nicht mit. Wer Dokumente von außen bearbeitet, legt die Schriften nach.

Für ein Belegarchiv ist das unkritisch – da wird gelesen und selten geändert. Für den täglichen Umgang mit fremden Word-Dateien gilt: Rundlauf prüfen, nicht darauf vertrauen.

Wie das zusammenspielt

Der Office-Server ist kein Client deines Browsers allein – er holt die Datei selbst beim Backend ab:

Browser ──────────► Office-Server        (Bedienoberfläche im <iframe>)
Office-Server ────► Aether  /wopi/...    (Datei holen, Datei zurückschreiben)

Beide Pfeile müssen funktionieren. Das ist der Punkt, an dem solche Installationen scheitern: Der Office-Server muss Aether unter seiner öffentlichen Adresse erreichen (PUBLIC_URL), nicht nur unter localhost. Ein Zugriffstoken mit kurzer Laufzeit hängt an jedem Aufruf; ohne gültiges Token liefert der WOPI-Endpunkt nichts.

Was beim Aufbau schiefgeht

Alles hier ist beim Testen tatsächlich passiert, in dieser Reihenfolge:

Symptom Ursache
Rahmen bleibt leer, Konsole: „Framing … violates default-src 'self'" Unsere eigene Sicherheitsrichtlinie. frame-src fällt auf default-src zurück; der Office-Ursprung muss ausdrücklich erlaubt sein. Passiert automatisch, sobald OFFICE_PROVIDER gesetzt ist.
Editor lädt, Dokument bleibt weiß ssl.termination=true ohne HTTPS davor. Collabora sagt dem Browser dann wss://, das es nicht gibt. Im HTTP-Test OFFICE_SSL_TERMINATION=false.
Log: „CheckFileInfo returned 0 … ECONNREFUSED" OFFICE_WOPI_URL zeigt auf 127.0.0.1:8090. Der Port ist auf loopback gebunden – aus einem Container nicht erreichbar. Im Compose den Dienstnamen nehmen.
Log: „WOPISrc must be URI-encoded" Behoben; die Adresse wird vollständig kodiert.
Log: „Failed to exec coolmount … needs CAP_SYS_ADMIN" Gelöst über --o:mount_jail_tree=false im Compose.
Fehlende Symbole (404 auf .svg) Lücken im Collabora-Image selbst. Kosmetisch.

Sicherheit

  • SSRF: Das Backend ruft den Office-Server auf. Ausgehende Ziele werden auf OFFICE_ALLOWED_HOSTS beschränkt – sonst wird aus einer Adresse in der Konfiguration ein Weg ins interne Netz.
  • Tokens sind dateibezogen und kurzlebig. Ein Token für Datei A öffnet Datei B nicht, auch nicht mit derselben Sitzung.
  • Schreiben folgt derselben Regel wie ein Upload: atomar schreiben, SHA-256 neu berechnen, Version anlegen, wenn Versionierung an ist. Ein Office-Server ist kein Sonderweg an der Datenintegrität vorbei.

Speicherbedarf – vor der Entscheidung lesen

Der Office-Server ist der mit Abstand hungrigste Teil dieser Anlage:

Bedarf Bemerkung
Collabora (CODE) ~1 GB im Leerlauf, +100–200 MB je offenem Dokument LibreOffice-Kern, startet pro Dokument einen Prozess
OnlyOffice Docs 2 GB Minimum, 4 GB empfohlen, dazu Swap bringt eigenes Postgres, RabbitMQ und Node mit
Euro-Office wie OnlyOffice, Abbild rund 5 GB derselbe Unterbau

Dazu kommt, was schon läuft: Postgres, Aether und – nicht unterschätzen – die Texterkennung. ocrmypdf und ghostscript nehmen sich bei einem großen Scan kurzzeitig mehrere hundert Megabyte.

Welchen nehmen?

  • Collabora, wenn der Speicher knapp ist oder Herkunft und ODF zählen. LibreOffice-Kern, europäisch, deutlich genügsamer.
  • OnlyOffice, wenn .docx/.xlsx zwischen Desktop-Office und Web hin und her wandern. Beste Treue bei OOXML, weil es nativ damit arbeitet.
  • Euro-Office, wenn du OnlyOffices Treue willst, aber nicht seine Herkunft. Seit Juni 2026 zu haben, von Nextcloud als Standard eingesetzt, derselbe WOPI-Weg. Der Haken ist derselbe wie bei OnlyOffice: Hunger nach Arbeitsspeicher, und das Abbild wiegt rund 5 GB.

Auf deinen 6 GB ist die Rechnung schnell gemacht. Postgres, Aether und die Texterkennung liegen schon darauf; für OnlyOffice oder Euro-Office bliebe zu wenig. Collabora ist dort die einzige, die trägt – und ein Wechsel später kostet einen Container und eine Zeile .env, nicht mehr.


Phasen-Roadmap

Phase Inhalt Status
0 Fundament (Monorepo, Docker, Migrations, Backend-Skeleton) ✅
1 Auth (Argon2id, Sessions), Storage (atomar + SHA-256), Datei-CRUD, Frontend ✅
2 Uploads mit Fortschritt, Papierkorb, Versionen, Share-Links, Thumbnails ✅
2.5 Sidebar-Favoriten, Ordner verschieben/hochladen, „Verschieben nach…" ✅
3 Admin-Panel, Gruppen(-mitgliedschaft), Theming, User-Profil ✅
4 WebDAV (/dav/), OIDC (optional) ✅ (im Test)
4.5 Härtung (Quota, Audit-Log, App-Passwörter), interne Shares, Gruppenordner, Logo/App-Symbol ✅ (im Test)
5 UI (Suche, Vorschau, Sortierung, Vor/Zurück) – Design-Rest offen 🔄
6 Dokumente: OCR + Volltextsuche ✅, Etiketten ✅, Absender ✅, PDF-Betrachter ✅ – Inbox vertagt 🔄
7 Office (WOPI-Host, Collabora / OnlyOffice) – Anleitung ✅ (im Test)
8 Härtung, 2FA, Observability, Extension-Points 🔲

Manuelle Test-Checkliste für die fertigen Phasen: siehe TESTING.md.