Files
FireAdministration/.env.example
T
Florian WildeandClaude Opus 5 ff40437a1a Phase 10: Mandantenfaehigkeit
Ein Server, mehrere Wehren. Ein Konto, mehrere Zugehoerigkeiten. Eine
Kreisebene, die zusieht.

Datenmodell und RLS trugen seit Phase 0 mehrere Wehren. Was fehlte, war
alles darueber: bis hierher war die Personenakte zugleich das Anmeldekonto,
und die gehoert genau einer Wehr. Wer in zweien gefuehrt wurde, brauchte
zwei Passwoerter, und die Anmeldung per E-Mail war mehrdeutig.

Neu sind `Benutzer` (das Konto) und `Mitgliedschaft` (die Zugehoerigkeit).
Das Konto traegt bewusst keine Mandantenkennung und kann keine tragen - es
ist der Angelpunkt, an dem ein Mensch zu mehreren Wehren gehoert. Geschuetzt
wird es deshalb ueber Rechte statt ueber RLS, wie `Berechtigung` seit Phase
0. Was es in einer Wehr darf, entscheidet seine Mitgliedschaft.

Die Sitzung behaelt ihre Form und entsteht nur anders - aus der aktiven
Mitgliedschaft statt aus der Person. Von 414 Fundstellen in 61 Dateien
musste dadurch keine angefasst werden. Die eine Ausnahme ist Absicht:
`personId` ist nullbar geworden, weil eine Aufsichtssitzung keine Akte hat,
und der Typechecker hat daraus genau die fuenf Stellen gemacht, an denen
die Kreisaufsicht nichts verloren hat.

Ihre Rechte werden gefiltert statt aufgezaehlt: alles, was `.lesen` heisst,
minus Kasse, Audit-Log, KI-Protokoll und Personenexport. So bekommt sie
neue Leserechte automatisch und neue Schreibrechte nie.

Die Migration ist datenerhaltend und bricht ab, statt einen halben Zustand
zu hinterlassen.

Nebenbei behoben: `db:test-reset` liess die Nachbarwehr stehen, der
Fehlversuch-Test sperrte das gemeinsame Testkonto, ein Testaufbau war
nicht wiederholbar, und die Wartelogik der Bildschirmfotos konnte
ausloesen, bevor eine Seite ueberhaupt geladen war.

581 Unit-Tests und 266 E2E-Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 13:43:24 +02:00

445 lines
22 KiB
Bash

# =============================================================================
# FLORIAN — Feuerwehr-Verwaltungsplattform
# Kopiere diese Datei nach ".env" und passe die Werte an:
# cp .env.example .env
# Alles, was hier steht, wird von docker-compose.yml und von lib/env.ts gelesen.
# lib/env.ts validiert beim Start mit Zod — fehlt eine Pflichtvariable, startet
# die App gar nicht erst, statt später halb zu funktionieren.
# =============================================================================
# -----------------------------------------------------------------------------
# Allgemein
# -----------------------------------------------------------------------------
# development | production | test — steuert Logging, Fehlerseiten und Caching.
NODE_ENV=development
# Öffentliche Basis-URL der Anwendung, ohne abschließenden Schrägstrich.
# Muss exakt der Adresse entsprechen, unter der Nutzer die App aufrufen —
# Auth.js baut daraus die OIDC-Redirect-URI.
APP_URL=http://localhost:3100
# Zeitzone für alle serverseitigen Fristen- und Terminberechnungen.
# Nicht ändern, außer die Wehr liegt außerhalb Deutschlands.
TZ=Europe/Berlin
# Host-Ports fuer die Entwicklung. Bewusst nicht die Standardports,
# damit FLORIAN neben anderen lokalen Stacks laufen kann.
APP_PORT=3100
GOTENBERG_PORT=3101
MAILPIT_UI_PORT=8125
MAILPIT_SMTP_PORT=1125
ADMINER_PORT=8081
# -----------------------------------------------------------------------------
# Traefik / Domains (nur für den Produktionsbetrieb relevant)
# In der Entwicklung greift docker-compose.override.yml und veröffentlicht die
# Dienste direkt auf localhost — Traefik wird dort nicht gebraucht.
# -----------------------------------------------------------------------------
# Hostname der Anwendung hinter Traefik, z. B. florian.beispielwehr.de
APP_DOMAIN=florian.localhost
# Hostname der MinIO-S3-API (Datei-Uploads, presignte URLs).
MINIO_DOMAIN=s3.florian.localhost
# Hostname der MinIO-Weboberfläche (Administration der Buckets).
MINIO_CONSOLE_DOMAIN=minio.florian.localhost
# Hostname des Traefik-Dashboards. Leer lassen, um es nicht zu veröffentlichen.
TRAEFIK_DASHBOARD_DOMAIN=traefik.florian.localhost
# E-Mail-Adresse für die ACME-Zertifikate (Let us Encrypt).
ACME_EMAIL=kommandant@beispielwehr.de
# Traefik-Entrypoint für HTTPS. "websecure" ist der Standard aus dieser Datei.
TRAEFIK_ENTRYPOINT=websecure
# -----------------------------------------------------------------------------
# PostgreSQL 17 + PostGIS
# -----------------------------------------------------------------------------
POSTGRES_USER=florian
# Vor dem ersten Start ersetzen: openssl rand -base64 24
POSTGRES_PASSWORD=bitte_aendern_postgres
POSTGRES_DB=florian
# Port, unter dem Postgres auf dem Host erreichbar ist (nur Entwicklung).
POSTGRES_PORT=5442
# Verbindungszeichenfolge der Anwendung. Läuft über den Datenbankbenutzer
# "florian_app", für den Row Level Security AKTIV ist — dieser Zugang sieht nur
# Daten der Organisation, die per SET LOCAL app.current_organisation gesetzt
# wurde (siehe src/lib/db.ts).
DATABASE_URL=postgresql://florian_app:bitte_aendern_app@postgres:5432/florian?schema=public
# Passwort des Anwendungsbenutzers. Muss zu DATABASE_URL passen und wird beim
# ersten Start von docker/postgres/10-init-app-role.sh gesetzt.
POSTGRES_APP_PASSWORD=bitte_aendern_app
# Verbindung für Migrationen und Seed. Nutzt den Eigentümer-Zugang, der RLS
# umgehen darf. Niemals im Anwendungscode verwenden.
DIRECT_DATABASE_URL=postgresql://florian:bitte_aendern_postgres@postgres:5432/florian?schema=public
# -----------------------------------------------------------------------------
# Redis (Warteschlangen für BullMQ, Rate-Limiting)
# -----------------------------------------------------------------------------
# Vor dem ersten Start ersetzen: openssl rand -base64 24
REDIS_PASSWORD=bitte_aendern_redis
REDIS_URL=redis://:bitte_aendern_redis@redis:6379
# Port auf dem Host (nur Entwicklung).
REDIS_PORT=6389
# Anzahl paralleler Jobs je Worker-Prozess.
WORKER_CONCURRENCY=5
# -----------------------------------------------------------------------------
# MinIO (S3-kompatibler Objektspeicher — ALLE Dateien liegen hier, nie im FS)
# -----------------------------------------------------------------------------
MINIO_ROOT_USER=florian
# Mindestens 8 Zeichen, vor dem ersten Start ersetzen.
MINIO_ROOT_PASSWORD=bitte_aendern_minio
# Endpunkt, den App und Worker intern ansprechen — im Compose-Netz der
# Dienstname, außerhalb der externe Hostname.
S3_ENDPOINT=minio
S3_PORT=9000
# true, sobald MinIO hinter Traefik mit TLS läuft.
S3_USE_SSL=false
S3_ACCESS_KEY=florian
S3_SECRET_KEY=bitte_aendern_minio
S3_REGION=eu-central-1
# Bucket für alle Nutzdateien (Prüfprotokolle, Fotos, Rechnungen, Exporte).
S3_BUCKET=florian
# Öffentlich erreichbare Basis-URL für presignte Download-Links. In der
# Entwicklung http://localhost:9010, in Produktion https://$MINIO_DOMAIN
S3_PUBLIC_URL=http://localhost:9010
# Gültigkeitsdauer presignter Links in Sekunden (Standard: 15 Minuten).
S3_URL_TTL=900
# Ports auf dem Host (nur Entwicklung): API und Weboberfläche.
MINIO_API_PORT=9010
MINIO_CONSOLE_PORT=9011
# -----------------------------------------------------------------------------
# Gotenberg (PDF-Erzeugung für Prüfprotokolle, Berichte, Rechnungen)
# -----------------------------------------------------------------------------
GOTENBERG_URL=http://gotenberg:3000
# Zeitlimit je PDF-Auftrag in Sekunden.
GOTENBERG_TIMEOUT=30
# -----------------------------------------------------------------------------
# Auth.js — Sitzungsverwaltung
# -----------------------------------------------------------------------------
# Signiert die Sitzungs-Cookies. Zwingend erforderlich, pro Installation neu:
# openssl rand -base64 32
AUTH_SECRET=bitte_aendern_mindestens_32_zeichen_lang
# Muss hinter einem Reverse Proxy (Traefik) auf true stehen, damit Auth.js die
# X-Forwarded-Header auswertet und korrekte Redirect-URIs erzeugt.
AUTH_TRUST_HOST=true
# Lebensdauer einer Sitzung in Sekunden (Standard: 8 Stunden).
AUTH_SESSION_MAX_AGE=28800
# -----------------------------------------------------------------------------
# OIDC gegen authentik
# Die authentik-Instanz wird SEPARAT betrieben und ist nicht Teil dieses Stacks.
# In authentik anzulegen: Provider "OAuth2/OpenID" (Client-Typ: confidential),
# Redirect-URI exakt $APP_URL/api/auth/callback/authentik
# Scopes: openid profile email — plus einen Gruppen-Scope, wenn Rollen aus
# authentik-Gruppen vorbelegt werden sollen (siehe AUTH_OIDC_GROUP_CLAIM).
# -----------------------------------------------------------------------------
# false blendet den OIDC-Knopf aus; dann bleibt nur der lokale Login. Sinnvoll
# für den Erststart, bevor authentik konfiguriert ist.
AUTH_OIDC_ENABLED=false
# Issuer-URL des Providers, OHNE /.well-known/openid-configuration.
# Bei authentik: https://<authentik-host>/application/o/<application-slug>/
AUTH_OIDC_ISSUER=https://auth.beispiel.de/application/o/florian/
AUTH_OIDC_CLIENT_ID=
AUTH_OIDC_CLIENT_SECRET=
# Beschriftung des Anmeldeknopfes in der Oberfläche.
AUTH_OIDC_NAME=Anmeldung über authentik
# Claim, aus dem die Gruppenzugehörigkeit gelesen wird. In authentik über ein
# Scope-Mapping bereitzustellen. Leer lassen, um das Mapping abzuschalten —
# dann werden Rollen ausschließlich in FLORIAN gepflegt.
AUTH_OIDC_GROUP_CLAIM=groups
# Zuordnung authentik-Gruppe -> FLORIAN-Rolle, kommagetrennt als "gruppe:ROLLE".
# Wirkt nur als VORBELEGUNG beim ersten Login; danach ist die in FLORIAN
# gepflegte Rolle führend und wird nicht mehr überschrieben.
AUTH_OIDC_GROUP_MAPPING=feuerwehr-kommandant:KOMMANDANT,feuerwehr-geraetewart:GERAETEWART,feuerwehr-atemschutz:ATEMSCHUTZGERAETEWART,feuerwehr-kassier:KASSIER,feuerwehr-schriftfuehrer:SCHRIFTFUEHRER,feuerwehr-ausbilder:AUSBILDER,feuerwehr-mitglied:MITGLIED
# true legt einen in authentik bekannten Nutzer beim ersten Login automatisch
# als Person an. false verlangt, dass die Person vorher in FLORIAN existiert
# und über die E-Mail-Adresse verknüpft wird.
AUTH_OIDC_AUTO_PROVISION=true
# Organisation (Slug), der automatisch angelegte Personen zugeordnet werden.
AUTH_OIDC_STANDARD_ORGANISATION=ff-musterhausen
# -----------------------------------------------------------------------------
# Lokaler E-Mail/Passwort-Login (Rückfalloption für Erststart und Notbetrieb)
# -----------------------------------------------------------------------------
# Nach der Einrichtung von authentik auf false setzen, damit nur noch OIDC geht.
AUTH_LOCAL_ENABLED=true
# Zugangsdaten des ersten Administrators, angelegt vom Seed-Skript.
# Nach dem ersten Login das Passwort in der Oberfläche ändern.
SEED_ADMIN_EMAIL=admin@beispielwehr.de
SEED_ADMIN_PASSWORT=Feuerwehr2026
# Das Konto der Kreisebene aus dem Beispielbestand (Phase 10): Mitglied in der
# eigenen Wehr, Aufsicht in der Nachbarwehr. Vorgabe leitet sich aus der
# Wehrkennung ab, also kbi@ff-musterhausen.de.
# SEED_KBI_EMAIL=
# SEED_KBI_PASSWORT=Feuerwehr2026
# bcrypt-Kostenfaktor. 12 ist ein guter Kompromiss aus Sicherheit und Tempo.
AUTH_BCRYPT_ROUNDS=12
# -----------------------------------------------------------------------------
# Terminal-/Kiosk-Anmeldung im Gerätehaus (PIN und RFID)
# -----------------------------------------------------------------------------
# Schaltet den Kiosk-Modus samt Terminal-Login frei.
KIOSK_ENABLED=true
# Gemeinsames Geheimnis, mit dem sich ein Terminal-Gerät gegenüber dem Server
# ausweist, bevor überhaupt eine PIN oder ein Token akzeptiert wird:
# openssl rand -hex 32
KIOSK_DEVICE_SECRET=bitte_aendern_kiosk_geheimnis
# Länge der Personen-PIN. 4 ist bequem, 6 ist sicherer.
KIOSK_PIN_LAENGE=6
# Nach wie vielen Fehlversuchen eine PIN gesperrt wird.
KIOSK_MAX_FEHLVERSUCHE=5
# Sperrdauer nach zu vielen Fehlversuchen, in Sekunden.
KIOSK_SPERRDAUER=300
# Automatische Abmeldung am Terminal nach Untätigkeit, in Sekunden.
KIOSK_IDLE_TIMEOUT=120
# -----------------------------------------------------------------------------
# Kartenmodule (Phase 6)
# -----------------------------------------------------------------------------
# FLORIAN laedt keine Kacheln aus dem Netz. Wo eine Wehr ausrueckt und wo ihre
# Hydranten liegen, ist niemandes Geschaeft — der Kartenserver laeuft selbst:
#
# docker compose --profile karte up -d
#
# Dafuer wird eine MBTiles-Datei des Ausrueckegebiets gebraucht (siehe README).
# Ohne KARTE_STIL_URL zeigt die Karte einen leeren Hintergrund mit Gitternetz;
# Hydranten, Objekte und Lagekarte funktionieren trotzdem.
KARTE_STIL_URL=http://localhost:8085/styles/basic/style.json
# Kartenmittelpunkt beim Oeffnen (Musterhausen).
KARTE_BREITENGRAD=48.5
KARTE_LAENGENGRAD=12.15
KARTE_ZOOM=13
# -----------------------------------------------------------------------------
# Atemschutz nach FwDV 7 (Phase 5)
# -----------------------------------------------------------------------------
# Der Sicherheitshinweis ueber jeder Ansicht der Atemschutzueberwachung:
# "Ergaenzendes System — die analoge Atemschutzueberwachung bleibt fuehrend."
# Er gehoert dorthin. Auf false setzen darf ihn nur, wer eine schriftliche
# Freigabe des Kommandanten hat — und deren Fundstelle gehoert in
# ATEMSCHUTZ_FREIGABE, sonst bleibt der Hinweis stehen.
ATEMSCHUTZ_HINWEIS=true
# Aktenzeichen oder Fundstelle der Freigabe, z. B. "Kommandantenbeschluss vom 12.03.2026".
ATEMSCHUTZ_FREIGABE=
# Abstand der Druckabfrage in Sekunden. FwDV 7 nennt keinen festen Wert;
# zehn Minuten sind die uebliche Praxis.
ATEMSCHUTZ_ABFRAGE_INTERVALL=600
# Ansprechdruck der Warneinrichtung in bar. Je nach Baujahr 50 bis 60.
ATEMSCHUTZ_WARNDRUCK=55
# Sicherheitszuschlag auf den errechneten Rueckzugsdruck, in bar.
ATEMSCHUTZ_ZUSCHLAG=10
# -----------------------------------------------------------------------------
# Wetterwarnungen auf dem Hallenmonitor (optional, DWD)
# Die einzige Verbindung nach draussen, die FLORIAN von sich aus aufbaut —
# deshalb standardmaessig aus. Ohne WETTER_ENABLED=true wird nichts abgerufen.
# -----------------------------------------------------------------------------
WETTER_ENABLED=false
# Warnzellen-Nummer der eigenen Gemeinde aus der DWD-Warnzellenliste:
# https://www.dwd.de/DE/leistungen/opendata/help/warnungen/cap_warncellids_csv.csv
DWD_WARNCELL=
DWD_WARNUNGEN_URL=https://www.dwd.de/DWD/warnungen/warnapp/json/warnings.json
# Zeitlimit fuer den Abruf, in Sekunden. Der Monitor darf davon nicht haengen.
WETTER_TIMEOUT=5
# Wie lange eine Antwort gilt, bevor neu gefragt wird, in Sekunden.
WETTER_ZWISCHENSPEICHER=300
# -----------------------------------------------------------------------------
# E-Mail-Versand (optional, hinter lib/integrations/mail.ts abstrahiert)
# Ohne MAIL_ENABLED=true wird nur ins Log geschrieben — praktisch für Tests.
# -----------------------------------------------------------------------------
MAIL_ENABLED=false
MAIL_HOST=smtp.beispiel.de
MAIL_PORT=587
MAIL_SECURE=false
MAIL_USER=
MAIL_PASSWORD=
MAIL_FROM=FLORIAN <florian@beispielwehr.de>
# -----------------------------------------------------------------------------
# Telegram-Benachrichtigungen (optional, abschaltbar)
# -----------------------------------------------------------------------------
TELEGRAM_ENABLED=false
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
# -----------------------------------------------------------------------------
# Push über ntfy (Phase 7, optional)
#
# ntfy ist ein winziger Push-Dienst, den man selbst betreibt: ein Container,
# eine App im Store, fertig. Man abonniert ein "Thema" (einen frei gewählten
# Namen) und bekommt Meldungen darauf — ohne Konto, ohne Telefonnummer.
#
# Läuft der Server im eigenen Netz, verlässt keine Nachricht das Gerätehaus.
# Deshalb gilt ntfy in FLORIAN als INTERNER Kanal und bekommt die
# ausführlichen Texte. Wer stattdessen das öffentliche ntfy.sh einträgt, macht
# ihn zu einem externen Dienst wie jeden anderen.
#
# ACHTUNG: Ein Thema ist nur so geheim wie sein Name. Wer ihn kennt, liest mit.
# FLORIAN schlägt deshalb Namen mit Zufallsanteil vor.
# -----------------------------------------------------------------------------
NTFY_ENABLED=false
# NTFY_URL=http://ntfy:80
# Nur nötig, wenn der eigene ntfy Zugriffsschutz eingeschaltet hat.
NTFY_TOKEN=
NTFY_TIMEOUT=10
# -----------------------------------------------------------------------------
# Web-Push in den Browser (Phase 7, optional)
#
# Der Weg, der ohne App auskommt. Die Meldung läuft über den Push-Dienst des
# Browserherstellers (Mozilla, Google, Apple) — verschlüsselt, aber sie
# verlässt das Netz der Wehr. Deshalb ist Web-Push ein EXTERNER Kanal und
# bekommt nur Kurzfassungen: "3 Prüfungen fällig", nicht welche.
#
# Schlüsselpaar erzeugen mit: npm run vapid
#
# ACHTUNG: Beide Schlüssel gehören ins Backup. Ein neuer öffentlicher Schlüssel
# macht alle bestehenden Push-Abos wertlos — jeder müsste Push neu erlauben,
# und niemand merkt es, die Meldungen bleiben einfach aus.
# -----------------------------------------------------------------------------
WEBPUSH_ENABLED=false
WEBPUSH_PUBLIC_KEY=
WEBPUSH_PRIVATE_KEY=
# Kontaktweg für den Push-Dienst, "mailto:..." oder eine Adresse.
WEBPUSH_KONTAKT=
# -----------------------------------------------------------------------------
# WhatsApp: bewusst NICHT.
#
# Es gibt keine WHATSAPP_-Variablen, und das ist eine Entscheidung, keine Lücke.
# Die Begründung steht im README unter "Warum kein WhatsApp" — kurz: die
# Business-Plattform kostet je Konversation, verlangt einen Vertrag mit Meta
# als Auftragsverarbeiter, und die App gleicht das Telefonbuch ab. Für eine
# Wehr, deren Daten im Gerätehaus bleiben sollen, ist das der falsche Weg.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Benachrichtigungen und Termine (Phase 7)
# -----------------------------------------------------------------------------
# Wann der Worker die Erinnerungen anlegt und verschickt (cron, Europe/Berlin).
# Bewusst am frühen Morgen und nicht nachts: eine Push-Meldung um drei Uhr
# weckt Leute, die am nächsten Tag arbeiten müssen.
BENACHRICHTIGUNG_LAUF=0 7 * * *
# Wie viele Tage vor einem Termin erinnert wird (kommagetrennt).
# "7,1": einmal in der Woche davor, damit man den Abend freihalten kann, und
# einmal am Vortag, weil man es bis dahin wieder vergessen hat.
TERMIN_ERINNERUNG_TAGE=7,1
# Wie oft eine fehlgeschlagene Zustellung wiederholt wird, bevor sie als
# fehlgeschlagen stehen bleibt. Stehen bleiben ist Absicht: eine still
# verschluckte Benachrichtigung ist schlimmer als eine sichtbar gescheiterte.
BENACHRICHTIGUNG_VERSUCHE=3
# Wie lange ZUGESTELLTE Benachrichtigungen aufgehoben werden.
# Fehlgeschlagene bleiben — sie zeigen einen kaputten Kanal an.
BENACHRICHTIGUNG_AUFBEWAHRUNG_TAGE=90
# -----------------------------------------------------------------------------
# LLM-Anbindung (optional; nur selbstgehostete Endpunkte, kein Cloud-Zwang)
# -----------------------------------------------------------------------------
# ES GIBT KEINEN RUECKFALL AUF EINEN ANBIETER. Laeuft der eigene Endpunkt
# nicht, fehlen die KI-Funktionen — sie werden nicht durch einen fremden
# Dienst ersetzt. Ein Einsatzbericht spricht ueber Menschen in ihrer
# schlechtesten Stunde; solche Texte gehoeren nirgendwo anders hin.
#
# Container starten: docker compose --profile ki up -d
# Modelle holen: docker compose exec ollama ollama pull <modell>
LLM_ENABLED=false
# OpenAI-kompatibler Endpunkt, z. B. eine lokale Ollama- oder vLLM-Instanz.
LLM_BASE_URL=http://ollama:11434/v1
LLM_API_KEY=
LLM_MODELL=qwen2.5:14b
# Zeitlimit fuer eine Antwort. Ein Bericht darf dauern, aber nicht ewig.
LLM_TIMEOUT=120
# Modell fuer Einbettungen (semantische Suche im Wiki).
#
# ACHTUNG: Die Spalte wikiabschnitt.einbettung ist auf 768 Dimensionen
# festgelegt — das liefert nomic-embed-text. Ein Modell mit anderer Laenge
# braucht eine Migration; wie, steht im README unter Phase 8.
LLM_EINBETTUNG_MODELL=nomic-embed-text
LLM_EINBETTUNG_DIMENSIONEN=768
# Modell, das Bilder lesen kann — fuer die Belegerfassung. Ohne dieses Modell
# werden Belege trotzdem abgelegt, die Angaben nur von Hand eingetragen.
# LLM_VISION_MODELL=llama3.2-vision
# -----------------------------------------------------------------------------
# Spracherkennung fuers Diktat (Phase 8, optional)
#
# Selbst betriebener faster-whisper im Compose-Profil "ki". Groesse ueber
# WHISPER_MODELL: "tiny" laeuft auf jedem Buerorechner, "base" ist der
# brauchbare Kompromiss, "small" erkennt Fachbegriffe merklich besser und
# will dafuer Zeit oder eine Grafikkarte.
# -----------------------------------------------------------------------------
WHISPER_ENABLED=false
WHISPER_URL=http://whisper:8000
WHISPER_MODELL=base
WHISPER_SPRACHE=de
# Zeitlimit fuer eine Aufnahme. Zehn Minuten Sprache brauchen Zeit.
WHISPER_TIMEOUT=300
# Hoechstlaenge einer Aufnahme in Sekunden.
DIKTAT_MAX_SEKUNDEN=600
# -----------------------------------------------------------------------------
# Einsätze und Übungen (Phase 3)
# -----------------------------------------------------------------------------
# Adressen zu Koordinaten auflösen. Vorgesehen ist ein SELBST betriebener
# Nominatim — Einsatzadressen gehören nicht an einen öffentlichen Dienst, und
# dessen Nutzungsbedingungen erlauben es ohnehin nicht.
GEOCODING_ENABLED=false
# NOMINATIM_URL=http://nominatim:8080
GEOCODING_TIMEOUT=5
# Auf welche Länder die Suche eingegrenzt wird (ISO-Codes, kommagetrennt).
GEOCODING_LAENDER=de,at,ch
# Einsatzportal für Fremdwehren: ein signierter Link ohne Anmeldung, über den
# eine Nachbarwehr den Stand sieht und Lagemeldungen absetzen kann.
PORTAL_ENABLED=true
# Wie lange ein neu erzeugter Link gilt.
PORTAL_GUELTIGKEIT_STUNDEN=24
# Höchstzahl Zugriffe je Link und Stunde.
PORTAL_MAX_ZUGRIFFE=120
# -----------------------------------------------------------------------------
# Wartung und Prüfung (Phase 2)
# -----------------------------------------------------------------------------
# Wann der nächtliche Lauf Prüftermine plant. Cron-Syntax, Zeitzone
# Europe/Berlin. Der Worker trägt den Zeitplan beim Start je Wehr selbst ein.
WARTUNG_NACHTLAUF=15 3 * * *
# Wann die Erinnerungen verschickt werden. Bewusst nach der Planung: sonst
# erinnert der Lauf an Termine, die es noch gar nicht gibt.
WARTUNG_ERINNERUNG_LAUF=45 3 * * *
# Wie weit im Voraus Termine angelegt werden. Ohne Horizont stünden Termine für
# die nächsten zehn Jahre im Dashboard.
WARTUNG_PLANUNGSHORIZONT_TAGE=180
# -----------------------------------------------------------------------------
# Datenschutz und Aufbewahrung (DSGVO)
# -----------------------------------------------------------------------------
# Aufbewahrungsfrist für Einsatz- und Prüfdokumentation in Jahren.
AUFBEWAHRUNG_JAHRE=10
# Aufbewahrungsfrist für den Audit-Log in Tagen. Der Log ist append-only;
# Älteres wird vom Worker exportiert, bevor es entfernt wird.
AUDIT_AUFBEWAHRUNG_TAGE=3650
# Frist in Tagen, nach der ein ausgetretenes Mitglied anonymisiert wird.
PERSON_ANONYMISIERUNG_TAGE=365
# -----------------------------------------------------------------------------
# Telemetrie bleibt aus. Nicht ändern.
# -----------------------------------------------------------------------------
NEXT_TELEMETRY_DISABLED=1
CHECKPOINT_DISABLE=1
# -----------------------------------------------------------------------------
# Testwehr fuer die E2E-Tests
#
# Die Playwright-Tests laufen gegen eine EIGENE Wehr, nicht gegen die
# Beispielwehr aus der Anleitung. Sonst wachsen deren Listen bei jedem Lauf,
# und die Bildschirmfotos aendern sich, ohne dass sich die Oberflaeche
# geaendert haette.
#
# Vor dem Lauf einmal: npm run db:test-reset
# -----------------------------------------------------------------------------
E2E_SLUG=ff-teststelle
E2E_NAME=Freiwillige Feuerwehr Teststelle
E2E_KURZNAME=FF Teststelle
E2E_ADMIN_EMAIL=e2e@teststelle.de
E2E_ADMIN_PASSWORT=Feuerwehr2026