Frage aus dem Betrieb: laesst sich ein Compose-Abschnitt ueber eine Variable
in der .env zu- und abschalten, statt ihn auszukommentieren? Bedingte Bloecke
kennt Compose nicht - aber COMPOSE_PROFILES darf in der .env stehen und
aktiviert das Profil, ohne dass ein Flag noetig ist.
COMPOSE_PROFILES= -> Caddy wird nicht angelegt (Standard)
COMPOSE_PROFILES=caddy -> Caddy startet bei `docker-compose up -d` mit
Vorteil gegenueber `--profile caddy`: der Schalter wirkt auch bei down, logs
und ps, wo das Flag leicht vergessen wird und dann verwaiste Container
zurueckbleiben.
Empirisch geprueft mit einem separaten Testprojekt (compose 1.29.2): ohne den
Eintrag startete nur der ungeschuetzte Dienst, mit Eintrag beide.
Dokumentiert in .env.example, im Kommentar am caddy-Dienst und in der README.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der zweite Stack unter docker/ war ein Duplikat aus Februar, das nie
mitgepflegt wurde - und genau deshalb schwer zu finden und leicht falsch zu
bedienen. Statt ihn zu loeschen (Caddy ist fuer Betreiber ohne eigenen
Reverse-Proxy zurecht gewuenscht) ist er jetzt in den Hauptstack integriert:
- caddy als optionaler Dienst mit `profiles: ["caddy"]` in docker-compose.yml.
Ohne `--profile caddy` wird er nicht einmal angelegt - empirisch geprueft mit
einem separaten Testprojekt: `up -d` startete nur den ungeschuetzten Dienst.
Der laufende Stack bleibt damit unveraendert.
- Caddyfile in den Projektstamm verschoben, proxy-Ziel auf den Dienstnamen
`opencrm` angepasst, Zertifikate unter ./data/caddy wie alle anderen Daten.
- DOMAIN, CADDY_DIR und CADDY_CONFIG_DIR in die .env.example aufgenommen.
- docker/ entfernt (Dockerfile, entrypoint.sh, docker-compose.yml,
.env.example, README.md). Das dortige Dockerfile basierte noch auf Alpine -
genau die Variante, von der das Projekt wegen Prisma-/TLS-Problemen bewusst
auf node:20-slim gewechselt ist. Das gepflegte backend/Dockerfile baut
Frontend und Backend ebenso.
- README: Abschnitt "Docker (Produktion)" ersetzt durch "Betrieb mit eigenem
SSL", inkl. Hinweisen zu Let's-Encrypt-Limits, DNS-Voraussetzung,
HTTPS_ENABLED und dem weiterhin offenen App-Port. Projektbaum und
.dockerignore nachgezogen.
Geprueft: docker-compose config gueltig, caddy validate gueltig, laufende
Container unberuehrt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ohne diesen Fix waere das Siegel im Docker-Betrieb wirkungslos geblieben - die
Variable wurde nirgends an den Container durchgereicht. docker-compose.yml
listet die Umgebungsvariablen einzeln auf und speist sie aus der .env im
Projektstamm; ein env_file gibt es nicht, backend/.env wird vom Container also
gar nicht gelesen. Dokumentiert und ergaenzt hatte ich bisher nur
backend/.env.example - also die Datei, die fuer den Container irrelevant ist.
- AUDIT_HMAC_KEY und AUDIT_HMAC_KEY_OLD in docker-compose.yml und in
docker/docker-compose.yml aufgenommen (beide mit :- Default, damit ohne Wert
weiterhin fail-safe ungesiegelt geschrieben wird)
- beide Variablen samt Erklaerung in die .env.example im Projektstamm und in
docker/.env.example uebernommen
- README benennt jetzt explizit, welche Datei fuer welchen Betrieb gilt
(Stamm-.env bei Docker, backend/.env nur ohne Container)
Verifiziert: `docker-compose config` loest AUDIT_HMAC_KEY korrekt auf.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Statt npm-Rebuild-Wartung aktualisiert sich der Bankleitzahlen-Datensatz
jetzt zur Laufzeit. Ein Scheduler (taeglich 03:30 + Catch-up 90s) prueft
gemaess konfigurierbarem Intervall und laedt current.json/next.json von
npm/jsDelivr (Paket bankdata-germany) in das neue Bind-Mount-Volume
BANKDATA_DIR (./data/bankdata -> /app/bankdata).
Lookup bevorzugt den Volume-Datensatz vor den ins Image gebackenen Daten
(Fallback). Es wird kein Fremdcode ausgefuehrt - nur JSON gelesen und die
current+next-Delta-Logik nachgebaut. Validierung (>=1000 Eintraege, Format)
+ atomarer Write (tmp+rename) schuetzen den guten Stand vor Muell.
Datenschutz: Der Updater sendet keine Kundendaten, laedt nur eine
oeffentliche Datendatei; abschaltbar; bei Fehler/ohne Egress greift Builtin.
Neue Einstellungen-Seite /settings/bank-data zeigt Datenstand, Update-
Verfuegbarkeit und bietet "Jetzt aktualisieren" + Auto-Update-Schalter +
Intervall. Endpoints GET /api/settings/blz, POST /api/settings/blz/update-now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die README versprach weiterhin "admin@admin.com / admin" als Default,
aber Pentest Runde 12 hat das hardcoded "admin" entfernt
(Komplexitäts-Policy-Verletzung). Der Seed generiert jetzt ein
28-Zeichen-Zufallspasswort und schreibt es einmal nach stdout.
Aktualisiert:
- Quick-Start-Header: Hinweis statt direktes Passwort
- "Erste Inbetriebnahme"-Block: docker-logs-Befehl + SEED_ADMIN_PASSWORD-Alternative
- "Erster Login"-Sektion: vollständige Anleitung inkl. Beispiel-Ausgabe
- "Production-Deployment"-Checkliste: aktualisiert
- .env.example: SEED_ADMIN_PASSWORD-Block dokumentiert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neue Section "Deployment-Modus: On-Prem vs. Cloud" im Production-
Deployment-Block. Erklaert, warum On-Prem-Default private IPs
erlaubt (Plesk/Dovecot lokal) und wann der Flag fuer Cloud-Deploys
auf true gesetzt werden soll. Cloud-Metadata-Endpoints sind
unabhaengig vom Flag immer geblockt.
.env.example: SSRF_BLOCK_PRIVATE_IPS=false als Default mit Block-
Kommentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Folge-Aufräumen zur Refresh-Cookie-Migration:
- .env.example: JWT_EXPIRES_IN von 7d auf 15m (Access-Token-Lifetime),
neue JWT_REFRESH_EXPIRES_IN=7d. Kommentar erklärt das Access-/Refresh-
Pattern (Memory vs. httpOnly-Cookie, transparenter Refresh).
- docker-compose.yml: durchreichen + Default mit 15m statt 7d, plus
JWT_REFRESH_EXPIRES_IN als neue Variable.
Bestandsinstallationen mit altem JWT_EXPIRES_IN=7d in der .env
funktionieren weiter (die Variable überschreibt den Default), aber bei
neuen Setups ist sofort der Branchenstandard aktiv.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`upgrade-insecure-requests` (CSP) + HSTS sperrten den Browser bei direktem
http://ip:port-Zugriff aus (ERR_SSL_PROTOCOL_ERROR auf den Vite-Assets,
weil Browser sie via https laden wollte).
Beide Header sind jetzt default OFF und werden nur gesetzt, wenn
HTTPS_ENABLED=true – also sobald ein TLS-Reverse-Proxy (Caddy/Traefik/Nginx)
vor OpenCRM steht. Lokale + non-TLS-Deployments laufen damit ohne Stolperfalle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der entrypoint prüft jetzt nach prisma db push, ob die User-Tabelle
leer ist – wenn ja, wird automatisch geseeded. Damit muss man bei
Erstinstallation nicht mehr daran denken, RUN_SEED=true zu setzen.
Logik:
RUN_SEED=true → Force-Seed (auch bei nicht-leerer DB; für Reset)
User-Count = 0 → Auto-Seed (Default-Verhalten bei leerer DB)
User-Count > 0 → kein Seed (DB schon initialisiert)
Implementiert via "node -e" mit @prisma/client – kein extra Tool nötig.
Fallback bei Fehlern: User-Count = -1, dann kein Seed.
.env.example aktualisiert: RUN_SEED bleibt 'false' als Default und ist
nur noch für Force-Reseed-Szenarien gedacht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bisher: Backend connectete als root (mit DB_ROOT_PASSWORD) – zu viele
Privilegien (GRANT ALL ON *.*).
Jetzt: Backend nutzt den App-User ${DB_USER}, den MariaDB beim ersten
Container-Start automatisch über MARIADB_USER/MARIADB_PASSWORD anlegt.
Dieser User bekommt von MariaDB direkt GRANT ALL PRIVILEGES auf
${DB_NAME}.* (= nur die OpenCRM-Datenbank, keine anderen Schemas).
Ausreichend für Prisma db push (DDL+DML auf opencrm.*),
nicht ausreichend für Schema-übergreifende Operationen oder
mysql.user-Manipulation – wie es sein soll.
DB_ROOT_PASSWORD bleibt für Adminer / Notfall-Wartung.
.env.example dokumentiert den Mechanismus.
Live-verifiziert:
- Container läuft mit DATABASE_URL=mysql://opencrm:***@db:3306/opencrm
- Prisma db push synced Schema
- Login + alle CRUD-Operationen funktionieren
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bisher: DATABASE_URL und die DB_USER/PASSWORD/etc. mussten parallel
gepflegt werden – Werte konnten auseinanderlaufen.
Fix:
- dotenv-expand installiert (löst ${VAR}-Substitution in .env)
- .env.example: DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}
- DB_HOST als neue Variable (Default localhost; Container überschreibt zu "db")
- Backend index.ts: dotenvExpand.expand() statt nur dotenv.config()
- Plus Fallback im Code: wenn DATABASE_URL leer aber DB_*-Werte vorhanden,
baut der Backend-Code die URL selbst zusammen (encodeURIComponent für
Sonderzeichen im Passwort).
docker-compose.yml setzt DATABASE_URL weiterhin explizit (Container-
internal Hostname "db") und überschreibt damit die Dev-Variante.
Live-verifiziert:
- Dev-Modus: mysql://root:***@localhost:3306/opencrm (substituiert)
- Container: mysql://root:***@db:3306/opencrm (compose explizit)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: ADMINER_DESIGN=dracula (oder adminer-dark) zeigte das Default-
Theme. Das offizielle Adminer-Image symverlinkt nur designs/.../adminer.css,
aber manche Designs haben adminer-dark.css, sodass der Symlink ins Leere
lief.
Fix: eigener entrypoint, der das erste .css im gewählten Design verlinkt
(unabhängig vom Filename). Anschließend wird der Original-entrypoint.sh
ausgeführt.
Live-verifiziert: dracula → adminer-dark.css symlink ok, HTML lädt
adminer.css mit 13 KB Theme-CSS.
Plus: .env.example listet alle ~28 verfügbaren Designs als Kommentar
und schlägt 'dracula' als Default vor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Big Move: vom backend-only-Setup zum vollständigen Container-Stack.
📁 Neue Struktur
- /.env (lokal, nicht getrackt) – zentrale Konfiguration für Dev + Docker
- /.env.example – Template mit allen Variablen
- /data/{db,uploads,factory-defaults,backups}/ – Bind-Mounts statt Volumes
(auf Wunsch: Daten bleiben im Projektverzeichnis)
- /backend/Dockerfile – Multi-Stage Build (Frontend + Backend)
- /backend/docker-entrypoint.sh – wartet auf DB, prisma db push, optional seed
🐳 docker-compose.yml (neu konsolidiert)
- mariadb 10.11 mit Bind-Mount ./data/db
- opencrm-app (Backend serviert Frontend statisch in production)
- adminer mit Theme pepa-linha-dark als DB-UI
- Ports + Pfade + Secrets alle aus .env
🔧 Backend
- index.ts dotenv-Loader: lädt zuerst Root /.env, dann backend/.env als
Fallback. Funktioniert nahtlos für npm run dev und für Container.
- backend/.env.example als Legacy-Fallback dokumentiert
📝 README
- Quick-Start mit Docker als empfohlener Default (3 Befehle)
- Tabelle der Daten-Verzeichnisse
- Hinweis auf RUN_SEED=true beim ersten Start
⚙ Konfigurierbar via .env
- OPENCRM_PORT (Backend extern), ADMINER_PORT (DB-UI), DB_PORT
- Daten-Pfade (DATA_DIR, DB_DATA_DIR, UPLOADS_DIR etc.)
- DB_NAME/USER/PASSWORD, JWT_SECRET, ENCRYPTION_KEY
- ADMINER_DESIGN (Theme-Auswahl)
Hinweis: Vor dem ersten `docker compose up -d` muss das laufende
`npm run dev`-Backend gestoppt werden (Port + DB-Conflict). Das alte
Volume `opencrm_mariadb_data` bleibt unangetastet als Notfall-Backup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>