# 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: 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) ### 1. Repository klonen ```bash git clone https://gitea.honeyy.duckdns.org/Docker/Aether.git aether cd aether ``` Zum **Betreiben** braucht es das Repository nicht – dafür genügen `deploy/docker-compose.yml`, eine `.env` und ein Datenverzeichnis. Klonen ist nur nötig, wenn du am Quelltext arbeiten oder selbst bauen willst. ### 2. `.env` anlegen ```bash cp deploy/.env.example deploy/.env ``` Dann in `deploy/.env` ausfüllen: ```env SESSION_SECRET=$(openssl rand -hex 32) DB_PASSWORD= PUBLIC_URL=https://cloud.example.com TRUSTED_PROXIES= ADMIN_USERNAME=admin ADMIN_PASSWORD= ``` ### 3. Starten ```bash cd deploy docker login gitea.honeyy.duckdns.org # nur beim ersten Mal docker compose up -d ``` Das Compose zieht ein **fertiges Abbild** aus der eigenen Registry (`gitea.honeyy.duckdns.org/docker/aether`). Für eine neue Umgebung reichen damit drei Dateien – `docker-compose.yml`, `.env` und das Datenverzeichnis. Backend und Frontend müssen dort nicht liegen und nicht gebaut werden. Wer aus dem Quelltext bauen will, kommentiert in `docker-compose.yml` `image:` aus und den `build:`-Block ein, dann: ```bash docker compose build app && docker compose up -d ``` Aktualisieren: ```bash docker compose pull app && docker compose up -d app ``` 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 ### 4. Öffnen Die App ist über deinen Reverse Proxy erreichbar (z. B. `https://cloud.example.com`). Direkt (ohne Proxy): http://localhost:8080/health --- ## Abbild bauen und veröffentlichen ```bash # 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. ```bash 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): ```bash docker run --rm -v "$PWD/../backend":/src -w /src golang:1.25-alpine go build ./... ``` ### Frontend ```bash 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 ```bash # 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: ``` / ├── users//home/… ← die eigenen Dateien, echte Namen │ /.trash/… ← Papierkorb │ /.versions/… ← frühere Fassungen ├── groups//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: ```bash 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: ```bash 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:///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///`. 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) Aether ist **WOPI-Host**. Das ist die Schnittstelle, über die Office-Server sich eine Datei holen und wieder zurückschreiben – **Collabora und OnlyOffice sprechen beide dieselbe.** Deshalb wird hier *ein* Adapter gebaut und der Anbieter per `.env` umgeschaltet, statt zweimal dasselbe zu integrieren. ### Was dazukommt | | | |---|---| | `OFFICE_PROVIDER` | `collabora`, `onlyoffice` oder leer (aus) | | `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. Der Container startet nur mit dem Profil, sonst kostet er Arbeitsspeicher, ohne gebraucht zu werden: ```bash docker compose --profile office up -d ``` ### 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