monitor/docker-compose.yml
fds2610-bot 9426406ee5 Sprachaufnahme: Agent-Kachel, Aufnahme-Webseite und API-Warteschlange
Setzt den in #84 skizzierten Workflow um: record.html nimmt im
Handy-Browser eine Sprachnotiz per MediaRecorder auf und laedt sie
unauthentifiziert zu POST /api/v1/agents/{id}/recordings hoch. Ein
Klick auf die Personen-Figur jedes registrierten Agenten in der
Buero-Visualisierung verlinkt dorthin, ein Zaehler am Schreibtisch
zeigt offene Aufnahmen. Der Agent selbst holt seine Warteschlange
ueber die bestehenden Api-Key-authentifizierten Endpunkte ab
(GET .../recordings, GET /recordings/{id}/audio) und markiert eine
Aufnahme nach der Transkription per PATCH als erledigt - die
eigentliche Transkription (whisper.cpp/faster-whisper) bleibt
agent-seitig, siehe fds2610/Agent#17.

Schliesst #85
2026-09-03 21:26:28 +02:00

119 lines
6.1 KiB
YAML

services:
api:
build:
context: ./api
dockerfile: docker/Dockerfile
volumes:
- ./api:/app
# Optional: echte status.txt aus der Produktion zum Testen einbinden, z.B.
# - ./status.txt:/status.txt:ro
working_dir: /app
# Ohne Restart-Policy bleiben api und web nach einem Reboot, einem OOM-Kill (die
# Produktionsmaschine hat 512 MiB RAM) oder einem Docker-Neustart einfach liegen,
# waehrend sync von allein zurueckkommt. "docker compose ps" zeigt dann genau einen
# laufenden Container und der Monitor antwortet gar nicht mehr - der Fehler sieht nach
# Firewall/Proxy aus, obwohl schlicht nichts auf 8090/8091 lauscht.
# unless-stopped statt always: ein bewusstes "docker compose stop" bleibt bestehen.
restart: unless-stopped
# Achtung: Eine Aenderung des Host-Ports muss in .env (MONITOR_API_BASE) nachgezogen
# werden - das Frontend laeuft im Browser und erreicht die API nicht ueber das
# Compose-Netzwerk, sondern ueber genau diesen Port.
ports:
- "8091:8080"
environment:
# Der eingebaute PHP-Server arbeitet Anfragen sonst streng nacheinander ab: eine einzige
# offene SSE-Verbindung (GET /api/v1/stream, bis zu 60s) blockiert damit jede weitere
# Anfrage - gemessen 16s fuer ein GET /api/v1/tickets. Ab dem zweiten offenen Monitor
# laeuft die API praktisch dauerhaft dicht, das Frontend faellt auf Demo-Daten zurueck
# und der PO reagiert dann auf nichts mehr (Fehlerbericht zu Issue #27).
PHP_CLI_SERVER_WORKERS: 4
MONITOR_DB_PATH: /app/data/monitor.sqlite
MONITOR_ADMIN_API_KEY: ${MONITOR_ADMIN_API_KEY:-dev-admin-key}
MONITOR_STATUS_FILE: /status.txt
MONITOR_DESK_COUNT: 8
MONITOR_SSE_MAX_ITERATIONS: 30
MONITOR_SSE_POLL_INTERVAL: 2
MONITOR_SYNC_TRIGGER_FILE: /app/data/sync-requested
CODEBERG_API_BASE: https://codeberg.org/api/v1
CODEBERG_TOKEN: ${CODEBERG_TOKEN:-}
# Secret des Codeberg-Webhooks. Ohne diesen Wert weist POST /api/v1/webhooks/codeberg
# jede Anfrage mit 401 ab - der Endpunkt ist aus dem Internet erreichbar und die
# Signatur ist sein einziger Schutz.
CODEBERG_WEBHOOK_SECRET: ${CODEBERG_WEBHOOK_SECRET:-}
# Zaehldatei fuer den Hooks-KPI (Issue #38, GET /api/v1/webhooks/stats): wird bei jedem
# Start dieses Containers von entrypoint.sh auf 0 zurueckgesetzt.
MONITOR_HOOKS_COUNTER_FILE: /app/data/hooks-count
# Sprachaufnahmen (Issue #85): Ablageort der Rohaufnahmen aus record.html und
# Obergrenze je Aufnahme. Die Groessengrenze muss zur Dockerfile-CMD passen
# (upload_max_filesize/post_max_size), sonst weist PHP grosse Uploads schon vor
# VoiceRecordingRepository ab.
MONITOR_RECORDINGS_PATH: /app/data/recordings
MONITOR_MAX_RECORDING_BYTES: ${MONITOR_MAX_RECORDING_BYTES:-26214400}
# Haelt Backlog/Done aus Codeberg aktuell (F4, siehe API.md Abschnitt "Codeberg-Sync"):
# laeuft dasselbe Image wie api und teilt sich ueber das ./api-Volume dieselbe SQLite-Datei.
# Ohne diesen Service bleibt das Board leer, solange kein Agent selbst Tickets meldet.
#
# Der Watch-Modus laeuft aus zwei Gruenden: periodisch alle MONITOR_SYNC_INTERVAL Sekunden
# und sofort, wenn der api-Container ueber die Trigger-Datei einen Codeberg-Webhook
# weiterreicht (siehe Sync\SyncTrigger). Mit funktionierendem Webhook ist das Intervall nur
# noch das Sicherheitsnetz fuer verpasste Zustellungen und darf deshalb gross sein.
#
# Dieser Service selbst baut weiterhin nur ausgehende Verbindungen auf und braucht kein
# Port-Mapping. Die eingehende Freigabe betrifft ausschliesslich den api-Service, ueber den
# Codeberg den Webhook zustellt.
sync:
build:
context: ./api
dockerfile: docker/Dockerfile
volumes:
- ./api:/app
working_dir: /app
restart: unless-stopped
depends_on:
- api
environment:
MONITOR_DB_PATH: /app/data/monitor.sqlite
MONITOR_SYNC_INTERVAL: ${MONITOR_SYNC_INTERVAL:-600}
MONITOR_SYNC_TRIGGER_FILE: /app/data/sync-requested
CODEBERG_API_BASE: https://codeberg.org/api/v1
CODEBERG_TOKEN: ${CODEBERG_TOKEN:-}
command: php bin/sync-codeberg.php --watch
# Statisches Frontend (html/) - Phase 2 der Neuausrichtung (Buero-Visualisierung).
# Nur zur lokalen Ansicht/Entwicklung, kein Build-Schritt noetig (Vanilla HTML/CSS/JS).
web:
image: nginx:alpine
# Siehe api: ohne Restart-Policy kommt das Frontend nach Reboot/OOM nicht von allein
# zurueck - und genau dieser Container bedient den nach aussen freigegebenen Port 8090.
restart: unless-stopped
volumes:
- ./html:/usr/share/nginx/html:ro
# Ueberlagert den Platzhalter html/API.md - der muss existieren, weil in
# einem read-only gemounteten Verzeichnis kein Mountpoint angelegt werden kann.
- ./API.md:/usr/share/nginx/html/API.md:ro
# Setzt office.html als Einstiegsseite (sonst 403 auf der Basis-URL) und liefert
# /config.js aus. Als Template gemountet, damit das nginx-Image beim Start
# MONITOR_API_BASE per envsubst einsetzt (Ausgabe: /etc/nginx/conf.d/default.conf).
- ./docker/nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro
environment:
# Wird in docker/nginx/default.conf.template eingesetzt und als /config.js an den
# Browser ausgeliefert. Leer = gleicher Origin (relatives /api/v1).
MONITOR_API_BASE: ${MONITOR_API_BASE:-}
# Speist nur den kosmetischen Reload-Countdown im Header (Issue #12) - gleicher
# Default wie beim sync-Service oben, damit die Anzeige zur tatsaechlichen
# Sync-Frequenz passt, wenn MONITOR_SYNC_INTERVAL nicht gesetzt ist.
MONITOR_SYNC_INTERVAL: ${MONITOR_SYNC_INTERVAL:-600}
ports:
- "8090:80"
# Fuehrt die Node-basierten Frontend-Tests (tests/frontend/) aus. Node/npm sind nur im
# Container noetig, nicht auf dem Host (analog zum api-Service mit PHP/Composer).
# Node >= 21 noetig: das Test-Skript in package.json uebergibt ein Glob-Muster an
# "node --test", das aeltere Versionen als Dateinamen lesen und nicht aufloesen.
frontend:
image: node:24-alpine
working_dir: /app
volumes:
- .:/app
command: npm test