diff --git a/.env.example b/.env.example index 331a42b..837d782 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,21 @@ -# Aether – das Nötigste für eine neue Umgebung. cp .env.example .env +# Aether – alle Einstellungen. cp .env.example .env +# +# Diese Datei gilt für beides: den Betrieb mit dem fertigen Abbild +# (docker-compose.example.yml hier im Wurzelverzeichnis) und die Entwicklung +# aus dem Quelltext (deploy/docker-compose.yml, liest deploy/.env). +# Für die Entwicklung: cp .env.example deploy/.env # # Zwei Fallen beim Bearbeiten: # $ im Wert → als $$ schreiben, sonst schneidet Compose ab # Kommentar hinter einem LEEREN Wert → der Kommentar wird zum Wert -# Erklärungen: README.md, Abschnitt „Einstellungen" -# Alle Schalter: deploy/.env.example +# Erklärungen und Fallstricke: README.md, Abschnitt „Einstellungen" -# ── Pflicht ─────────────────────────────────────────────────────────────────── -APP_ENV=production -# Adresse, unter der der Browser die Cloud sieht. Ohne Schrägstrich am Ende. +# ══ Pflicht ═══════════════════════════════════════════════════════════════════ + +APP_ENV=production # development | production +# 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. PUBLIC_URL=https://cloud.example.com # openssl rand -hex 32 SESSION_SECRET= @@ -28,57 +35,128 @@ DATA_PATH=/pfad/zu/aether-data APP_UID=1000 APP_GID=1000 -# ── Reverse Proxy ───────────────────────────────────────────────────────────── -# Nur diesen Adressen wird X-Forwarded-For/-Proto geglaubt. -TRUSTED_PROXIES=172.18.0.1 -# Port auf dem Wirt. Im Container bleibt es 8080. -APP_PORT=8080 -# Wer den Port erreichen darf. 127.0.0.1 = nur dieser Rechner (Vorgabe, -# davor gehört der Proxy). 0.0.0.0 = das ganze Netz – dann unverschlüsselt. -APP_BIND=127.0.0.1 - -# Wie viele frühere Fassungen einer Datei aufgehoben werden. Gilt nur für -# Dateien, an denen „Keep versions" eingeschaltet ist – Standard ist aus. -MAX_VERSIONS_KEPT=5 - -# ── Erster Start ────────────────────────────────────────────────────────────── -# Legt den Admin an. Danach ADMIN_PASSWORD leeren. +# ══ Erster Start ══════════════════════════════════════════════════════════════ +# Legt den Admin an. Danach ADMIN_PASSWORD leeren – das Konto bleibt. # Auch hier: ein $ muss $$ heißen (siehe DB_PASSWORD). ADMIN_USERNAME=admin ADMIN_PASSWORD= -# ── Anmeldung über einen Identitätsanbieter (optional) ─────────────────────── -# Leerer OIDC_ISSUER = aus. Sonst die vollständige Adresse des Anbieters, -# z. B. https://id.example.com – nicht seinen Namen. Falsch gesetzt bricht -# der Start ab. +# ══ Reverse Proxy ═════════════════════════════════════════════════════════════ +# IPs/CIDRs der Proxys, denen X-Forwarded-For/-Proto geglaubt wird. Steht hier +# das Falsche, sieht die Anwendung überall die IP des Proxys – Rate-Limit und +# Audit-Log werden damit wertlos, ohne dass etwas nach Fehler aussieht. +TRUSTED_PROXIES=172.18.0.1 +# Port auf dem Wirt. Im Container bleibt es 8080. +APP_PORT=8080 +# Wer den Port erreichen darf. 127.0.0.1 = nur dieser Rechner (Vorgabe, davor +# gehört der Proxy). 0.0.0.0 = das ganze Netz – dann unverschlüsselt. +APP_BIND=127.0.0.1 +APP_LISTEN_ADDR=:8080 # im Container; der Proxy leitet hierher + +# ══ Sitzung ═══════════════════════════════════════════════════════════════════ +# Wie lange eine Anmeldung gilt – **ab der letzten Benutzung**, nicht ab dem +# Anmelden. Wer die Cloud regelmäßig öffnet, bleibt angemeldet; wer sie so +# lange nicht anfasst, meldet sich neu an. 2592000 = 30 Tage. # -# Kommentar NIE hinter einen leeren Wert setzen: `OIDC_ISSUER= # text` macht -# den Text zum Wert. Hinter einem gefüllten Wert ist er dagegen harmlos. +# Kürzer ist sinnvoll, wenn mehrere Leute denselben Rechner benutzen. Alle +# Sitzungen sofort beenden: SESSION_SECRET wechseln und neu starten. +SESSION_MAX_AGE=2592000 # Sekunden + +# ══ Speicher ══════════════════════════════════════════════════════════════════ +STORAGE_PATH=/data # im Container +TMP_PATH=/data/tmp +# Frühere Fassungen je Datei. Greift nur bei eingeschaltetem „Keep versions". +MAX_VERSIONS_KEPT=5 + +# ══ Anmeldung über einen Identitätsanbieter (optional) ════════════════════════ +# Leerer OIDC_ISSUER = aus. Sonst die vollständige ADRESSE des Anbieters, +# z. B. https://id.example.com – nicht seinen Namen. Falsch gesetzt bricht der +# Start ab. OIDC_ISSUER= OIDC_CLIENT_ID=aether OIDC_CLIENT_SECRET= -# Muss exakt /api/v1/auth/oidc/callback sein. +# Muss EXAKT /api/v1/auth/oidc/callback sein. OIDC_REDIRECT_URL=https://cloud.example.com/api/v1/auth/oidc/callback -# Bestehendes Konto verbinden statt ein zweites anzulegen. -# VOR der ersten Anmeldung setzen – danach ist es zu spät. +# Bestehendes Konto gleichen Namens mit der Identität verbinden, statt ein +# zweites anzulegen. VOR der ersten Anmeldung setzen – danach ist es zu spät. +# Nur bei einem Anbieter, dem die Namen gehören: Bei freier Registrierung wäre +# es die Übernahme fremder Konten. OIDC_LINK_BY_USERNAME=false # Beschriftung des Knopfes auf der Anmeldeseite. OIDC_BUTTON_LABEL=Sign in with SSO / OIDC -# ── Office (optional) ───────────────────────────────────────────────────────── +# ══ Office (optional) ═════════════════════════════════════════════════════════ # Leer = aus, auch wenn ein Office-Container läuft. -# Schnipsel für Collabora, OnlyOffice und Euro-Office: README.md +# Compose-Schnipsel für Collabora, OnlyOffice und Euro-Office: README.md +# collabora | onlyoffice (auch Euro-Office) OFFICE_PROVIDER= +# Drei Adressen für drei Richtungen – die häufigste Fehlerquelle. +# # Backend → Office. Port je nach Anbieter: # Collabora http://office:9980 # OnlyOffice/Euro-Office http://office OFFICE_URL=http://office:9980 # Browser → Office. Deine Domain oder LAN-IP, nicht der Compose-Name. OFFICE_PUBLIC_URL=https://office.example.com -# Office → Aether. Weder localhost noch der veröffentlichte Port. -OFFICE_WOPI_URL=http://app:8080 -# Gegen SSRF: was das Backend anfragen darf. +# Office → Aether. +# +# **Bei Collabora die öffentliche Adresse der Cloud**, nicht app:8080: Aus +# diesem Wert baut Collabora sein frame-ancestors, und der Browser darf den +# Rahmen sonst nicht zeigen ("Framing … violates 'frame-ancestors …'"). +# Collabora holt die Datei dann über deinen Reverse Proxy. +OFFICE_WOPI_URL=https://cloud.example.com +# Gegen SSRF: was das Backend anfragen darf. Der Host aus OFFICE_URL zählt +# immer dazu. OFFICE_ALLOWED_HOSTS=office # Nur OnlyOffice/Euro-Office, mind. 32 Zeichen: openssl rand -hex 32 +# Muss mit JWT_SECRET des Office-Containers übereinstimmen. Ohne ihn schlägt +# das Wandeln nach PDF fehl (der Server antwortet `{"error":-8}`). Für den +# WOPI-Weg wird er nicht gebraucht, der hat eigene Token. OFFICE_JWT_SECRET= +OFFICE_INSECURE_TLS=false # nur für Tests mit selbstsigniertem Zertifikat + +# ══ Volltextsuche (OCR) ═══════════════════════════════════════════════════════ +# Standardmäßig an; fehlen ocrmypdf/tesseract, schaltet es sich selbst ab. +# OCR_ENABLED=false +# OCR_LANGUAGES=deu+eng # weitere brauchen ein apk-Paket im Dockerfile +# OCR_MAX_PAGES=50 # 0 = unbegrenzt; rund 1 Minute je Seite +# OCR_TIMEOUT=10m # Notbremse je Datei + +# ══ App-Umschalter (optional) ═════════════════════════════════════════════════ +# Andere selbst gehostete Apps im Menü der Seitenleiste. +# EXTERNAL_APPS=InkNote|https://notes.example.com|📝 + +# ══ HoneyOS-Leiste (optional) ═════════════════════════════════════════════════ +# Ursprung, von dem die Leiste geladen wird. Wird an zwei Stellen gebraucht und +# kommt deshalb aus einer Quelle: die CSP erlaubt ihn (script-src + +# connect-src), und die Oberfläche lädt das Skript von dort nach. +# Wirkt beim **Neustart** – kein Neubau nötig. +# Leer = keine Leiste, und die CSP bleibt so eng wie ohne sie. +HONEYBAR_ORIGIN= +# Ausweis, mit dem sich die Leiste die App-Liste holt. Muss zu HONEYOS_APPS_KEY +# drüben passen. Nur nötig, wenn HoneyOS auf einer anderen Site liegt als +# Aether – dann schickt der Browser die HoneyOS-Sitzung nicht mit. +HONEYBAR_KEY= + +# ══ Nur für deploy/docker-compose.yml ═════════════════════════════════════════ +# Diese vier steuern den Office-Container aus dem Entwicklungs-Compose. Beim +# Betrieb mit eigenem Office-Container stehen sie in dessen Compose-Datei. +# +# Wer seine Dateien in Collabora öffnen darf – derselbe Host wie in +# OFFICE_WOPI_URL, aber als regulärer Ausdruck: Punkte maskieren, Port +# anhängen. Fehlt er, lehnt Collabora ab: +# "No authorized hosts found matching the target host […] in config" +OFFICE_ALIASGROUP=https://cloud\.example\.com:443 +OFFICE_SSL=false +OFFICE_SSL_TERMINATION=false # true nur, wenn davor wirklich HTTPS endet +OFFICE_PORT=9980 + +# ══ Später ════════════════════════════════════════════════════════════════════ +# MEILISEARCH_URL=http://meilisearch:7700 +# MEILISEARCH_KEY= + +# ══ WebDAV ════════════════════════════════════════════════════════════════════ +# Läuft unter /dav/, kein Schalter nötig. Der Proxy muss +# PROPFIND/MKCOL/MOVE/COPY durchreichen. Anmeldung ausschließlich mit +# App-Passwörtern, nie mit dem Kontopasswort. diff --git a/README.md b/README.md index 5f9f78f..cf251f6 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,15 @@ 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. | +| **Betreiben** | `docker-compose.example.yml` im Wurzelverzeichnis. Zieht das fertige Abbild, braucht keinen Quelltext. | +| **Entwickeln** | `deploy/docker-compose.yml`. Baut aus dem Quelltext. | + +Die **`.env.example` im Wurzelverzeichnis gilt für beide** – eine Datei, alle +Einstellungen. Sie lag bis zum 02.09.2026 doppelt vor (einmal schlank hier, +einmal vollständig unter `deploy/`), und die beiden liefen auseinander: Der +produktiven fehlten am Ende zwölf Schlüssel, darunter `SESSION_MAX_AGE`, und +sie empfahl für `OFFICE_WOPI_URL` einen Wert, der mit Collabora nicht +funktioniert. ### 1. Dateien holen @@ -66,7 +73,7 @@ cd aether ```bash cp .env.example .env # beim Betreiben -cp deploy/.env.example deploy/.env # beim Entwickeln +cp .env.example deploy/.env # beim Entwickeln (dorthin schaut das Compose) ``` Ausfüllen musst du mindestens: @@ -83,9 +90,10 @@ 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`. +Alles Weitere – Sitzungsdauer, OIDC, Office, OCR, App-Umschalter – steht mit +Erklärung in derselben Datei, nach Abschnitten geordnet. Der letzte Abschnitt +gilt nur für den Entwicklungs-Compose; beim Betrieb mit eigenem +Office-Container stehen diese Werte in dessen Compose-Datei. ### 3. Starten @@ -121,14 +129,8 @@ 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. +**Eine** Beispieldatei: `.env.example` im Wurzelverzeichnis, für Betrieb wie +Entwicklung. Sie ist knapp gehalten – was dahintersteckt, steht hier. ### Was schiefgeht, wenn man es falsch setzt diff --git a/TESTING.md b/TESTING.md index 74a49ac..89b990b 100644 --- a/TESTING.md +++ b/TESTING.md @@ -721,12 +721,13 @@ Hinter einem **gefüllten** Wert ist ein Kommentar harmlos. Hinter einem den OIDC-Aufbau mit dem Kommentartext als Adresse – und weil ein Fehlschlag dort den Start abbricht, kam die Cloud nicht hoch. -In `deploy/.env.example` steckten vier weitere solche Zeilen, darunter +In der (inzwischen zusammengeführten) `.env.example` steckten vier weitere +solche Zeilen, darunter `OFFICE_PROVIDER= # collabora | onlyoffice`: Office wäre mit Unsinn als Anbieter eingeschaltet worden. Alle vier umgestellt, der Kommentar steht jetzt darüber. -- [ ] `grep -nE '^[A-Z_]+=[[:space:]]*#' .env.example deploy/.env.example` +- [ ] `grep -nE '^[A-Z_]+=[[:space:]]*#' .env.example` liefert nichts 🔎 - [ ] Frische Instanz allein aus den Beispieldateien startet 🔎 diff --git a/deploy/.env.example b/deploy/.env.example deleted file mode 100644 index d69a063..0000000 --- a/deploy/.env.example +++ /dev/null @@ -1,138 +0,0 @@ -# Aether – alle Schalter. cp deploy/.env.example deploy/.env -# -# Zwei Fallen beim Bearbeiten: -# $ im Wert → als $$ schreiben, sonst schneidet Compose ab -# Kommentar hinter einem LEEREN Wert → der Kommentar wird zum Wert -# Erklärungen und Fallstricke: README.md, Abschnitt „Einstellungen" -# -# Für den reinen Betrieb genügt die kürzere .env.example im -# Wurzelverzeichnis – hier steht zusätzlich alles Optionale. - -# ── App ─────────────────────────────────────────────────────────────────────── -APP_ENV=production # development | production -APP_LISTEN_ADDR=:8080 # im Container; der Proxy leitet hierher -PUBLIC_URL=https://cloud.example.com -# IPs/CIDRs der Proxys, denen X-Forwarded-For/-Proto geglaubt wird. -TRUSTED_PROXIES=172.18.0.1 - -# ── Datenbank ───────────────────────────────────────────────────────────────── -# DB_HOST ist der Dienstname aus dem Compose, nicht localhost. -DB_HOST=postgres -DB_PORT=5432 -DB_NAME=aether -DB_USER=aether -# Jedes Zeichen erlaubt – nur ein $ muss $$ heißen oder der Wert in -# 'einfache Anführungszeichen'. Sonst schneidet Compose lautlos ab. (README) -DB_PASSWORD=changeme_strong_password - -# ── Sitzung ─────────────────────────────────────────────────────────────────── -SESSION_SECRET=changeme_generate_with_openssl_rand_hex_32 # openssl rand -hex 32 -# Wie lange eine Sitzung gilt – **ab der letzten Benutzung**, nicht ab dem -# Anmelden. Wer die Cloud regelmäßig öffnet, bleibt angemeldet; wer sie so -# lange nicht anfasst, meldet sich neu an. 2592000 = 30 Tage. -# Kürzer ist sinnvoll, wenn mehrere Leute denselben Rechner benutzen. -SESSION_MAX_AGE=2592000 # Sekunden - -# ── Speicher ────────────────────────────────────────────────────────────────── -STORAGE_PATH=/data # im Container -TMP_PATH=/data/tmp -# Verzeichnis auf dem Wirt. Nur lesend anfassen – zum Hineinlegen: WebDAV. -DATA_PATH=/pfad/zu/aether-data -# `id -u` / `id -g` – sonst gehören die Dateien einem Container-Nutzer. -APP_UID=1000 -APP_GID=1000 -# Frühere Fassungen je Datei. Greift nur bei eingeschaltetem „Keep versions". -MAX_VERSIONS_KEPT=5 -# Port auf dem Wirt. Im Container bleibt es 8080. -APP_PORT=8080 -# Wer den Port erreichen darf. 127.0.0.1 = nur dieser Rechner (Vorgabe). -# 0.0.0.0 = das ganze Netz – dann unverschlüsselt. -APP_BIND=127.0.0.1 - -# ── Erster Start ────────────────────────────────────────────────────────────── -# Legt den Admin an. Danach ADMIN_PASSWORD leeren. -# Auch hier: ein $ muss $$ heißen (siehe DB_PASSWORD). -ADMIN_USERNAME=admin -ADMIN_PASSWORD=changeme_strong_admin_password - -# ── OIDC (optional) ─────────────────────────────────────────────────────────── -# Leerer OIDC_ISSUER = aus. OIDC_REDIRECT_URL muss exakt -# /api/v1/auth/oidc/callback sein und beim Anbieter eingetragen. -# OIDC_ISSUER=https://your-idp.example.com # Adresse, nicht der Name -# OIDC_CLIENT_ID=aether -# OIDC_CLIENT_SECRET= -# OIDC_REDIRECT_URL=https://cloud.example.com/api/v1/auth/oidc/callback -# Bestehendes Konto verbinden statt ein zweites anzulegen. -# VOR der ersten Anmeldung setzen – danach ist es zu spät. -# OIDC_LINK_BY_USERNAME=true -# Beschriftung des Knopfes auf der Anmeldeseite. -# OIDC_BUTTON_LABEL=Anmelden mit PocketID - -# ── HoneyOS-Leiste ──────────────────────────────────────────────────────────── -# Ursprung, von dem die Leiste geladen wird. Wird an zwei Stellen gebraucht und -# kommt deshalb aus einer Quelle: die CSP erlaubt ihn (script-src + -# connect-src), und die Oberflaeche laedt das Skript von dort nach. -# -# Wirkt beim **Neustart** — kein Neubau noetig. -# Leer = keine Leiste, und die CSP bleibt so eng wie ohne sie. -HONEYBAR_ORIGIN=https://os.example.com -# Ausweis, mit dem sich die Leiste die App-Liste holt. Muss zu HONEYOS_APPS_KEY -# drueben passen. Nur noetig, wenn HoneyOS auf einer anderen Site liegt als -# Aether — dann schickt der Browser die HoneyOS-Sitzung nicht mit. -# -# Gleiche Site (os.beispiel.duckdns.org + cloud.beispiel.duckdns.org): kann leer -# bleiben, die Sitzung reicht. -HONEYBAR_KEY= - -# ── Volltextsuche (OCR) ─────────────────────────────────────────────────────── -# Standardmäßig an; fehlen ocrmypdf/tesseract, schaltet es sich selbst ab. -# OCR_ENABLED=false -# OCR_LANGUAGES=deu+eng # weitere brauchen ein apk-Paket im Dockerfile -# OCR_MAX_PAGES=50 # 0 = unbegrenzt; rund 1 Minute je Seite -# OCR_TIMEOUT=10m # Notbremse je Datei - -# ── App-Umschalter ──────────────────────────────────────────────────────────── -# Andere selbst gehostete Apps im Menü der Seitenleiste. -# Format: Name|URL|Emoji – mehrere durch Komma. Leer = kein Menü. -EXTERNAL_APPS= - -# ── Office (optional) ───────────────────────────────────────────────────────── -# Leer = aus, auch wenn ein Office-Container läuft. Schnipsel: README.md -# collabora | onlyoffice (auch Euro-Office) -OFFICE_PROVIDER= -# Backend → Office. Collabora lauscht auf 9980, OnlyOffice/Euro-Office auf 80. -OFFICE_URL= -# Browser → Office; leer = wie OFFICE_URL -OFFICE_PUBLIC_URL= -# Office → Aether; leer = PUBLIC_URL. -# Weder localhost noch der veröffentlichte Port – beides scheitert. -# **Bei Collabora die öffentliche Adresse der Cloud**, nicht app:8080: Aus -# diesem Wert baut Collabora sein frame-ancestors, und der Browser darf den -# Rahmen sonst nicht zeigen. Siehe README, Abschnitt Office. -OFFICE_WOPI_URL= -# Gegen SSRF; der Host aus OFFICE_URL zählt immer dazu. -OFFICE_ALLOWED_HOSTS= -# Nur OnlyOffice/Euro-Office, mind. 32 Zeichen. Muss mit JWT_SECRET des -# Office-Containers übereinstimmen: Ohne ihn schlägt das Wandeln nach PDF fehl -# (der Server antwortet `{"error":-8}`). Für den WOPI-Weg wird er nicht -# gebraucht, der hat eigene Token. -OFFICE_JWT_SECRET= -OFFICE_INSECURE_TLS=false # nur für Tests mit selbstsigniertem Zertifikat - -# Nur für den Collabora-Container aus deploy/docker-compose.yml. -# Derselbe Host wie in OFFICE_WOPI_URL, aber als regulärer Ausdruck: Punkte -# maskieren, Port anhängen. Beispiel: -# OFFICE_ALIASGROUP=https://cloud\.example\.com:443 -# Fehlt er, lehnt Collabora mit „No authorized hosts found" ab. -OFFICE_ALIASGROUP=http://app:8080 -OFFICE_SSL=false -OFFICE_SSL_TERMINATION=false # true nur, wenn davor wirklich HTTPS endet -OFFICE_PORT=9980 - -# ── Später ──────────────────────────────────────────────────────────────────── -# MEILISEARCH_URL=http://meilisearch:7700 -# MEILISEARCH_KEY= - -# ── WebDAV ──────────────────────────────────────────────────────────────────── -# Läuft unter /dav/, kein Schalter nötig. Der Proxy muss -# PROPFIND/MKCOL/MOVE/COPY durchreichen. diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 8bc1dc7..2321add 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -6,6 +6,9 @@ # Zum **Betreiben** ist docker-compose.example.yml im Wurzelverzeichnis # gedacht: schlanker und ohne Quelltext, es zieht das fertige Abbild. # +# Die Einstellungen kommen aus deploy/.env – als Vorlage dient die eine +# .env.example im Wurzelverzeichnis: cp .env.example deploy/.env +# # Erklärungen zu allem hier: README.md services: