Files
Florian WildeandClaude Opus 5 845942f36c 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>
2026-09-03 00:13:53 +02:00

112 lines
4.3 KiB
Docker

# =============================================================================
# FLORIAN — Mehrstufiger Build.
# deps : Abhängigkeiten installieren (wird gecacht)
# builder : Prisma-Client erzeugen, Next.js bauen, Worker transpilieren
# runner : schlankes Laufzeit-Image, non-root, mit Healthcheck
# =============================================================================
ARG NODE_VERSION=22.20.0
# -----------------------------------------------------------------------------
# Stufe 1: Abhängigkeiten
# -----------------------------------------------------------------------------
FROM node:${NODE_VERSION}-bookworm-slim AS deps
WORKDIR /app
# openssl wird von Prisma benötigt, ca-certificates für ausgehendes TLS.
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ENV NEXT_TELEMETRY_DISABLED=1
COPY package.json package-lock.json* .npmrc ./
RUN npm ci --include=dev
# -----------------------------------------------------------------------------
# Stufe 2: Build
# -----------------------------------------------------------------------------
FROM node:${NODE_VERSION}-bookworm-slim AS builder
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Prisma-Client aus dem Schema erzeugen — ohne Datenbankverbindung.
RUN npx prisma generate
# Der Next.js-Build braucht formal gültige Variablen, verbindet sich aber nicht.
# Die echten Werte kommen zur Laufzeit aus der Umgebung.
RUN DATABASE_URL="postgresql://build:build@localhost:5432/build?schema=public" \
DIRECT_DATABASE_URL="postgresql://build:build@localhost:5432/build?schema=public" \
AUTH_SECRET="build_time_platzhalter_mindestens_32_zeichen" \
KIOSK_DEVICE_SECRET="build_time_platzhalter" \
SKIP_ENV_VALIDATION=1 \
npm run build
# Worker separat nach CommonJS transpilieren, damit im Runtime-Image kein
# TypeScript-Toolchain mehr nötig ist.
RUN npx esbuild src/worker/index.ts \
--bundle \
--platform=node \
--target=node22 \
--format=cjs \
--packages=external \
--sourcemap \
--outfile=dist/worker/index.js
# -----------------------------------------------------------------------------
# Stufe 3: Laufzeit
# -----------------------------------------------------------------------------
FROM node:${NODE_VERSION}-bookworm-slim AS runner
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl ca-certificates tini \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
ENV TZ=Europe/Berlin
# Eigener, unprivilegierter Benutzer. Läuft NICHT als root.
RUN groupadd --system --gid 1001 florian \
&& useradd --system --uid 1001 --gid florian --home /app --shell /usr/sbin/nologin florian
# Standalone-Ausgabe von Next.js: enthält nur die tatsächlich benötigten Module.
COPY --from=builder --chown=florian:florian /app/.next/standalone ./
COPY --from=builder --chown=florian:florian /app/.next/static ./.next/static
COPY --from=builder --chown=florian:florian /app/public ./public
# Worker-Bundle und dessen Laufzeitabhängigkeiten.
COPY --from=builder --chown=florian:florian /app/dist ./dist
# Prisma: Schema und Migrationen fürs Deployment, plus erzeugter Client.
COPY --from=builder --chown=florian:florian /app/prisma ./prisma
COPY --from=builder --chown=florian:florian /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder --chown=florian:florian /app/node_modules/@prisma ./node_modules/@prisma
COPY --from=builder --chown=florian:florian /app/node_modules/prisma ./node_modules/prisma
COPY --chown=florian:florian docker/entrypoint.sh /usr/local/bin/entrypoint.sh
COPY --chown=florian:florian docker/healthcheck.js /app/healthcheck.js
RUN chmod +x /usr/local/bin/entrypoint.sh
USER florian
EXPOSE 3000
# Prüft den Endpunkt /api/health — der fragt Datenbank und Redis mit ab.
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
CMD node /app/healthcheck.js || exit 1
# tini als PID 1, damit Signale sauber durchgereicht werden.
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
CMD ["node", "server.js"]