POST /api/publish: rendert SiteSnapshot->HTML in _staging_new/, atomic swap -> live/ (rename, ein Syscall). publicsite-nginx:alpine serviert live/ read-only auf Port 8081. Shared Volume api(rw)/publicsite(ro). Manager bleibt LAN-only. 5 neue Tests (atomic swap, UTF-8, mehrfach), 184/184 gruen. compose config OK. Vhost-Snippet + web-deploy.md (Deutsch) beigelegt; <DOMAIN> wartet auf Julian.
150 lines
4.9 KiB
YAML
150 lines
4.9 KiB
YAML
# GerbilManager — TrueNAS Custom Application
|
|
# ==============================================
|
|
# Vor dem ersten Start:
|
|
# 1. Kopiere deploy/truenas/.env.example -> deploy/truenas/.env und setze die Werte.
|
|
# 2. Lege die Dataset-Pfade auf der NAS an (pgdata, photos, backups).
|
|
# 3. docker compose -f deploy/truenas/compose.yaml up -d
|
|
#
|
|
# Zugriff: http://<NAS-IP>:${PORT:-80}
|
|
# API-Doku: http://<NAS-IP>:${PORT:-80}/scalar
|
|
|
|
services:
|
|
|
|
# --- PostgreSQL-Datenbank ---
|
|
db:
|
|
image: postgres:17-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
|
POSTGRES_DB: gerbilmanager
|
|
POSTGRES_USER: postgres
|
|
volumes:
|
|
- pgdata:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U postgres -d gerbilmanager"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
|
|
# --- .NET API (GerbilManagerWebAPI) ---
|
|
api:
|
|
image: "${REGISTRY:-truenas:13000/gulum}/gerbilmanager-api:${TAG:-latest}"
|
|
build:
|
|
context: ../..
|
|
dockerfile: GerbilManagerWebAPI/Dockerfile
|
|
restart: unless-stopped
|
|
environment:
|
|
ASPNETCORE_ENVIRONMENT: Production
|
|
# Verbindung zur Postgres-DB im selben Compose-Netz
|
|
ConnectionStrings__gerbilmanager: "Host=db;Port=5432;Database=gerbilmanager;Username=postgres;Password=${POSTGRES_PASSWORD}"
|
|
# Speicherort der hochgeladenen Fotos (NAS-Dataset gemounted unter /data/photos)
|
|
Photos__RootPath: /data/photos
|
|
# AR-3: Data Protection Key-Ring (persistiert Gmail-App-Passwort-Verschlüsselung über Redeployments)
|
|
DataProtection__KeyRingPath: /data/keys
|
|
# AR-4: KI-Funktionen (Verkaufstext + Posteingang-Entwurf, Sektion AI; beliebiger OpenAI-kompatibler Anbieter)
|
|
# Anbieter-Optionen und Schlüssel-Beispiele: docs/ai-provider.md
|
|
# Leer lassen = KI deaktiviert (503 AiKeyMissing statt Fehler)
|
|
AI__BaseUrl: "${AI__BaseUrl:-}"
|
|
AI__ApiKey: "${AI__ApiKey:-}"
|
|
AI__Model: "${AI__Model:-gemini-flash-latest}"
|
|
# WEB-2: Pfad wo POST /api/publish die oeffentliche Seite hinschreibt
|
|
PublicSite__RootPath: /data/publicsite
|
|
volumes:
|
|
- photos:/data/photos
|
|
- keys:/data/keys
|
|
- publicsite:/data/publicsite
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 90s
|
|
|
|
# --- nginx Frontend (React SPA + API-Proxy) ---
|
|
frontend:
|
|
image: "${REGISTRY:-truenas:13000/gulum}/gerbilmanager-frontend:${TAG:-latest}"
|
|
build:
|
|
context: ../..
|
|
dockerfile: gerbil-manager-web/Dockerfile
|
|
restart: unless-stopped
|
|
ports:
|
|
- "${PORT:-80}:80"
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
|
|
# --- nginx Public Site (WEB-2) ---
|
|
# Serviert NUR die statische oeffentliche Seite (live/ aus dem publicsite-Volume).
|
|
# SICHERHEIT: Kein Proxy auf api/frontend — nur statisches HTML nach aussen.
|
|
# Julian's externer nginx-Proxy leitet <DOMAIN> auf Port 8081 weiter.
|
|
publicsite:
|
|
image: nginx:alpine
|
|
restart: unless-stopped
|
|
ports:
|
|
- "${PUBLICSITE_PORT:-8081}:80"
|
|
volumes:
|
|
- publicsite:/usr/share/nginx/html:ro
|
|
- ./nginx/publicsite.conf:/etc/nginx/conf.d/default.conf:ro
|
|
depends_on:
|
|
- api
|
|
|
|
# --- Backup-Sidecar (taeglicher pg_dump + Foto-Archiv + Rotation) ---
|
|
backup:
|
|
image: postgres:17-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
PGPASSWORD: "${POSTGRES_PASSWORD}"
|
|
POSTGRES_HOST: db
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_DB: gerbilmanager
|
|
BACKUP_KEEP_DAYS: "${BACKUP_KEEP_DAYS:-7}"
|
|
volumes:
|
|
- photos:/data/photos:ro
|
|
- backups:/backups
|
|
- ./scripts:/scripts:ro
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
entrypoint: ["/bin/sh", "/scripts/entrypoint.sh"]
|
|
|
|
volumes:
|
|
# NAS-Datasets als Bind-Mounts (Pfade in .env konfigurieren).
|
|
# TrueNAS: Dataset-Pfad z.B. /mnt/SSD/gerbil/pgdata
|
|
pgdata:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${PGDATA_PATH:-/mnt/gerbil/pgdata}"
|
|
photos:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${PHOTOS_PATH:-/mnt/gerbil/photos}"
|
|
# AR-3: Data Protection key ring — persistiert Gmail-App-Passwort-Verschlüsselung.
|
|
# Muss ein persistentes NAS-Dataset sein (nicht dasselbe wie photos).
|
|
keys:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${KEYS_PATH:-/mnt/gerbil/keys}"
|
|
backups:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${BACKUPS_PATH:-/mnt/gerbil/backups}"
|
|
# WEB-2: gemeinsames Volume fuer api (rw) und publicsite-nginx (ro).
|
|
publicsite:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${PUBLICSITE_PATH:-/mnt/JailStorage/DockerVolumes/gerbilmanager/publicsite}"
|