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
176 lines
6.8 KiB
YAML
176 lines
6.8 KiB
YAML
# =============================================================================
|
|
# Betreuungs-Cockpit - Produktivbetrieb
|
|
# =============================================================================
|
|
#
|
|
# docker compose up -d --build
|
|
#
|
|
# Kein Reverse Proxy in dieser Datei: der ist bereits vorhanden. Die
|
|
# Anwendung beendet selbst kein TLS und horcht standardmaessig nur auf
|
|
# 127.0.0.1:3000.
|
|
#
|
|
# Liegt der Proxy auf einem anderen Server, setze APP_BIND in der .env auf
|
|
# die Adresse, ueber die er herankommt - siehe docs/INSTALLATION.md,
|
|
# Schritt 7.
|
|
#
|
|
# Die Datenbank hat bewusst KEINE Portfreigabe. Sie ist nur ueber das
|
|
# interne Docker-Netz erreichbar. Wer mit psql heran muss, geht ueber
|
|
# docker compose exec db psql -U cockpit betreuungs_cockpit
|
|
# =============================================================================
|
|
|
|
name: betreuungs-cockpit
|
|
|
|
services:
|
|
db:
|
|
image: postgres:17-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER:-cockpit}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD fehlt in der .env}
|
|
POSTGRES_DB: ${POSTGRES_DB:-betreuungs_cockpit}
|
|
# Deutsche Sortierung und Zeichensatz - wichtig fuer Namenslisten
|
|
# und fuer die Volltextsuche mit der Konfiguration "german".
|
|
POSTGRES_INITDB_ARGS: '--encoding=UTF8 --locale=de_DE.UTF-8'
|
|
LANG: de_DE.UTF-8
|
|
volumes:
|
|
- postgres-daten:/var/lib/postgresql/data
|
|
- ./backups:/backups
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-cockpit} -d ${POSTGRES_DB:-betreuungs_cockpit}']
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 30s
|
|
networks: [intern]
|
|
# Kein "ports:" - die Datenbank ist von aussen nicht erreichbar.
|
|
|
|
minio:
|
|
# S3-kompatibler Objektspeicher fuer die Dokumente. Wer einen externen
|
|
# Speicher in der EU nutzt (z.B. Hetzner Object Storage), kann diesen
|
|
# Dienst weglassen und in der .env S3_ENDPOINT darauf zeigen lassen.
|
|
# Auch dann gilt: AV-Vertrag nach Art. 28 DSGVO abschliessen.
|
|
image: minio/minio:RELEASE.2025-01-20T14-49-07Z
|
|
restart: unless-stopped
|
|
command: server /daten --console-address ":9001"
|
|
environment:
|
|
MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:?S3_ACCESS_KEY_ID fehlt in der .env}
|
|
MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:?S3_SECRET_ACCESS_KEY fehlt in der .env}
|
|
MINIO_BROWSER_REDIRECT_URL: ${MINIO_KONSOLE_URL:-http://127.0.0.1:9001}
|
|
volumes:
|
|
- minio-daten:/daten
|
|
healthcheck:
|
|
test: ['CMD', 'mc', 'ready', 'local']
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 20s
|
|
ports:
|
|
# Nur an die Loopback-Adresse gebunden. Die Konsole erreichst du
|
|
# ueber einen SSH-Tunnel oder ueber deinen vorhandenen Proxy.
|
|
- '127.0.0.1:9001:9001'
|
|
networks: [intern]
|
|
|
|
app:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
args:
|
|
# In der Oberflaeche und unter /api/gesundheit sichtbar. Setzen mit:
|
|
# GIT_COMMIT=$(git rev-parse --short HEAD) docker compose up -d --build
|
|
GIT_COMMIT: ${GIT_COMMIT:-}
|
|
BUILD_ZEIT: ${BUILD_ZEIT:-}
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
minio:
|
|
condition: service_healthy
|
|
environment:
|
|
NODE_ENV: production
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-cockpit}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-betreuungs_cockpit}?schema=public&connection_limit=10
|
|
AUTH_SECRET: ${AUTH_SECRET:?AUTH_SECRET fehlt in der .env}
|
|
AUTH_URL: ${AUTH_URL:?AUTH_URL fehlt in der .env}
|
|
AUTH_TRUST_HOST: 'true'
|
|
ENC_KEY_V1: ${ENC_KEY_V1:?ENC_KEY_V1 fehlt in der .env - ohne ihn sind die verschluesselten Felder nicht lesbar}
|
|
# Bleibt leer, solange nicht rotiert wird. Die Anwendung ueberspringt
|
|
# leere Schluesselvariablen - siehe src/lib/env.ts.
|
|
ENC_KEY_V2: ${ENC_KEY_V2:-}
|
|
ENC_KEY_AKTIV: ${ENC_KEY_AKTIV:-1}
|
|
INDEX_KEY: ${INDEX_KEY:?INDEX_KEY fehlt in der .env}
|
|
SESSION_MAX_AGE: ${SESSION_MAX_AGE:-1800}
|
|
REAUTH_MAX_AGE: ${REAUTH_MAX_AGE:-300}
|
|
LOGIN_MAX_VERSUCHE: ${LOGIN_MAX_VERSUCHE:-5}
|
|
LOGIN_FENSTER_SEKUNDEN: ${LOGIN_FENSTER_SEKUNDEN:-900}
|
|
LOGIN_SPERRE_SEKUNDEN: ${LOGIN_SPERRE_SEKUNDEN:-900}
|
|
SPEICHER_TREIBER: s3
|
|
S3_ENDPOINT: ${S3_ENDPOINT:-http://minio:9000}
|
|
S3_REGION: ${S3_REGION:-eu-central}
|
|
S3_BUCKET: ${S3_BUCKET:-betreuungs-cockpit}
|
|
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID}
|
|
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY}
|
|
UPLOAD_MAX_BYTES: ${UPLOAD_MAX_BYTES:-26214400}
|
|
OCR_AKTIV: ${OCR_AKTIV:-true}
|
|
SMTP_HOST: ${SMTP_HOST:-}
|
|
SMTP_PORT: ${SMTP_PORT:-587}
|
|
SMTP_USER: ${SMTP_USER:-}
|
|
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
|
SMTP_FROM: ${SMTP_FROM:-}
|
|
TZ: Europe/Berlin
|
|
ports:
|
|
# Standard ist die Loopback-Adresse: dann erreicht die Anwendung nur
|
|
# ein Proxy, der auf demselben Rechner laeuft.
|
|
#
|
|
# Liegt der Reverse Proxy auf einem ANDEREN Server, muss hier die
|
|
# Adresse stehen, ueber die er herankommt - am besten die eines
|
|
# privaten Netzes oder eines WireGuard-Tunnels:
|
|
#
|
|
# APP_BIND=10.0.0.5
|
|
#
|
|
# 0.0.0.0 nur setzen, wenn eine Firewall den Port ausschliesslich
|
|
# fuer die IP des Proxy-Servers oeffnet. Die Anwendung beendet kein
|
|
# TLS und gehoert nie unverschluesselt ins offene Netz.
|
|
- '${APP_BIND:-127.0.0.1}:${APP_PORT:-3000}:3000'
|
|
networks: [intern]
|
|
healthcheck:
|
|
test: ['CMD', 'node', '-e', "fetch('http://127.0.0.1:3000/api/gesundheit').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 40s
|
|
|
|
sicherung:
|
|
# Taeglich um 03:15 Uhr ein verschluesseltes Backup nach ./backups.
|
|
# Legt zusaetzlich die Pruefsumme ab und raeumt alte Staende auf.
|
|
image: postgres:17-alpine
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
environment:
|
|
PGHOST: db
|
|
PGUSER: ${POSTGRES_USER:-cockpit}
|
|
PGPASSWORD: ${POSTGRES_PASSWORD}
|
|
PGDATABASE: ${POSTGRES_DB:-betreuungs_cockpit}
|
|
SICHERUNG_PASSWORT: ${SICHERUNG_PASSWORT:?SICHERUNG_PASSWORT fehlt in der .env}
|
|
SICHERUNG_AUFBEWAHRUNG_TAGE: ${SICHERUNG_AUFBEWAHRUNG_TAGE:-30}
|
|
TZ: Europe/Berlin
|
|
volumes:
|
|
- ./backups:/backups
|
|
- ./scripts/sicherung.sh:/sicherung.sh:ro
|
|
- ./scripts/wiederherstellung.sh:/wiederherstellung.sh:ro
|
|
entrypoint: ['/bin/sh', '-c']
|
|
command:
|
|
- |
|
|
apk add --no-cache openssl coreutils >/dev/null
|
|
echo "15 3 * * * /bin/sh /sicherung.sh >> /backups/sicherung.log 2>&1" > /etc/crontabs/root
|
|
echo "Sicherungsdienst bereit. Naechster Lauf 03:15 Uhr."
|
|
crond -f -l 8
|
|
networks: [intern]
|
|
|
|
volumes:
|
|
postgres-daten:
|
|
minio-daten:
|
|
|
|
networks:
|
|
intern:
|
|
driver: bridge
|