Release v1.0.1 #2

Merged
AxonByteDev merged 10 commits from beta into main 2026-09-14 13:37:17 +00:00
Owner
  • fix: abgebrochener Beta-Lauf überspringt keine Nummer mehr
  • fix: Update-Protokoll scrollt in sich, nicht die ganze Seite
  • fix: Deinstallation behandelt das Datenbank-Volume richtig
  • feat: PostgreSQL im Betrieb, SQLite für Tests und lokal
  • fix: Container und Update-Dienst benutzen dieselbe UID
  • docs: Diagnose für einen stehengebliebenen Update-Watcher
  • fix: Abmelden und Version stehen am Ende des Menüs
  • fix: Update-Watcher wird beim Installieren auch gestartet
  • fix: Seitenleiste überlagert sich nicht mehr, Installation ohne Zwangslektüre
  • fix: Installationsbefehl setzt LESSCHARSET
- fix: abgebrochener Beta-Lauf überspringt keine Nummer mehr - fix: Update-Protokoll scrollt in sich, nicht die ganze Seite - fix: Deinstallation behandelt das Datenbank-Volume richtig - feat: PostgreSQL im Betrieb, SQLite für Tests und lokal - fix: Container und Update-Dienst benutzen dieselbe UID - docs: Diagnose für einen stehengebliebenen Update-Watcher - fix: Abmelden und Version stehen am Ende des Menüs - fix: Update-Watcher wird beim Installieren auch gestartet - fix: Seitenleiste überlagert sich nicht mehr, Installation ohne Zwangslektüre - fix: Installationsbefehl setzt LESSCHARSET
Auf einem frisch aufgesetzten Server meldet less beim Durchsehen des
Installers "install.sh may be a binary file. See it anyway?" und zeigt nichts
an.

Die Datei ist in Ordnung. Eine frische Installation startet meist in der
Locale C, und less hält die Umlaute im Skript dann für Binärdaten. Ausgerechnet
der Schritt, der zum Lesen vor dem Ausführen einlädt, sah damit aus, als sei
der Download beschädigt.

LESSCHARSET=utf-8 im Aufruf räumt das aus, in README, Installationsanleitung
und im Kopf des Skripts selbst. Die Anleitung nennt zusätzlich die Meldung im
Wortlaut, damit sie beim Suchen gefunden wird.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zwei Sachen aus dem echten Betrieb.

**Seitenleiste.** Der Fußbereich mit Abmelden-Knopf und Versionszeile stand
absolut am unteren Rand, die Menüliste lief im normalen Fluss darüber hinweg.
Sobald die Liste höher war als der Bildschirm — Administratoren sehen drei
Punkte mehr als andere — schob sie sich darunter und "Abmelden" lag über
"Update".

Jetzt ist die Leiste eine Flex-Spalte aus drei Zonen: Logo und Fuß behalten
ihre Höhe, die Liste nimmt den Rest und scrollt bei Bedarf. Damit kann sich
nichts mehr überlagern, unabhängig von Fensterhöhe und Anzahl der Einträge.
Eine Trennlinie über dem Fuß macht die Grenze sichtbar.

Das Klappmenü auf dem Handy hat dieselbe Liste und deshalb dasselbe Problem
von unten: Ohne Deckel reicht es über den unteren Bildschirmrand hinaus und
die letzten Einträge sind nicht erreichbar. Es bekommt eine Maximalhöhe und
scrollt.

Geprüft mit einem Vorher-Nachher-Rendering bei absichtlich niedrigem Fenster.

**Installation.** Der Einzeiler schob das komplette Skript durch less, bevor
überhaupt etwas passierte. Das steht jetzt nicht mehr im Weg — heruntergeladen
wird weiterhin erst und dann ausgeführt, statt curl direkt in bash zu leiten,
sodass sich die Datei ansehen kann, wer möchte. Wie das trotz Locale C lesbar
wird, steht in der Installationsanleitung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Das Selbst-Update blieb auf einem frisch installierten Server bei "Warte auf
den Host-Dienst …" stehen, ohne dass irgendwo ein Fehler auftauchte.

Der Installer rief `systemctl enable` auf, aber nie `start`. Damit war
filadex-updater.path zwar für den nächsten Systemstart vorgemerkt, lief aber
nicht — niemand sah die Datei data/update.request. Die Kopfzeilen der Units
nennen `enable --now`; der Installer wich davon ab. Bis zum ersten Neustart
des Servers konnte das Selbst-Update also gar nicht funktionieren.

Nebenbei: Nach dem Bauen wird jetzt auch filadex.service gestartet. Gebaut
wird direkt über docker compose, weil nur dort --build möglich ist; systemd
hielt die Unit deshalb für "inactive", während FilaDex längst lief.

Die zweite Hälfte des Problems saß im Backend. Eine liegengebliebene
Anforderungsdatei sperrte jedes weitere Update dauerhaft: "Es läuft bereits
ein Update — bitte abwarten", obwohl gar nichts lief und auch nie etwas
gelaufen war. Aus einer ausgefallenen Aktualisierung wurde so eine, die sich
nicht einmal wiederholen ließ.

FilaDex verwirft eine Anforderung jetzt, wenn seit über zehn Minuten weder an
ihr noch am Protokoll etwas passiert ist. Zehn Minuten sind sicher: Solange
der Host-Dienst arbeitet, schreibt auch der Docker-Build fortlaufend in
update.log. Das Protokoll nennt dann den wahrscheinlichen Grund und den
Befehl zum Nachsehen.

docs/Selbst-Update.md beschreibt den Fall samt Diagnose.

359 Backend-Tests, 33 Frontend-Tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der Fußbereich der Seitenleiste war unten festgenagelt, während die Menüliste
im normalen Fluss darüber hinweglief. Der vorige Versuch hat daraus drei Zonen
gemacht — half, war aber mehr Aufbau als nötig.

Jetzt hängen Abmelden und die Versionszeile schlicht am Ende der Liste und
scrollen mit. Es gibt keinen festgenagelten Bereich mehr, also auch nichts, was
sich überlagern könnte — unabhängig davon, wie hoch das Fenster ist und wie
viele Einträge dazukommen. Eine Trennlinie über "Abmelden" hält es optisch
auseinander.

Das Klappmenü auf dem Handy zeigt die Versionszeile jetzt ebenfalls. Vorher
stand sie nur in der Seitenleiste und war vom Telefon aus gar nicht zu sehen.

Dazu die Versionsanzeige selbst: APP_VERSION kommt aus dem Git-Tag, und ohne
erreichbaren Tag liefert `git describe` den Commit-Hash. Daraus wurde "v48200c6"
— ein "v" vor einem Hash behauptet eine Versionsnummer, die es nicht gibt. Sieht
der Wert nicht nach einer Version aus, steht dort jetzt "Stand 48200c6".

Geprüft mit einem Rendering ans Ende der Liste gescrollt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drei Befunde lassen sich auseinanderhalten — Watcher läuft nicht, systemd hat
den Dienst wegen zu häufiger Startversuche gesperrt, oder die Anforderung ist
längst abgearbeitet. Alle drei sehen in update.log gleich aus: still.

Dazu der Hinweis, dass geänderte systemd-Units bestehende Installationen nicht
über das Selbst-Update erreichen. Der Updater läuft als filadex, nicht als
root, und fasst /etc/systemd/system nicht an.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Das Selbst-Update konnte auf einem frisch installierten Server nie
funktionieren. Im Journal:

    apply-update.sh: /opt/filadex/data/update.log: Permission denied
    rm: cannot remove '/opt/filadex/data/update.request': Permission denied

Der Prozess im Container lief fest auf UID 1000, der Dienstbenutzer auf dem
Host dagegen auf dem, was `useradd --system` gerade frei fand — also unter
1000. Beide greifen über den Bind-Mount auf denselben Ordner zu. Der Container
legte dort Dateien an, die der Update-Dienst weder beschreiben noch löschen
konnte.

Der Kommentar im Dockerfile beschrieb die Annahme sogar ("muss mit den Rechten
des host-seitigen Bind-Mounts übereinstimmen") — der Installer hat sie nur nie
erfüllt: Er setzte data/ auf 1000:1000, also auf die Kennung des Containers
statt auf die des Benutzers, der damit arbeiten muss.

Maßgeblich ist jetzt der Host: Seine UID lässt sich nicht frei wählen, die des
Containers schon. Der Installer ermittelt sie, trägt sie als FILADEX_UID und
FILADEX_GID in die .env ein, und docker-compose.yml startet den Container
damit. data/ wird auf dieselbe Kennung gezogen — auch nachträglich, sodass eine
bestehende Installation sich mit einem erneuten Aufruf des Installers einrenkt.

Die Folgen waren schwerer zu deuten als die Ursache: Weil die Anforderung
liegen blieb, löste die Path-Unit sofort wieder aus, und systemd sperrte den
Dienst schließlich wegen zu häufiger Startversuche. In der Oberfläche sah das
aus wie ein Watcher, der gar nicht läuft — dieselbe Anzeige wie beim vorigen
Fehler, aber eine völlig andere Ursache.

Deshalb prüft apply-update.sh die Schreibrechte jetzt einmal am Anfang und
scheitert mit einer Meldung, die beide UIDs nennt und den Befehl zum Beheben,
statt sich mit "Permission denied" durch jeden Schritt zu kämpfen. Bleibt die
Anforderung trotzdem liegen, sagt das Skript ausdrücklich, dass es deshalb
gleich wieder gestartet wird.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Im Container läuft FilaDex jetzt gegen PostgreSQL 17 in einem eigenen Dienst.
Welche Datenbank es ist, entscheidet allein DATABASE_URL — lokal und in der
Testsuite bleibt SQLite der Standard, weil dort weder Server noch Container
nötig sind.

Zwei Datenbanken zu unterstützen ist kein Selbstzweck: Ohne SQLite bräuchte
jeder Testlauf und jeder Start von dev.sh einen laufenden Docker-Daemon, und
genau der ist auf einem Entwicklungsrechner das Unzuverlässigste am Aufbau.
Mit SQLite allein wäre die Testsuite dagegen kein Beweis mehr für den Betrieb.
Deshalb läuft dieselbe Suite auf Wunsch gegen einen echten Server:

    ./scripts/check.sh --postgres

Das hat sich sofort ausgezahlt. PostgreSQL verwirft nach einer fehlgeschlagenen
Anweisung die gesamte Transaktion — die bisherige Migrationslogik führte ALTER
TABLE blind aus und verschluckte den Fehler "Spalte existiert schon". Unter
SQLite ging das gut; unter PostgreSQL hätte die erste vorhandene Spalte alle
folgenden Migrationen blockiert, mit "current transaction is aborted". Jetzt
wird vorher nachgesehen, statt hinterher gedeutet — auch ohne den Unterschied
zwischen den Datenbanken die ehrlichere Variante.

Die Sicherung kann beide Formen: pg_dump für PostgreSQL, die Backup-Funktion
für SQLite. Beide Endungen werden gemeinsam rotiert, sonst bliebe nach einem
Wechsel die alte Sorte für immer liegen. pg_dump läuft im Datenbankcontainer
selbst, damit Client und Server zusammenpassen, ohne dass jemand daran denken
muss.

postgresql-client-17 im Dockerfile und postgres:17-alpine in docker-compose.yml
gehören zusammen und sind beide ausdrücklich festgenagelt, das Basisimage
gleich mit. Ein neuerer Client schreibt Anweisungen, die ein älterer Server
nicht kennt; beim Zurückspielen sieht das nach einer kaputten Sicherung aus.
Einer Sicherung, der man nicht traut, nützt niemandem.

Der Installer erzeugt das Datenbankpasswort einmalig und fasst es danach nie
wieder an — ein neues Passwort würde den bestehenden Datenbestand
unzugänglich machen, weil der Benutzer nur beim allerersten Start angelegt
wird. Ohne Passwort startet docker compose bewusst gar nicht erst, statt mit
einem Standardwert zu laufen. dev.sh legt für den Container-Betrieb eine .env
an, und --reset wirft jetzt auch das Docker-Volume weg, sonst überlebte der
Bestand einen "Reset".

Geprüft: 360 Tests gegen SQLite und dieselben 360 gegen PostgreSQL 17. Dazu
der komplette Stack im Container — Seed-Daten, Beispielbestand über die API,
Vollsicherung aus der Oberfläche, Schema komplett gelöscht und aus dem Dump
zurückgespielt: 12 Rollen und 90 Buchungen wieder da, null Fehlerzeilen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seit die Daten in PostgreSQL liegen, reichte die bisherige Deinstallation nicht
mehr — und zwar in beide Richtungen falsch.

"Daten löschen" ließ das Docker-Volume stehen. Eine Neuinstallation erzeugt ein
frisches Passwort, trifft aber auf eine Datenbank, deren Benutzer noch mit dem
alten angelegt wurde: Der Container startet, die Anmeldung scheitert, und
FilaDex meldet nur "Verbindung fehlgeschlagen". Jetzt geht das Volume mit.

"Daten behalten" war schlimmer: Der Ordner data/ wurde beiseite gelegt, die
.env aber mitsamt dem Installationsverzeichnis gelöscht. Ohne das Passwort
daraus ist das Volume wertlos — der Datenbankbenutzer wird nur beim allerersten
Start angelegt, danach kommt niemand mehr heran. Aus "behalten" wäre also
stiller Datenverlust geworden. Die .env wandert jetzt mit in die Sicherung, und
das Volume bleibt ausdrücklich stehen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bei jeder neuen Protokollzeile sprang die Seite nach unten, bis vom Fenster
fast nichts mehr zu sehen war.

Ursache war scrollIntoView auf einem Marker am Ende des Protokolls: Die
Methode scrollt jeden scrollbaren Vorfahren mit, also auch das Dokument selbst.
Gemessen an einer nachgestellten Seite: scrollY springt von 0 auf 469, obwohl
nur ein Kasten von 120 Pixeln Höhe aktualisiert wurde. Gesetzt wird jetzt
scrollTop am Protokollfeld — dabei bleibt scrollY auf 0, und die neueste Zeile
ist trotzdem zu sehen.

Dazu die zweite Hälfte derselben Unart: Wer hochscrollt, um eine frühere Zeile
nachzulesen, wurde bisher bei der nächsten Aktualisierung wieder ans Ende
gerissen. Ein onScroll-Handler merkt sich jetzt, ob der Leser überhaupt noch am
Ende steht, und nur dann wird nachgezogen.

Bewusst beim Scrollen fortgeschrieben statt beim Nachtragen ausgerechnet:
Treffen mehrere Zeilen auf einmal ein — beim Docker-Build der Normalfall —,
wäre der Abstand zum Ende hinterher schon zu groß, und das Protokoll bliebe
mitten im Lauf stehen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zwei Fehler, die zusammen dafür sorgten, dass Versionsnummern verloren gingen.
In der Release-Liste fehlten dadurch v1.0.1-beta.5 und -beta.7, obwohl beide
Tags existierten — Installationen im Kanal "beta" haben diese Versionen nie zu
sehen bekommen.

**Der Wettlauf.** Beim Push eines Tags legt Forgejo im Hintergrund selbst einen
Eintrag dafür an. Kommt unser API-Aufruf dazwischen, scheitert er an der
Eindeutigkeit:

    unique constraint violation: duplicate key value violates "UQE_release_n"

Der Eintrag ist dabei weder unter /releases noch unter /releases/tags/<tag> zu
sehen, es gibt also nichts zu reparieren — man muss schlicht einen Moment
warten. Gegen das echte Forgejo nachgestellt: derselbe Aufruf, der eben noch
mit HTTP 500 scheiterte, liefert beim zweiten Mal 201. Das Skript versucht es
jetzt bis zu fünfmal mit wachsendem Abstand.

**Die übersprungene Nummer.** Bricht ein Lauf nach dem Tag-Push ab, ist der Tag
oben und der Eintrag fehlt. Der nächste Aufruf zählte einfach weiter — die
angefangene Version blieb als Lücke zurück. Jetzt sieht das Skript nach, ob zum
höchsten vorhandenen Tag ein Eintrag fehlt, und bietet an, diesen Stand
nachzutragen statt einen neuen anzufangen.

Gefragt wird nur bei einem eindeutigen 404. Ein nicht erreichbarer Server oder
ein abgelehnter Token heißt "weiß nicht", nicht "fehlt" — sonst wollte das
Skript einen längst fertigen Release nachtragen. Gegen das echte Forgejo
lesend geprüft: 200 bei vorhandenen Einträgen, 404 bei einem erfundenen Tag,
000 bei totem Server.

Beim Nachtragen zählt der Changelog bis zum Tag statt bis HEAD. Sonst stünden
im Eintrag Änderungen, die in dieser Version gar nicht drin sind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
AxonByteDev/FilaDex!2
No description provided.