Phase 0: Fundament der Feuerwehr-Verwaltungsplattform
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>
This commit is contained in:
+231
@@ -0,0 +1,231 @@
|
||||
# =============================================================================
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 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=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# LLM-Anbindung (optional; nur selbstgehostete Endpunkte, kein Cloud-Zwang)
|
||||
# -----------------------------------------------------------------------------
|
||||
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
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 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
|
||||
Reference in New Issue
Block a user