# Adventure Land – Docker-Setup Lokale Installation des [Adventure Land MongoDB Ports](https://github.com/kaansoral/adventureland_mongodb) mit Docker Compose: MongoDB, Backend (Web-Client) und Game-Server, inklusive automatischem Datenbank-Seed. ## Voraussetzungen - Docker Engine mit Compose v2 (`docker compose version`) - Ports **8090** (Web) und **7192** (Game-Server) müssen auf dem Host frei sein - Internetzugang beim ersten Build (die Quellcode-Repos werden geklont, Node-Pakete installiert) ## Start ```sh docker compose up -d --build ``` Danach im Browser öffnen: **http://localhost:8090** Der erste Start dauert ein paar Minuten (Image-Build inkl. `npm install`). Beim ersten Start laufen zwei Hilfs-Container: `mongo-init` initialisiert das Replica-Set, `seed` importiert die 49 Kartendaten. Erst danach starten Backend und Game-Server. Bei späteren Starts überspringt sich der Seed selbst. **Konto anlegen:** Auf der Startseite unter „Steam“ auf **„Registrieren [Zum Entwickeln]“** klicken, E-Mail + Passwort eingeben. Danach Charakter erstellen und auf den Charakter klicken – man landet direkt im Spiel. **Charakter Limits anheben** Folgendes Kommando setzt für alle existierenden Spieler das Charakter Limit auf 18. ```sh docker compose exec al-mongo mongosh adventureland --eval \ 'db.user.updateMany({}, { $set: { "info.slots": 18 } })' ``` **Admin werden:** Die Admin-Seiten (`/admin/executor`, `/admin/renderer`) sind standardmäßig gesperrt. `is_admin` in `common/admin.js` prüft das Feld `user.admin`; setzen lässt es sich direkt in MongoDB: ```sh docker compose exec al-mongo mongosh adventureland --eval \ 'db.user.updateOne({"info.email": "du@example.com"}, { $set: { admin: true } })' ``` Alternativ in `config/options.js` `unsecure_admin: true` setzen – dann gilt **jeder** Besucher als Admin (`/admin/executor` erlaubt Code-Ausführung), daher nur bei rein lokalem Zugriff sinnvoll. **GM werden (Game):** Der Web-Admin ist **kein** Game-GM. Die GM-Rolle hängt am Charakter, nicht am Benutzerkonto: Der Game-Server prüft `player.role` bzw. `player.p.role` auf `"gm"`. Charakter vorher ausloggen, dann in MongoDB setzen: ```sh docker compose exec al-mongo mongosh adventureland --eval \ 'db.character.updateOne( { name: "CharName" }, { $set: { role: "gm", "info.p.role": "gm" } } )' ``` Danach den Charakter neu einloggen. `role: "gm"` gibt die In-Game-GM-Rechte und ist unabhängig von `user.admin` (Web-Admin). Wer **nur** von den Multi-Account- Limits ausgenommen werden will, aber keine GM-Rechte braucht, setzt stattdessen `info.p.free`: ```sh docker compose exec al-mongo mongosh adventureland --eval \ 'db.character.updateOne( { name: "CharName" }, { $set: { "info.p.free": true } } )' ``` | Feld | Wirkung | | --- | --- | | `user.admin: true` | Web-Admin: `/admin/executor`, `/admin/renderer`; keine In-Game-GM-Rechte | | Charakter `role: "gm"` | In-Game-GM; wird außerdem von den Limits ausgenommen | | Charakter `info.p.free: true` | nur Limit-Ausnahme; keine GM-Rechte | ## Aufbau | Service | Image | Aufgabe | | ------------ | ------------------- | ------------------------------------------------------------- | | `mongo` | `mongo:8.0` | Datenbank, läuft als Single-Node-Replica-Set `rs0` (Transaktionen) | | `mongo-init` | `mongo:8.0` | Einmalig: `rs.initiate()` falls noch nicht geschehen | | `seed` | `adventureland:local` | Einmalig: importiert die Kartengeometrie, überspringt sich selbst | | `backend` | `adventureland:local` | Express-Backend, Web-Client, API – Port 8090 | | `game` | `adventureland:local` | Socket.IO-Game-Server – Port 7192 | Die Spieldaten (Image) werden beim Build aus drei Repositories geklont und liegen im Container unter `/app` – es wird nichts in diesen Ordner geklont. ## Konfiguration `config/` wird als `/app/secretsandconfig` in die Container gemountet und ersetzt das Template aus dem Upstream-Repo: - `config/keys.js` – MongoDB-URI, Master-Keys, Stripe/Steam/SES (alles leer bzw. feste Entwicklungs-Defaults). Die Werte sind absichtlich stabil, damit Sitzungen und Serverdatensätze Neustarts überleben. - `config/options.js` – `base_url`, Ports, Serverdefinition `local`, Multi-Account-Limits. Beide Dateien sind normale Node-Module; nach Änderungen Container neu starten: ```sh docker compose restart backend game ``` > Wenn du die Ports änderst, passe sie an **drei** Stellen an: > `docker-compose.yml` (`ports`), `config/options.js` (`port`, `local_port`, > `address`) und `base_url` (inkl. Port). ### Multi-Account-Limits `config/options.js` steuert, wie viele Charaktere gleichzeitig online sein dürfen: | Option | Bedeutung | aktuell | | --- | --- | --- | | `ip_limit` | Nicht-Händler pro IP (mal `ipx` einer IP-Ausnahme) | `9` | | `character_limit` | Nicht-Händler pro Konto | `3` | | `merchant_ip_limit` | Händler pro IP | `3` | | `merchant_owner_limit` | Händler pro Konto | `1` | Die Werte gelten beim Login (`is_player_allowed`) und im Backend-Cron (`enforce_limitations`). Der Patch `docker/patch-multi-account-limits.js` ersetzt dabei die früher fest codierten Grenzen (3 Nicht-Händler, 1 Händler). Mit den aktuellen Werten können z. B. drei Konten von derselben IP jeweils drei Nicht-Händler-Charaktere online haben (9 insgesamt); ein zehntes Nicht-Händler-Charakter bzw. ein vierter Händler wird abgewiesen. `character_limit` ist die Zahl der **gleichzeitig online** erlaubten Charaktere, nicht die Zahl der Charakterplätze (`info.slots`); `ip_limit` ist ebenfalls keine Slotzahl. Eine `IP_`-Ausnahme mit `{ exception: true, info: { limit: N } }` multipliziert nur `ip_limit` für Nicht-Händler, nicht die Händler-Limits. Nicht-freie Händler zählen; `p.free` und GM-Charaktere fallen heraus. `merchant_owner_limit: 1` erlaubt einen Händler pro Konto; Admin- und User-Händler auf getrennten Konten sind damit kein Problem. Sollen beide auf **demselben** Konto laufen, `merchant_owner_limit` auf `2` setzen. > Hinter einem Reverse-Proxy (Caddy) muss die echte Client-IP als > `X-Forwarded-For` ankommen. Sonst sieht der Server nur die Proxy-/Docker-IP > und alle Spieler teilen sich dieselben Limits. Siehe > `deploy/Caddyfile.example`. ### MongoDB-Zugriff Die Datenbank ist nur im Compose-Netz erreichbar. Für einen Blick hinein: ```sh docker compose exec al-mongo mongosh adventureland --eval 'db.getCollectionNames()' ``` ## Logs & Status ```sh docker compose ps docker compose logs -f backend game ``` ## Stoppen, Zurücksetzen, Sichern ```sh docker compose stop # anhalten docker compose down # Container entfernen, Daten behalten docker compose down -v # ALLES löschen, inkl. Konten, Charakteren, Karten ``` Nach `down -v` importiert der `seed`-Container die Karten beim nächsten Start wieder. Backup/Restore des Datenbank-Volumes: ```sh docker compose stop backend game docker run --rm -v adventureland_mongo-data:/data -v "$PWD":/backup alpine \ tar czf /backup/mongo-data-$(date +%F).tar.gz -C /data . docker compose start backend game ``` Restore: `docker compose down`, das Volume leeren und das Archiv (`tar xzf mongo-data-….tar.gz -C `) zurückkopieren, dann `docker compose up -d` – `seed` erkennt die vorhandenen Collections und überspringt den Import. ## Aktualisieren Das Image klont beim Build den jeweils aktuellen `main`-Branch: ```sh docker compose build --pull --no-cache docker compose up -d ``` Ein Neu-Seed ist nicht nötig; bestehende Konten, Charaktere und Karten bleiben erhalten. Der Build wendet zusätzlich den lokalen Patch aus `docker/patch-drm-check.js` an (siehe „Gut zu wissen“). Verschiebt Upstream die betroffene Zeile in `node/server.js`, bricht der Build mit einer klaren Meldung ab – dann das Suchmuster im Skript anpassen. Ebenso ersetzt `docker/patch-attribution.js` die Hilfe-Zeile im Login („E-Mail, für alles“) durch einen Self-Host-Hinweis mit Link auf https://adventure.land. Die Texte liegen in `docker/attribution/en.js` und `docker/attribution/de.json`, alle anderen Sprachen fallen auf Englisch zurück. Auch dieser Patch bricht den Build ab, wenn die Upstream-Vorlage nicht mehr passt. `docker/patch-custom-game.js` hakt den Loader für `files/custom/game.js` in Backend und Game-Server ein und macht die Lone-Wolf-Raten datengetrieben (siehe „Spieldaten anpassen“). Fehlt ein Anker, bricht der Build ebenfalls ab. `docker/patch-auto-verify-email.js` markiert bei aktivem `options.auto_verify_email` neue Konten beim Signup und bestehende beim nächsten Login als verifiziert und überspringt die Verifikationsmail. Ohne den Schalter bleibt das Upstream-Verhalten unverändert. Auch dieser Patch bricht den Build ab, wenn ein Anker in `api.js` fehlt. `docker/patch-tutorial-reward-auth.js` macht die `auth_id`-Pflicht der Tutorial- und Klassenbelohnungen abschaltbar. Bei `options.tutorial_reward_requires_auth_id: false` können auch reine Web-Accounts `c0` und Klassenbelohnungen abholen; fehlt die Option oder steht sie auf `true`, gilt das Upstream-Verhalten. Fehlt einer der beiden Anker in `node/server.js`, bricht der Build ab. `docker/patch-multi-account-limits.js` macht die Händler- und Nicht-Händler- Limits konfigurierbar. Upstream prüft beim Login `options.ip_limit` und `options.character_limit`, würgt im Backend-Cron aber pauschal nach 3 Nicht-Händlern bzw. 1 Händler ab. Der Patch liest beide Stellen aus `config/options.js` (`ip_limit`, `character_limit`, `merchant_ip_limit`, `merchant_owner_limit`). Fehlt ein Anker in `node/server_functions.js` oder `adventure_functions.js`, bricht der Build ab. ## Spieldaten anpassen (`files/custom/game.js`) Werte bestehender Elemente und neue Elemente lassen sich ohne Quelltextänderung über `files/custom/game.js` setzen. Die Datei wird in beide Spielcontainer als `/app/custom/game.js` gemountet und beim Start gelesen; Änderungen brauchen nur einen Neustart, keinen Rebuild: ```sh docker compose restart al-backend al-game ``` Aufbau (alle Angaben optional): - `items`, `monsters`, `npcs`, `maps`, `sets`, `craft`, `skills`, `conditions`, `classes`, `levels`, `upgrades`, `compounds`, `multipliers`, `titles`, `tokens`, `dismantle`: Objekte werden tief zusammengeführt, Arrays ersetzt. Neue IDs sind erlaubt, wenn sie eine vorhandene Grafik aus `design/dimensions.js` nutzen (Items und Monster brauchen ein `skin`). Neue Karten gehen nicht: ihre Geometrie liegt in MongoDB (`MP_*`). - `drops`: `{ main: [[0.001, "item_id"]] }` hängt Zeilen an die Tabelle an. - `gold`: überschreibt `drops.gold`, z. B. `{ base: 0.8 }`. - `phrases`: überschreibt Anzeigetexte. `"*"` gilt für alle Sprachen, ein Sprachcode (`de`, `en`, ...) hat Vorrang. Nötig, wenn sich Zahlen in einem Tooltip ändern. - `apply(game)`: Funktion für Sonderfälle, läuft nach dem Zusammenführen. Beim Start loggen beide Container `custom-game: loaded ...`; neue IDs stehen als `(new: ...)` dabei. Unbekannte Top-Level-Schlüssel, unbekannte Drop-Tabellen, fehlende Items und fehlende Grafiken brechen den Start mit einer klaren Meldung ab, statt das Spiel halb gepatcht zu starten. Enthaltenes Beispiel: Der Buff „Lone Wolf“ gibt 10× XP statt 3×. Die Multiplikatoren liest der Game-Server aus der Condition-Definition (Patch in `docker/patch-custom-game.js`), der Tooltip-Text kommt aus `phrases`. Die Datei ist versioniert (`files/custom/`), `files/db` bleibt von Git ausgenommen. Grenzen: Alles, was in `design/*.js` als Datum steht, ist überschreibbar. Konstanten, die im Code stehen (z. B. andere Encouragement-Raten), brauchen weiterhin einen Patch wie den für Lone Wolf. Endet ein Season-Event zur Laufzeit, können eventmodifizierte Monsterfelder auf den Stand vor der Custom-Datei zurückgesetzt werden; dann den Game-Server neu starten. ## Gut zu wissen - **E-Mail-Verifizierung:** AWS SES ist nicht konfiguriert. Deshalb setzt `options.auto_verify_email: true` neue Konten beim Signup und bestehende beim nächsten Login auf „verifiziert“; die Verifikationsmail wird übersprungen. Der „Not Verified“-Debuff (Gold/Glück −25 %) tritt damit nicht mehr auf. Auf `false` gestellt, gilt wieder der Upstream-Ablauf: Im Backend-Log erscheint `send_email error InvalidClientTokenId`, und der Verifizierungslink muss manuell geöffnet werden: ```sh docker compose exec al-mongo mongosh --quiet adventureland --eval \ 'db.user.find({}, {"info.email": 1, "info.everification": 1}).forEach((u) => print("http://localhost:8090/ev/" + u._id + "/" + u.info.everification))' ``` Link im Browser öffnen, danach neu einloggen. Alternativ `info.verified` direkt auf `true` setzen (siehe Admin-Befehl oben). Die `/ev`-Route bleibt in beiden Fällen funktionsfähig. - **Tutorial-Belohnung (`c0`):** Jedes verifizierte Konto kann nach abgeschlossenem Tutorial einmal `c0` abholen. Die Belohnung ist ein zufälliger kosmetischer Look aus dem `cosmo0`-Pool (über 100 Varianten). Der Game-Server prüft `player.verified` und normalerweise `player.auth_id` (Steam/MAS, `node/server.js`, `ureward`/`creward`). Bei reinen Web-Accounts ist `auth_id` leer; deshalb steht `options.tutorial_reward_requires_auth_id` hier auf `false`, damit auch sie die Belohnung erhalten. Auf `true` gestellt, gilt wieder die Upstream-Sperre für Web-Accounts. Die alten Klassen-Belohnungen (`creward`) nutzen denselben Schalter, sind aber in `design/classes.js` auskommentiert und werden vom Client nicht angefragt; Hardcore-Rewards gelten nur auf dem Hardcore-Server. - **„Authorization Failure“ (Gold/Glück −85 %, XP −20 %):** Das ist die DRM-Prüfung des Game-Servers. Sie verlangt für jedes Konto ab 2019 eine Steam-/Mac-App-Store-ID, die nur der jeweilige Game-Client ausstellen kann. Eine selbst gehostete Instanz kann das nicht: Steam-Tickets brauchen den privaten Publisher-Key, und die Steam-Anmeldung im Browser setzt diese ID nicht. Deshalb patcht das Image die Prüfung über `docker/patch-drm-check.js` und Compose setzt `AL_DRM_CHECK: "0"`; nach dem nächsten Login verschwindet die Bedingung. Mit `AL_DRM_CHECK: "1"` (oder ohne die Variable) gilt wieder das Upstream-Verhalten. - **Der Relay in `docker/entrypoint.sh`:** Das Backend spricht Game-Server über die Adresse an, die auch der Browser benutzt (`localhost:7192`). Innerhalb des Backend-Containers wäre das es selbst, deshalb leitet ein kleines `socat`-Relay `127.0.0.1:7192` an den `game`-Container weiter. Ohne das schlagen Admin-/Server-Funktionen (`server_eval`) fehl. - **Lizenz/Attribution:** Die AdventureLandOnlyUse-Lizenz verlangt einen crawlable Link auf https://adventure.land. Der ersetzt deshalb die „E-Mail, für alles“-Zeile im Login (siehe `docker/patch-attribution.js`). Zusätzlich ist einmalig eine kurze Mail an hello@adventure.land fällig, wenn die Instanz öffentlich erreichbar geht – das ist keine Code-Änderung. - **Nur lokal erreichbar:** Die Ports sind standardmäßig an `127.0.0.1` des Hosts gebunden. Für Zugriff aus dem LAN in `docker-compose.yml` die `127.0.0.1:`-Präfixe entfernen und `base_url` (sowie ggf. `servers.local.address`) in `config/options.js` auf die Host-IP bzw. den Hostnamen umstellen. Der Browser verbindet sich direkt mit `servers.local.address`. - **Passwort-/Key-Änderungen:** `config/keys.js` enthält Entwicklungs-Platzhalter. Vor einem öffentlich erreichbaren Betrieb ersetzen. - **Quellcode anpassen:** Der Code liegt im Image, nicht im Host-Ordner. Für eigene Änderungen im `Dockerfile` über die Build-Args `AL_REPO`, `AL_REF`, `COMMON_REPO`/`COMMON_REF` bzw. `CONFIG_REPO`/`CONFIG_REF` einen Fork oder Branch eintragen und mit `docker compose build` neu bauen. Alternativ in `docker-compose.override.yml` einen Checkout nach `/app` mounten und dabei `node_modules` aus dem Image in ein eigenes Volume auslagern. # docker-builder ## setup Set Repo / Org vars: - AOIT_GIT_URL - NTFY_HOST - NTFY_USER - NTFY_CHANNEL Set Repo / Org Secrets - TOKEN_SVC_CI (for download repo) - TOKEN_SVC_DOCKER (for upload container) - REGISTRY_USER (for upload container) - NTFY_PASS