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): speichert Datum + Anzahl der # heute empfangenen Webhooks (ein neuer Tag beginnt bei 0). 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