Legt den kompletten Unterbau an, auf dem die Fachmodule aufsetzen. Betrieb: - docker-compose.yml mit Traefik, App, Worker, PostgreSQL 17 + PostGIS, Redis, MinIO und Gotenberg; Entwicklungs-Override ohne Traefik, dafuer mit Mailpit und Adminer - Mehrstufiges Dockerfile: eigener Benutzer, tini als PID 1, Healthcheck - .env.example mit einem Kommentar zu jeder Variable Mandantentrennung: - Row Level Security auf allen mandantenpflichtigen Tabellen - Zwei getrennte Datenbankverbindungen: florian_app mit aktiver RLS fuer die Anwendung, Eigentuemer-Zugang nur fuer Migration, Seed und Anmeldung - fuerOrganisation() als einziger Weg zu Fachdaten Nachvollziehbarkeit: - AuditLog append-only per Trigger; UPDATE ist dauerhaft gesperrt, DELETE nur im gekennzeichneten Aufbewahrungslauf - Geheimnisse werden vor dem Schreiben entfernt Anmeldung: - OIDC gegen ein extern betriebenes authentik, Rollen aus Gruppen nur als Vorbelegung beim ersten Login - Lokaler E-Mail/Passwort-Login als Rueckfallweg - Terminal per PIN und RFID mit zwei Schranken (Geraet, dann Person) Weiteres: - lib/authz.ts mit 9 Rollen und 38 Einzelrechten - lib/storage.ts (MinIO), lib/audit.ts, lib/warteschlange.ts (BullMQ) - Integrationen fuer Mail, Telegram und LLM hinter abschaltbaren Interfaces - tRPC v11, Basis-Layout mit Seitenleiste und dunkler Darstellung - Seed mit einer Beispielwehr: 16 Personen, 3 Fahrzeuge, 40 Geraete - 32 Vitest-Tests (davon 8 gegen eine echte Datenbank), 10 Playwright-Tests - README, Anleitung mit Screenshots, Roadmap, Aufwandsschaetzung, Devlog, Versionierungsregeln und Changelog Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.8 KiB
Anleitung
Bedienung von FLORIAN, Stand Phase 0 (Version 0.1.0). Die Bilder zeigen die
Beispielwehr aus dem Seed und werden mit npm run screenshots neu erzeugt.
Was hier noch fehlt — Mitglieder, Geräte, Prüfungen — kommt in den folgenden Phasen dazu. Der Stand ist bewusst schmal: Phase 0 baut das Fundament, nicht die Fachmodule. Siehe ROADMAP.md.
1. Anmelden
Aufgerufen wird FLORIAN im Browser unter der Adresse, die der Administrator
eingerichtet hat — in der Entwicklung http://localhost:3100.
Die Anmeldeseite zeigt genau die Wege, die in dieser Installation eingerichtet sind:
| Weg | Wann |
|---|---|
| Anmeldung über authentik | Der Regelweg, sobald authentik eingerichtet ist. Ein Klick, keine Passworteingabe in FLORIAN. |
| E-Mail und Passwort | Beim Erststart, und wenn authentik gerade nicht erreichbar ist. |
| Terminal im Gerätehaus | Am Wandtablet, mit PIN oder Ausweiskarte. Eigene Oberfläche, kommt in Phase 4. |
Beim allerersten Start gibt es genau ein Konto: das aus SEED_ADMIN_EMAIL und
SEED_ADMIN_PASSWORT in der .env. Vorgabe:
admin@beispielwehr.de
Feuerwehr2026
Dieses Passwort gehört nach dem ersten Login geändert.
Wenn die Anmeldung nicht klappt
Nach mehreren Fehlversuchen wird das Konto vorübergehend gesperrt — voreingestellt
nach fünf Versuchen für fünf Minuten (KIOSK_MAX_FEHLVERSUCHE,
KIOSK_SPERRDAUER). Die Meldung nennt nicht, ob die E-Mail-Adresse oder das
Passwort falsch war; das ist Absicht und verhindert, dass sich vorhandene
Konten erraten lassen.
2. Die Übersicht
Nach der Anmeldung steht die Übersicht der eigenen Wehr.
Von oben nach unten:
Kopfzeile. Name der Wehr und darunter, wer angemeldet ist und mit welchen Rollen. Wer mehrere Rollen trägt — der Gerätewart ist oft auch Ausbilder — sieht alle.
Sicherheitshinweis. Der gelbe Kasten bleibt stehen, solange die Wehr die Anwendung nicht ausdrücklich freigegeben hat. Er ist keine Formalität: Atemschutzüberwachung und Alarmmonitor sind sicherheitsrelevant, und bis zur Freigabe bleibt die analoge Überwachung führend.
Kennzahlen. Vier Kacheln mit aktiven Mitgliedern, Fahrzeugen, Geräten und fälligen Prüfungen. Sie zählen nur die eigene Wehr — was eine andere Wehr auf derselben Installation hat, ist nicht sichtbar und nicht abfragbar.
Stand des Aufbaus. Welche Phasen fertig sind und was als Nächstes kommt.
Die Seitenleiste
Links stehen alle Module. Was noch nicht existiert, ist ausgegraut und trägt
rechts sein Phasenkürzel — P2 heißt „kommt in Phase 2".
Was jemand überhaupt sieht, hängt an seinen Rechten. Ein einfaches Mitglied sieht die Mitgliederliste, aber nicht das Protokoll. Wichtig zum Verständnis: das Ausblenden ist nur Bequemlichkeit. Der eigentliche Schutz sitzt auf dem Server — wer eine Adresse direkt aufruft, für die ihm das Recht fehlt, bekommt trotzdem nichts.
3. Helle und dunkle Darstellung
Oben rechts wird zwischen hell und dunkel umgeschaltet. Voreingestellt ist die Einstellung des Betriebssystems.
Die dunkle Darstellung ist bewusst kein invertiertes Schwarz, sondern ein warmes Grau. Wer abends vom Fahrzeug hereinkommt und ans Terminal tritt, soll nicht geblendet werden.
4. Rollen und was sie dürfen
Neun Rollen, jede mit einem festen Satz Rechte. Die Zuordnung steht zentral in
src/lib/authz.ts und wird beim Seed in die Datenbank gespiegelt — eine Wehr
kann sie also nachträglich zuschneiden, ohne dass jemand Code ändert.
| Rolle | Darf im Kern |
|---|---|
| Administrator | alles, auch über Wehren hinweg |
| Kommandant | alles innerhalb der eigenen Wehr, einschließlich Rollen vergeben und Protokoll einsehen |
| Gerätewart | Fahrzeuge und Geräte anlegen, ändern, aussondern; Prüfungen planen und durchführen |
| Atemschutzgerätewart | zusätzlich Atemschutzgeräte, Träger und die Überwachung nach FwDV 7 |
| Kassier | Buchungen und Kassenabschluss (Modul noch nicht geplant, siehe Roadmap) |
| Schriftführer | Mitglieder pflegen, Einsätze und Berichte erfassen |
| Ausbilder | Übungen planen, Teilnahme und Qualifikationen erfassen |
| Mitglied | eigene Daten, Listen lesen |
| Terminal | nur anmelden und erfassen — nie verwalten |
Zwei Dinge sind bewusst so gebaut:
Der Gerätewart kommt nicht an die Kasse, der Kassier nicht an die Geräte. Trennung nach Verantwortung, nicht nach Bequemlichkeit.
Das Terminal darf fast nichts. Wer am Wandtablet arbeitet, meldet sich zusätzlich mit PIN oder Karte an und handelt dann unter seiner eigenen Person. Das Terminal selbst ist nur die Tür.
5. Anmeldung über authentik einrichten
authentik läuft getrennt von FLORIAN und ist nicht Teil dieses Stacks.
In authentik
- Neuen Provider anlegen, Typ OAuth2/OpenID Provider
- Client-Typ: Confidential
- Redirect-URI genau so eintragen:
https://<ihre-florian-adresse>/api/auth/callback/authentik - Scopes:
openid,profile,email - Für die Rollenvorbelegung zusätzlich ein Scope-Mapping, das die Gruppen des
Benutzers als Claim
groupsausliefert - Eine Application anlegen und mit dem Provider verbinden. Der Slug der Application ist der letzte Teil der Issuer-URL.
In der .env
AUTH_OIDC_ENABLED=true
AUTH_OIDC_ISSUER=https://auth.beispiel.de/application/o/florian/
AUTH_OIDC_CLIENT_ID=<aus authentik>
AUTH_OIDC_CLIENT_SECRET=<aus authentik>
AUTH_OIDC_GROUP_CLAIM=groups
AUTH_OIDC_GROUP_MAPPING=feuerwehr-kommandant:KOMMANDANT,feuerwehr-geraetewart:GERAETEWART
AUTH_OIDC_AUTO_PROVISION=true
AUTH_OIDC_STANDARD_ORGANISATION=ff-musterhausen
Danach die Anwendung neu starten. Auf der Anmeldeseite erscheint der Knopf für authentik.
Wie die Rollenvorbelegung wirkt
Die Zuordnung greift nur beim allerersten Login einer Person. Danach ist die in FLORIAN gepflegte Rolle führend, und authentik überschreibt sie nicht mehr. Wer also einem Mitglied in FLORIAN die Rolle „Gerätewart" gibt, behält sie — auch wenn die authentik-Gruppe etwas anderes sagt.
Steht AUTH_OIDC_AUTO_PROVISION=false, muss die Person vorher in FLORIAN
angelegt sein. Verknüpft wird über die E-Mail-Adresse.
Wenn authentik ausfällt
Solange AUTH_LOCAL_ENABLED=true steht, bleibt der Passwort-Login als
Rückfallweg erreichbar. Für den Wirkbetrieb ist es sinnvoll, mindestens ein
Konto mit Passwort zu behalten — sonst sperrt ein Ausfall von authentik die
ganze Wehr aus.
6. Terminal-Anmeldung im Gerätehaus
Die Oberfläche dazu kommt in Phase 4; der Anmeldeweg steht bereits.
Am Terminal wird zweifach geprüft:
- Das Gerät weist sich mit
KIOSK_DEVICE_SECRETaus. Ohne diesen Wert nimmt der Server keine PIN und keine Karte an. - Die Person meldet sich mit einer sechsstelligen PIN (zusammen mit ihrer Mitgliedsnummer) oder mit einer RFID-Karte an.
Beides wird nur als Hash gespeichert — weder die PIN noch die Kartenkennung liegen im Klartext in der Datenbank.
PINs, die sich zu leicht raten lassen, weist FLORIAN ab: lauter gleiche
Ziffern und fortlaufende Folgen wie 123456.
Aus dem Seed lässt sich das ausprobieren: Mitgliedsnummer 0001 (Andreas
Huber), PIN 471203.
7. Was protokolliert wird
Jede schreibende Änderung erzeugt einen Eintrag mit Zeitpunkt, Person, betroffenem Datensatz und den geänderten Feldern im Vorher-Nachher-Vergleich. Auch jede Anmeldung — erfolgreich wie gescheitert.
Zwei Eigenschaften sind wichtig:
Der Log lässt sich nicht ändern. Nicht über die Oberfläche, nicht über die Datenbank, auch nicht vom Administrator. Die Datenbank selbst verhindert es.
Er lässt sich nur im Aufbewahrungslauf kürzen. Nach der eingestellten
Frist (AUDIT_AUFBEWAHRUNG_TAGE, Vorgabe 10 Jahre) sichert der
Hintergrundprozess die alten Einträge und entfernt sie danach. Das ist der
einzige Weg, auf dem etwas aus dem Log verschwindet.
Passwörter, PINs und Kartenkennungen stehen nie im Log — sie werden vor dem Schreiben herausgenommen.
Einsehbar ist der Log mit dem Recht system.auditlog, das Kommandant und
Administrator haben. Die Ansicht dazu kommt in Phase 1.
8. Wenn etwas nicht geht
| Beobachtung | Wahrscheinliche Ursache | Was hilft |
|---|---|---|
| Die Anwendung startet nicht und meldet fehlende Variablen | .env unvollständig |
Die Meldung nennt jede fehlende Variable. Erklärungen dazu stehen in .env.example. |
| Anmeldung schlägt immer fehl, obwohl das Passwort stimmt | Konto nach Fehlversuchen gesperrt | Fünf Minuten warten, oder gesperrtBis in der Datenbank zurücksetzen |
| Listen sind leer, obwohl Daten da sind | Kein Mandantenkontext gesetzt | Das ist der Schutzmechanismus. Der Zugriff muss über fuerOrganisation() laufen. |
| Dateien lassen sich nicht hochladen | MinIO nicht erreichbar oder Bucket fehlt | http://localhost:9011 öffnen; docker compose logs minio-init |
| Der Knopf für authentik fehlt | Issuer, Client-ID oder Secret nicht gesetzt | Alle drei sind nötig, sonst wird der Weg ausgeblendet |
docker compose up startet Traefik mit |
Override-Datei wurde nicht mitgeladen | In Produktion -f docker-compose.yml angeben |
Zustand aller Randsysteme auf einen Blick:
http://localhost:3100/api/health
Antwortet mit 200 und "status": "ok", wenn Datenbank, Objektspeicher und
Warteschlange erreichbar sind — sonst mit 503 und der Angabe, welcher Dienst
fehlt.
Bilder erneuern
docker compose up -d
npm run dev
npm run screenshots
Die Bilder entstehen in docs/screenshots/, jeweils in heller und dunkler
Darstellung, und werden mit eingecheckt — die Anleitung soll auch dann lesbar
sein, wenn gerade kein Stack läuft.


