diff --git a/docs/admin/api.md b/docs/admin/api.md new file mode 100644 index 0000000..4038c8e --- /dev/null +++ b/docs/admin/api.md @@ -0,0 +1,130 @@ +# Game-Server-API (`/server.api/...`) + +Der Game-Server läuft separat vom Backend und lauscht lokal auf Port `7192`. +Seine Admin-Endpunkte liegen unter `/server.api/` und sind **nicht** die +öffentliche JSON-API des Backends (`/api`). + +## Endpunkte + +| Methode | Endpunkt | Zweck | +|---|---|---| +| `POST` | `/server.api/eval` | JavaScript im Game-Server ausführen | +| `POST` | `/server.api/cupdate` | Shells eines online Characters setzen | +| `POST` | `/server.api/shutdown` | Game-Server herunterfahren | +| `POST` | `/server.api/new_friend` | intern: Freund hinzufügen | +| `POST` | `/server.api/lost_friend` | intern: Freund entfernen | + +Alle diese Endpunkte verlangen `spass` = `keys.ACCESS_MASTER`. Der Key erlaubt +beliebige Codeausführung im Game-Server und darf niemals öffentlich werden. + +## `ACCESS_MASTER` lesen + +```sh +cd /data/ai/gaming/adventureland + +ACCESS_MASTER="$(docker compose exec -T al-game node -e \ + 'process.stdout.write(require("/app/secretsandconfig/keys").ACCESS_MASTER)')" +``` + +Den Wert nicht in Chat, Logs oder Commits schreiben. + +## `/server.api/eval` + +Felder: + +- `spass` – `ACCESS_MASTER` +- `code` – JavaScript, wird im Game-Server ausgeführt +- `data` – JSON-String, im Code als `data` verfügbar + +Der Code läuft im Modul-Scope des Game-Servers. Verfügbar sind unter anderem +`players`, `name_to_id`, `get_player(name)`, `add_item`, `resend`, +`calculate_player_stats` und `output`. + +### Gold geben (live) + +```sh +curl -sS -X POST http://127.0.0.1:7192/server.api/eval \ + --data-urlencode "spass=$ACCESS_MASTER" \ + --data-urlencode 'code=var p=get_player("CharName"); if(p){ p.gold += 100000; resend(p,"reopen+u+cid"); output={ok:true,gold:p.gold}; } else { output={ok:false,reason:"offline"}; }' \ + --data-urlencode 'data={}' +``` + +### Item geben (live) + +```sh +curl -sS -X POST http://127.0.0.1:7192/server.api/eval \ + --data-urlencode "spass=$ACCESS_MASTER" \ + --data-urlencode 'code=var p=get_player("CharName"); if(p){ var n=add_item(p,{name:"hpot0",q:100},{announce:false}); resend(p,"reopen+u+cid"); output={ok:true,slot:n}; } else { output={ok:false,reason:"offline"}; }' \ + --data-urlencode 'data={}' +``` + +`add_item` prüft Inventarplatz und stapelt stackbare Items. `announce:false` +verhindert eine globale Fundmeldung. + +### XP geben (live) + +```sh +curl -sS -X POST http://127.0.0.1:7192/server.api/eval \ + --data-urlencode "spass=$ACCESS_MASTER" \ + --data-urlencode 'code=var p=get_player("CharName"); if(p){ p.xp += 100000; resend(p,"reopen"); output={ok:true,level:p.level,xp:p.xp}; } else { output={ok:false,reason:"offline"}; }' \ + --data-urlencode 'data={}' +``` + +`resend(p,"reopen")` ruft `calculate_player_stats` auf und führt Level-Ups aus. + +## `/server.api/cupdate` + +Felder: + +- `spass` – `ACCESS_MASTER` +- `id` – Character-Name (`player.id` ist der Name, nicht `CH_...`) +- `cash` – neuer Gesamtstand der Shells +- `ncash` – Betrag für die „received shells“-Meldung; wird **nicht** addiert + +`cupdate` setzt `player.cash` auf `cash`. Es ist kein Inkrement. Deshalb zuerst +den neuen Gesamtstand in MongoDB schreiben und dann `cupdate` aufrufen. + +```sh +# 1. Account-Cash erhöhen +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.updateOne({"info.email":"du@example.com"}, { $inc: { cash: 1000 } })' + +# 2. Neuen Gesamtstand lesen +NEW_TOTAL="$(docker compose exec -T al-mongo mongosh adventureland --quiet --eval \ + 'db.user.findOne({"info.email":"du@example.com"},{cash:1}).cash')" + +# 3. Online-Character aktualisieren +curl -sS -X POST http://127.0.0.1:7192/server.api/cupdate \ + --data-urlencode "spass=$ACCESS_MASTER" \ + --data-urlencode 'id=CharName' \ + --data-urlencode "cash=$NEW_TOTAL" \ + --data-urlencode 'ncash=1000' +``` + +Ohne online Character reicht der MongoDB-Wert; beim nächsten Login wird +`user.cash` geladen. + +## Wichtige Hinweise + +- **Shells** sind accountweit (`user.cash`). Ein `/cupdate` ändert nur die + laufende Kopie im Game-Server; die DB muss separat aktualisiert werden. +- **Gold** ist pro Character (`character.info.gold` / `player.gold`) oder + accountweit in der Bank (`user.info.gold`). +- `eval` ist mächtig und ungefiltert. Nur lokal oder hinter einer + vertrauenswürdigen Admin-Schicht betreiben. +- `aadd_item` nicht verwenden: Es ruft `reopen()` auf, das im Node-Port nicht + definiert ist. Stattdessen `add_item` + `resend`. +- Der Game-Server speichert live geänderte Character-Daten regelmäßig. Für + dauerhafte Änderungen an Offline-Characters die MongoDB-Variante verwenden; + für laufende Characters `eval`. +- `ACCESS_MASTER` rotieren, bevor der Port öffentlich erreichbar ist. Der + Default steht in `files/config/keys.js`. + +## Quellen + +- `source-repos/adventureland_mongodb/node/server.js` – `server_api`-Router, + `/eval`, `/cupdate`, `/shutdown`; `add_item`, `resend` +- `source-repos/adventureland_mongodb/adventure_functions.js` – + `server_url`, `server_eval`, `server_eval_safe`, `character_eval` +- `files/config/options.js` – `api_path: "/server.api/"`, `local_port: 7192` +- `source-repos/adventureland_mongodb/README.md` – Admin-/Executor-Hinweise diff --git a/docs/admin/docker.md b/docs/admin/docker.md new file mode 100644 index 0000000..7489fd5 --- /dev/null +++ b/docs/admin/docker.md @@ -0,0 +1,249 @@ +# Docker- und MongoDB-Admin + +Diese Variante arbeitet direkt auf den Containern und der MongoDB. Sie ist +nützlich für Offline-Änderungen, Recovery und Prüfungen. Für laufende +Characters ist die Game-Server-API (`docs/admin/api.md`) oder der +Admin-Executor (`docs/admin/executor.md`) meist besser geeignet. + +## Stack + +| Service | Aufgabe | Port | +|---|---|---| +| `al-mongo` | MongoDB mit Replica-Set `rs0` | intern `27017` | +| `al-backend` | Express-Backend, Web-Client, `/api` | `8090` | +| `al-game` | Socket.IO-Game-Server, `/server.api` | `7192` | + +MongoDB ist im Compose-Setup **nicht** auf den Host veröffentlicht. Der Zugriff +läuft über `docker compose exec`. + +## MongoDB-Shell + +```sh +cd /data/ai/gaming/adventureland + +docker compose exec al-mongo mongosh adventureland +``` + +Einzelnes Kommando: + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.findOne({ "info.email": "du@example.com" }, { _id: 1, name: 1, cash: 1, "info.slots": 1 })' +``` + +## Account und Character finden + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.findOne( + { "info.email": "du@example.com" }, + { _id: 1, name: 1, cash: 1, "info.gold": 1, "info.slots": 1, "info.characters": 1 } + )' +``` + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.character.findOne( + { "info.name": "CharName" }, + { _id: 1, owner: 1, level: 1, xp: 1, online: 1, server: 1, "info.gold": 1, "info.items": 1 } + )' +``` + +## Shells geben + +Shells sind accountweit und liegen in `user.cash`. + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.updateOne( + { "info.email": "du@example.com" }, + { $inc: { cash: 1000 } } + )' +``` + +Danach neu einloggen oder den neuen Stand per `/server.api/cupdate` an online +Characters schicken (siehe `docs/admin/api.md`). + +## Bank-Gold geben + +Bank-Gold ist accountweit und liegt in `user.info.gold`. + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.updateOne( + { "info.email": "du@example.com" }, + { $inc: { "info.gold": 100000 } } + )' +``` + +Neu einloggen bzw. Bank betreten. Ist der Character bereits in der Bank, wird +`player.user.gold` live nicht automatisch aktualisiert. + +## Character-Gold geben + +Character-Gold liegt in `character.info.gold`. + +> Nur bearbeiten, solange der Character **offline** ist (`online: false`, +> `server: ""`). Sonst überschreibt der Game-Server den Wert beim nächsten Sync. + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.character.updateOne( + { "info.name": "CharName" }, + { $inc: { "info.gold": 100000 } } + )' +``` + +## Item ins Character-Inventar + +Item-Namen stehen in `design/items.js` (`G.items`), zum Beispiel `hpot0`, +`mpot0`, `helmet`, `shoes`. Das Inventar fasst maximal 42 Einträge. + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.character.updateOne( + { "info.name": "CharName" }, + { $push: { "info.items": { name: "hpot0", q: 100 } } } + )' +``` + +Das ist ein roher Push: kein Stacking, keine Inventarprüfung. Für laufende +Characters `add_item` per Game-Server-API verwenden. + +## Item in die Bank + +Bank-Packs liegen in `user.info.items0` bis `user.info.items47`. + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.updateOne( + { "info.email": "du@example.com" }, + { $push: { "info.items0": { name: "hpot0", q: 100 } } } + )' +``` + +Pack-Größe beachten (bis 42). Neu einloggen bzw. Bank betreten. + +## Charakterplätze setzen + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.updateOne( + { "info.email": "du@example.com" }, + { $set: { "info.slots": 18 } } + )' +``` + +Maximum 18. Der Wert wird bei jeder Anfrage gelesen; kein Neustart nötig. + +## Admin setzen + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.updateOne( + { "info.email": "du@example.com" }, + { $set: { admin: true } } + )' +``` + +Danach neu einloggen. Der Admin-Executor ist dann unter +`http://localhost:8090/admin/executor` erreichbar. + +## GM setzen + +GM ist eine Character-Rolle, kein Web-Admin. + +```sh +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.character.updateOne( + { "info.name": "CharName" }, + { $set: { role: "gm", "info.p.role": "gm" } } + )' +``` + +Danach den Character neu einloggen. + +## Keys lesen + +`ACCESS_MASTER`, `BOT_MASTER` und `SERVER_MASTER` stehen in +`files/config/keys.js` und sind im Container unter +`/app/secretsandconfig/keys.js` gemountet. + +```sh +docker compose exec -T al-game node -e \ + 'console.log(require("/app/secretsandconfig/keys").ACCESS_MASTER)' +``` + +Den Wert nicht in Chat, Logs oder Commits schreiben. Vor öffentlichem Betrieb +rotieren und `al-backend` sowie `al-game` neu starten. + +## Backup und Restore + +Backup: + +```sh +docker compose exec -T al-mongo mongodump \ + --db adventureland --archive=/tmp/al-backup.gz + +docker compose cp al-mongo:/tmp/al-backup.gz ./al-backup.gz +``` + +Restore (destruktiv, überschreibt die aktuelle DB): + +```sh +docker compose cp ./al-backup.gz al-mongo:/tmp/al-backup.gz + +docker compose exec -T al-mongo mongorestore \ + --archive=/tmp/al-backup.gz --drop +``` + +Vor jedem Restore den aktuellen Stand sichern. `--drop` entfernt vorhandene +Collections. + +## Logs und Status + +```sh +docker compose ps +docker compose logs -f al-backend +docker compose logs -f al-game +docker compose logs -f al-mongo +``` + +## Neustart und Rebuild + +Nach Änderungen an `files/config/`: + +```sh +docker compose restart al-backend al-game +``` + +Nach Änderungen am Image oder an Build-Patches: + +```sh +docker compose build +docker compose up -d +``` + +Nach Änderungen an `files/custom/game.js` reicht in der Regel ein Neustart der +beiden App-Container. + +## Sicherheit + +- MongoDB ist nicht auf dem Host veröffentlicht; nicht zusätzlich exposen. +- `ACCESS_MASTER` erlaubt Codeausführung im Game-Server. Nicht öffentlich + erreichbar machen. +- `al-backend` und `al-game` sind im Compose-Setup an alle Host-Interfaces + gebunden. Für öffentlichen Betrieb Firewall/Caddy davorsetzen. +- Default-Keys aus `files/config/keys.js` vor öffentlichem Zugriff ersetzen. +- Offline-Characters vor DB-Änderungen ausloggen; sonst überschreibt der + Game-Server die Werte. + +## Quellen + +- `docker-compose.yml` – Services, Ports, Volumes +- `Dockerfile` – Build-Patches und Runtime +- `files/config/keys.js` – Admin-Keys +- `files/config/options.js` – Ports, Serverdefinition, Limits +- `source-repos/adventureland_mongodb/adventure_functions.js` – + `character_to_info`, `update_character`, `update_user_data`, `user_to_server` +- `source-repos/adventureland_mongodb/api.js` – `create_character_api` diff --git a/docs/admin/executor.md b/docs/admin/executor.md new file mode 100644 index 0000000..a8533e5 --- /dev/null +++ b/docs/admin/executor.md @@ -0,0 +1,256 @@ +# Admin-Executor (`/admin/executor`) + +Der Admin-Executor ist die bequemste Variante für administrative Änderungen. +Er führt **Backend-JavaScript** aus – nicht Game-Server-JavaScript – und hat +Zugriff auf die Backend-Funktionen und die MongoDB. + +## Zugang + +Voraussetzung ist `user.admin = true` oder `Local: true` zusammen mit +`unsecure_admin: true` in `files/config/options.js`. + +`user.admin` direkt setzen: + +```sh +cd /data/ai/gaming/adventureland + +docker compose exec al-mongo mongosh adventureland --eval \ + 'db.user.updateOne({"info.email": "du@example.com"}, { $set: { admin: true } })' +``` + +Danach im Browser neu einloggen und öffnen: + +```text +http://localhost:8090/admin/executor +``` + +Der frühere Weg `/admin/make/user/admin` ist im Code deaktiviert +(`common_engine/admin.js` enthält ein frühes `return;`). + +> **Sicherheit:** Der Executor erlaubt beliebige Codeausführung im Backend. +> `unsecure_admin: true` macht jeden lokalen Besucher zum Admin. Niemals +> zusammen mit öffentlichem Zugriff verwenden. + +## Was im Executor verfügbar ist + +Neben normalem JavaScript stehen unter anderem zur Verfügung: + +- `req` – der Express-Request des Executor-Aufrufs +- `output` – wird als `[OUTPUT]` an die Oberfläche zurückgegeben +- `inspect` – wird als JSON zurückgegeben +- `console.log()` / `console.error()` – landen in `[LOGS]` +- `db` – MongoDB-Handle +- `get_user_by_email(email)`, `get_character(name)`, `get(id)` +- `save(entity)`, `tx()`, `tx_get()`, `tx_save()` +- `update_characters(user, reason, name, shells)` +- `character_eval(character, code, data)` +- `server_eval(server, code, data)`, `server_eval_safe(server, code, data)` +- `keys` – die Server-Keys, inklusive `ACCESS_MASTER` + +`character_eval` führt Code auf dem Game-Server aus. Dort heißt der laufende +Character `player`. Ist der Character offline, passiert nichts. + +## Beispiele + +### Shells geben (accountweit) + +Shells liegen in `user.cash`. `update_characters` schickt den neuen Stand an +alle online Characters. + +```js +var u = await get_user_by_email("du@example.com"); +if (!u) throw new Error("user not found"); + +var amount = 1000; +u.cash = (u.cash || 0) + amount; +await save(u); + +await update_characters(u, "", "", amount); + +output = { email: u.info.email, cash: u.cash }; +``` + +### Bank-Gold geben (accountweit) + +Bank-Gold liegt in `user.info.gold`. + +```js +var u = await get_user_by_email("du@example.com"); +if (!u) throw new Error("user not found"); + +var amount = 100000; +u.info.gold = (u.info.gold || 0) + amount; +await save(u); + +output = { bank_gold: u.info.gold }; +``` + +Danach neu einloggen bzw. die Bank betreten. Steht der Character bereits in der +Bank, ist `player.user.gold` live veraltet; dann zusätzlich per +`character_eval` aktualisieren oder neu einloggen. + +### Character-Gold geben + +Character-Gold liegt in `character.info.gold`. + +```js +var ch = await get_character("CharName"); +if (!ch) throw new Error("character not found"); + +var amount = 100000; + +if (ch.online || ch.server) { + await character_eval( + ch, + "player.gold += " + amount + "; resend(player, 'reopen+u+cid');" + ); + output = { character: ch.info.name, mode: "live" }; +} else { + ch.info.gold = (ch.info.gold || 0) + amount; + await save(ch); + output = { character: ch.info.name, gold: ch.info.gold, mode: "db" }; +} +``` + +### Item geben + +Live nutzt `add_item` Stacking und Inventarprüfung. Offline wird direkt in +`character.info.items` geschrieben; dort gibt es keine Prüfung. + +```js +var ch = await get_character("CharName"); +if (!ch) throw new Error("character not found"); + +var item = { name: "hpot0", q: 100 }; + +if (ch.online || ch.server) { + await character_eval( + ch, + "add_item(player, " + JSON.stringify(item) + ", {announce:false});" + + "resend(player, 'reopen+u+cid');" + ); + output = { character: ch.info.name, item: item, mode: "live" }; +} else { + ch.info.items = ch.info.items || []; + ch.info.items.push(item); + await save(ch); + output = { character: ch.info.name, item: item, mode: "db" }; +} +``` + +Item-Namen stehen in `design/items.js` (`G.items`), zum Beispiel `hpot0`, +`mpot0`, `helmet`, `shoes`. Das Inventar fasst maximal 42 Einträge. + +### XP geben + +```js +var ch = await get_character("CharName"); +if (!ch) throw new Error("character not found"); + +var amount = 100000; + +if (ch.online || ch.server) { + await character_eval( + ch, + "player.xp += " + amount + "; resend(player, 'reopen');" + ); + output = { character: ch.info.name, mode: "live" }; +} else { + ch.xp = (ch.xp || 0) + amount; + ch.info.xp = ch.xp; + await save(ch); + output = { character: ch.info.name, xp: ch.xp, mode: "db" }; +} +``` + +`resend(player, "reopen")` ruft `calculate_player_stats` auf und führt +Level-Ups aus. Offline passiert das beim nächsten Login. + +### Item in die Bank legen + +Bank-Packs liegen in `user.info.items0` bis `user.info.items47`. + +```js +var u = await get_user_by_email("du@example.com"); +if (!u) throw new Error("user not found"); + +u.info.items0 = u.info.items0 || []; +u.info.items0.push({ name: "hpot0", q: 100 }); +await save(u); + +output = { pack: "items0", count: u.info.items0.length }; +``` + +Danach neu einloggen bzw. die Bank betreten. Pack-Größe beachten (bis 42). + +### Charakterplätze setzen + +```js +var u = await get_user_by_email("du@example.com"); +if (!u) throw new Error("user not found"); + +u.info.slots = 18; +await save(u); + +output = { slots: u.info.slots }; +``` + +`info.slots` wird bei jeder Anfrage gelesen; ein Neustart ist nicht nötig. +Maximum sind 18. + +### Account verifizieren + +```js +var u = await get_user_by_email("du@example.com"); +if (!u) throw new Error("user not found"); + +u.info.verified = true; +await save(u); + +output = { verified: true }; +``` + +### GM setzen + +GM ist eine Character-Rolle, kein Web-Admin. + +```js +var ch = await get_character("CharName"); +if (!ch) throw new Error("character not found"); + +ch.role = "gm"; +ch.info.p = ch.info.p || {}; +ch.info.p.role = "gm"; +await save(ch); + +output = { character: ch.info.name, role: "gm" }; +``` + +Danach den Character neu einloggen. + +## Grenzen + +- Der Executor ist **kein** Satz benannter Befehle wie `/give shells`. + Es ist eine Codebox. +- Er läuft im Backend. Für Funktionen, die es nur im Game-Server gibt + (`players`, `add_item`, `resend`), `character_eval` bzw. `server_eval` + verwenden. +- Offline-Änderungen an `character.info.*` werden beim nächsten Sync des + Game-Servers überschrieben, wenn der Character online ist. Dann live + arbeiten oder den Character vorher ausloggen. +- `aadd_item` aus `node/server.js` nicht verwenden: Es ruft eine nicht + definierte `reopen()`-Funktion auf. Stattdessen `add_item(...)` + + `resend(...)`. + +## Quellen + +- `source-repos/common_engine/admin.js` – `/admin/executor`, `execute_api`, + `is_admin` +- `source-repos/common_engine/htmls/admin_executor.html` – Oberfläche +- `source-repos/adventureland_mongodb/adventure_functions.js` – + `get_user_by_email`, `get_character`, `update_characters`, + `character_eval`, `server_eval`, `update_character`, `update_user_data`, + `user_to_server` +- `source-repos/adventureland_mongodb/node/server.js` – `add_item`, + `calculate_player_stats`, `resend` +- `source-repos/adventureland_mongodb/design/items.js` – Item-Definitionen