Zwei Fehler, beide erst im Betrieb sichtbar.
1. Schritt 3 der Ersteinrichtung meldete "Bitte pruefe die markierten
Felder" - und markierte keines. Man kam dort nicht weiter.
Das kurze Formular schickt neun Felder, das vollstaendige unter
Stammdaten alle. Beide gehen durch dasselbe Schema, und die optionalen
Felder waren zwar nullable, aber nicht optional: Ein Feld, das gar
nicht abgeschickt wurde, ist undefined, nicht null. Beanstandet wurden
also zwei Dutzend Felder, die auf dem Bildschirm nie standen.
Damit das nicht wiederkommt, unterscheidet die Aktion jetzt zwischen
Eingabefehler und Formular-Schema-Bruch: Beanstandet die Pruefung ein
Feld, das nicht abgeschickt wurde, sagt sie das ausdruecklich und
schreibt es ins Serverlog, statt es dem Nutzer als Eingabefehler
unterzuschieben, den er nirgends finden kann.
2. Eine betreute Person liess sich nicht endgueltig loeschen. Postgres
setzt beim Loeschen den Verweis im Audit-Log auf NULL, und genau
dieses UPDATE hat der Unveraenderlichkeits-Trigger abgewiesen. Damit
scheiterte auch das Leeren des Papierkorbs nach 30 Tagen.
Der Trigger erlaubt jetzt genau eine Ausnahme: das Nullen der beiden
Fremdschluessel. Jede andere Aenderung bleibt verboten, auch das
Setzen eines Fremdschluessels auf einen anderen Wert. Der Nachweis
bleibt vollstaendig - es verschwindet nur der Verweis auf einen
Datensatz, den es nicht mehr gibt.
Geprueft: sieben neue Tests gegen die tatsaechlichen Formularinhalte, der
Ablauf einmal komplett im Browser bis zum Dashboard, und der Trigger
gegen eine laufende Datenbank - Loeschen gelingt, Faelschen scheitert.
Etappe: 8
Phase: Haertung
.env.example setzte APP_BIND=127.0.0.1 als fertigen Wert, mitten zwischen
lauter anderen fertigen Werten. Wer die Datei kopiert und seinen Reverse
Proxy auf einem anderen Server betreibt, kommt damit nicht an die
Anwendung heran - und der Fehler sieht aus, als laege es am Proxy.
Der sichere Standard bleibt 127.0.0.1. Er wird nur nicht mehr als
"schon erledigt" praesentiert, sondern als Entscheidung mit drei
benannten Faellen: Proxy daneben, Proxy woanders mit privatem Netz,
Proxy woanders ohne. Beim dritten Fall steht die Firewallregel dabei.
Die Installationsanleitung nennt APP_BIND jetzt schon in Schritt 3, wo
die .env ausgefuellt wird - nicht erst in Schritt 7, wo der Proxy
drankommt. Wer der Anleitung von oben nach unten folgt, stolpert sonst
erst vier Schritte spaeter darueber.
Dazu ein eigener Abschnitt in der Fehlersuche mit den zwei curl-Proben,
die den Unterschied zwischen falscher Bindeadresse und Firewall zeigen -
und dem Hinweis, dass dafuer kein Neubau noetig ist.
Am Programm aendert sich nichts.
Etappe: 8
Phase: Haertung
docker-compose.yml reicht ENC_KEY_V2 immer an den Behaelter durch - mit
leerem Wert, wenn in der .env keiner steht. src/lib/env.ts erkannte
Schluessel aber am Variablennamen statt am Inhalt, fand also ein leeres
ENC_KEY_V2 und brach ab mit "Umgebungsvariable ENC_KEY_V2 fehlt".
Die Meldung war doppelt irrefuehrend: Sie nannte einen Schluessel, den
niemand wollte, und sie erschien an einer Stelle, die damit nichts zu tun
hat - beim Einrichten des zweiten Faktors, als Serverfehler ohne Text.
Betroffen war jede Stelle, die verschluesselte Felder schreibt oder
liest, also praktisch alles ausser der Anmeldung selbst. Die kam durch,
weil sie ohne Verschluesselung auskommt - der Fehler trat deshalb erst
auf der naechsten Seite auf, und der Zusammenhang war nicht zu sehen.
Leere Schluesselvariablen werden jetzt uebersprungen. Sieben Tests decken
die Auswertung ab, darunter genau diese Konstellation.
Die Meldung beim Schluesselwechsel nennt jetzt ausserdem, welche
Versionen vorhanden sind, und die richtige Reihenfolge: erst den neuen
Schluessel eintragen, dann ENC_KEY_AKTIV umstellen.
Dazu scripts/anmeldung-pruefen.mjs. Das Anmeldeformular sagt bewusst
nicht, woran eine Anmeldung scheiterte - sonst liesse sich daran ablesen,
welche Adressen es gibt. Auf dem Server darf man es wissen: Jeder Versuch
wird mit Grund protokolliert, und das Skript liest ihn vor, zusammen mit
dem Zustand des Zugangs, dem Stand der Sperre und der AUTH_URL.
Etappe: 8
Phase: Haertung
Das Verzeichnis scripts/ wurde beim Bauen nie in die Laufzeitstufe
uebernommen. Der in der Anleitung dokumentierte Befehl
docker compose exec app node scripts/totp-geheimnis.mjs ...
endete deshalb mit "Cannot find module". Betroffen war nicht nur dieses
eine Skript, sondern alle, die im laufenden Behaelter gedacht sind - auch
wartungsschluessel.mjs und schluessel-erzeugen.mjs.
Kopiert wird jetzt das ganze Verzeichnis, nicht einzelne Dateien. Sonst
faellt beim naechsten Betriebsskript wieder auf, dass es fehlt - und zwar
erst dann, wenn man es braucht. Die Entwicklungsskripte darin laufen dort
mangels Werkzeugen nicht, kosten aber nur ein paar Kilobyte.
Geprueft im fertigen Abbild und gegen eine echte Datenbank: Das Skript
liegt unter /app/scripts, laeuft als Benutzer cockpit und gibt dasselbe
Geheimnis aus wie auf dem Entwicklungsrechner.
Die Anleitung nennt jetzt zusaetzlich den Weg fuer alle, die noch eine
aeltere Fassung laufen haben und nicht sofort aktualisieren wollen:
docker compose cp legt die Datei einmalig in den Behaelter.
Etappe: 8
Phase: Haertung
Bis jetzt stand nirgends, welcher Stand laeuft. Beim Aktualisieren auf
einem Server ist das die erste Frage.
CHANGELOG.md nach Keep a Changelog, Versionsnummern nach SemVer. Der
Changelog sagt, was sich beim Arbeiten aendert - nicht, welche Datei
angefasst wurde. Dazu eine Tabelle, was Major, Minor und Patch fuer den
Betreiber praktisch bedeuten, und der Hinweis, dass sich eine
Datenbank-Migration nicht zurueckrollen laesst.
Die Version steht bewusst an zwei Stellen: package.json und
src/lib/version.ts. Ein Import der package.json waere im
Standalone-Build eine andere Datei als die des Projekts - die Zahl haenge
dann an der Laufzeit. Ein Test haelt beide zusammen, und
"npm run version" setzt sie gemeinsam samt Changelog-Abschnitt.
Committet und getaggt wird nicht automatisch. Ein Changelog-Eintrag, den
niemand gegengelesen hat, ist keinen Deut besser als keiner.
Sichtbar wird die Version unten in der Seitenleiste und unter
/api/gesundheit - dort mit Git-Stand, den der Docker-Build hineinreicht.
Auf der Anmeldeseite steht sie nicht: wer nicht angemeldet ist, muss
nicht wissen, welche Fassung hier laeuft.
Etappe: 8
Phase: Haertung