initial version
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# Build context for the Adventure Land image. The game itself is cloned from
|
||||||
|
# GitHub inside the Dockerfile, so only the Dockerfile and helper scripts are
|
||||||
|
# needed here.
|
||||||
|
config
|
||||||
|
data
|
||||||
|
*.md
|
||||||
|
.env
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# Adventure Land (MongoDB Edition) - image build
|
||||||
|
#
|
||||||
|
# The game is split across three upstream repositories:
|
||||||
|
# adventureland_mongodb -> /app
|
||||||
|
# common_engine -> /app/common
|
||||||
|
# adventureland_secretsandconfig -> /app/secretsandconfig (template only,
|
||||||
|
# replaced at runtime by ./config)
|
||||||
|
#
|
||||||
|
# All three are cloned at build time, so `docker compose build --pull` is the
|
||||||
|
# way to update the game.
|
||||||
|
|
||||||
|
ARG NODE_IMAGE=node:22-bookworm-slim
|
||||||
|
|
||||||
|
FROM ${NODE_IMAGE} AS build
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends git ca-certificates python3 make g++ \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV GIT_TERMINAL_PROMPT=0
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ARG AL_REPO=https://github.com/kaansoral/adventureland_mongodb.git
|
||||||
|
ARG AL_REF=main
|
||||||
|
ARG COMMON_REPO=https://github.com/kaansoral/common_engine.git
|
||||||
|
ARG COMMON_REF=main
|
||||||
|
ARG CONFIG_REPO=https://github.com/kaansoral/adventureland_secretsandconfig.git
|
||||||
|
ARG CONFIG_REF=main
|
||||||
|
|
||||||
|
RUN git clone --depth 1 --single-branch --branch "${AL_REF}" "${AL_REPO}" . \
|
||||||
|
&& git clone --depth 1 --single-branch --branch "${COMMON_REF}" "${COMMON_REPO}" common \
|
||||||
|
&& git clone --depth 1 --single-branch --branch "${CONFIG_REF}" "${CONFIG_REPO}" secretsandconfig \
|
||||||
|
&& rm -rf .git common/.git secretsandconfig/.git
|
||||||
|
|
||||||
|
# Backend (Express) and game server (Socket.IO) have separate dependency trees.
|
||||||
|
RUN npm install --omit=dev --no-audit --no-fund \
|
||||||
|
&& cd node && npm install --omit=dev --no-audit --no-fund \
|
||||||
|
&& rm -rf /app/node/node_modules/geoip-lite \
|
||||||
|
&& rm -rf /app/node_modules/geoip-lite
|
||||||
|
|
||||||
|
# Local patch: make the DRM login check switchable (docker/patch-drm-check.js).
|
||||||
|
# A self-hosted server cannot produce Steam/MAS auth ids, so the check would
|
||||||
|
# otherwise flag every account with the "authfail" condition. Kept after the
|
||||||
|
# npm install so the dependency layers stay cached across patch changes.
|
||||||
|
COPY docker/patch-drm-check.js /tmp/patch-drm-check.js
|
||||||
|
RUN node /tmp/patch-drm-check.js /app/node/server.js
|
||||||
|
|
||||||
|
|
||||||
|
FROM ${NODE_IMAGE} AS runtime
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ca-certificates socat \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=build --chown=node:node /app /app
|
||||||
|
COPY --chown=node:node docker/ /app/docker/
|
||||||
|
|
||||||
|
# Local/dev mode (see config/options.js). The seed script refuses to run when
|
||||||
|
# NODE_ENV=production, so keep this out of the way for the seeding container.
|
||||||
|
ENV NODE_ENV=development
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
EXPOSE 8090 7192
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/docker/entrypoint.sh"]
|
||||||
|
|
||||||
|
CMD ["node", "main.js"]
|
||||||
@@ -1,3 +1,182 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
**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 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.
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
### MongoDB-Zugriff
|
||||||
|
|
||||||
|
Die Datenbank ist nur im Compose-Netz erreichbar. Für einen Blick hinein:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose exec 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 <volume-mountpoint>`) 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.
|
||||||
|
|
||||||
|
## Gut zu wissen
|
||||||
|
|
||||||
|
- **E-Mail-Verifizierung:** AWS SES ist nicht konfiguriert. Beim Registrieren
|
||||||
|
erscheint im Backend-Log `send_email error InvalidClientTokenId`. Das ist
|
||||||
|
erwartet – die Registrierung funktioniert trotzdem, es wird nur keine Mail
|
||||||
|
verschickt. Bis zur Verifizierung hängt das Konto an „Not Verified“
|
||||||
|
(Gold/Glück −25 %). Der Verifizierungslink funktioniert auch ohne Mailversand;
|
||||||
|
er lässt sich in MongoDB nachsehen:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose exec 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).
|
||||||
|
|
||||||
|
- **„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.
|
||||||
|
- **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
|
# docker-builder
|
||||||
|
|
||||||
## setup
|
## setup
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
name: adventureland
|
||||||
|
|
||||||
|
# ensure both containers use same version of mongo
|
||||||
|
x-mongo_image: &mongo_image mongo:8.0
|
||||||
|
|
||||||
|
# Shared settings for the two game containers. Both use the same image, so
|
||||||
|
# `docker compose build` builds it once.
|
||||||
|
x-app: &app
|
||||||
|
image: git.ao-it.net/gaming/adventureland:latest
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
NODE_IMAGE: node:22-bookworm-slim
|
||||||
|
AL_REF: main
|
||||||
|
COMMON_REF: main
|
||||||
|
CONFIG_REF: main
|
||||||
|
restart: unless-stopped
|
||||||
|
init: true
|
||||||
|
environment:
|
||||||
|
MONGODB_URI: mongodb://al-mongo:27017/adventureland?replicaSet=rs0
|
||||||
|
MONGODB_NAME: adventureland
|
||||||
|
# See docker/patch-drm-check.js: without Steam/MAS auth a self-hosted
|
||||||
|
# server would flag every account with "Authorization Failure".
|
||||||
|
# Set to "1" to restore the upstream DRM check.
|
||||||
|
AL_DRM_CHECK: "0"
|
||||||
|
volumes:
|
||||||
|
# Editable keys/options from the host (replaces the cloned template).
|
||||||
|
- ./files/config:/app/secretsandconfig:ro
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
# MongoDB runs as a single-node replica set: the game uses transactions,
|
||||||
|
# which MongoDB only allows on replica set members.
|
||||||
|
al-mongo:
|
||||||
|
image: *mongo_image
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
|
||||||
|
networks:
|
||||||
|
- adventureland
|
||||||
|
volumes:
|
||||||
|
- ./files/db:/data/db
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mongosh", "--quiet", "--eval", "quit(db.adminCommand({ ping: 1 }).ok ? 0 : 1)"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 24
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
# One-shot: initiates the replset on a fresh database volume. Safe to rerun.
|
||||||
|
al-mongo-init:
|
||||||
|
image: *mongo_image
|
||||||
|
restart: "no"
|
||||||
|
networks:
|
||||||
|
- adventureland
|
||||||
|
depends_on:
|
||||||
|
al-mongo:
|
||||||
|
condition: service_healthy
|
||||||
|
entrypoint: ["/bin/bash", "/scripts/mongo-init.sh"]
|
||||||
|
volumes:
|
||||||
|
- ./docker/mongo-init.sh:/scripts/mongo-init.sh:ro
|
||||||
|
|
||||||
|
# One-shot: imports the 49 map documents from scripts/seed_mongodb.js.
|
||||||
|
#
|
||||||
|
# The seed script only accepts a direct local connection, so this container
|
||||||
|
# shares MongoDB's network namespace and talks to 127.0.0.1. It skips the
|
||||||
|
# import (exit 0) when the database already has collections.
|
||||||
|
al-seed:
|
||||||
|
<<: *app
|
||||||
|
restart: "no"
|
||||||
|
environment:
|
||||||
|
MONGODB_URI: mongodb://127.0.0.1:27017/adventureland
|
||||||
|
MONGODB_NAME: adventureland
|
||||||
|
network_mode: "service:al-mongo"
|
||||||
|
depends_on:
|
||||||
|
al-mongo-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
entrypoint: ["/bin/sh", "/app/docker/seed.sh"]
|
||||||
|
|
||||||
|
# Web backend: serves the game client on http://localhost:8090
|
||||||
|
al-backend:
|
||||||
|
<<: *app
|
||||||
|
command: ["node", "main.js"]
|
||||||
|
environment:
|
||||||
|
# See docker/entrypoint.sh: lets the backend reach the game server at
|
||||||
|
# the address it advertises to browsers (localhost:7192).
|
||||||
|
AL_GAME_RELAY: "1"
|
||||||
|
AL_GAME_HOST: al-game
|
||||||
|
AL_GAME_PORT: "7192"
|
||||||
|
networks:
|
||||||
|
- adventureland
|
||||||
|
- backend
|
||||||
|
ports:
|
||||||
|
- 8090:8090
|
||||||
|
depends_on:
|
||||||
|
al-seed:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", 'fetch("http://127.0.0.1:8090/").then((r) => process.exit(r.status < 500 ? 0 : 1)).catch(() => process.exit(1))']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 12
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
# Game server (Socket.IO) - clients connect to localhost:7192 directly.
|
||||||
|
al-game:
|
||||||
|
<<: *app
|
||||||
|
command: ["node", "server.js", "local"]
|
||||||
|
working_dir: /app/node
|
||||||
|
networks:
|
||||||
|
- adventureland
|
||||||
|
- backend
|
||||||
|
ports:
|
||||||
|
- 7192:7192
|
||||||
|
depends_on:
|
||||||
|
al-seed:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
al-backend:
|
||||||
|
condition: service_started
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", 'fetch("http://127.0.0.1:7192/").then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 12
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
adventureland:
|
||||||
|
backend:
|
||||||
|
# external: true
|
||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Container entrypoint.
|
||||||
|
#
|
||||||
|
# The backend talks to game servers through the address that is advertised to
|
||||||
|
# browsers (servers.<key>.address, e.g. localhost:7192). Inside the backend
|
||||||
|
# container "localhost" is the container itself, not the game container, so
|
||||||
|
# with AL_GAME_RELAY=1 a small TCP forwarder publishes the game port on
|
||||||
|
# 127.0.0.1 for the backend only.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "${AL_GAME_RELAY:-0}" = "1" ]; then
|
||||||
|
GAME_HOST="${AL_GAME_HOST:-al-game}"
|
||||||
|
GAME_PORT="${AL_GAME_PORT:-7192}"
|
||||||
|
echo "Relaying 127.0.0.1:${GAME_PORT} -> ${GAME_HOST}:${GAME_PORT}"
|
||||||
|
socat "TCP-LISTEN:${GAME_PORT},fork,reuseaddr,bind=127.0.0.1" "TCP:${GAME_HOST}:${GAME_PORT}" &
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Initiates the single-node replica set "rs0" if it does not exist yet and
|
||||||
|
# waits until the member is primary. Runs as a one-shot container.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HOST="${MONGO_HOST:-al-mongo}"
|
||||||
|
PORT="${MONGO_PORT:-27017}"
|
||||||
|
|
||||||
|
echo "Waiting for MongoDB at ${HOST}:${PORT} ..."
|
||||||
|
until mongosh --host "${HOST}" --port "${PORT}" --quiet \
|
||||||
|
--eval 'db.adminCommand({ ping: 1 }).ok' >/dev/null 2>&1; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$(mongosh --host "${HOST}" --port "${PORT}" --quiet \
|
||||||
|
--eval 'try { rs.status().ok } catch (e) { 0 }')" != "1" ]; then
|
||||||
|
echo "Initiating replica set rs0 ..."
|
||||||
|
mongosh --host "${HOST}" --port "${PORT}" --quiet \
|
||||||
|
--eval 'rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "al-mongo:27017" }] })'
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Waiting for primary ..."
|
||||||
|
until mongosh --host "${HOST}" --port "${PORT}" --quiet \
|
||||||
|
--eval 'db.hello().isWritablePrimary' | grep -q true; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Replica set rs0 is ready."
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Local patch for the Adventure Land game server (applied at image build time).
|
||||||
|
//
|
||||||
|
// The upstream DRM check in node/server.js (start_character) treats every
|
||||||
|
// account created after 2019-02-01 as unverified unless it carries a Steam or
|
||||||
|
// Mac App Store auth id. A self-hosted server cannot issue those ids (Steam
|
||||||
|
// tickets need the game's private publisher key, and Steam OpenID sign-in needs
|
||||||
|
// a publisher web API key), so every login gets the "authfail" condition:
|
||||||
|
// gold/luck -85%, xp -20%.
|
||||||
|
//
|
||||||
|
// AL_DRM_CHECK=0 disables the check. Unset or any other value keeps the
|
||||||
|
// upstream behaviour. The script fails loudly when the upstream line is gone,
|
||||||
|
// so a code update cannot silently skip the patch.
|
||||||
|
|
||||||
|
const fs = require("node:fs");
|
||||||
|
|
||||||
|
const file = process.argv[2] || "/app/node/server.js";
|
||||||
|
|
||||||
|
const original =
|
||||||
|
'if (owner.created > new Date(2019, 1, 1) && !gf(owner, "legacy_override")) drm = true;';
|
||||||
|
const patched =
|
||||||
|
'if (process.env.AL_DRM_CHECK !== "0" && owner.created > new Date(2019, 1, 1) && !gf(owner, "legacy_override")) drm = true;';
|
||||||
|
|
||||||
|
const source = fs.readFileSync(file, "utf8");
|
||||||
|
|
||||||
|
if (source.includes(patched)) {
|
||||||
|
console.log("patch-drm-check: already applied");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.split(original).length !== 2) {
|
||||||
|
console.error(
|
||||||
|
"patch-drm-check: expected exactly one occurrence of the upstream DRM check in " + file,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(file, source.replace(original, patched));
|
||||||
|
console.log("patch-drm-check: applied to " + file);
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Seeds the bundled map geometry into MongoDB.
|
||||||
|
#
|
||||||
|
# scripts/seed_mongodb.js only touches a database that has no collections and
|
||||||
|
# refuses a second run, so this wrapper checks first and skips the import on an
|
||||||
|
# already seeded volume.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
cd /app
|
||||||
|
|
||||||
|
: "${MONGODB_URI:?MONGODB_URI is not set}"
|
||||||
|
DB_NAME="${MONGODB_NAME:-adventureland}"
|
||||||
|
|
||||||
|
existing=$(node -e '
|
||||||
|
const { MongoClient } = require("mongodb");
|
||||||
|
const uri = process.env.MONGODB_URI;
|
||||||
|
const name = process.env.MONGODB_NAME || "adventureland";
|
||||||
|
(async () => {
|
||||||
|
const client = new MongoClient(uri, { serverSelectionTimeoutMS: 15000 });
|
||||||
|
await client.connect();
|
||||||
|
const collections = await client.db(name).listCollections({}, { nameOnly: true }).toArray();
|
||||||
|
await client.close();
|
||||||
|
process.stdout.write(String(collections.length));
|
||||||
|
})().catch((error) => {
|
||||||
|
console.error("MongoDB check failed:", error.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
')
|
||||||
|
|
||||||
|
if [ "${existing}" -gt 0 ]; then
|
||||||
|
echo "Database '${DB_NAME}' already has ${existing} collection(s) - skipping seed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Database '${DB_NAME}' is empty - importing map data ..."
|
||||||
|
exec node scripts/seed_mongodb.js --confirm "${DB_NAME}"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Adventure Land - local Docker configuration (keys)
|
||||||
|
//
|
||||||
|
// Replaces secretsandconfig/keys.js from the upstream repository. Values are
|
||||||
|
// development defaults: change them before exposing this stack to anyone but
|
||||||
|
// yourself. Unlike the upstream template, the values here are stable across
|
||||||
|
// restarts, so sessions and server records survive a `docker compose restart`.
|
||||||
|
//
|
||||||
|
// MongoDB connection settings can be overridden per container through the
|
||||||
|
// MONGODB_URI / MONGODB_NAME environment variables (see docker-compose.yml).
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
const env = (name, fallback) => process.env[name] || fallback;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
server_keyword: env("SERVER_KEYWORD", "local-server-keyword-change-me"),
|
||||||
|
|
||||||
|
mongodb_uri: env("MONGODB_URI", "mongodb://al-mongo:27017/adventureland?replicaSet=rs0"),
|
||||||
|
mongodb_name: env("MONGODB_NAME", "adventureland"),
|
||||||
|
// e.g. { tlsCAFile: path.resolve(__dirname, "ca.crt") }
|
||||||
|
mongodb_config: {},
|
||||||
|
|
||||||
|
stripe_test_api_key: "",
|
||||||
|
stripe_test_pkey: "",
|
||||||
|
stripe_api_key: "",
|
||||||
|
stripe_pkey: "",
|
||||||
|
steam_web_apikey: "",
|
||||||
|
steam_publisher_web_apikey: "",
|
||||||
|
sdk_password: env("SDK_PASSWORD", "local-sdk-password-change-me"),
|
||||||
|
amazon_ses_user: "",
|
||||||
|
amazon_ses_key: "",
|
||||||
|
|
||||||
|
// Admin / server-to-server keys, only meaningful for local play.
|
||||||
|
ACCESS_MASTER: env("ACCESS_MASTER", "local-access-master-change-me"),
|
||||||
|
BOT_MASTER: env("BOT_MASTER", "local-bot-master-change-me"),
|
||||||
|
SERVER_MASTER: env("SERVER_MASTER", "local-server-master-change-me"),
|
||||||
|
|
||||||
|
discord_token: env("DISCORD_TOKEN", ""),
|
||||||
|
apple_token: "",
|
||||||
|
steam_key: "",
|
||||||
|
};
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Adventure Land - local Docker configuration (options)
|
||||||
|
//
|
||||||
|
// Replaces secretsandconfig/options.js from the upstream repository.
|
||||||
|
//
|
||||||
|
// base_url must match the origin you open in the browser. If you change the
|
||||||
|
// published port in docker-compose.yml, change it here as well:
|
||||||
|
// base_url -> http://localhost:<backend port>
|
||||||
|
// servers.local.address -> localhost:<game server port>
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
machines = {
|
||||||
|
local: {
|
||||||
|
key: "",
|
||||||
|
ip: "0.0.0.0",
|
||||||
|
user: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
servers = {
|
||||||
|
local: {
|
||||||
|
region: "DE",
|
||||||
|
name: "I",
|
||||||
|
path: "/socket.io/",
|
||||||
|
// Separate Socket.IO endpoint for the MessagePack parser; must differ
|
||||||
|
// from `path` and is advertised to clients through the server record.
|
||||||
|
msgpack_path: "/socket.io-msgpack/",
|
||||||
|
api_path: "/server.api/",
|
||||||
|
local_ip: "0.0.0.0",
|
||||||
|
local_port: 7192,
|
||||||
|
address: "localhost:7192",
|
||||||
|
machine: "local",
|
||||||
|
db: "dev",
|
||||||
|
secure: false,
|
||||||
|
nginx: false,
|
||||||
|
Dev: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
project_name: "adventureland",
|
||||||
|
name: "Adventure Land",
|
||||||
|
base_url: "http://localhost:8090",
|
||||||
|
Dev: true,
|
||||||
|
Local: true,
|
||||||
|
Prod: false,
|
||||||
|
Staging: false,
|
||||||
|
Engine: "mongodb",
|
||||||
|
observer_map: "main",
|
||||||
|
merchant_map: "main",
|
||||||
|
port: 8090,
|
||||||
|
close_timeout: 4000,
|
||||||
|
ip_limit: 3,
|
||||||
|
character_limit: 3,
|
||||||
|
fast_sdk: 0,
|
||||||
|
machines: machines,
|
||||||
|
servers: servers,
|
||||||
|
cookie_key: "auth",
|
||||||
|
// SECURITY: with Local + unsecure_admin every visitor counts as admin, which
|
||||||
|
// opens /admin/executor (arbitrary code execution). Off by default; only
|
||||||
|
// enable it while the port is reachable from your machine alone.
|
||||||
|
unsecure_admin: true,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user