Files
minecraft-atm10-stats/docs/OPERATIONS.md
T
lucashahmannandClaude Opus 5.5 d45d1821df feat(deploy): ausgehender HTTP(S)-Proxy für Build und Laufzeit
- Compose reicht HTTP_PROXY/HTTPS_PROXY/NO_PROXY als Build-Argumente an beide Images weiter
  (npm ci, Google Fonts) und zur Laufzeit an api und worker; api, db, web stehen immer in NO_PROXY
- Spielerköpfe werden über undici EnvHttpProxyAgent geladen (Node-fetch ignoriert Proxy-Variablen)
- Doku: Proxy-Abschnitt mit Whitelist und Fehlerbildern
- geprüft mit lokalem Squid: Build nur über registry.npmjs.org, fonts.googleapis.com, fonts.gstatic.com;
  zur Laufzeit nur mc-heads.net

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:35:46 +02:00

205 lines
11 KiB
Markdown

# Betrieb
Für den Betreiber des Portals: Installation auf dem DMZ-Server hinter OPNsense und Traefik, Einspielen der Mod
bei Mine-Hoster, laufender Betrieb und Fehlerbehebung.
## 1. Voraussetzungen
- Server in der DMZ mit Docker und Docker Compose v2
- Traefik **v3** mit Docker-Provider, einem HTTPS-Entrypoint und Cert-Resolver; Traefik hängt an einem externen
Docker-Netz (z. B. `traefik`)
- DNS-Eintrag für die Portal-Domain, der auf die öffentliche IP der OPNsense zeigt
- Zugang zum Mine-Hoster-Gamepanel (SFTP-Daten unter *Einstellungen → SFTP-Details*)
> Traefik **v2**: Die Middleware heißt dort `ipwhitelist` statt `ipallowlist`. In `deploy/docker-compose.yml`
> die Zeile `traefik.http.middlewares.atm-ingest-allow.ipallowlist.sourcerange` entsprechend umbenennen.
### Ausgehender Proxy (z. B. Squid in der DMZ)
Muss der Server über einen Proxy ins Internet, trage ihn in `deploy/.env` ein:
```bash
HTTP_PROXY=http://squid-proxy.example.local:3128
HTTPS_PROXY=http://squid-proxy.example.local:3128
NO_PROXY=localhost,127.0.0.1,.local
```
Compose reicht die Werte als Build-Argumente an beide Images und zur Laufzeit an api und worker weiter. Die
Container-Namen `api`, `db` und `web` hängt es automatisch an `NO_PROXY` an, sodass interner Verkehr nie über den
Proxy läuft. Eine Proxy-Einstellung für den Docker-Daemon (`/etc/systemd/system/docker.service.d/proxy.conf`) gilt
nur für Image-Pulls. Für `npm ci` im Build reicht sie **nicht**; ohne die Einträge oben bricht der Build mit
„npm error Exit handler never called!“ ab.
Whitelist für den Proxy (alle Verbindungen sind `CONNECT …:443`):
| Domain | Wofür | Wann |
|---|---|---|
| `registry.npmjs.org` | npm-Pakete | Build, Pflicht |
| `fonts.googleapis.com`, `fonts.gstatic.com` | Schriften des Dashboards | Build, Pflicht |
| `registry-1.docker.io`, `auth.docker.io`, `production.cloudflare.docker.com` | Basis-Images (über die Daemon-Einstellung) | Pull, Pflicht |
| `mc-heads.net` | Spielerköpfe | Laufzeit, optional (ohne: graue Platzhalter) |
Der SFTP-Abgleich zum Gameserver ist eine direkte SSH-Verbindung und läuft nicht über den Proxy; dafür braucht
der DMZ-Server eine eigene Firewall-Freigabe (siehe OPNsense).
## 2. Portal installieren
```bash
git clone <repo> atm10-stats && cd atm10-stats
cp deploy/.env.example deploy/.env
openssl rand -hex 32 # Wert für INGEST_HMAC_SECRET
openssl rand -base64 24 # Wert für POSTGRES_PASSWORD
nano deploy/.env # DOMAIN, TRAEFIK_*, INGEST_ALLOWED_IPS, Secrets, optional SFTP_*
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build
docker compose -f deploy/docker-compose.yml --env-file deploy/.env ps # api, web, db: healthy
```
Admin-Konto anlegen (Passwort mindestens 12 Zeichen, wird interaktiv abgefragt) und Ingest-Token für die Mod erzeugen:
```bash
docker compose -f deploy/docker-compose.yml --env-file deploy/.env exec api node dist/cli.js admin:create lucas
docker compose -f deploy/docker-compose.yml --env-file deploy/.env exec api node dist/cli.js token:create
```
Das Token wird **nur einmal** angezeigt. Später lässt es sich unter `/admin` erneuern.
### Routing
| Pfad | Ziel | Hinweis |
|---|---|---|
| `https://DOMAIN/api/ingest*` | api:3001 | nur von `INGEST_ALLOWED_IPS` (Middleware `atm-ingest-allow`) |
| `https://DOMAIN/api/*` | api:3001 | öffentliche Lese-API, `/api/live` (SSE), `/api/admin/*` |
| `https://DOMAIN/*` | web:3000 | Dashboard |
`/api/live` ist ein Server-Sent-Events-Stream. Auf diese Route keine Kompressions- oder Buffering-Middleware legen.
Die API sendet dafür `X-Accel-Buffering: no` und alle 25 s einen Heartbeat.
### OPNsense
1. **Eingehend**: HTTPS (443) zur DMZ/Traefik wie für andere Dienste. Die Einschränkung von `/api/ingest` auf
den Gameserver übernimmt Traefik (`INGEST_ALLOWED_IPS`). Die IP des Mine-Hoster-Servers ergibt sich aus der
Serveradresse im Gamepanel (Hostname per `nslookup` auflösen).
Optional zusätzlich in OPNsense: Alias mit dieser IP und eine eigene Regel nur für den Ingest-Host.
2. **Ausgehend (nur für den SFTP-Abgleich)**: DMZ-Server → SFTP-Host/Port des Mine-Hoster-Servers (TCP).
Ohne diese Regel meldet `/sources` „Fehler beim letzten Abgleich“.
## 3. Mod bei Mine-Hoster einspielen
Die Mod ist **rein serverseitig**: Spieler installieren nichts, Clients ohne die Mod können normal verbinden.
1. Mod bauen (oder fertiges JAR aus einem Release verwenden):
```bash
./mod/gradlew -p mod build # → mod/build/libs/atmstats-1.0.0.jar
```
2. Im Gamepanel den Server **stoppen**.
3. Per SFTP (WinSCP/FileZilla) `atmstats-1.0.0.jar` nach `mods/` hochladen.
4. Server starten und nach dem Start wieder stoppen. Dabei entsteht `config/atmstats-server.toml`.
5. Konfiguration per SFTP bearbeiten:
```toml
[atmstats]
enabled = true
endpoint = "https://stats.example.de"
serverId = "atm10"
token = "<Token aus token:create>"
hmacSecret = "<INGEST_HMAC_SECRET aus deploy/.env>"
statsSnapshotMinutes = 5
questScanMinutes = 2
healthIntervalSeconds = 60
maxOutboxMb = 50
```
6. Server starten und in `logs/latest.log` nach `atmstats:` suchen:
- `atmstats: backend reachable`: fertig, Daten laufen.
- `atmstats: backend NOT reachable: …`: siehe Abschnitt 6. Die Ereignisse bleiben in `atmstats/outbox/`
auf dem Gameserver und werden gesendet, sobald die Verbindung klappt.
Ohne `endpoint`, `token` oder `hmacSecret` bleibt die Mod passiv und schreibt nur eine Hinweiszeile ins Log.
**ATM10-Updates**: Die Mod verwendet nur APIs, die in NeoForge 21.1.x stabil sind, und deklariert NeoForge ab
21.1.0. Bei Pack-Updates innerhalb von Minecraft 1.21.1 bleibt das JAR gültig. Der Mod-Ordner wird bei
Modpack-Updates manchmal ersetzt: danach prüfen, ob `mods/atmstats-*.jar` noch vorhanden ist.
## 4. SFTP-Abgleich (optional, empfohlen)
In `deploy/.env` `SFTP_HOST`, `SFTP_PORT`, `SFTP_USER`, `SFTP_PASSWORD` setzen (Gamepanel → Einstellungen →
SFTP-Details). `SFTP_ROOT` ist das Serververzeichnis, `SFTP_WORLD_DIR` der Weltordner (Standard `world`,
`level-name` aus `server.properties`). Danach `docker compose … up -d worker`.
Der Worker holt alle 15 Minuten nur geänderte Dateien. Unter `/admin` lässt sich ein Abgleich sofort anstoßen.
Ohne funktionierende Mod liefert SFTP trotzdem Gesamtzähler, Advancements, Quests und (aus dem Log) Beitritte;
Echtzeit-Daten, Todesursachen und Serverleistung fehlen dann.
Das SFTP-Passwort ist das des Gamepanels und gewährt Schreibzugriff. Der Worker liest ausschließlich.
## 5. Laufender Betrieb
### Updates
```bash
git pull
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build
```
Datenbankmigrationen laufen beim Start von api und worker automatisch (serialisiert über einen Advisory-Lock).
### Token und Secret erneuern
- **Ingest-Token**: `/admin` → *Ingest-Token erneuern*. Das alte Token wird sofort ungültig. Das neue in
`config/atmstats-server.toml` eintragen und den Gameserver neu starten. Bis dahin puffert die Mod und
meldet `authentication failed` im Log; es geht nichts verloren, solange das Outbox-Limit nicht erreicht ist.
- **HMAC-Secret**: in `deploy/.env` und in der Mod-Konfiguration gleichzeitig ändern, dann api und Gameserver neu starten.
### Backups
Der Container `backup` erstellt täglich `pg_dump`-Dateien im Volume `backups` und löscht sie nach
`BACKUP_KEEP_DAYS` (Standard 14). Sofortiges Backup:
```bash
docker compose -f deploy/docker-compose.yml --env-file deploy/.env exec backup sh /backup.sh once
```
Wiederherstellung (ersetzt alle Daten):
```bash
C="docker compose -f deploy/docker-compose.yml --env-file deploy/.env"
$C stop api worker web
$C exec backup ls -t /backups # gewünschte Datei wählen
$C exec backup dropdb atm
$C exec backup createdb atm
$C exec backup pg_restore --dbname=atm --no-owner /backups/<datei>.dump
$C start api worker web
```
Das Volume `backups` liegt auf demselben Host. Für echte Ausfallsicherheit die Dateien zusätzlich extern sichern
(z. B. per Borg/Restic vom Docker-Volume-Pfad).
### Verhalten bei Neustarts
| Situation | Verhalten |
|---|---|
| Gameserver stoppt normal | Mod schreibt `player_leave` und `server_stop` in die Outbox und sendet sie beim nächsten Start; nach 10 min ohne Health-Meldung schließt der Worker offene Sitzungen |
| Gameserver stürzt ab | beim nächsten Start schließt `server_start` offene Sitzungen zum letzten Lebenszeichen (kein Aufblähen der Spielzeit) |
| Backend nicht erreichbar | Mod puffert auf der Festplatte (max. `maxOutboxMb`, 7 Tage), sendet mit Backoff 1 s … 5 min weiter; bei Platzmangel werden zuerst alte Statistik-Snapshots verworfen |
| Doppelte Lieferung | jede Meldung hat eine eindeutige ID, Duplikate werden ignoriert |
| API/Worker-Neustart | zustandslos; der Worker setzt den Log-Abgleich am gespeicherten Byte-Offset fort |
## 6. Fehlerbilder
| Meldung | Ursache | Lösung |
|---|---|---|
| Mod: `backend NOT reachable: …ConnectException` / `HttpTimeoutException` | Mine-Hoster blockiert ausgehende Verbindungen, DNS falsch oder OPNsense/Traefik lässt nicht durch | `curl -v https://DOMAIN/api/health` von außen testen; Traefik-Log prüfen (403 = IP nicht in `INGEST_ALLOWED_IPS`); falls der Hoster ausgehend blockiert: `enabled = false` und nur SFTP nutzen |
| Mod: `clock skew: server clock differs …` | Uhr des Gameservers oder des DMZ-Servers weicht > 5 min ab | NTP auf dem DMZ-Server prüfen; beim Hoster melden, falls der Gameserver falsch geht |
| Mod: `authentication failed (bad_token)` | Token falsch oder widerrufen | neues Token erzeugen und eintragen |
| Mod: `authentication failed (bad_signature)` | `hmacSecret` ≠ `INGEST_HMAC_SECRET` | Werte angleichen |
| Mod: `batch rejected by backend … moved to outbox/rejected` | Daten passten nicht zum Protokoll (z. B. veraltete Mod-Version) | Datei in `atmstats/outbox/rejected/` samt `.reason.txt` prüfen, Mod und Backend auf gleichen Stand bringen |
| Mod: `outbox limit reached, dropped …` | Backend länger nicht erreichbar | Verbindung reparieren; ggf. `maxOutboxMb` erhöhen |
| `/sources`: SFTP „Fehler beim letzten Abgleich“ | Zugangsdaten, Port oder OPNsense-Regel | Fehlertext unter `/admin` → *Letzte Fehler* |
| Live-Ticker bleibt leer, Seite sonst ok | Proxy puffert SSE | keine Kompressions-/Buffering-Middleware auf `/api/live` |
| Build: `npm error Exit handler never called!` nach ~2 min | `npm ci` erreicht `registry.npmjs.org` nicht (ausgehender Proxy/Firewall) | Proxy in `deploy/.env` eintragen und Domains freischalten (Abschnitt 1, „Ausgehender Proxy“) |
| Build: Fehler beim Laden von Google Fonts | `fonts.googleapis.com`/`fonts.gstatic.com` nicht freigeschaltet | in der Proxy-Whitelist ergänzen |
| Nur graue Spielerköpfe | `mc-heads.net` nicht erreichbar | in der Proxy-Whitelist ergänzen (optional) |
| Login unter `/admin` klappt nicht ohne HTTPS | Cookie ist `Secure` | Portal über HTTPS aufrufen (lokal: `NODE_ENV=development`) |
Spieler, die nicht im Portal erscheinen möchten, lassen sich unter `/admin` ausblenden: Sie verschwinden aus allen
öffentlichen Ansichten, Ranglisten und Live-Meldungen. Die Daten bleiben gespeichert, sodass sich das Ausblenden
rückgängig machen lässt.