L10b Betrieb & Aufräumen: Deploy – craftvia-worker, CI-Testjob, DEPLOY.md, Certvia-Doku archiviert

- docker-compose.coolify(.prebuilt).yml: Service craftvia-worker (Target worker, Chromium,
  shm_size 1gb, gleiche Härtung), Craftvia-Variablen für app und worker.
- Dockerfile: worker-Stage mit HOME=/home/app (Chromium-Profil für non-root); lokaler
  docker build der Targets runner und worker erfolgreich, PDF-Erzeugung im Image geprüft.
- .env.example/.env.prod.example/.env.coolify.example: alle Craftvia-Variablen inkl. RLS,
  KI-Provider, PDF_CHROMIUM_PATH, OFFLINE_MAX_DAYS, API_RATE_LIMIT_*, AI_GENERATION_RETENTION_DAYS,
  AI_MONTHLY_TOKEN_LIMIT.
- CI (.github, .gitea): Job gate mit Postgres (pgvector) und Redis als Service: migrate deploy,
  seed, Passwort für craftvia_app, tsc, lint, build, npm run test.
- docs/craftvia/DEPLOY.md (aus den Certvia-Deploy-Docs abgeleitet): Architektur, Domains, Secrets,
  Worker, Migrationen, RLS-Aktivierung, Backup/Restore, KI, Rate Limits, Aufbewahrung, Smoke,
  Update/Rollback. build-and-push-images.sh baut craftvia-worker.
- Certvia-/ISMS-Dokumente aus docs/ nach docs/_certvia-archiv/ (mit README); Verweise in README.md
  und Skript-Kommentaren angepasst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 18:19:19 +02:00
co-authored by Claude Opus 5
parent 21d6dc016a
commit cadaedc6cc
38 changed files with 902 additions and 76 deletions
+106 -3
View File
@@ -4,7 +4,10 @@
# - Konfiguration über Coolify-Env-Variablen statt env_file: .env
# - Service "migrate": Init-Job (prisma migrate deploy + Rollen-Rechte-Sync), läuft einmalig VOR app
# - kein mailhog (Dev); Service "worker" = SEC1 Mail-Worker (BullMQ/Redis-Queue)
# - Service "craftvia-worker" = Craftvia-Job-Worker (Import-Extraktion, Transkription,
# Berichts-PDF mit Chromium, Bild-Derivate) aus der Dockerfile-Stage "worker"
# In Coolify als "Docker Compose Location" -> docker-compose.coolify.yml setzen.
# Betriebsdoku: docs/craftvia/DEPLOY.md
#
# Härtung (F-11/F-18):
# - F-11: migrate nutzt die schlanke "migrate"-Stage (kein Next-Build), Images gepinnt.
@@ -97,13 +100,36 @@ services:
# wählt bzw. als Env-Fallback. Muss auf das gemountete `backups`-Volume zeigen,
# sonst sind Sicherungen beim Redeploy flüchtig. DB-Config hat Vorrang vor dieser Var.
BACKUP_LOCAL_DIR: ${BACKUP_LOCAL_DIR:-/app/.backups}
AI_PROVIDER: ${AI_PROVIDER}
AI_API_KEY: ${AI_API_KEY}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_SECURE: ${SMTP_SECURE:-}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_FROM: ${SMTP_FROM}
MAIL_FROM_NAME: ${MAIL_FROM_NAME:-}
MAIL_REPLY_TO: ${MAIL_REPLY_TO:-}
# Basis für absolute Links (Mails, PDFs); leer = Fallback AUTH_URL.
APP_BASE_URL: ${APP_BASE_URL:-}
# Craftvia-KI (ARCHITEKTUR §4.5). Ohne ANTHROPIC_API_KEY bzw. TRANSCRIPTION_API_KEY
# graceful degradation (Status "disabled", manuelle Eingabe).
AI_EXTRACTION_PROVIDER: ${AI_EXTRACTION_PROVIDER:-anthropic}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-}
TRANSCRIPTION_PROVIDER: ${TRANSCRIPTION_PROVIDER:-openai-compatible}
TRANSCRIPTION_API_URL: ${TRANSCRIPTION_API_URL:-}
TRANSCRIPTION_API_KEY: ${TRANSCRIPTION_API_KEY:-}
TRANSCRIPTION_MODEL: ${TRANSCRIPTION_MODEL:-}
# Tokenbudget je Mandant je Kalendermonat (ein+aus); 0 = unbegrenzt.
AI_MONTHLY_TOKEN_LIMIT: ${AI_MONTHLY_TOKEN_LIMIT:-0}
# Optionaler Malware-Scan der Uploads (clamd INSTREAM); leer = nur Typ-/Magic-Byte-Prüfung.
CLAMAV_HOST: ${CLAMAV_HOST:-}
CLAMAV_PORT: ${CLAMAV_PORT:-3310}
# PWA: ab wie vielen Tagen ein Offline-Bundle als veraltet gilt (1–365, Default 7).
OFFLINE_MAX_DAYS: ${OFFLINE_MAX_DAYS:-7}
# Rate Limits je Nutzer/Minute: /api/v1/** allgemein bzw. Einsatz-/Sync-Endpunkte
# (/api/v1/sync, /api/v1/uploads, /api/v1/field/**).
API_RATE_LIMIT_PER_MINUTE: ${API_RATE_LIMIT_PER_MINUTE:-300}
API_FIELD_RATE_LIMIT_PER_MINUTE: ${API_FIELD_RATE_LIMIT_PER_MINUTE:-1200}
# Persistenter lokaler Backup-Zielspeicher (überlebt Redeploys).
volumes:
- backups:/app/.backups
@@ -242,6 +268,83 @@ services:
condition: service_completed_successfully
restart: unless-stopped
# Craftvia-Job-Worker (ARCHITEKTUR §4.4, scripts/craftvia-worker.ts): je BullMQ-Queue ein
# Worker für import-extraction, transcription, report-pdf, image-derivatives.
# OHNE diesen Dienst bleiben Import-Extraktion, Transkription, Berichts-PDFs und
# Bild-Derivate in der Queue liegen (die App reiht bei gesetztem REDIS_URL nur ein).
# Eigene Dockerfile-Stage "worker": tsx + src + Prisma-Client + Debian-Chromium und
# Schriften (fonts-dejavu-core, fonts-liberation) — HTML→PDF läuft NUR hier, nie in app.
# Processors greifen über dbForTenant zu → bei RLS_ENFORCED=true wie app über
# RLS_DATABASE_URL (Rolle craftvia_app). Egress (default-Netz) für Anthropic-/
# Transkriptions-API und SMTP. Kein Port, kein Traefik.
craftvia-worker:
build:
context: .
target: worker
command: ["npx", "tsx", "scripts/craftvia-worker.ts"]
environment:
DATABASE_URL: ${DATABASE_URL}
RLS_ENFORCED: ${RLS_ENFORCED:-false}
RLS_DATABASE_URL: ${RLS_DATABASE_URL}
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
AUTH_SECRET: ${AUTH_SECRET}
MFA_ENC_KEY: ${MFA_ENC_KEY:-}
AUTH_URL: ${AUTH_URL}
APP_BASE_URL: ${APP_BASE_URL:-}
# Objektspeicher (Garage): Import-PDFs/Sprachnotizen lesen, PDFs/Derivate schreiben.
S3_ENDPOINT: ${S3_ENDPOINT}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET}
S3_REGION: ${S3_REGION:-us-east-1}
# KI-Provider (siehe app). Ohne Key: Jobs enden mit Status "disabled".
AI_EXTRACTION_PROVIDER: ${AI_EXTRACTION_PROVIDER:-anthropic}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-}
TRANSCRIPTION_PROVIDER: ${TRANSCRIPTION_PROVIDER:-openai-compatible}
TRANSCRIPTION_API_URL: ${TRANSCRIPTION_API_URL:-}
TRANSCRIPTION_API_KEY: ${TRANSCRIPTION_API_KEY:-}
TRANSCRIPTION_MODEL: ${TRANSCRIPTION_MODEL:-}
AI_MONTHLY_TOKEN_LIMIT: ${AI_MONTHLY_TOKEN_LIMIT:-0}
# Aufbewahrung KI-Protokoll (AiGeneration): Ein-/Ausgaben älter als N Tage leeren.
AI_GENERATION_RETENTION_DAYS: ${AI_GENERATION_RETENTION_DAYS:-180}
# Chromium aus dem Debian-Paket (im Image bereits gesetzt, hier explizit).
PDF_CHROMIUM_PATH: /usr/bin/chromium
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_SECURE: ${SMTP_SECURE:-}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_FROM: ${SMTP_FROM}
MAIL_FROM_NAME: ${MAIL_FROM_NAME:-}
MAIL_REPLY_TO: ${MAIL_REPLY_TO:-}
# Chromium nutzt /dev/shm für Renderer-Speicher; render.ts setzt zusätzlich
# --disable-dev-shm-usage, 1 GB schützt dennoch vor Abstürzen bei Fotoberichten.
shm_size: "1gb"
networks:
- backend
- default
security_opt:
- "no-new-privileges:true"
cap_drop:
- ALL
deploy:
resources:
limits:
cpus: "1.0"
memory: 1536M
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
garage:
condition: service_healthy
garage-provision:
condition: service_completed_successfully
migrate:
condition: service_completed_successfully
restart: unless-stopped
postgres:
image: pgvector/pgvector:0.8.0-pg16
@@ -301,7 +404,7 @@ services:
restart: unless-stopped
# Objektspeicher: Garage (S3-kompatibel) — ersetzt den früheren minio-Service
# (MinIO Community EOL/Maintenance-Mode). Konzept: docs/KONZEPT-garage-migration.md.
# (MinIO Community EOL/Maintenance-Mode). Konzept: docs/_certvia-archiv/KONZEPT-garage-migration.md.
# Buckets/Keys werden NICHT über die S3-API angelegt, sondern vom Init-Job
# "garage-provision" (Admin-API). Nichts nach außen (kein Traefik/ports:) —
# rein clusterintern, wie minio zuvor. Version gepinnt (kein latest).