diff --git a/.env.example b/.env.example index 0b14c01..ea7ff07 100644 --- a/.env.example +++ b/.env.example @@ -1,91 +1,58 @@ -# Aether – das Nötigste für eine neue Umgebung. -# -# cp .env.example .env -# -# Vollständige Liste aller Schalter (OIDC, OCR, Thumbnails, Kontingente …): -# deploy/.env.example +# Aether – das Nötigste für eine neue Umgebung. cp .env.example .env +# Erklärungen: README.md, Abschnitt „Einstellungen" +# Alle Schalter: deploy/.env.example # ── Pflicht ─────────────────────────────────────────────────────────────────── - -# Steuert nur die Form der Logausgabe: `development` schreibt sie farbig und -# mehrzeilig, `production` als JSON für deinen Logsammler. APP_ENV=production - - -# Wie die Cloud von außen erreichbar ist. Wird für OIDC-Rücksprünge, -# WOPI-Rückrufe und Freigabe-Links benutzt – ein falscher Wert fällt erst -# dort auf, nicht beim Start. +# Adresse, unter der der Browser die Cloud sieht. Ohne Schrägstrich am Ende. PUBLIC_URL=https://cloud.example.com - # openssl rand -hex 32 SESSION_SECRET= -# `postgres` ist der Dienstname aus dem Compose, nicht localhost – aus dem -# Container heraus ist localhost er selbst. Ohne diese Zeile greift die -# Voreinstellung `localhost`, und der Start endet in „migrations failed". +# DB_HOST ist der Dienstname aus dem Compose, nicht localhost. DB_HOST=postgres DB_PORT=5432 DB_NAME=aether DB_USER=aether -# Jedes Zeichen ist erlaubt, auch % @ : / und Leerzeichen. +# Jedes Zeichen erlaubt, auch % @ : / und Leerzeichen. DB_PASSWORD= -# Wohin die Dateien auf dem Wirt kommen. Ein gewöhnliches Verzeichnis, kein -# Docker-Volume – so kommst du per SFTP oder Dateimanager daran. +# Verzeichnis auf dem Wirt. Nur lesend anfassen – zum Hineinlegen: WebDAV. DATA_PATH=/pfad/zu/aether-data - -# `id -u` und `id -g`. Ohne das gehören die Dateien einem Container-Nutzer. +# `id -u` / `id -g` – sonst gehören die Dateien einem Container-Nutzer. APP_UID=1000 APP_GID=1000 # ── Reverse Proxy ───────────────────────────────────────────────────────────── - -# X-Forwarded-For/-Proto werden NUR von diesen Adressen geglaubt. Steht hier -# das Falsche, sieht die Anwendung überall die IP des Proxys – Rate-Limit und -# Audit-Log werden damit wertlos. +# Nur diesen Adressen wird X-Forwarded-For/-Proto geglaubt. TRUSTED_PROXIES=172.18.0.1 - -# Port auf dem Wirt, gebunden auf loopback. Im Container bleibt es 8080. +# Port auf dem Wirt, auf loopback gebunden. Im Container bleibt es 8080. APP_PORT=8080 # ── Erster Start ────────────────────────────────────────────────────────────── - -# Legt beim allerersten Start den Admin an. Danach hier leeren – das Konto -# bleibt, das Passwort steht sonst dauerhaft in der Datei. +# Legt den Admin an. Danach ADMIN_PASSWORD leeren. ADMIN_USERNAME=admin ADMIN_PASSWORD= # ── Anmeldung über einen Identitätsanbieter (optional) ─────────────────────── -# Leerer OIDC_ISSUER = aus, dann nur Benutzername + Passwort. -# Die Rücksprung-Adresse muss exakt /api/v1/auth/oidc/callback sein -# und beim Anbieter als erlaubte Redirect-URI eingetragen werden. +# Leerer OIDC_ISSUER = aus. OIDC_ISSUER= OIDC_CLIENT_ID=aether OIDC_CLIENT_SECRET= +# Muss exakt /api/v1/auth/oidc/callback sein. OIDC_REDIRECT_URL=https://cloud.example.com/api/v1/auth/oidc/callback - -# Bestehendes Konto mit derselben Kennung verbinden, 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 eigenen Anbieter (PocketID, Authentik), dessen Konten du selbst -# anlegst. Bei freier Registrierung wäre es die Übernahme fremder Konten. +# Bestehendes Konto verbinden statt ein zweites anzulegen. +# VOR der ersten Anmeldung setzen – danach ist es zu spät. OIDC_LINK_BY_USERNAME=false # ── Office (optional) ───────────────────────────────────────────────────────── -# Leer lassen = aus. Den passenden Container-Schnipsel (Collabora, OnlyOffice -# oder Euro-Office) findest du im README – er bringt seine eigenen Variablen -# mit. Hier steht nur, was Aether selbst wissen muss. -# -# Drei Adressen für drei Richtungen, und sie sind selten gleich: -# OFFICE_URL Backend → Office-Server -# OFFICE_PUBLIC_URL Browser → Office-Server -# OFFICE_WOPI_URL Office-Server → Aether (NICHT localhost – aus seinem -# Container heraus ist das er selbst) +# Leer = aus. Container-Schnipsel: README.md OFFICE_PROVIDER= +# Backend → Office OFFICE_URL=http://office:9980 +# Browser → Office OFFICE_PUBLIC_URL=https://office.example.com +# Office → Aether. NICHT localhost, NICHT der veröffentlichte Port. OFFICE_WOPI_URL=http://app:8080 - -# Ziele, die das Backend anfragen darf – gegen SSRF. Nur der Office-Server. +# Gegen SSRF: was das Backend anfragen darf. OFFICE_ALLOWED_HOSTS=office diff --git a/README.md b/README.md index ba9ceaa..5caec96 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,80 @@ 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](#office-collabora--onlyoffice--euro-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 ```bash diff --git a/TESTING.md b/TESTING.md index 0c8b3a4..be8dbb5 100644 --- a/TESTING.md +++ b/TESTING.md @@ -646,6 +646,35 @@ breiteste. --- +## Frische Umgebung nur aus den Beispieldateien (24.08.2026) + +Der einzige Test, der zählt: `docker-compose.example.yml` + `.env.example` +kopieren, Passwörter eintragen, starten. Nichts sonst. + +Beim ersten Durchlauf brachte er sofort einen Fehler ans Licht, den keine +Codeprüfung findet: + +``` +migrations up to date +FTL blob storage init failed error="mkdir /app/data: permission denied" +``` + +`STORAGE_PATH` hat die Voreinstellung `./data` – richtig für ein Binary, das +man von Hand startet, falsch im Container, wo daraus `/app/data` wird statt +des Mounts unter `/data`. Die schlanke `.env.example` führte den Schlüssel +nicht, weil er „eine brauchbare Vorgabe" hat. Hatte er nicht. + +Jetzt setzt das **Abbild** `STORAGE_PATH` und `TMP_PATH` selbst. Es kennt seine +Verzeichnisstruktur besser als jede Beispieldatei; eine `.env` darf weiterhin +überschreiben. + +- [ ] Frische Umgebung nur aus den beiden Beispieldateien → `migrations up to + date`, `admin account ready`, `listening` 🔎 +- [ ] `/health` 200, Anmeldung 200, `data/tmp` liegt auf dem Wirt 🔎 +- [ ] Dabei ein Passwort mit `%` und `@` verwendet 🔎 + +--- + ## Datenbank-Passwort mit Sonderzeichen (24.08.2026) Die Verbindungszeichenkette wurde mit `fmt.Sprintf` zusammengeklebt. Beim diff --git a/deploy/.env.example b/deploy/.env.example index 710133e..5dd2797 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -1,160 +1,89 @@ -# Aether – Environment Variables -# Copy to .env and fill in values. Never commit .env! +# Aether – alle Schalter. cp deploy/.env.example deploy/.env +# 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 ─────────────────────────────────────────────────────────────────────── APP_ENV=production # development | production - -# Internal listen address. Your external reverse proxy forwards here. -APP_LISTEN_ADDR=:8080 - -# Public-facing URL (used for OIDC redirects, WOPI callbacks, share links). -# Must match what the browser sees — include scheme + domain, no trailing slash. +APP_LISTEN_ADDR=:8080 # im Container; der Proxy leitet hierher PUBLIC_URL=https://cloud.example.com - -# Comma-separated IPs or CIDRs of reverse proxies allowed to set -# X-Forwarded-For / X-Forwarded-Proto. Everyone else is untrusted. -# Example: 172.18.0.1,10.0.0.0/8 +# IPs/CIDRs der Proxys, denen X-Forwarded-For/-Proto geglaubt wird. TRUSTED_PROXIES=172.18.0.1 -# ── Database ───────────────────────────────────────────────────────────────── +# ── 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, auch % @ : / und Leerzeichen. DB_PASSWORD=changeme_strong_password -# ── Session ─────────────────────────────────────────────────────────────────── -# Generate with: openssl rand -hex 32 -SESSION_SECRET=changeme_generate_with_openssl_rand_hex_32 -SESSION_MAX_AGE=86400 # seconds (24h) +# ── Sitzung ─────────────────────────────────────────────────────────────────── +SESSION_SECRET=changeme_generate_with_openssl_rand_hex_32 # openssl rand -hex 32 +SESSION_MAX_AGE=86400 # Sekunden -# ── Storage ─────────────────────────────────────────────────────────────────── -STORAGE_PATH=/data +# ── 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 +# Port auf dem Wirt, auf loopback gebunden. +APP_PORT=8080 -# ── Admin seed ──────────────────────────────────────────────────────────────── -# Used only on first startup to create the initial admin account. -# Remove or leave blank after first start. +# ── Erster Start ────────────────────────────────────────────────────────────── +# Legt den Admin an. Danach ADMIN_PASSWORD leeren. ADMIN_USERNAME=admin ADMIN_PASSWORD=changeme_strong_admin_password -# ── OIDC (optional, Phase 4) ───────────────────────────────────────────────── -# Wenn OIDC_ISSUER leer ist, ist OIDC-Login deaktiviert (kein SSO-Button). -# OIDC_REDIRECT_URL muss exakt /api/v1/auth/oidc/callback sein -# und beim IdP als erlaubte Redirect-URI registriert werden. +# ── 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 # 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 -# ── WebDAV ──────────────────────────────────────────────────────────────────── -# Erreichbar unter /dav/ (Basic Auth, Username + Passwort). Kein Setting nötig. -# Reverse Proxy muss PROPFIND/MKCOL/MOVE/COPY durchreichen. +# ── 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 -# ── Office / WOPI ───────────────────────────────────────────────────────────── -# Steht weiter unten, mit allen Adressen. (Hier stand bis zum 24.08.2026 ein -# Rest aus der Planung mit OFFICE_SERVER_URL – den Schlüssel gibt es nicht.) +# ── App-Umschalter ──────────────────────────────────────────────────────────── +# Andere selbst gehostete Apps im Menü der Seitenleiste. +# Format: Name|URL|Emoji – mehrere durch Komma. Leer = kein Menü. +EXTERNAL_APPS= -# ── Features ───────────────────────────────────────────────────────────────── +# ── Office (optional) ───────────────────────────────────────────────────────── +# Leer = aus. Container-Schnipsel: README.md +OFFICE_PROVIDER= # collabora | onlyoffice (auch Euro-Office) +OFFICE_URL= # Backend → Office +OFFICE_PUBLIC_URL= # Browser → Office; leer = wie OFFICE_URL +# Office → Aether; leer = PUBLIC_URL. +# NICHT localhost, NICHT der veröffentlichte Port – beides scheitert. +OFFICE_WOPI_URL= +OFFICE_ALLOWED_HOSTS= # gegen SSRF; Host aus OFFICE_URL zählt immer +OFFICE_INSECURE_TLS=false # nur für Tests mit selbstsigniertem Zertifikat + +# Nur für den Collabora-Container aus deploy/docker-compose.yml: +OFFICE_ALIASGROUP=http://app:8080 +OFFICE_SSL=false +OFFICE_SSL_TERMINATION=false # produktiv true; false ohne HTTPS davor +OFFICE_PORT=9980 + +# ── Später ──────────────────────────────────────────────────────────────────── # MEILISEARCH_URL=http://meilisearch:7700 # MEILISEARCH_KEY= -# ── App-Umschalter ──────────────────────────────────────────────────────────── -# Andere selbst gehostete Apps, die im Menü oben in der Seitenleiste erscheinen. -# Format: Name|URL|Emoji – mehrere durch Komma getrennt. Leer = kein Menü. -# Beispiel: EXTERNAL_APPS=InkNote|https://notes.honeyy.duckdns.org|📓 -EXTERNAL_APPS= - -# ── OIDC: bestehendes Konto verbinden ─────────────────────────────────────── -# Es gibt schon ein Konto mit demselben Benutzernamen, aber ohne Bindung an den -# Identitätsanbieter – was soll passieren? -# -# false (Voreinstellung): Es entsteht ein ZWEITES Konto. Da der Name vergeben -# ist, heißt es "name2" – leer, ohne Adminrechte, ohne deine Dateien. -# true: Das bestehende Konto wird mit der Identität verbunden. Dateien, -# Adminrecht und Freigaben bleiben; ab dann meldest du dich darüber an. -# -# Auf "true" nur, wenn du dem Anbieter glaubst, dass "marc" bei ihm derselbe -# "marc" ist wie hier – also bei einem eigenen Anbieter (PocketID, Authentik), -# dessen Konten du selbst anlegst. Bei einem Anbieter mit freier Registrierung -# wäre es die Übernahme fremder Konten. -# -# Verbunden wird nur, was noch KEINE Bindung hat – ein Konto, das schon zu einer -# anderen Identität gehört, wird nie überschrieben. -# OIDC_LINK_BY_USERNAME=true - -# ── Wo die Dateien liegen ──────────────────────────────────────────────────── -# Ein gewöhnliches Verzeichnis auf dem Wirt, kein Docker-Volume. Damit kommst -# du per SFTP, rsync oder im Dateimanager an deine Dateien – sie liegen dort -# als echte Ordner mit echten Namen (users//home/…). -# -# Nur lesend benutzen: Wer von Hand hineinschreibt, hat eine Datei, die die -# Cloud nicht kennt. Zum Hineinlegen ist WebDAV der Weg. -DATA_PATH=/pfad/zu/aether-data - -# Der Container läuft unter dieser Kennung, damit die Dateien im Verzeichnis -# oben **dir** gehören und nicht einem Container-Nutzer. `id -u` und `id -g` -# verraten deine. -APP_UID=1000 -APP_GID=1000 - -# Port auf dem Wirt – gebunden auf **loopback**, davor gehört dein Reverse -# Proxy. Im Container bleibt es immer 8080; hier steht nur, unter welchem Port -# der Wirt ihn anbietet. -APP_PORT=8080 - -# ── Volltextsuche über den Inhalt (OCR) ────────────────────────────────────── -# Holt den Text aus PDFs und Bildern, damit auch ein Scan namens -# "scan_0042.pdf" über seinen Inhalt auffindbar ist. Das Original wird dabei -# nicht verändert – der Text landet daneben in der Datenbank. -# -# Standardmäßig an. Fehlen ocrmypdf/tesseract im Image, schaltet sich der -# Arbeiter beim Start von selbst ab und die Cloud läuft ohne Volltext weiter. -# OCR_ENABLED=false - -# Sprachmodelle für tesseract. Im Image liegen deu und eng; weitere brauchen -# ein zusätzliches apk-Paket (tesseract-ocr-data-) im Dockerfile. -# OCR_LANGUAGES=deu+eng - -# Höchstzahl erkannter Seiten je Datei. OCR kostet rund eine Minute je -# gescannter Seite – ohne Grenze blockiert ein dickes Handbuch die -# Warteschlange für alles andere. 0 = unbegrenzt. -# OCR_MAX_PAGES=50 - -# Notbremse je Datei, falls eine einzelne Seite entgleist. -# OCR_TIMEOUT=10m - -# ── Office (optional) ──────────────────────────────────────────────────────── -# Leer = aus. Die Anwendung ist ohne Office vollständig. -# -# Beide Anbieter sprechen WOPI, es gibt also nur einen Adapter: -# collabora – LibreOffice-Kern, ~1 GB Arbeitsspeicher, ODF nativ -# onlyoffice – beste .docx/.xlsx-Treue, 2 GB Minimum / 4 GB empfohlen -OFFICE_PROVIDER= -OFFICE_URL= -# Adresse, unter der der **Browser** den Office-Server erreicht. Leer = dieselbe -# wie OFFICE_URL. Die beiden sind selten gleich: Im Compose-Netz heisst der -# Server `collabora`, im Browser muss dort ein aufloesbarer Name stehen. -OFFICE_PUBLIC_URL= -# Adresse, unter der der **Office-Server** Aether erreicht. Leer = PUBLIC_URL. -# Der am leichtesten uebersehene Weg: Der Office-Server holt die Datei selbst -# ab, und aus seinem Container heraus ist „localhost" er selbst. -# Laufen beide im selben Compose-Projekt, ist das der Dienstname: http://app:8080 -# Der veroeffentlichte Port (127.0.0.1:8090) taugt **nicht** – auf loopback -# gebunden, aus einem Container also nicht erreichbar (ECONNREFUSED). -OFFICE_WOPI_URL= -# Adressen, die das Backend anfragen darf – gegen SSRF. Der Host aus OFFICE_URL -# zählt immer dazu; hier kommt nur hinein, was zusätzlich erlaubt sein soll. -OFFICE_ALLOWED_HOSTS= -# Nur für den Testaufbau mit selbstsigniertem Zertifikat auf demselben Rechner. -OFFICE_INSECURE_TLS=false -# Für den mitgelieferten Collabora-Container (docker compose --profile office): -# unter welcher Adresse er Aether erreicht. Punkte maskieren – Collabora liest -# den Wert als regulären Ausdruck. -OFFICE_ALIASGROUP=http://app:8080 -OFFICE_SSL=false -# Endet HTTPS an deinem Reverse Proxy? Produktiv true, im reinen HTTP-Test -# false – sonst verlangt Collabora vom Browser eine wss://-Verbindung, die es -# nicht gibt, und der Editor bleibt leer. -OFFICE_SSL_TERMINATION=false -OFFICE_PORT=9980 +# ── WebDAV ──────────────────────────────────────────────────────────────────── +# Läuft unter /dav/, kein Schalter nötig. Der Proxy muss +# PROPFIND/MKCOL/MOVE/COPY durchreichen. diff --git a/deploy/Dockerfile b/deploy/Dockerfile index b88139e..f1f0950 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -34,6 +34,16 @@ RUN apk add --no-cache ca-certificates tzdata && \ chown -R aether:aether /data WORKDIR /app +# Wo die Blobs liegen. Steht **hier**, nicht nur in der .env: Die Voreinstellung +# im Code ist "./data" – richtig für ein Binary, das man von Hand startet, +# falsch im Container, wo daraus /app/data würde. Genau das ist beim ersten +# Aufbau aus der schlanken .env passiert: Migrationen liefen durch, dann +# "mkdir /app/data: permission denied". Das Abbild kennt seine eigene +# Verzeichnisstruktur besser als jede Beispieldatei; eine .env darf es +# weiterhin überschreiben. +ENV STORAGE_PATH=/data \ + TMP_PATH=/data/tmp + # Tesseract vermisst beim allerersten Aufruf die OpenCL-Geräte und schreibt # dabei einen langen Diagnoseblock nach stderr. ocrmypdf ruft davor # `tesseract --list-langs` auf und liest die Antwort – der Block bringt seinen