feat: ATM10-Statistikportal
Statistikportal für den All-the-Mods-10-Server (Minecraft 1.21.1, NeoForge 21.1): - mod/: serverseitige NeoForge-Mod atmstats – Ereignisse, Live-Zähler, Servergesundheit, FTB-Dateien; Outbox auf der Festplatte, signierter gzip-Versand per HTTPS mit Backoff - backend/: Fastify + PostgreSQL – idempotenter Ingest, Projektionen, Parser für SNBT, FTB Quests/Teams, world/stats, world/advancements und latest.log; Ranglisten, Snapshots, Retention, öffentliche API, SSE-Live-Updates, Admin mit argon2/CSRF, SFTP-Worker - web/: Next.js-Dashboard – Übersicht, Ranglisten, Spielerprofile, ATM10, Server, Datenquellen, Verwaltung - shared/: Protokoll v1 und API-Typen; tools/: Simulator, Log-Anonymisierer, RCON-Client - deploy/: Docker Compose hinter Traefik, Backup; docs/: Architektur, Datenquellen, Betrieb Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
56d2078e6a
commit
e66f89f49e
214 files changed
+27077
No files matched your search
@@ -0,0 +1,105 @@
|
||||
# Architektur
|
||||
|
||||
Für Entwickler, die am Portal weiterarbeiten. Die vollständige Design-Begründung steht in
|
||||
`docs/superpowers/specs/2026-10-13-atm10-stats-design.md`.
|
||||
|
||||
## Leitidee
|
||||
|
||||
Alle Datenquellen erzeugen **Ereignisse desselben Protokolls** (v1, `shared/src/protocol.ts`) und laufen durch
|
||||
**eine** Pipeline (`backend/src/pipeline/process.ts`):
|
||||
|
||||
```
|
||||
Mod (Push) ──┐
|
||||
├─▶ processEvents(events, source)
|
||||
SFTP (Pull) ─┘ 1. INSERT INTO events … ON CONFLICT (event_id) DO NOTHING ← Idempotenz
|
||||
2. nur neue Ereignisse → Projektionen (sessions, stat_current, deaths, quest_progress …)
|
||||
3. nach COMMIT → pg_notify('atm_live') → LiveHub → SSE-Clients
|
||||
```
|
||||
|
||||
Lese-API und Frontend kennen nur die projizierten Tabellen, nie die Quelle.
|
||||
|
||||
## Komponenten
|
||||
|
||||
### Mod `atmstats` (`mod/`)
|
||||
|
||||
- `core/`: reines Java ohne Minecraft, vollständig unit-getestet
|
||||
- `OutboxJournal`: Journal auf der Festplatte (`current.jsonl` → `seg-<n>.jsonl`; ein Segment = ein Batch,
|
||||
gelöscht erst nach Bestätigung; Limits mit Vorrang für Ereignisse vor Snapshots; abgeschnittene letzte Zeile
|
||||
nach Absturz wird verworfen)
|
||||
- `Shipper`: Queue → Journal → HTTPS; Backoff 1 s … 5 min mit Jitter; 4xx-Batches nach `rejected/`
|
||||
- `Signer`, `UuidV7`, `Backoff`, `EventJson`, `StatsDiff`, `HealthWindow`, `FileChangeTracker`
|
||||
- `mc/`: dünne NeoForge-Schicht
|
||||
- `GameHooks`: Event-Handler auf dem Server-Thread. Sie erzeugen nur Records und legen sie in eine `ConcurrentLinkedQueue`
|
||||
- `StatsCollector`: kopiert alle 5 min die Zählertabelle online Spieler (max. 2 Spieler pro Tick; Zugriff
|
||||
über Access Transformer auf `StatsCounter.stats`); Umrechnung und Diff im Worker-Thread
|
||||
- `HealthMonitor`: misst Tickdauer per `ServerTickEvent.Pre/Post`
|
||||
- `FileWatcher`: reicht geänderte FTB-Dateien unverändert als `file_sync` weiter (Parsing im Backend)
|
||||
- `WorkerThread`: einziger Hintergrund-Thread für Serialisierung, Datei- und Netzwerk-I/O
|
||||
- `ServerInfoCollector`: Versionen und Modliste über `ModList`
|
||||
|
||||
### Backend (`backend/`)
|
||||
|
||||
| Modul | Aufgabe |
|
||||
|---|---|
|
||||
| `ingest/` | `POST /api/ingest`: Bearer-Token (als SHA-256 gespeichert), Zeitfenster ±5 min, HMAC über die übertragenen Bytes, gzip mit 2-MB-Grenze, Validierung je Ereignis |
|
||||
| `pipeline/` | `processEvents`, Projektionen je Ereignistyp |
|
||||
| `parsers/` | SNBT (FTB-Format ohne Kommas), FTB-Quests, `world/stats`, `world/advancements`, `latest.log` |
|
||||
| `stats/` | Ranglisten-Definitionen (`metrics.ts`), SQL je Board, Snapshots, Retention |
|
||||
| `routes/` | öffentliche API, SSE (`/api/live`), Admin |
|
||||
| `worker/` | SFTP-Abgleich (`FileSource`: SFTP oder lokal), Jobs mit `pg_try_advisory_lock` |
|
||||
| `live/` | Notifier (Ereignis → Live-Meldung, ausgeblendete Spieler gefiltert) und LiveHub (LISTEN, Ringpuffer 500, Resume über `Last-Event-ID`) |
|
||||
|
||||
Zwei Prozesse aus einer Codebasis: `server.ts` (api) und `worker.ts`. Migrationen (`backend/migrations/*.sql`)
|
||||
führt der eigene Migrator beim Start aus.
|
||||
|
||||
### Frontend (`web/`)
|
||||
|
||||
Next.js App Router, Server Components holen Daten direkt bei der API (`API_INTERNAL_URL`), der Browser nutzt
|
||||
same-origin `/api`. Live-Teile (Ticker, Diagramm-Umschalter, Admin) sind Client-Komponenten. Design-Tokens in
|
||||
`src/app/globals.css`: Deepslate-Flächen, ATM-Erzfarben als Akzente, validierte Diagrammpalette.
|
||||
|
||||
## Protokoll v1
|
||||
|
||||
```
|
||||
POST /api/ingest
|
||||
Authorization: Bearer <token>
|
||||
X-ATM-Server: <serverId>
|
||||
X-ATM-Timestamp: <unix ms>
|
||||
X-ATM-Signature: hex(HMAC-SHA256(secret, timestamp + "." + hex(sha256(body))))
|
||||
Content-Encoding: gzip
|
||||
{ "protocol": 1, "events": [ … ] } → { accepted, duplicate, rejected }
|
||||
```
|
||||
|
||||
Ereignistypen: `server_start`, `server_stop`, `player_join`, `player_leave`, `player_death`, `entity_kill`,
|
||||
`advancement`, `dimension_change`, `stats_snapshot`, `file_sync`, `health`. Beispiele:
|
||||
`shared/fixtures/protocol/`. Push-Ereignisse tragen UUIDv7, SFTP-Ereignisse deterministische UUIDv5 aus Datei und
|
||||
Inhalt. Dadurch ist auch wiederholtes Einlesen idempotent.
|
||||
|
||||
**Zählerregel**: Werte sind absolut. Pro (Spieler, Kategorie, Schlüssel) gewinnt die Beobachtung mit dem neuesten
|
||||
Zeitstempel, unabhängig von der Quelle und auch wenn der Wert kleiner ist.
|
||||
|
||||
## Datenmodell (Kern)
|
||||
|
||||
`players`, `name_history`, `sessions`, `events`, `stat_current`, `stat_snapshots`, `deaths`, `kills`,
|
||||
`advancements`, `dimension_visits`, `teams`, `team_members`, `quest_progress`, `quest_defs`, `file_cache`,
|
||||
`server_info`, `server_health`, `server_health_hourly`, `sync_state`, `sync_requests`, `app_errors`,
|
||||
`admin_users`, `admin_sessions`, `ingest_tokens`. Ranglisten werden live aus `stat_current` berechnet
|
||||
(Index `(category, key, value DESC)`), Zeiträume aus den Tages-Snapshots.
|
||||
|
||||
## Sicherheit
|
||||
|
||||
- Ingest: Token + HMAC + Zeitfenster + Rate-Limit + IP-Allowlist in Traefik; Payload-Grenzen; Pfad-Traversal-Schutz für `file_sync`
|
||||
- Admin: argon2id, Session-Cookie `httpOnly; Secure; SameSite=Strict; Path=/api/admin`, CSRF-Token-Header, Login-Rate-Limit
|
||||
- Keine Tokens, Signaturen oder Cookies in Logs (pino-Redaction); Spieler-IPs werden nirgends gespeichert
|
||||
- Spielerköpfe lädt die API serverseitig (Cache 24 h), damit Besucher keine Drittanbieter kontaktieren
|
||||
- Datenbank nur im internen Docker-Netz
|
||||
|
||||
## Tests
|
||||
|
||||
| Ebene | Was |
|
||||
|---|---|
|
||||
| `shared` | Fixtures gegen Schemas |
|
||||
| `backend` (Vitest, echte Postgres) | Ingest-Auth und Idempotenz, alle Projektionen inkl. Absturz-Sitzungen und Namenswechsel, Parser an echten Serverdateien, Ranglisten, Snapshots/Retention, API inkl. ausgeblendeter Spieler, SSE, Admin/CSRF, Worker inkl. Log-Rotation |
|
||||
| `mod` (JUnit) | Journal (Neustart, Absturz, Rotation, Limits), Shipper (Backoff, Ablehnung, Uhrabweichung), Serialisierung gegen `shared/fixtures`, Signatur-Testvektor |
|
||||
| `web` (Vitest + Testing Library) | Formatierung, Ranglisten-Karte, Heatmap |
|
||||
| E2E (Playwright) | Smoke-Test gegen den Compose-Stack, Desktop und Mobil |
|
||||
@@ -0,0 +1,72 @@
|
||||
# Datenquellen und Einschränkungen
|
||||
|
||||
Jede Zahl im Portal stammt aus einer der unten genannten Quellen. Nichts wird geschätzt oder erfunden.
|
||||
Wo eine Quelle fehlt, zeigt das Portal einen leeren Zustand oder den Hinweis, woher die Daten kommen werden.
|
||||
|
||||
## Grundlage: was am echten Server geprüft wurde
|
||||
|
||||
Stand 13.10.2026, aus einer Kopie des Servers (`server-snapshot/`, nicht im Repository, enthält Spieler-IPs):
|
||||
|
||||
| Fakt | Beleg |
|
||||
|---|---|
|
||||
| Minecraft 1.21.1 | `DataVersion 3955` in `world/stats/*.json` |
|
||||
| 464 Mods, u. a. FTB Quests 2101.1.36, FTB Teams 2101.1.11, Mekanism 10.7.19, Apotheosis 8.8.0, Spark 1.10.124 | Modliste des Servers |
|
||||
| FTB-Quest-Fortschritt je Team als SNBT (`completed`, `started`, `task_progress`, `claimed_rewards`) | `world/ftbquests/<team>.snbt` |
|
||||
| Mod-eigene Zähler in den Vanilla-Statistiken: `apotheosis:world_tiers_activated`, `lootr:looted_stat`, `waystones:waystone_activated` | `world/stats/*.json` |
|
||||
| Item-Statistiken enthalten Einträge aus Mekanism, Powah, Mystical Agriculture, Ars Nouveau, Iron's Spellbooks, Create, Modern Industrialization, Oritech u. v. m. | `world/stats/*.json` |
|
||||
| Log enthält Beitritte (mit UUID), Verlassen, Advancements/Ziele/Herausforderungen im Klartext; Zeilenenden CRLF | `logs/latest.log` |
|
||||
| NeoForge 21.1.219 und Java 21 (Testlauf der Mod auf einem lokalen NeoForge-Server) | `server_start`-Ereignis der Mod |
|
||||
| Quest-Konfiguration: 66 Kapitel (`chapters/*.snbt`), 4 790 Quests, Texte über ftbquestslangsplitter in `lang/en_us/**/*.snbt_merged` (decken alle 3 103 Titel der 1,5 MB großen `lang/en_us.snbt` ab); Titel mit Farbcodes (`&a`, `&6&l`, `\&` für ein echtes &) | `config/ftbquests/quests/` |
|
||||
| Von 585 IDs in `completed` (CDOGamer) sind nur 267 Quests, der Rest Aufgaben und Kapitel | Abgleich mit der Quest-Konfiguration |
|
||||
| FTB-Teams: `id`, `type` (player/party/server), `ranks` (UUID → Rang), `properties."ftbteams:display_name"` | `world/ftbteams/player/*.snbt` |
|
||||
|
||||
Die exakte NeoForge- und Java-Version **des Produktionsservers** meldet die Mod beim ersten Start (Seite *Server*).
|
||||
|
||||
## Welche Kennzahl woher kommt
|
||||
|
||||
| Kennzahl | Quelle | Aktualität | Hinweis |
|
||||
|---|---|---|---|
|
||||
| Online-Status, Sitzungen, Heatmap „Wann gespielt wird“ | Mod: Beitritt/Verlassen; Fallback: Server-Log | sofort; Fallback ≤ 15 min | Log-Sitzungen werden nur genutzt, wenn die Mod seit 10 min nichts gesendet hat; liefert die Mod später nach, werden überlappende Sitzungen nicht doppelt gezählt |
|
||||
| Spielzeit, Tode, besiegte Mobs, Sprünge, Schaden | Vanilla-Zähler: Mod liest sie live, SFTP liest `world/stats` | 5 min (Mod) bzw. beim Speichern der Welt (SFTP) | die neueste Beobachtung gewinnt |
|
||||
| Strecken je Fortbewegungsart | Vanilla-Zähler `*_one_cm` | wie oben | „Weltenbummler“ ohne Fallstrecke |
|
||||
| Abgebaut / hergestellt / benutzt / aufgesammelt je Item und Mod | Vanilla-Zähler `minecraft:mined` usw. | wie oben | Mod-Zuordnung über den Namespace der Item-ID |
|
||||
| Technik / Magie / Farming | Summe hergestellter und benutzter Items der Mods einer Kategorie | wie oben | Zuordnung in `backend/config/mod-categories.json` |
|
||||
| Diamantenbank | abgebaute Diamanterze (normal + Tiefenschiefer) | wie oben | misst Abbau, **nicht** Besitz |
|
||||
| Schatzjäger, Weltenstufe | `lootr:looted_stat`, `apotheosis:world_tiers_activated` | wie oben | |
|
||||
| Todesursachen, Killer | Mod: `LivingDeathEvent` | sofort | ohne Mod nur Anzahl und `killed_by`-Zähler |
|
||||
| Boss- und Spieler-Kills mit Waffe | Mod: `LivingDeathEvent`, Boss = NeoForge-Tag `c:bosses` | sofort | normale Mob-Kills nur als Zähler (Last) |
|
||||
| Advancements | Mod: `AdvancementEarnEvent`; SFTP: `world/advancements`; Log (nur für Spieler ohne Advancement-Datei) | sofort / ≤ 15 min | Rezept-Freischaltungen ausgenommen. Die Mod zählt nur Advancements mit Anzeige im Spiel; aus `world/advancements` kommen auch solche ohne Anzeige, weil die Datei keine Anzeige-Informationen enthält. Ohne Mod kann „Abenteurer“ daher etwas höher ausfallen |
|
||||
| Besuchte Dimensionen | Mod: Dimensionswechsel | sofort | erst ab Installation der Mod |
|
||||
| FTB-Quest-Fortschritt | `world/ftbquests/*.snbt` (Mod alle 2 min oder SFTP) | 2–15 min | pro Team; gezählt werden nur IDs, die in der Quest-Konfiguration als Quest vorkommen |
|
||||
| Quest- und Kapiteltitel | `config/ftbquests/quests/chapters/*.snbt` + `lang/en_us/**/*.snbt_merged` (Mod oder SFTP; andere Sprachen und Belohnungstabellen werden nicht übertragen) | bei Änderung | Farbcodes entfernt |
|
||||
| Party-Teams | `world/ftbteams/{player,party}/*.snbt` | bei Änderung | |
|
||||
| TPS, Tickdauer (Ø, 95. Perzentil, Max), Spieler, Chunks, Speicher | eigene Messung der Mod um jeden Tick | jede Minute | unabhängig von Spark |
|
||||
| Versionen, Modliste | Mod beim Serverstart (`ModList`) | bei jedem Start | |
|
||||
| Verlaufsdiagramme, 7-/30-Tage-Ranglisten | stündliche und tägliche Snapshots durch den Worker | stündlich | Verläufe beginnen mit der Inbetriebnahme; Stunden 30 Tage, Tage unbegrenzt |
|
||||
|
||||
## Bewusst nicht erfasst
|
||||
|
||||
Ohne verlässliche serverseitige Quelle oder nur über ungeprüfte Interna einzelner Mods erreichbar:
|
||||
|
||||
- **Energie** (Erzeugung und Verbrauch von Mekanism-, Powah- und anderen Maschinen)
|
||||
- **Inhalte von AE2- und Refined-Storage-Netzen**
|
||||
- **Mana, Source, Zauber, Rituale** (Ars Nouveau, Iron's Spellbooks, Occultism); Magie-Aktivität stammt aus Items
|
||||
- **MineColonies-Kolonien**
|
||||
- **Spielerpositionen, Todesorte, Heatmaps der Welt**: Datenschutz (Basen) und Last
|
||||
- **Inventare/Besitz** (daher misst die Diamantenbank den Abbau)
|
||||
|
||||
Erweiterungen sind über neue Ereignistypen im Protokoll möglich, jeweils erst nach Prüfung der Mod-API in
|
||||
der installierten Version.
|
||||
|
||||
## Einschränkungen durch Hoster und Mods
|
||||
|
||||
| Einschränkung | Auswirkung | Status |
|
||||
|---|---|---|
|
||||
| Ob Mine-Hoster **ausgehendes HTTPS** erlaubt, ist nicht dokumentiert | ohne Freigabe nur SFTP-Modus (keine Echtzeit, keine Todesursachen, keine Leistungsdaten) | wird beim ersten Start geprüft: Log-Zeile `atmstats: backend reachable` |
|
||||
| `world/stats` wird vom Server nur beim Speichern geschrieben | SFTP-Zähler hinken bis zum nächsten Autosave hinterher | Mod liefert Live-Werte alle 5 min |
|
||||
| Der Log-Ausschnitt enthielt keinen Startabschnitt | Versionen nicht aus dem Log lesbar | Mod meldet sie beim Start |
|
||||
| Log-Zeitstempel sind in der lokalen Zeitzone des Servers | Zeitzone muss stimmen (`TIMEZONE`, Standard Europe/Berlin) | für Mine-Hoster nicht bestätigt; betrifft nur log-basierte Daten |
|
||||
| **FTB Teams**: Party-Dateien auf dem Server derzeit nicht vorhanden | Party-Mitgliedschaft wird aus `ranks` (member/officer/owner) gelesen; Format an den echten Spieler-Team-Dateien geprüft, Party-Dateien nutzen dieselben Felder | ohne Party zählt jeder Spieler als eigenes Team (wie im Spiel) |
|
||||
| **Quest-Titel**: 1 789 von 4 790 Quests haben keinen eigenen Titel | sie zählen mit, erscheinen aber nicht namentlich (im Spiel heißen sie nach dem Aufgaben-Item, das serverseitig nicht übersetzbar ist) | geprüft an der echten Konfiguration (66 Kapitel, 3 029 Titel) |
|
||||
| SFTP-Passwort = Gamepanel-Passwort | volle Schreibrechte im Worker-Container | Worker liest nur; `.env` schützen |
|
||||
| Spieler-IPs stehen im Server-Log | Datenschutz | werden nie gespeichert; Test-Fixtures anonymisiert |
|
||||
@@ -0,0 +1,173 @@
|
||||
# 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.
|
||||
|
||||
## 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` |
|
||||
| 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.
|
||||
@@ -365,4 +365,5 @@ Neustartverhalten, Fehlerbilder), `mod/README.md`.
|
||||
3. Struktur von `config/ftbquests/quests/` und `world/ftbteams/` → Betreiber
|
||||
stellt Kopie bereit, bevor Quest-Titel/Team-Namen-Parser finalisiert werden;
|
||||
bis dahin werden Quest-IDs angezeigt.
|
||||
→ Geklärt am 13.10.2026 anhand der echten Dateien, siehe `docs/DATA-SOURCES.md`.
|
||||
4. SFTP-Host/Port/Benutzer → `.env` des Betreibers (nicht im Repo).
|
||||
Reference in new issue
Block a user