369 lines
21 KiB
Markdown
369 lines
21 KiB
Markdown
# ATM10 Stats – Design-Spezifikation
|
||
|
||
Stand: 2026-10-13 · Status: zur Freigabe
|
||
|
||
## 1. Ziel
|
||
|
||
Ein öffentliches, modernes Statistikportal für den bei Mine-Hoster gehosteten
|
||
All-The-Mods-10-Server (ATM10). Vorbild für Inhalt und Anmutung ist
|
||
craftattack.one/stats (Kennzahlen, „Gerade online“, Top-10-Listen); das Portal
|
||
geht darüber hinaus mit Spielerprofilen, historischen Diagrammen und
|
||
ATM10-spezifischen Auswertungen (Mods, FTB Quests, Servergesundheit).
|
||
|
||
Erfolgskriterien:
|
||
|
||
- Spieler sehen ihre echten Werte innerhalb weniger Sekunden (Ereignisse) bzw.
|
||
≤ 5 Minuten (Zähler) nach dem Geschehen.
|
||
- Keine erfundenen Daten: Jede angezeigte Kennzahl hat eine dokumentierte,
|
||
nachprüfbare Quelle.
|
||
- Die Mod verursacht keine messbare Tick-Last; kein Datenverlust bei Neustarts
|
||
von Gameserver oder Backend.
|
||
- Betrieb per Docker Compose hinter dem vorhandenen Traefik in der DMZ.
|
||
|
||
## 2. Verifizierte Ausgangslage
|
||
|
||
Quelle: `server-snapshot/` (vom Betreiber per SFTP bereitgestellt).
|
||
|
||
| Fakt | Beleg |
|
||
|---|---|
|
||
| Minecraft 1.21.1 | `DataVersion: 3955` in `world/stats/*.json` |
|
||
| NeoForge 21.1.x, Java 21 | Modliste (`*-neoforge-1.21.1-*`), MC 1.21.1 setzt Java 21 voraus. Exakte NeoForge-/Java-Version: fehlt im Log-Ausschnitt → wird von der Mod beim Start ermittelt und gemeldet |
|
||
| 464 Mod-JARs | `mod-list.txt` |
|
||
| FTB Quests 2101.1.36, FTB Teams 2101.1.11, FTB Chunks, FTB Ranks, FTB Essentials | `mod-list.txt` |
|
||
| Quest-Fortschritt pro **Team** als SNBT (`task_progress`, `completed`, `started`, `claimed_rewards`, `completion_count`) | `world-ftbquests/*.snbt` |
|
||
| Spark 1.10.124 installiert | `mod-list.txt` (wird **nicht** als API genutzt) |
|
||
| Log enthält Login (UUID + IP), Join/Leave, Advancements/Goals/Challenges im Klartext, „Can't keep up“ | `latest.log` |
|
||
| Server läuft mit ~2,2 GB Heap und hat Lags bis 10 s | `latest.log` (AllTheLeaks, Can't keep up) |
|
||
| Mine-Hoster: SFTP-Vollzugriff, eigene Mods in `mods/` möglich | Mine-Hoster-Support (SFTP-, Mods-, Modpack-Artikel) |
|
||
| Ausgehendes HTTPS vom Gameserver | **unbekannt** → Hybrid-Design (Push + SFTP-Pull) |
|
||
|
||
Mod-spezifische Zähler, die bereits in Vanilla-Stats auftauchen:
|
||
`apotheosis:world_tiers_activated`, `lootr:looted_stat`,
|
||
`waystones:waystone_activated`. Item-/Block-Statistiken enthalten Einträge aus
|
||
u. a. mekanism, powah, mysticalagriculture, ars_nouveau, irons_spellbooks,
|
||
modern_industrialization, oritech, create, refinedstorage, draconicevolution,
|
||
silentgear, occultism, theurgy, xycraft_world, minecolonies.
|
||
|
||
Nicht installiert (daher keine Integration): Blood Magic, Botania.
|
||
|
||
Datenschutz: `latest.log` enthält Spieler-IP-Adressen. `server-snapshot/` wird
|
||
nicht versioniert; Testfixtures aus dem Log werden anonymisiert (IP entfernt).
|
||
|
||
## 3. Architektur
|
||
|
||
```
|
||
Mine-Hoster (ATM10, NeoForge 1.21.1) Eigener Server in der DMZ
|
||
┌──────────────────────────────┐ HTTPS POST OPNsense → Traefik → Docker Compose
|
||
│ atmstats (serverseitige Mod) │ ──────────────▶ ┌────────────────────────────────┐
|
||
│ Event-Hooks, Outbox-Journal │ Bearer + HMAC │ api Fastify/TS: ingest, │
|
||
│ async Sender mit Backoff │ │ REST, SSE, admin │
|
||
└──────────────────────────────┘ │ worker SFTP-Pull, Snapshots, │
|
||
▲ world/stats, world/ftbquests, │ Retention │
|
||
│ config/ftbquests, logs │ web Next.js (App Router) │
|
||
└───────────── SFTP-Pull ◀────────────────│ db PostgreSQL 16 │
|
||
│ backup pg_dump täglich │
|
||
└────────────────────────────────┘
|
||
```
|
||
|
||
Monorepo:
|
||
|
||
```
|
||
mod/ NeoForge-Mod (Gradle, Java 21, ModDevGradle)
|
||
backend/ api + worker (TypeScript, Fastify, gemeinsamer Code, zwei Entrypoints)
|
||
web/ Next.js-Frontend (TypeScript)
|
||
shared/ gemeinsame TS-Typen + Zod-Schemas (Ingest-Protokoll, API-DTOs)
|
||
deploy/ docker-compose.yml, .env.example, Backup-Skript
|
||
tools/ Simulator (sendet realistische Mod-Batches), Fixture-Anonymisierer
|
||
docs/ README-Ergänzungen, ARCHITECTURE, DATA-SOURCES, OPERATIONS
|
||
```
|
||
|
||
### 3.1 Datenquellen und Zuständigkeit
|
||
|
||
| Daten | Primärquelle | Fallback |
|
||
|---|---|---|
|
||
| Online-Status, Sessions | Mod: Login/Logout-Events | SFTP: Log-Parser (`joined/left the game`, `UUID of player`) |
|
||
| Gesamtzähler (Stats) | SFTP: `world/stats/<uuid>.json` (maßgeblich, absolut) | — |
|
||
| Zähler zwischen Speicherungen | Mod: 5-min-Snapshot der Live-`ServerStatsCounter` online Spieler (absolute Werte) | — |
|
||
| Tode (Ursache, Killer, Dimension, Position grob) | Mod: `LivingDeathEvent` | Log: nicht verlässlich (Death-Messages lokalisiert/mod-abhängig) → nur Zähler aus Stats |
|
||
| Spieler-/Boss-Kills mit Waffe/Dimension | Mod: `LivingDeathEvent` mit Spieler als Quelle | Stats `minecraft:killed` (nur Zähler) |
|
||
| Advancements | Mod: `AdvancementEvent.AdvancementEarnEvent` | Log-Parser (`has made the advancement/reached the goal/completed the challenge`) |
|
||
| Dimensionswechsel | Mod: `PlayerChangedDimensionEvent` | — |
|
||
| FTB-Quest-Fortschritt | Mod liest `world/ftbquests/*.snbt` (eigener Thread, nur bei Änderung) | SFTP: dieselben Dateien |
|
||
| Quest-Definitionen (Kapitel, Titel) | Mod liest `config/ftbquests/quests/**` beim Start | SFTP |
|
||
| Teams | SNBT-Dateien von FTB Teams/Quests (`world/ftbteams/` wird beim Bau verifiziert) | SFTP |
|
||
| Versionen, Modliste | Mod: `ModList`, `SharedConstants`, `System.getProperty("java.version")` beim Start | Log-Header (falls vorhanden) |
|
||
| Servergesundheit (MSPT/TPS, Spieler, geladene Chunks, Heap) | Mod: eigene Messung über `ServerTickEvent.Pre/Post`, 60-s-Aggregat | Log: „Can't keep up“-Zählung |
|
||
|
||
Konfliktregel für Zähler: Werte sind **absolut** (nicht Deltas). `stat_current`
|
||
übernimmt pro Schlüssel den Wert mit dem neuesten Beobachtungszeitpunkt;
|
||
ein kleinerer Wert ersetzt einen größeren nur, wenn er aus einer neueren
|
||
Stats-Datei stammt (z. B. nach Admin-Reset). Damit sind wiederholte und
|
||
verspätete Lieferungen unschädlich.
|
||
|
||
Quest-Texte: Titel können als Übersetzungsschlüssel vorliegen
|
||
(`ftbquestslangsplitter` ist installiert). Die Mod liest zusätzlich
|
||
`config/ftbquests/quests/lang/en_us.snbt` bzw. die Lang-Dateien, falls
|
||
vorhanden; sonst wird die Quest-ID angezeigt. Exakte Pfade werden in der
|
||
Umsetzung anhand einer Kopie von `config/ftbquests/` verifiziert, bevor der
|
||
Parser fest verdrahtet wird.
|
||
|
||
### 3.2 Bewusst nicht unterstützt
|
||
|
||
Ohne verlässliche, dokumentierte serverseitige Quelle oder nur mit
|
||
ungeprüften Mod-Interna erreichbar; wird im Portal unter „Datenquellen“
|
||
transparent ausgewiesen:
|
||
|
||
- Energieproduktion/-verbrauch von Maschinen (Mekanism, Powah …)
|
||
- Inhalte von AE2-/Refined-Storage-Netzwerken
|
||
- Mana, Zauber, Source (Ars Nouveau, Iron's Spellbooks), Occultism-Rituale
|
||
- MineColonies-Kolonien
|
||
- exakte Spielerposition/Heatmaps (Datenschutz + Last)
|
||
|
||
Spätere Erweiterung ist über das Ereignisprotokoll möglich (neue Event-Typen),
|
||
jeweils erst nach Prüfung der jeweiligen Mod-API gegen die installierte Version.
|
||
|
||
## 4. Mod `atmstats`
|
||
|
||
- NeoForge 21.1.x (Build gegen die zur ATM10-Version passende Version),
|
||
Java 21, ModDevGradle. Mod-ID `atmstats`.
|
||
- Rein serverseitig: `displayTest = "IGNORE_ALL_VERSION"` in
|
||
`neoforge.mods.toml`; Clients brauchen die Mod nicht. Keine Registrierungen
|
||
(Blöcke, Items, Netzwerkpakete).
|
||
- Keine Compile-Abhängigkeit auf andere Mods.
|
||
|
||
### 4.1 Threading und Last
|
||
|
||
- Event-Handler auf dem Server-Thread erzeugen nur kleine, unveränderliche
|
||
Event-Objekte und legen sie in eine `ConcurrentLinkedQueue` (O(1), keine
|
||
I/O, keine Serialisierung).
|
||
- Stats-Snapshot alle 5 min (konfigurierbar): auf dem Server-Thread werden je
|
||
online Spieler die Zählerwerte in ein `Object2IntMap` kopiert; Serialisierung
|
||
und Versand erfolgen im Mod-eigenen Daemon-Thread. Der Snapshot wird über
|
||
mehrere Ticks verteilt, falls viele Spieler online sind (max. 2 Spieler/Tick).
|
||
- Ein einzelner Daemon-Worker-Thread: Queue → Journal → Sender.
|
||
SNBT-Dateilesen ebenfalls dort (nur bei geänderter mtime, alle 2 min).
|
||
- Tick-Messung: `System.nanoTime()` in Pre/Post, Aggregat (avg/p95/max MSPT)
|
||
im Ringpuffer, Versand einmal pro Minute.
|
||
|
||
### 4.2 Zuverlässigkeit
|
||
|
||
- Event-ID: UUIDv7 (zeitlich sortierbar), erzeugt beim Entstehen des Events.
|
||
- Outbox-Journal: `<server>/atmstats/outbox/` als Segmentdateien
|
||
(JSON Lines, fsync beim Rotieren, max. 1 MB/Segment). Ein Segment wird erst
|
||
gelöscht, wenn das Backend alle IDs bestätigt hat.
|
||
- Begrenzung: max. 50 MB bzw. 7 Tage; bei Überschreitung werden die ältesten
|
||
**Stats-Snapshots** zuerst verworfen (sind durch spätere ersetzbar), Ereignisse
|
||
zuletzt; Verwerfen wird geloggt.
|
||
- Beim Serverstopp (`ServerStoppingEvent`): Queue synchron ins Journal flushen
|
||
(Zeitlimit 2 s), Session-Ende für alle online Spieler erzeugen.
|
||
- Beim Start: Journal-Segmente werden erneut gesendet; ein `server_start`-Event
|
||
ermöglicht dem Backend, offene Sessions eines Absturzes zu schließen.
|
||
- Versand: `java.net.http.HttpClient` (HTTP/2, TLS-Verifikation an), Batches
|
||
bis 500 Events/256 KB, gzip, Timeout 10 s. Backoff exponentiell
|
||
1 s → 5 min mit Jitter. Bei 4xx (außer 408/429) wird der Batch nicht endlos
|
||
wiederholt, sondern in `outbox/rejected/` abgelegt und geloggt.
|
||
- Startup-Selbsttest: `GET /api/ingest/ping` → Ergebnis ins Log
|
||
(„atmstats: backend reachable / NOT reachable: <Grund>“). Damit wird die
|
||
offene Frage „ausgehendes HTTPS erlaubt?“ beim ersten Start beantwortet.
|
||
|
||
### 4.3 Konfiguration (`config/atmstats-server.toml`, NeoForge ModConfigSpec)
|
||
|
||
`enabled`, `endpoint`, `serverId`, `token`, `hmacSecret`,
|
||
`statsSnapshotMinutes` (5), `questScanMinutes` (2), `healthIntervalSeconds`
|
||
(60), `maxOutboxMb` (50). Ohne Token bleibt die Mod passiv (nur Log-Hinweis).
|
||
|
||
### 4.4 Protokoll (Version 1)
|
||
|
||
```
|
||
POST /api/ingest
|
||
Authorization: Bearer <token>
|
||
X-ATM-Server: <serverId>
|
||
X-ATM-Timestamp: <unix ms>
|
||
X-ATM-Signature: hex(HMAC-SHA256(secret, timestamp + "." + sha256(body)))
|
||
Content-Encoding: gzip
|
||
{ "protocol": 1, "events": [ { "id": "<uuidv7>", "type": "...", "ts": <ms>, ...payload } ] }
|
||
→ 200 { "accepted": ["<id>", ...], "duplicate": ["<id>", ...], "rejected": [{ "id", "reason" }] }
|
||
```
|
||
|
||
Event-Typen: `server_start` (Versionen, Modliste), `server_stop`,
|
||
`player_join`, `player_leave`, `player_death`, `entity_kill`, `advancement`,
|
||
`dimension_change`, `stats_snapshot` (absolute Werte je Kategorie),
|
||
`quest_progress` (Team-ID, abgeschlossene Quest-IDs mit Zeitstempel,
|
||
Task-Fortschritt-Zähler), `quest_defs` (Kapitel/Quests/Titel, nur bei Änderung,
|
||
mit Hash), `team_info`, `health`. Schemas als Zod in `shared/` und als
|
||
JSON-Schema-Testfixtures, gegen die auch die Java-Serialisierung getestet wird.
|
||
|
||
## 5. Backend
|
||
|
||
TypeScript, Node 22 LTS, Fastify, Zod, `pg` mit SQL (kein ORM),
|
||
Migrationen mit `node-pg-migrate`. Zwei Prozesse aus einer Codebasis:
|
||
|
||
**api**
|
||
- `GET /api/ingest/ping` (authentifiziert), `POST /api/ingest`.
|
||
Prüft Token (konstante Zeit), Zeitfenster ±5 min, HMAC; Rate-Limit;
|
||
Body-Limit 2 MB entpackt. Verarbeitung pro Batch in einer Transaktion:
|
||
`INSERT INTO events … ON CONFLICT (event_id) DO NOTHING`, nur neu
|
||
eingefügte Events werden projiziert (sessions, deaths, stat_current …).
|
||
Danach Benachrichtigung via `pg_notify('atm_live', …)`.
|
||
- Öffentlich: `GET /api/overview`, `/api/online`,
|
||
`/api/leaderboards` (Liste der Boards), `/api/leaderboards/:board?period=all|30d|7d`,
|
||
`/api/players?search=`, `/api/players/:uuid`,
|
||
`/api/players/:uuid/history?metric=&range=`, `/api/players/:uuid/items?mod=`,
|
||
`/api/mods` (Modliste + Aktivität je Mod), `/api/quests` (Kapitelfortschritt),
|
||
`/api/server` (Versionen, Health-Verlauf), `/api/sources` (Datenquellen-Status:
|
||
letzter Push, letzter SFTP-Sync), `/api/avatar/:uuid` (Proxy-Cache für
|
||
Spielerköpfe, 24 h; keine Besucher-IPs an Dritte).
|
||
- `GET /api/live` (SSE): abonniert `LISTEN atm_live`; Events `online`,
|
||
`death`, `advancement`, `quest`, `counters`; Heartbeat 25 s,
|
||
`Last-Event-ID`-Resume aus einem In-Memory-Ringpuffer (500 Einträge).
|
||
- Admin (`/api/admin/*`): Login mit Benutzer/Passwort (argon2id),
|
||
Session-Cookie (httpOnly, Secure, SameSite=Strict), CSRF-Token im Header,
|
||
Login-Rate-Limit. Funktionen: Status Mod/SFTP, letzte Fehler, manueller
|
||
Sync, Spieler ausblenden/einblenden, Ingest-Token rotieren (Anzeige einmalig).
|
||
Erster Admin wird per CLI (`npm run admin:create`) angelegt.
|
||
- Ausgeblendete Spieler erscheinen in keiner öffentlichen Antwort.
|
||
- Sicherheitsheader (über Fastify-Helmet), CORS nur eigene Domain.
|
||
|
||
**worker**
|
||
- SFTP-Pull (ssh2-sftp-client) alle 15 min (konfigurierbar): Verzeichnislisten
|
||
von `world/stats`, `world/advancements`, `world/ftbquests`,
|
||
`config/ftbquests/quests`, `logs/latest.log`. Herunterladen nur bei geänderter
|
||
Größe/mtime (Zustand in `sync_state`); Log inkrementell ab Byte-Offset,
|
||
Rotation erkannt über kleineren Größenwert. Ergebnisse durchlaufen dieselben
|
||
Projektionen wie Ingest (synthetische, deterministische Event-IDs aus
|
||
Datei+Inhalt-Hash → idempotent).
|
||
- Advancements-Dateien (`world/advancements/<uuid>.json`) liefern die
|
||
vollständige Liste erreichter Advancements inkl. Zeitpunkten.
|
||
- Snapshot-Job: stündlich `stat_snapshots` (Auswahl relevanter Metriken je
|
||
Spieler), täglich um 00:05 Tageswerte; Retention: stündlich 30 Tage,
|
||
täglich unbegrenzt; `server_health` Minutenwerte 14 Tage, danach
|
||
Stundenaggregate.
|
||
- Sitzungsbereinigung: offene Sessions ohne Lebenszeichen > 10 min nach
|
||
`server_start` werden mit letztem bekannten Zeitpunkt geschlossen.
|
||
- Job-Locking über `pg_try_advisory_lock`, damit Jobs nie doppelt laufen.
|
||
|
||
Der Worker ist optional: ohne SFTP-Zugangsdaten läuft das System nur mit Push.
|
||
Ohne erreichbaren Push läuft es nur mit Pull (dann Online-Status und Tode mit
|
||
bis zu 15 min Verzögerung bzw. nur als Zähler).
|
||
|
||
## 6. Datenbank
|
||
|
||
Kern-Tabellen (Details in Migrationen):
|
||
|
||
- `players(uuid PK, name, first_seen, last_seen, hidden bool)`
|
||
- `name_history(uuid, name, seen_at)`
|
||
- `teams(team_id PK, name, type)`, `team_members(team_id, uuid)`
|
||
- `events(event_id PK, server_id, type, ts, received_at, source 'push'|'sftp', payload jsonb)`
|
||
- `sessions(id, uuid, started_at, ended_at, end_reason)`
|
||
- `stat_current(uuid, category, key, value bigint, observed_at, source, PK(uuid, category, key))`
|
||
- `stat_snapshots(uuid, metric, bucket timestamptz, granularity 'hour'|'day', value bigint)`
|
||
- `deaths(event_id, uuid, ts, dimension, cause, killer_type, killer_name)`
|
||
- `kills(event_id, uuid, ts, dimension, victim_type, is_boss, weapon)`
|
||
- `advancements(uuid, advancement_id, achieved_at, title, PK(uuid, advancement_id))`
|
||
- `quest_defs(quest_id PK, chapter_id, chapter_title, title, def_hash)`
|
||
- `quest_progress(team_id, quest_id, completed_at, PK(team_id, quest_id))`
|
||
- `server_info(id, started_at, mc_version, neoforge_version, java_version, mods jsonb)`
|
||
- `server_health(bucket, mspt_avg, mspt_p95, mspt_max, tps, players, chunks, heap_used_mb, heap_max_mb)`
|
||
- `sync_state(path PK, size, mtime, offset, hash, synced_at)`
|
||
- `admin_users`, `admin_sessions`, `ingest_tokens(id, hash, created_at, revoked_at)`
|
||
|
||
Ranglisten werden aus `stat_current` mit Indizes auf `(category, key, value DESC)`
|
||
berechnet. Abgeleitete Metriken (z. B. Gesamtstrecke = Summe aller `*_one_cm`,
|
||
Items je Mod = Summe nach Namespace) liegen als SQL-Views vor. Eine
|
||
Materialized View `leaderboard_cache` wird höchstens alle 60 s
|
||
`REFRESH … CONCURRENTLY` aktualisiert.
|
||
|
||
## 7. Ranglisten (Start-Set, alle aus belegten Quellen)
|
||
|
||
| Board | Metrik |
|
||
|---|---|
|
||
| Dauerzocker | `minecraft:custom/minecraft:play_time` (Ticks → h) |
|
||
| Friedhof | `minecraft:custom/minecraft:deaths` |
|
||
| Monsterjäger | `minecraft:custom/minecraft:mob_kills` |
|
||
| Abrissbirne | Summe `minecraft:mined/*` |
|
||
| Weltenbummler | Summe `minecraft:custom/*_one_cm` (→ km) |
|
||
| Flieger | `minecraft:custom/minecraft:fly_one_cm` |
|
||
| Diamantenbank | `minecraft:mined/minecraft:diamond_ore` + `deepslate_diamond_ore` (Abbau, nicht Besitz – so ausgewiesen) |
|
||
| Techniker | Summe `crafted`+`used` in Tech-Namespaces (mekanism, mekanismgenerators, powah, modern_industrialization, oritech, create, immersiveengineering, appliedenergistics2, refinedstorage, draconicevolution, integrateddynamics, pneumaticcraft) |
|
||
| Magier | dito Magie-Namespaces (ars_nouveau, ars_elemental, ars_additions, irons_spellbooks, occultism, theurgy, rootsclassic, forbidden_arcanus, evilcraft, reliquary) |
|
||
| Farmer | dito (mysticalagriculture, mysticalagradditions, productivebees, botanypots, pamhc2*, farmersdelight, herbsandharvest) |
|
||
| Questmeister | Anzahl abgeschlossener Quests des Teams (Teamleaderboard + Spieler über Mitgliedschaft) |
|
||
| Abenteurer | Anzahl Advancements |
|
||
| Schatzjäger | `lootr:looted_stat` |
|
||
| Weltenstufe | `apotheosis:world_tiers_activated` |
|
||
|
||
Die Namespace-Kategorien liegen in einer editierbaren Konfigurationsdatei
|
||
(`backend/config/mod-categories.json`) und werden mit der echten Modliste
|
||
abgeglichen; Namespaces ohne installierte Mod werden ignoriert und geloggt.
|
||
|
||
## 8. Frontend
|
||
|
||
Next.js (App Router), TypeScript, Tailwind CSS, Recharts. Dunkles,
|
||
eigenständiges Gaming-Design (kein Nachbau von craftattack-Assets), responsiv,
|
||
zugänglich (Kontrast, Tastatur, `prefers-reduced-motion`). Server-Rendering mit
|
||
kurzer Revalidierung; Live-Teile clientseitig per SSE.
|
||
|
||
Seiten:
|
||
- `/` Übersicht: Kennzahlen (Online, Spielstunden gesamt, Tode, Quests,
|
||
Advancements), „Gerade online“, Live-Ticker, Top-10-Karten.
|
||
- `/leaderboards` und `/leaderboards/[board]` (Zeitraum all/30d/7d aus Snapshots).
|
||
- `/players` (Suche) und `/players/[name]`: Kopf, Spielzeit, Sessions-Heatmap
|
||
(Wochentag × Stunde), Verlaufsdiagramme, Bewegung nach Art, Kampf
|
||
(Kills nach Mob, Todesursachen), Items nach Mod (Tabs mined/crafted/used/picked_up),
|
||
Advancements, Team-Quests, Rangpositionen.
|
||
- `/atm10`: Aktivität je Mod/Kategorie, Quest-Kapitelfortschritt, meistgenutzte
|
||
Mods.
|
||
- `/server`: Versionen (MC, NeoForge, Java, ATM10-Modliste mit Suche), TPS/MSPT-,
|
||
Spieler- und Heap-Verlauf.
|
||
- `/sources`: Datenquellen, Aktualität, Einschränkungen (aus `DATA-SOURCES`).
|
||
- `/admin`: Login, Status, Sync, Spieler ausblenden, Token-Rotation.
|
||
|
||
## 9. Deployment und Betrieb
|
||
|
||
- `deploy/docker-compose.yml`: `db` (postgres:16-alpine, Volume), `api`,
|
||
`worker`, `web`, `backup`. Nur `web` und `api` hängen am externen
|
||
Traefik-Netz (`TRAEFIK_NETWORK`); `db` nur im internen Netz.
|
||
- Traefik-Labels: Router `Host(${DOMAIN}) && PathPrefix(/api)` → api,
|
||
`Host(${DOMAIN})` → web; Entrypoint/Cert-Resolver per `.env`. Optionale
|
||
Middleware `ipAllowList` für `/api/ingest` (`INGEST_ALLOWED_IPS`).
|
||
- OPNsense: eingehend 443 → Traefik (bereits vorhanden); für den Pull-Fallback
|
||
ausgehend DMZ-Host → Mine-Hoster-SFTP-Host/-Port.
|
||
- Healthchecks für alle Container; `api` und `worker` warten auf DB und führen
|
||
Migrationen beim Start aus (Advisory-Lock).
|
||
- Backup: täglicher `pg_dump -Fc` mit 14-Tage-Rotation in ein Volume;
|
||
Restore-Anleitung in `OPERATIONS.md`.
|
||
- Logs strukturiert (pino, JSON), keine Tokens/IPs im Log.
|
||
|
||
## 10. Tests
|
||
|
||
- Mod (JUnit 5, ohne Minecraft-Laufzeit): Journal (Schreiben, Rotation, Recovery
|
||
nach abgeschnittener letzter Zeile), Backoff, Batch-Bildung, HMAC, JSON gegen
|
||
Schema-Fixtures, SNBT-Parser an den echten Quest-Dateien. Minecraft-abhängiger
|
||
Code ist dünn und von der Logik getrennt. Zusätzlich manueller Testlauf auf
|
||
einem lokalen NeoForge-Server (`runServer`).
|
||
- Backend (Vitest + Postgres im Compose-Testprofil): Ingest-Auth (falsches
|
||
Token, abgelaufener Zeitstempel, falsche Signatur), Idempotenz (gleicher Batch
|
||
doppelt), Projektionen, Stats-Parser an echten `world/stats`-Dateien,
|
||
Log-Parser an anonymisiertem Log, Ranglisten-SQL, SSE.
|
||
- Frontend: Komponententests (Vitest + Testing Library) und Playwright-Smoke-Test
|
||
gegen den Compose-Stack mit Simulator-Daten.
|
||
- Simulator (`tools/simulator`): erzeugt signierte Batches im echten Protokoll.
|
||
|
||
## 11. Dokumentation
|
||
|
||
`README.md` (Überblick, Schnellstart), `docs/ARCHITECTURE.md`,
|
||
`docs/DATA-SOURCES.md` (jede Kennzahl → Quelle, Aktualität, Einschränkungen),
|
||
`docs/OPERATIONS.md` (Deployment Traefik/OPNsense, Mod-Installation per
|
||
Mine-Hoster-SFTP, Konfiguration, Token-Rotation, Backup/Restore,
|
||
Neustartverhalten, Fehlerbilder), `mod/README.md`.
|
||
|
||
## 12. Offene Punkte, die in der Umsetzung geklärt werden
|
||
|
||
1. Exakte NeoForge-Version des Servers → Mod-Build gegen 21.1.x; Kompatibilität
|
||
innerhalb 21.1 ist gegeben, finale Version per Startup-Log bestätigen.
|
||
2. Ausgehendes HTTPS bei Mine-Hoster → Selbsttest beim ersten Start.
|
||
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.
|
||
4. SFTP-Host/Port/Benutzer → `.env` des Betreibers (nicht im Repo).
|