Beim Löschen von convert.go ist Aethers einzige Verwendung des JWT-Schlüssels
mitgegangen: signJWT stand dort. Zurück blieb ein Feld auf dem Client, das
niemand liest, plus Config-Eintrag und Aufrufparameter – Go meldet ungenutzte
Strukturfelder nicht, deshalb lief der Build durch. Eine Einstellung, die
nichts mehr tut, ist schlimmer als keine: Wer sie setzt, glaubt, sie wirke.
OFFICE_JWT_SECRET bleibt in der .env, wird aber vom **Office-Container**
gelesen, nicht von Aether. Geprüft mit Euro-Office: JWT_ENABLED=true und
Aether ohne jedes Signieren – der WOPI-Weg läuft.
Doku richtiggestellt, nachdem Marc den Compose-Schnipsel angezweifelt hat:
- „Euro-Office ist hier nie gelaufen" stimmte nicht mehr. Jetzt steht da, was
tatsächlich geprüft ist (Abbild, Start, Discovery, CheckFileInfo, Ausliefern,
mobileEdit) und was nicht: PutFile, also das Zurückschreiben.
- Der Absatz zu /ConvertService.ashx beschrieb gelöschten Code.
- OFFICE_PUBLIC_URL: die Falle benannt, die einen Tag gekostet hat. Stand dort
localhost, lief alles am Docker-Rechner und blieb am Telefon schwarz – dort
ist localhost das Telefon. Ohne Fehlermeldung.
- Zur Portbindung: Die Office-Schnipsel binden auf 0.0.0.0, anders als Aether
selbst. Das ist nötig, weil der Browser direkt hin muss – steht jetzt dabei,
samt dem sauberen Weg über einen Reverse Proxy.
Geprüft: go build/vet sauber, alle 234 Prüfungen der sieben Selbsttest-Suiten
grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Office-Dokument öffnete am Telefon als gewandeltes PDF, weil OnlyOffices
Oberfläche auf 400 Pixeln nichts vom Dokument übrig lässt. Die Begründung war
falsch: Die Ermittlungsliste bietet zu jeder Endung mehrere Aktionen an, und
OnlyOffice/Euro-Office führen neben `edit` auch `mobileEdit` – dieselbe
Adresse mit `?mobile=1`, dahinter eine für kleine Schirme gebaute Oberfläche.
`EditURL` kannte nur `edit` und `view`; die Handy-Fassung lag die ganze Zeit
in der Tabelle und wurde nie abgerufen.
Entschieden wird am Zeigegerät, nicht an der Fensterbreite – ein schmales
Fenster auf dem Schreibtisch ist immer noch eine Maus.
Mit dem Anlass fällt die Notlösung: preview.go (229 Zeilen), convert.go (271)
samt Test, files.preview_version (Migration 000026), die Sonderabfrage im GC,
die Route und dreizehn Fundstellen im Frontend. convert.go hatte keinen
anderen Aufrufer – die gesamte Umwandlung nach PDF existierte allein für
diese Ansicht. Wer ein PDF braucht, exportiert es im Editor. Die Dateien
unter previews/ sammelt der GC ein.
Zwei Fehler, die dabei sichtbar wurden:
fix(history): „Edit" schloss die Datei, statt den Editor zu öffnen. Beim
Wechsel räumt React erst die alte Ansicht ab; der Aufräumer von useBackClose
ruft history.back(), das verzögert wirkt und deshalb den Eintrag verbrauchte,
den der Editor gerade angelegt hatte. Ein Zähler für selbst ausgelöste
Schritte verschluckt genau diese popstate.
fix(filetypes): `.txt` ging gar nicht auf. Es stand in officeExtensions und
lief damit in den Office-Zweig statt in den eingebauten Text-Editor – am
Telefon in die PDF-Umwandlung, die daran scheiterte. `md`, `log` und `rst`
standen nie drin; das war die Asymmetrie.
Dazu Euro-Office als Compose-Profil (Abbild 6,99 GB, nachgemessen – die
Ankündigung nennt 5) und die Android-App als Idee.
Geprüft: go build/vet sauber, tsc sauber, 37 Vitest-Tests, alle 234
Prüfungen der sieben Selbsttest-Suiten grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beides auf einer eigens aufgesetzten Instanz durchgespielt:
* **Update einer laufenden Instanz**: Schema 19 -> 23 mit Daten in der
Datenbank. Konten, Ordner, Dateien und Etikettenzuordnungen unveraendert,
kein dirty-Zustand, /health 200. Ein Neuaufbau bringt nichts, was das
Update nicht auch bringt.
* **Grosser Wiederherstellungsfall**: Container und Datenbank weggeworfen,
nur die Dateien aus der Sicherung zurueck. Funktioniert -- aber nur, wenn
die Konten vorher mit *demselben* Benutzernamen wieder angelegt werden.
Der Ordnername auf der Platte ist die Zuordnung. Ohne diesen Schritt
bleibt der Abgleich stehen und sagt es je Datei ("Konto ... gibt es
nicht"); er raet nicht.
Danach standen Ordnerkette, Unterordner, Umlaute, Groessen, Pruefsummen
und Dateitypen wieder da, der Download lieferte den urspruenglichen
Inhalt, .trash und .versions blieben aussen vor.
Dazu aufgeschrieben, was aus einer reinen Dateisicherung **nicht**
zurueckkommt: Etiketten, Absender, Belegdatum, Freigabelinks, fruehere
Fassungen, Kalender und Termine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aether fuehrt zwei Buecher: die Bytes auf der Platte und die Eintraege in
der Datenbank. Wer Dateien von aussen hineinlegt -- per SFTP oder aus
einer Sicherung zurueckgeholt --, erzeugt nur das eine. Die Datei liegt
da, Aether weiss nichts von ihr, und der GC haelt sie nach 24 Stunden
fuer einen Rest und loescht sie.
Neu deshalb internal/rescan, die Gegenrichtung zum GC, und ein Knopf
unter Admin -> Audit. Drei Faelle:
Datei da, kein Eintrag Eintrag anlegen, samt Ordnerkette,
Pruefsumme und Dateityp
Eintrag da, Datei fort Eintrag entfernen, dazu Vorschaubild und
fruehere Fassungen
von aussen ersetzt Groesse und Pruefsumme uebernehmen
Zwei Schritte, nie einer: Check liest nur, Apply aendert. Ein Knopf, der
beides in einem taete, gaebe niemandem die Gelegenheit, vorher
hinzusehen -- und was hier geloescht wird, ist nicht der Papierkorb.
Nicht angefasst werden .trash/ und .versions/. Beide liegen im selben
Baum, gehoeren aber dem System: Wuerden sie uebernommen, tauchten
geloeschte Dateien wieder auf und jede fruehere Fassung wuerde zu einer
eigenen Datei.
Zwei Fallen, die erst der Praxistest zeigte:
- Der Dateityp muss aus Endung *oder Inhalt* kommen. Beim Hochladen
schickt der Browser ihn mit; eine per SFTP abgelegte Datei bringt
niemanden mit, der ihn kennt. Dazu hat das Alpine-Abbild keine
/etc/mime.types, und Gos eingebaute Liste kennt nicht einmal .txt --
jede Textdatei waere als application/octet-stream gelandet.
- COALESCE($1, $2) ohne Cast: Postgres haelt die Parameter dann fuer
Text und bricht mit "operator does not exist: uuid = text" ab. Die
Ordnerkette wurde nie angelegt.
Die Pruefsumme wird trotz des vollstaendigen Lesevorgangs berechnet. Ein
Eintrag ohne sie waere ein Eintrag, dessen Beschaedigung beim
Herunterladen niemand bemerkt -- genau die Zusage, die Aether sonst
ueberall einhaelt.
scripts/rescan-selbsttest.py mit 20 Pruefungen in fuenf Gruppen, darunter
"Pruefen aendert nichts" und "Papierkorb bleibt unangetastet". Nebenbei
raeumt der Freigabe-Selbsttest jetzt auch seinen Papierkorb und nimmt
einen eindeutigen Kontonamen je Lauf -- ein abgebrochener Lauf blockierte
sonst jeden weiteren mit 409.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Kalender kam im README bislang mit keinem Wort vor -- weder in der
Phasentabelle noch als Abschnitt, obwohl er ein eigener Zugangsweg mit
eigener Anmeldung ist. Jetzt steht er neben WebDAV, wo er hingehoert:
gleiche Adresse-plus-App-Passwort-Logik, gleiche Sperre nach zehn
Fehlversuchen.
Zwei Dinge stehen dort ausdruecklich, weil sie beim Einrichten sonst
eine halbe Stunde Suche kosten:
- Die Kalenderfarbe holt DAVx5 nur beim Erfassen der Sammlungen, nicht
bei jedem Abgleich. Sie bleibt im Telefon alt, bis man dort einmal
"Kalenderliste aktualisieren" antippt. Am Geraet nachgemessen.
- Kalender verwalten kann die Kalender-App des Telefons gar nicht;
Android ueberlaesst das der Sync-App.
Im Plan die Phasen 1 bis 3 auf erledigt, und darunter das, was ueber die
Phasen hinaus dazukam, weil es sich beim Pruefen als noetig erwies:
Umhaengen, EXDATE, getctag/calendar-color, Kalenderverwaltung vom Client
aus, die beiden Selbsttests.
Abbild veroeffentlicht als 2026.09.06 und latest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beide waren Ballast, aber aus verschiedenen Gründen.
Der App-Umschalter (EXTERNAL_APPS, AppSwitcher.tsx) hing seit dem
30.07.2026 nirgends mehr in der Oberfläche – auf Marcs Wunsch damals aus
der Seitenleiste genommen, "weil eine gemeinsame Leiste für alle Apps
noch überlegt wird". Diese Leiste gibt es inzwischen: die HoneyBar. Das
Backend las die Variable trotzdem weiter und lieferte external_apps in
der öffentlichen Konfiguration aus. Weg damit, samt Typ und Parser.
HONEYBAR_KEY war der Ausweis für die App-Liste, wenn HoneyOS auf einer
fremden Site liegt. Bei Marc liegen honeyos.duckdns.org und
cloud.honeyos.duckdns.org unter derselben Site, das Sitzungscookie fließt
also mit. Nachgesehen, wie HoneyOS entscheidet: Es prüft zuerst die
Sitzung (SubFromRequest) und greift erst danach zum Schlüssel – die
Leiste kommt hier ohne ihn an ihre Liste. Gedacht war er für Widgets;
die sind vertagt.
Aether schickt damit kein data-key mehr; HoneyOS behält seine Seite der
Prüfung, für den Fall, dass später doch eine App auf fremder Domain dazu
kommt.
Nachgeprüft: go build, go vet und die Tests laufen; das Abbild baut ohne
TypeScript-Fehler; die öffentliche Konfiguration liefert weder
honeybar_key noch external_apps; im gebauten Bundle kommt data-key nicht
mehr vor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auf Marcs Wunsch. Die bisherige Vorgabe 127.0.0.1 passt nur, wenn der
Reverse Proxy auf demselben Rechner läuft – steht er woanders (bei Marc
auf einem VPS), kommt er an loopback gar nicht heran, und die Vorgabe war
schlicht der falsche Wert für den häufigeren Fall.
Der Preis steht jetzt an allen drei Stellen dabei, statt nur gemeint zu
sein: Der Port ist dann auch aus dem eigenen Netz offen, und TLS endet
erst am Proxy. Im README dazu eine Tabelle, welcher Wert zu welchem
Aufbau gehört – eigener Rechner, anderer Rechner, Tunnel, oder Proxy im
selben Docker-Netz (dann braucht es gar kein ports:).
build/docker-compose.yml behält 127.0.0.1: Das ist die
Entwicklungsumgebung auf diesem Rechner, dort ist loopback richtig.
Gegengeprüft: Frische Kopie aus den Beispieldateien veröffentlicht auf
0.0.0.0; APP_BIND=127.0.0.1 in der .env engt wieder auf loopback ein.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vier Schlüssel darin steuerten den mitgelieferten Collabora-Container
statt Aether: OFFICE_ALIASGROUP, OFFICE_SSL, OFFICE_SSL_TERMINATION,
OFFICE_PORT. Wer Office selbst betreibt, richtet das in dessen eigener
Compose-Datei ein – in einer Vorlage für den Betrieb der Anwendung haben
sie nichts zu suchen.
Sie stehen jetzt kommentiert in build/docker-compose.yml, wo sie wirken;
die Erklärung zu aliasgroup1 (regulärer Ausdruck, Punkte maskieren, Port
anhängen) ist dorthin mitgewandert. Die Rückfallwerte im Compose gab es
schon, ein frischer Aufbau ohne diese vier läuft also weiter.
Meilisearch ist als "noch nicht gebaut" gekennzeichnet, statt einen
Schalter vorzutäuschen: Kein Go-Code liest die beiden Werte, gesucht wird
mit der Volltextsuche von Postgres.
Der Rest bleibt ausführlich – die Vorlage ist auch die Übersicht, was
möglich ist.
Gegengeprüft: Beide Compose-Dateien lösen mit der gekürzten Vorlage als
einziger .env sauber auf; Collabora bekommt aus den Rückfällen
aliasgroup1=http://app:8080 und ssl.enable=false.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"deploy" klang nach Ausrollen, drin steht das Gegenteil: das Bau-Rezept
und das Compose, das aus dem Quelltext baut. Ausgerollt wird mit
docker-compose.example.yml im Wurzelverzeichnis. Marc ist genau darüber
gestolpert.
Zwei Dinge mussten dafür vorher fest werden, sonst hätte das Umbenennen
die Entwicklungsdatenbank gekostet: Compose leitet den Projektnamen aus
dem Ordnernamen ab und hätte unter "build" ein neues, leeres Projekt mit
neuem, leerem Volume angelegt. Der Projektname steht deshalb jetzt im
Compose (name: aether), und das Volume trägt seinen alten Namen
ausdrücklich weiter (deploy_postgres_data) – unschön, aber die Daten
bleiben.
Ebenfalls angepasst: .gitignore. Dort stand deploy/.env; nach dem
Umbenennen wäre build/.env nur noch von der allgemeinen .env-Zeile
gedeckt gewesen.
Nachgeprüft nach dem Umzug: Abbild baut, Stapel startet, 113 Dateien in
der Datenbank wie vorher, Volume unverändert deploy_postgres_data,
GET / → 200.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Es gab sie doppelt: schlank im Wurzelverzeichnis (für den Betrieb) und
vollständig unter deploy/ (für die Entwicklung). Gedacht war das als
Arbeitsteilung, geworden ist es Drift. Der produktiven fehlten zwölf
Schlüssel – darunter SESSION_MAX_AGE, STORAGE_PATH und die
HoneyOS-Leiste –, und sie empfahl für OFFICE_WOPI_URL weiterhin
app:8080, also genau den Wert, an dem Collabora den Rahmen verweigert.
Jetzt eine Datei im Wurzelverzeichnis für beides, nach Abschnitten
geordnet, mit einem eigenen Abschnitt für die vier Werte, die nur den
Entwicklungs-Compose betreffen. deploy/.env.example ist weg, die
Verweise in README, TESTING und deploy/docker-compose.yml zeigen auf die
verbliebene Datei.
Gegengeprüft: Jeder Schlüssel, den config.go liest, kommt darin vor
(40 Stück, kein Rest). Ein Container mit dieser Datei als env_file
liefert die erwarteten Werte, auch den maskierten OFFICE_ALIASGROUP.
Dabei selbst in die dokumentierte Falle getappt und wieder heraus:
"OFFICE_PROVIDER= # collabora | onlyoffice" macht den Kommentar zum
Wert. Der Test aus TESTING.md hat es gefunden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Marc flog trotz täglicher Nutzung heraus: Die Frist lief ab dem
Anmelden. Ein Ablauf, der den Anwesenden trifft und nicht den
Abwesenden, erkauft keine Sicherheit, sondern nur Ärger.
Jetzt verlängert jeder angemeldete Aufruf die Sitzung – höchstens einmal
pro Stunde, sonst trüge jede Antwort ein Set-Cookie (beim Laden einer
Dateiliste dreißig Stück). Vorgabe 30 Tage ab letzter Benutzung;
bestehende .env-Werte bleiben unangetastet.
security(auth): Frist galt nur im Browser, nicht für die Signatur
Beim Bauen gefunden, war schon vorher drin: NewSessionStore ersetzte
store.Options, ohne store.MaxAge() zu rufen. Die Codecs behielten damit
gorillas Voreinstellung von 30 Tagen. Wirkung: Der Browser warf das
Cookie nach SESSION_MAX_AGE weg – wer den Wert vorher herauskopiert
hatte, konnte ihn 30 Tage lang weiterbenutzen. Wer die Frist kürzte, um
sie zu kürzen, hatte nichts gekürzt.
Mit Test festgehalten, und der Test ist gegengeprüft: Ohne den Aufruf
schlägt er fehl ("abgelaufene Sitzung gilt weiter"), mit ihm läuft er
durch.
Ein Fehler steckte dabei zuerst im Test selbst: gorilla legt die
entschlüsselte Sitzung im Zusammenhang der Anfrage ab. Zweimal dieselbe
Anfrage zu befragen prüfte den Zwischenspeicher, nicht die Frist. Jetzt
baut jede Prüfung eine frische Anfrage.
Live gegengeprüft: Anmeldung setzt das Cookie, drei angemeldete Aufrufe
danach setzen keins (die Bremse greift).
Dazu die gemessenen Leerlaufwerte der Office-Server im README statt der
Zahlen aus deren Dokumentation (Collabora 51 MB, OnlyOffice 396 MB).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Regel galt für beide Anbieter – das war falsch. Marcs Kritik betraf
nur OnlyOffice: Es baut auf dem Telefon dieselbe Oberfläche auf wie am
PC. Collabora bringt für kleine Schirme eine eigene Ansicht mit, die
taugt; dort wäre der Umweg über ein PDF ein Rückschritt.
Der Anbieter steht jetzt in der öffentlichen Konfiguration
(office_provider), und die Oberfläche entscheidet daran. Der Endpunkt
selbst kann weiterhin beides.
Nachgeholt: Der ganze Weg ist jetzt auch mit Collabora gemessen, nicht
nur mit OnlyOffice – gegen die Dev-Instanz, die damit läuft.
erster Abruf 0,347 s, zweiter 0,009 s, eine Datei auf der Platte
(OnlyOffice zum Vergleich: 1,05 s – es muss die Datei erst abholen)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Am Finger öffnet ein Office-Dokument jetzt das gewandelte PDF im eigenen
Betrachter; "Edit" daneben führt trotzdem in den Editor. Mit Maus ändert
sich nichts.
Der Web-Editor ist eine vollständige Textverarbeitung – zwischen seinen
Leisten bleibt auf 400 Pixeln vom Dokument ein Rechteck. Collabora und
OnlyOffice verhalten sich gleich; deshalb kein besserer Bedienmodus,
sondern ein anderes Werkzeug. Gewandelt wird von dem Server, der auch
bearbeitet: Sonst sähe man unterwegs ein LibreOffice-Layout eines
Dokuments, für das man OnlyOffice gewählt hat.
Je Dokument gibt es GENAU EIN PDF, und das ist keine Frage des
Aufräumens, sondern des Schlüssels: previews/<id>.pdf trägt die Fassung
nicht im Namen, eine neue Fassung überschreibt. files.preview_version
sagt, ob das Abgelegte noch passt.
Sicherheit: Die Adresse, die OnlyOffice zurückgibt, wird von uns
abgerufen – der Wähler des Transports lässt nur OFFICE_ALLOWED_HOSTS zu,
zusätzlich eine ausdrückliche Prüfung mit Test. JWT-Signatur (HS256) für
die eigene API des Dokumentenservers. Fremde Kennung 404, ohne Anmeldung
401. Das Ergebnis muss mit %PDF- beginnen, sonst landete eine
Fehlerseite im Zwischenspeicher.
Gemessen gegen einen echten OnlyOffice-Server im Docker-Netz:
erster Abruf 1,05 s -> zweiter 0,009 s
erneut hochgeladen -> Fassung 2, preview_version 2, weiterhin 1 Datei
GC: herrenloses PDF von vorgestern weg, echtes bleibt
endgültig gelöscht -> PDF weg; Papierkorb -> PDF bleibt
ohne Anmeldung 401, fremde Kennung 404, Nicht-Office 415
Dazu im Admin-Bereich neu: Platzbedarf der abgeleiteten Daten
(Vorschaubilder, PDFs, Profilbilder). Sie zählen gegen kein Kontingent –
ein Ordner, der wächst, ohne dass ihn jemand sieht, ist eine stille
Überraschung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Collabora-Schnipsel empfahl `aliasgroup1: http://app:8080` und
`OFFICE_WOPI_URL=http://app:8080`. Beides kann nicht funktionieren,
sobald der Browser die Cloud unter einer anderen Adresse sieht als der
Docker-Dienstname – also immer:
* Collabora baut aus WOPISrc sein frame-ancestors. Steht dort der
interne Name, lehnt der Browser die Einbettung ab.
* aliasgroup1 ist die Positivliste der WOPI-Hosts. Steht der Host
nicht drin: "No authorized hosts found matching the target host".
Beide Zeilen nennen jetzt die öffentliche Adresse, aliasgroup1 als
regulären Ausdruck mit Port. Dazu die Begründung im Text (samt der
Folge, dass Collabora die Datei dann über den Reverse Proxy holt), zwei
Zeilen in der Fehlertabelle und derselbe Hinweis in der .env.example.
Außerdem der Kenntnisstand zu OnlyOffice nachgezogen: Start, Umwandlung
nach PDF und JWT sind hier inzwischen gemessen, der WOPI-Kreis nicht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Schickt der Anbieter ein `picture`, wird es bei der Anmeldung geholt,
mittig auf 256x256 zugeschnitten und oben rechts angezeigt. Fehlt es
oder scheitert das Holen, bleibt es beim Buchstabenkreis - die
Anmeldung selbst hängt nicht davon ab.
Die Bildadresse kommt aus einem Token, also von außen. Erlaubt ist
deshalb ausschließlich der Ursprung aus OIDC_ISSUER, Weiterleitungen
eingeschlossen; dazu 4 MB und 5 Sekunden als Deckel. Ohne das wäre es
eine Aufforderung an den Server, für einen Fremden beliebige Adressen
im internen Netz anzufragen.
Neu: Migration 000019 (avatar_key/-source/-updated_at), Paket
internal/avatar, Route GET /api/v1/users/{id}/avatar. Der GC durchsucht
"avatars/" mit und kennt die Schlüssel - sonst räumte er die Bilder
nach einem Tag weg.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Knopf hiess fest "Sign in with SSO / OIDC". Der Anbieter heisst bei
jedem anders; "Anmelden mit PocketID" sagt dem, der davorsteht, mehr.
Vorgabe unveraendert. Der Wert geht nur ueber /public/config, wenn OIDC
ueberhaupt an ist.
Beim Testen dabei selbst einen Fehler gebaut und eingefangen: Ein
Kommentar hinter einem LEEREN Wert wird in einer .env zum Wert.
OIDC_ISSUER= # z. B. https://... startete den OIDC-Aufbau mit dem
Kommentartext als Adresse, und der Start brach ab.
In deploy/.env.example steckten vier weitere solche Zeilen, darunter
OFFICE_PROVIDER= # collabora | onlyoffice - Office waere mit Unsinn als
Anbieter eingeschaltet worden. Alle umgestellt, Warnung an den Dateikopf.
Dazu im README belegt, was beim Ausschalten der Versionierung passiert:
Fassungen bleiben, auch im Papierkorb; erst das Leeren entfernt sie samt
Blobs, und zwar sofort statt erst beim GC.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Zahl stand doppelt im Quelltext: als const im Backend und noch einmal
im Verlaufs-Dialog, verbunden nur durch den Kommentar "muss zu
maxVersionsKept im Backend passen". Ein Satz ist keine Zusicherung - beim
ersten Aendern haette der Dialog eine andere Zahl genannt als die, die der
Server anwendet.
Jetzt eine .env-Variable (Vorgabe 5), die der Server ueber
/api/v1/public/config mitliefert. Werte unter 1 werden verworfen: Die Zahl
geht als OFFSET in die Aufraeumabfrage, und OFFSET 0 loeschte jede Fassung -
aus "keine Grenze" wuerde "kein Verlauf".
Gemessen: MAX_VERSIONS_KEPT=3, Datei siebenmal ueberschrieben, uebrig
bleiben v6, v5, v4 bei aktueller v7.
Dazu ein README-Abschnitt "Versionen" - Standard aus, warum das so ist,
Kontingent, und dass jedes Strg+S in Office eine Fassung anlegt. Bisher
stand zu alldem nichts im README.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nach dem Hin und Her um Profile, Ports und Escaping war Sediment
liegengeblieben:
- README: "Drei Adressen fuer drei Richtungen" stand zweimal; die
Profil-Erklaerung war eine Chronik ("das war nicht immer so") statt
einer Anleitung. Jetzt ein Absatz.
- .env.example (beide): der Office-Block begann mit einem Hinweis auf
COMPOSE_PROFILES, den man nur braucht, wenn man selbst ein Profil
anlegt - als erste Zeile eines Abschnitts unbrauchbar. Raus.
- TESTING: der grosse Office-Abschnitt behauptete noch "Getestet mit
Collabora im Compose-Profil office" und pruefte "ohne --profile startet
der Container nicht". Beides ueberholt.
- Umlaute vereinheitlicht (laeuft neben gehoert), $-Hinweis von vier auf
zwei Zeilen.
Dazu zwei neue Zeilen in der Fehlertabelle des README: Container entsteht
gar nicht (Profil) und Container laeuft, aber kein Menuepunkt (leerer
OFFICE_PROVIDER oder falscher Port).
Geprueft: beide Compose-Dateien gueltig, jeder Schluessel aus dem Code
steht in deploy/.env.example, keine doppelten Absaetze mehr.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
profiles: [office] war als Sparmassnahme gedacht: Der Container laeuft nur
auf Wunsch. In Portainer/Dockge/Komodo gibt es aber kein --profile, und der
Dienst entstand dort nie – ohne Fehlermeldung, ohne Hinweis, ohne Weg ueber
die Bedienung.
Eine Voreinstellung, bei der das Einfuegen des Schnipsels nichts bewirkt,
ist die falsche. Alle drei Schnipsel starten jetzt mit (geprueft: app office postgres). Abschalten: docker compose stop
office. Wer ein Profil will, haengt die Zeile selbst an.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Office-Container entstand nie, ohne Fehlermeldung: profiles: [office]
startet nur mit --profile office, und in Portainer/Dockge/Komodo gibt es
diesen Schalter gar nicht.
COMPOSE_PROFILES=office in der .env wirkt genauso und braucht keine
Kommandozeile – Compose liest die Variable fuer sich selbst. Gemessen mit
den echten Dateien: ohne die Zeile 'app postgres', mit ihr
'app office postgres'.
Steht jetzt in beiden .env.example, beiden Compose-Kopfzeilen, README und
TESTING.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die README-Schnipsel fuer OnlyOffice und Euro-Office verlangen
OFFICE_JWT_SECRET, in den Beispieldateien stand es nicht – Compose warnt
dann nur 'variable is not set' und der Dokumentenserver laeuft mit leerem
Geheimnis.
Dazu praeziser, wo es bei OnlyOffice/Euro-Office nach Aktenlage haken
wuerde: IP-Filter des Integrators und Proof-Key-Signatur. Beides
ungeprueft, aber besser benannt als verschwiegen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nachfrage aus der Praxis: printenv zeigt nach dem Escapen ein einzelnes $,
und das sah nach einem zweiten Fehler aus. Es ist das gewuenschte Ergebnis –
angemeldet wird mit dem einfachen $. Dazu der Hinweis, dass einfache
Anfuehrungszeichen dasselbe ohne Verdopplung leisten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein $ im Wert wird von Docker Compose als Variable gelesen und schneidet
ab: Sicher$Passwort2026 kommt als Sicher an (gemessen). Angelegt wird ein
Konto mit einem Passwort, das niemand kennt; die Anmeldung scheitert mit
'invalid credentials', obwohl alles richtig eingetippt ist. Die Datenbank
laeuft dabei weiter, weil Postgres denselben abgeschnittenen Wert bekommt.
Der Start nennt jetzt die Laenge des Passworts (nicht den Wert) und warnt
unter 12 Zeichen mit dem Hinweis auf $$. Ohne diese Zahl war der Fehler
von aussen nicht zu sehen: Compose meldet zwar 'variable is not set', aber
auf stderr beim up, nicht im Container-Log.
Escape-Tabelle in README und beiden .env.example. Ein Backslash hilft
nicht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Port war fest auf 127.0.0.1 gebunden. Richtig, solange ein Reverse
Proxy davorsteht – beim Ausprobieren in einer Testumgebung heißt es aber
ERR_CONNECTION_REFUSED, sobald man über die IP zugreift, und nichts im
Log deutet darauf hin: Der Kernel lehnt ab, bevor die Anwendung etwas
davon sieht.
APP_BIND ist jetzt eine Variable, Vorgabe bleibt 127.0.0.1. Gilt für die
App und für die Office-Container in allen drei README-Schnipseln.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Das Wurzel-Beispiel enthielt keinen einzigen OIDC-Schlüssel – wer sich
über PocketID anmelden will, fand nirgends, wohin damit. Jetzt drin,
samt der Warnung zu OIDC_LINK_BY_USERNAME: vor der ersten Anmeldung
setzen, danach existiert das zweite Konto schon.
Der Office-Container ist aus dem Beispiel raus. Es gibt drei zur Auswahl,
jeder bringt eigene Variablen mit, und man will genau einen – sie stehen
jetzt als anhängbare Schnipsel im README (Collabora, OnlyOffice,
Euro-Office). Alle drei gegen das Beispiel geprüft: docker compose config
läuft durch.
Euro-Office (seit 09.06.2026, in Nextcloud Hub 26) ist ein
OnlyOffice-Fork und läuft über denselben Adapter – OFFICE_PROVIDER
bleibt onlyoffice, nur ein anderes Abbild.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Compose in deploy/ war zu über der Hälfte Kommentar (73 von 137
Zeilen) und mischte zwei Zwecke. Jetzt getrennt:
Wurzel docker-compose.example.yml + .env.example – betreiben,
zieht das fertige Abbild, braucht keinen Quelltext
deploy/ docker-compose.yml + .env.example – entwickeln,
baut aus dem Quelltext, kennt alle Schalter
Die schlanke .env.example führt nur die Pflichtwerte und Office; die
vollständige Liste mit Erklärungen bleibt in deploy/.env.example.
Die kopierte /docker-compose.yml ist ignoriert – sie gehört in die
Umgebung, nicht ins Repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Abbild lag zuerst unter gitea…/honey/aether – also beim Nutzer statt
bei der Organisation, unter der die anderen Docker-Projekte liegen. Neuer
Pfad: gitea…/docker/aether, gleicher Digest. Das alte Paket ist gelöscht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA.
Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb,
Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming,
Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office
(Collabora/OnlyOffice).
Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort)
sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>