mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-11-15 11:33:54 +01:00
chore: prepare the repository for public release
Removes operator-private material from the shipped tree, corrects documentation
that had drifted from the code, deletes dead code, and applies a consistent
prose style.
Private material moved out of the public tree (docs/private/, git-ignored):
- the wallet keyfile recovery runbook and its helper scripts, which together
published the mainnet wallet file path, the service account, backup snapshot
filenames, and the wallet RPC listener state. The helper duplicated the same
constants, so relocating the document alone would not have removed them.
- the stagenet parallel-candidate runbook and its fixtures.
- the dated production-state record, the internal remediation plan, and the
TODO list. None are reference documentation for an outside reader.
Redacted or genericised:
- operator home-directory paths in shipped scripts and units. The asset build
script now takes KENNEY_ASSETS_DIR instead of a hard-coded path.
- private LAN addresses in tests, env examples and deploy docs, replaced with
RFC 5737 documentation addresses.
- the default alert sender and the notification hub example.
- the Monero wallet unit now matches the Wownero one: a dedicated service
account and a config file, instead of the operator's login account with the
wallet password in argv where any local user can read it from /proc.
Ignore rules now cover every .env variant rather than the bare name, plus local
agent state. The deployment docs instruct operators to create .env.mainnet and
.env.stagenet, which the exact-name rule would have allowed to be committed.
Documentation corrected against the code: per-chain dungeon level counts, the
premium unlock rule, Tavern chat scope, backup retention, the chain-profile
fallback, the environment template location, and several sections describing
shipped work as pending.
Dead code removed: configPersistence, rpccalls, a duplicate vendored ROT.js
bundle, the unused tile-picker overlay, the unreachable legacy game-size path
and its vestigial parameter, spectator stubs that shadowed the real
SpectatorManager, and commented-out debugging. nip98 now imports the shared
nostr loader rather than duplicating it.
Prose style: em-dashes removed throughout in favour of colons, semicolons and
commas; comments restated as present-tense descriptions of the code rather than
narration of past changes.
127 suites / 1015 tests pass.
This commit is contained in:
+194
-125
@@ -2,34 +2,36 @@
|
||||
|
||||
This guide covers deploying Wownerogue to a production environment.
|
||||
|
||||
For evidence about the release currently serving `play.wowne.ro`, see the dated
|
||||
[production-state record](PRODUCTION_STATE_2026-07-25.md). That record is not a substitute for this
|
||||
runbook or authorization for another activation.
|
||||
|
||||
## Operated profiles versus independent deployments
|
||||
|
||||
Such Software (`apps@such.software`) operates only `play.wowne.ro` (Wownero mainnet
|
||||
pay-for-credits leaderboard/prestige, no prize/payout/cash-out and not marketed as gambling) and
|
||||
`monerogue.app` (Monero stagenet direct-entry-only **NO REAL VALUE** 2×/3× solo test gambling
|
||||
mechanics, with purchased-credit entry and crypto-match payouts off).
|
||||
Classification of any product under applicable law requires jurisdiction-specific advice.
|
||||
Their reviewed environment templates set `OPERATED_PRODUCT_PROFILE`, so preflight/startup rejects
|
||||
scope drift. Independent MIT deployments must leave that variable unset and identify their actual
|
||||
operator.
|
||||
Such Software (`apps@such.software`) operates only `play.wowne.ro` (Wownero mainnet free play plus
|
||||
pay-for-credits leaderboard/prestige, with no prize, payout, or cash-out, and not marketed as
|
||||
gambling) and `monerogue.app` (Monero stagenet direct entry only, **NO REAL VALUE** test coins, with
|
||||
single-player 2x/3x test gambling mechanics, purchased-credit entry disabled, and crypto match
|
||||
payouts off). Both profiles are defined in `src/config/operatedProductProfiles.js` and selected by
|
||||
`OPERATED_PRODUCT_PROFILE`; preflight and normal startup reject any network, identity, or
|
||||
economic-scope drift from the selected profile. Classification of any product under applicable law
|
||||
requires jurisdiction-specific advice.
|
||||
|
||||
MIT rights are subject to retaining the copyright and permission notice. The software is provided
|
||||
“AS IS”, without warranty, and the code/docs are not legal advice or compliance approval. Each
|
||||
third-party operator is solely responsible for its deployment, legal obligations, funds, players,
|
||||
claims, and support; Such Software neither endorses nor accepts responsibility for it.
|
||||
Independent MIT deployments leave `OPERATED_PRODUCT_PROFILE` unset and identify their actual
|
||||
operator. MIT rights are subject to retaining the copyright and permission notice. The software is
|
||||
provided "AS IS", without warranty, and the code and documentation are not legal advice or
|
||||
compliance approval. Each third-party operator is solely responsible for its deployment, legal
|
||||
obligations, funds, players, claims, and support. Such Software neither endorses nor accepts
|
||||
responsibility for it.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 22.x LTS or later
|
||||
- PostgreSQL 12+
|
||||
- Node.js 22.x LTS or later (`src/package.json` requires `node >=22`, `npm >=10`)
|
||||
- PostgreSQL 12 or later
|
||||
- Wownero or Monero wallet-rpc running and synced
|
||||
- A domain with SSL certificate (via reverse proxy)
|
||||
- A domain with a TLS certificate, terminated by a reverse proxy
|
||||
|
||||
The full environment template is `src/.env.example`. Operated profile templates are
|
||||
`src/.env.mainnet.example` and `src/.env.stagenet.example`; `src/.env.match.example` covers race
|
||||
mode. The five-line `.env.example` at the repository root is not a configuration reference.
|
||||
|
||||
---
|
||||
|
||||
@@ -39,16 +41,17 @@ claims, and support; Such Software neither endorses nor accepts responsibility f
|
||||
- [ ] Configure TLS via reverse proxy (Nginx/Caddy)
|
||||
- [ ] Set secure database credentials
|
||||
- [ ] Configure wallet-rpc with authentication
|
||||
- [ ] Set `ADMIN_API_KEY` for admin endpoints
|
||||
- [ ] Set `ADMIN_API_KEY` (production requires a non-placeholder secret of at least 32 characters)
|
||||
- [ ] Run `npm run preflight` with the final environment
|
||||
- [ ] Keep `PAYOUTS_ENABLED=false` on real-money prestige instances
|
||||
- [ ] Complete a stagenet payment and payout before enabling a payout instance
|
||||
- [ ] Firewall: allow Node 3000/3001 only from the reverse proxy; keep wallet/daemon RPC loopback-only
|
||||
- [ ] Protect `/admin.html` with basic auth
|
||||
- [ ] Firewall: allow Node 3000/3001 only from the reverse proxy; keep wallet and daemon RPC
|
||||
loopback-only
|
||||
- [ ] Protect the admin page (served at both `/admin` and `/admin.html`) with basic auth
|
||||
- [ ] Set up database backups (see [LOGS_AND_BACKUP.md](./LOGS_AND_BACKUP.md))
|
||||
- [ ] Configure log rotation
|
||||
- [ ] Run database migrations
|
||||
- [ ] If 3D is offered, provision the gitignored runtime GLBs and verify library/model requests
|
||||
- [ ] If 3D is offered, provision the gitignored runtime GLBs and verify library and model requests
|
||||
stay same-origin
|
||||
|
||||
---
|
||||
@@ -56,7 +59,7 @@ claims, and support; Such Software neither endorses nor accepts responsibility f
|
||||
## Create Dedicated User
|
||||
|
||||
Create an isolated, non-login service identity. Release directories and the `current` link remain
|
||||
root-owned; the service identity receives read/traverse access only and must never own its code:
|
||||
root-owned; the service identity receives read and traverse access only and never owns its code:
|
||||
|
||||
```bash
|
||||
sudo useradd --system --shell /usr/sbin/nologin --home-dir /var/www/wownerogue --no-create-home wownerogue
|
||||
@@ -74,16 +77,22 @@ sudo install -d -m 0750 -o wownerogue -g wownerogue /var/log/wownerogue
|
||||
|
||||
Build the runtime artifact from a clean, reviewed commit as described in
|
||||
[RELEASE_ARTIFACT.md](./RELEASE_ARTIFACT.md). The production host needs no Git checkout, deploy key,
|
||||
or write-capable source identity. The reviewed fleet
|
||||
`playbooks/wowngeon-stage-candidate.yml` role transfers exactly one tarball to a newly selected
|
||||
root-owned mode-0700 candidate inbox and compares its remote SHA-256 with the independently
|
||||
recorded literal digest. Follow the fleet Wowngeon runbook; the role is a one-shot custody boundary,
|
||||
not deployment.
|
||||
or write-capable source identity.
|
||||
|
||||
The executable extraction/activation boundary lives only in the separately reviewed fleet
|
||||
The artifact contains exactly `LICENSE`, `html`, and `src`, minus environment files, tests, the
|
||||
balance simulator, and the operator-only scripts under `src/scripts`. Documentation, service units,
|
||||
firewall and backup scripts, and root-level operator tooling are deliberately excluded: operational
|
||||
artifacts keep their own pinned provenance in the source and fleet repositories rather than becoming
|
||||
part of the application runtime because they share a Git commit.
|
||||
|
||||
The fleet `playbooks/wowngeon-stage-candidate.yml` role transfers exactly one tarball to a newly
|
||||
selected root-owned mode-0700 candidate inbox and compares its remote SHA-256 with the
|
||||
independently recorded literal digest. It is a one-shot custody boundary, not deployment.
|
||||
|
||||
The executable extraction and activation boundary lives only in the separately reviewed fleet
|
||||
repository as the default-closed `wowngeon_release_activate` role. This application checkout cannot
|
||||
attest which fleet revision is installed or authorize its execution. Follow the hash-pinned fleet
|
||||
runbook and literal receipt contract; do not manually extract beneath `releases/`, install
|
||||
runbook and literal receipt contract. Do not manually extract beneath `releases/`, install
|
||||
dependencies there, change `current`, restart an application, or run migrations merely because a
|
||||
blob has arrived.
|
||||
|
||||
@@ -103,11 +112,11 @@ node --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
The reviewed fleet extraction/activation role installs exactly the locked production graph in its
|
||||
The fleet extraction and activation role installs exactly the locked production graph in its
|
||||
private, writable staging directory with
|
||||
`npm ci --omit=dev --ignore-scripts --no-audit --no-fund`, verifies it with
|
||||
`npm ls --omit=dev`, and seals the finished release root-owned and non-writable before it can be
|
||||
selected. Never make a release tree writable by `wownerogue`. Online advisory lookup is a separate
|
||||
selected. Never make a release tree writable by `wownerogue`. Online advisory lookup is a separate,
|
||||
explicitly authorized source-review operation; never run `npm audit fix` in staging or on a live
|
||||
release.
|
||||
|
||||
@@ -123,11 +132,11 @@ sudo chmod 0640 /etc/wownerogue/app.env
|
||||
```
|
||||
|
||||
Do not use recursive ownership changes on `/var/www/wownerogue`; they can hand the service process
|
||||
write access to immutable code or the rollback selector. The reviewed fleet role must verify
|
||||
every individual release's ownership and modes before activation.
|
||||
write access to immutable code or the rollback selector. The fleet activation role verifies every
|
||||
individual release's ownership and modes before activation.
|
||||
|
||||
**Database ownership** - the application currently runs its own migrations at startup, so its
|
||||
database role must own the database/schema (or otherwise retain DDL rights). Create it this way:
|
||||
**Database ownership.** The application runs its own migrations at startup, so its database role
|
||||
must own the database and schema, or otherwise retain DDL rights. Create it this way:
|
||||
|
||||
```sql
|
||||
-- Create role with minimal privileges
|
||||
@@ -136,34 +145,47 @@ CREATE DATABASE wownerogue OWNER wownerogue;
|
||||
```
|
||||
|
||||
Do not revoke schema `CREATE` from this role while application startup owns migration execution;
|
||||
that makes a fresh release fail partway through startup. A separate one-shot migrator role is the
|
||||
follow-up path if runtime DDL privileges need to be removed.
|
||||
that makes a fresh release fail partway through startup.
|
||||
|
||||
---
|
||||
|
||||
## Database Setup
|
||||
|
||||
### Run Migrations
|
||||
|
||||
Database migrations run automatically, in filename order, inside one transaction per file and are
|
||||
recorded in `schema_migrations`. Do not apply migration files directly with `psql`: bypassing the
|
||||
ledger makes startup attempt them again. Before a release, take a restricted PostgreSQL backup and
|
||||
test the complete migration set against a restored copy of each production database using the
|
||||
fail-closed [disposable clone migration gate](CLONE_MIGRATIONS.md).
|
||||
Migrations in `src/migrations` run automatically at startup in filename order. Each file is applied
|
||||
and recorded in the `schema_migrations` ledger inside one transaction, so a migration can never be
|
||||
applied but unrecorded. Do not apply migration files directly with `psql`: bypassing the ledger
|
||||
makes startup attempt them again.
|
||||
|
||||
The migration ledger does not prove that rows predating a `NOT VALID` constraint are clean. Run
|
||||
the read-only historical audit and the rollback-only native validation proof described in
|
||||
[`FINANCIAL_CONSTRAINT_VALIDATION.md`](FINANCIAL_CONSTRAINT_VALIDATION.md) for each restored
|
||||
database. The native `VALIDATE CONSTRAINT` gate deliberately refuses live database names.
|
||||
Before a release, take a restricted PostgreSQL backup and test the complete migration set against a
|
||||
restored copy of each production database using the fail-closed
|
||||
[disposable clone migration gate](CLONE_MIGRATIONS.md).
|
||||
|
||||
The migration ledger does not prove that rows predating a `NOT VALID` constraint are clean. Run the
|
||||
read-only historical audit and the rollback-only native validation proof described in
|
||||
[FINANCIAL_CONSTRAINT_VALIDATION.md](FINANCIAL_CONSTRAINT_VALIDATION.md) for each restored database.
|
||||
The native `VALIDATE CONSTRAINT` gate refuses live database names.
|
||||
|
||||
If the Wownero mainnet service will export accounting events, review and configure the durable
|
||||
[financial-event outbox](FINANCIAL_EVENT_EXPORT.md) before cutover. Monero stagenet export must
|
||||
remain unset: those no-value test events are marked ignored locally.
|
||||
[financial-event outbox](FINANCIAL_EVENT_EXPORT.md) before cutover. Monero stagenet export stays
|
||||
unset: those no-value test events are marked ignored locally.
|
||||
|
||||
Install the reviewed `wowngeon-db-backup.sh`, `.service`, and `.timer` through the hash-pinned fleet
|
||||
operations change—not from the runtime artifact—and create
|
||||
`/var/backups/wowngeon/daily` as `postgres:postgres` mode `0700`, then enable the timer. Each run
|
||||
writes custom-format dumps atomically, verifies their catalogs with `pg_restore --list`, and records
|
||||
a SHA-256 sidecar. Configure retention/remote replication according to the operator's recovery
|
||||
policy; the supplied job deliberately does not delete backups.
|
||||
### Backups
|
||||
|
||||
Install `wowngeon-db-backup.sh`, `.service`, and `.timer` through the hash-pinned fleet operations
|
||||
change rather than from the runtime artifact, create `/var/backups/wowngeon/daily` as
|
||||
`postgres:postgres` mode `0700`, then enable the timer.
|
||||
|
||||
Each run writes custom-format dumps atomically, verifies their catalogs with `pg_restore --list`,
|
||||
chmods them `0600`, and records a SHA-256 sidecar. The script refuses any backup root other than
|
||||
`/var/backups/wowngeon/daily` and refuses a missing root or a symlink.
|
||||
|
||||
Dumps and sidecars older than `BACKUP_RETENTION_DAYS` (default 14) are deleted, and interrupted
|
||||
temporary files older than one day are cleaned up. This bounds a same-disk directory so it cannot
|
||||
fill `/`; it is an operational convenience, not the retention archive. Long-horizon history belongs
|
||||
in a separate, independently encrypted offsite vault configured according to the operator's recovery
|
||||
policy.
|
||||
|
||||
### Database Reset (Development Only)
|
||||
|
||||
@@ -176,25 +198,30 @@ npm run db:create # Recreate schema
|
||||
|
||||
## systemd Service
|
||||
|
||||
Install reviewed application, wallet, backup, and firewall artifacts only through the hash-pinned
|
||||
fleet operations change; the runtime artifact intentionally has no service units. Application
|
||||
units read secrets from `/etc/wownerogue/app.env` or `/etc/monerogue/app.env` plus the narrow
|
||||
wallet-RPC environment, never from an immutable release directory. They run `npm run preflight`
|
||||
before every start and use `/var/www/<instance>/current/src` so rollback is an atomic symlink
|
||||
switch.
|
||||
Install application, wallet, backup, and firewall units only through the hash-pinned fleet
|
||||
operations change; the runtime artifact has no service units. Application units read secrets from
|
||||
`/etc/wownerogue/app.env` or `/etc/monerogue/app.env` plus the narrow wallet-RPC environment, never
|
||||
from an immutable release directory. They run `scripts/preflight.js` as `ExecStartPre` before every
|
||||
start and use `/var/www/<instance>/current/src` as the working directory, so rollback is an atomic
|
||||
symlink switch.
|
||||
|
||||
For the two Such Software services, do not replace this boundary with manual `install`, `systemctl`,
|
||||
or symlink commands. Follow the default-closed wallet candidate/promotion, candidate validation,
|
||||
or symlink commands. Follow the default-closed wallet candidate and promotion, candidate validation,
|
||||
and one-instance activation procedures in [DEPLOY_INSTANCES.md](DEPLOY_INSTANCES.md) and the
|
||||
hash-pinned `~/src/such-fleet/RUNBOOK-wowngeon.md`. Independent MIT operators must design and review
|
||||
their own equivalent control plane.
|
||||
hash-pinned `such-fleet/RUNBOOK-wowngeon.md`. Independent MIT operators design and review their own
|
||||
equivalent control plane.
|
||||
|
||||
The systemd hardening options:
|
||||
- `NoNewPrivileges` - prevents privilege escalation
|
||||
- `ProtectSystem=strict` - mounts filesystem read-only except allowed paths
|
||||
- `ProtectHome=yes` - hides /home, /root, /run/user
|
||||
- `PrivateTmp=yes` - isolates /tmp
|
||||
- `ReadWritePaths` - whitelists the wallet state directory in wallet-RPC units
|
||||
The reviewed units drop the ambient environment with a broad `UnsetEnvironment` list (loader,
|
||||
proxy, `PG*`, and `npm_config_*` variables among others) and apply these systemd hardening options:
|
||||
|
||||
- `NoNewPrivileges` prevents privilege escalation
|
||||
- `ProtectSystem=strict` mounts the filesystem read-only except allowed paths
|
||||
- `ProtectHome=yes` hides `/home`, `/root`, and `/run/user`
|
||||
- `PrivateTmp=yes` isolates `/tmp`
|
||||
- `CapabilityBoundingSet=` and `AmbientCapabilities=` remove all capabilities
|
||||
- `RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6` and `SystemCallArchitectures=native` narrow the
|
||||
kernel surface
|
||||
- `ReadWritePaths` whitelists the wallet state directory in wallet-RPC units
|
||||
|
||||
---
|
||||
|
||||
@@ -205,19 +232,15 @@ If using Nginx Proxy Manager:
|
||||
1. **Add Proxy Host**
|
||||
- Domain Names: `yourdomain.com`
|
||||
- Scheme: `http`
|
||||
- Forward Hostname/IP: Your server's LAN IP (e.g., `192.168.1.100`)
|
||||
- Forward Hostname/IP: your server's LAN IP (for example `192.168.1.100`)
|
||||
- Forward Port: `3000`
|
||||
- Enable "Websockets Support" toggle
|
||||
|
||||
Record the proxy's fixed source address in `/etc/wowngeon/firewall.env` as
|
||||
`NPM_SOURCE_IPV4=<address>`. Direct access to 3000/3001 must be rejected; this also makes the
|
||||
single-hop `TRUST_PROXY_HOPS=1` setting safe.
|
||||
- Enable the "Websockets Support" toggle
|
||||
|
||||
2. **SSL Tab**
|
||||
- Request a new SSL certificate
|
||||
- Enable "Force SSL"
|
||||
|
||||
3. **Advanced Tab** - Paste this config for WebSocket support:
|
||||
3. **Advanced Tab**: paste this config for WebSocket support:
|
||||
|
||||
```nginx
|
||||
location /socket.io/ {
|
||||
@@ -232,17 +255,37 @@ location /socket.io/ {
|
||||
}
|
||||
```
|
||||
|
||||
Record the proxy's fixed source address in `/etc/wowngeon/firewall.env` as
|
||||
`NPM_SOURCE_IPV4=<address>`. `wowngeon-firewall.sh` rejects every non-loopback TCP connection to
|
||||
ports 3000 and 3001 that does not originate from that address. Because direct access is rejected,
|
||||
the single-hop `TRUST_PROXY_HOPS=1` setting is safe. The application clamps `TRUST_PROXY_HOPS` to
|
||||
the range 1 through 8.
|
||||
|
||||
---
|
||||
|
||||
## Browser Asset Origin
|
||||
|
||||
Three.js is an exact production dependency and is served from the application origin at
|
||||
`/vendor/three/<version>`, with the version embedded in the URL so an immutable cache cannot survive
|
||||
an upgrade to different bytes. `RENDERER_CDN_ENABLED` defaults to `false`, which keeps the CSP from
|
||||
granting third-party CDN scripts the same privileges as the game and payment UI. Setting it to
|
||||
`true` in production logs a warning and widens `script-src` and `connect-src` to jsDelivr.
|
||||
|
||||
Generated 3D avatar models under `html/assets/generated/` are gitignored and are not part of the
|
||||
release artifact. Provision them separately on any instance that offers the 3D renderer.
|
||||
|
||||
---
|
||||
|
||||
## Updating the Deployment
|
||||
|
||||
Run the full suite from the clean source commit, then build and verify the runtime artifact using
|
||||
[RELEASE_ARTIFACT.md](./RELEASE_ARTIFACT.md). Use the fleet blob-staging playbook, then proceed only
|
||||
through the reviewed, hash-pinned `wowngeon_release_activate` role after its clone, wallet,
|
||||
accounting, drain, predecessor, and receipt gates pass. That role—not an application service
|
||||
identity or a manual shell session—builds a new immutable
|
||||
`/var/www/<instance>/releases/<release-id>`, installs the locked graph, verifies the clone/preflight
|
||||
evidence, seals ownership, and atomically selects `current`.
|
||||
through the hash-pinned `wowngeon_release_activate` role after its clone, wallet, accounting, drain,
|
||||
predecessor, and receipt gates pass. That role, not an application service identity or a manual
|
||||
shell session, builds a new immutable `/var/www/<instance>/releases/<release-id>`, installs the
|
||||
locked graph, verifies the clone and preflight evidence, seals ownership, and atomically selects
|
||||
`current`.
|
||||
|
||||
Never run `git pull`, `npm install`, or `npm audit fix` inside the active release. Keep the previous
|
||||
release and database backup until the new version has passed public health, WebSocket,
|
||||
payment-intake, and (stagenet only) payout smoke tests.
|
||||
@@ -251,7 +294,7 @@ payment-intake, and (stagenet only) payout smoke tests.
|
||||
|
||||
## Multi-Instance Deployment
|
||||
|
||||
To run multiple instances (e.g., Wownero on port 3000 and Monero stagenet on port 3001):
|
||||
To run multiple instances, for example Wownero on port 3000 and Monero stagenet on port 3001:
|
||||
|
||||
### Directory Structure
|
||||
|
||||
@@ -275,68 +318,84 @@ To run multiple instances (e.g., Wownero on port 3000 and Monero stagenet on por
|
||||
| `PRIMARY_WALLET_ENDPOINT` | http://127.0.0.1:34570 | http://127.0.0.1:38083 |
|
||||
| `PRIMARY_RPC_ENDPOINT` | http://127.0.0.1:34568 | http://127.0.0.1:38081 |
|
||||
|
||||
### Daemon redundancy and graceful degradation
|
||||
### Separate systemd Service
|
||||
|
||||
Install the hash-pinned fleet copy of `monerogue.service`; it is absent from the runtime artifact.
|
||||
Do not hand-maintain a second, weaker unit. The reviewed template carries the same preflight,
|
||||
graceful-stop, immutable-release path, and systemd sandbox as the mainnet service.
|
||||
|
||||
---
|
||||
|
||||
## Daemon Redundancy and Graceful Degradation
|
||||
|
||||
`RPC_ENDPOINTS` takes any number of daemons, comma or whitespace separated, in **preference order**:
|
||||
|
||||
```
|
||||
RPC_ENDPOINTS=http://127.0.0.1:34568,http://10.42.1.40:34568,http://10.42.1.41:34568
|
||||
RPC_ENDPOINTS=http://127.0.0.1:34568,http://192.0.2.40:34568,http://192.0.2.41:34568
|
||||
```
|
||||
|
||||
`PRIMARY_RPC_ENDPOINT` / `FALLBACK_RPC_ENDPOINT` still work and are appended to the list. The list
|
||||
is deduplicated — note that the stock configuration sets primary and fallback to the *same* host, so
|
||||
by default there is exactly one node and no redundancy at all. Add at least one genuinely separate
|
||||
daemon before relying on failover.
|
||||
`PRIMARY_RPC_ENDPOINT` and `FALLBACK_RPC_ENDPOINT` remain supported and are appended to an explicit
|
||||
list. When no list is given, primary (or `http://127.0.0.1:34568`) and fallback form the list on
|
||||
their own. The result is deduplicated, so a stock configuration that points primary and fallback at
|
||||
the same host yields exactly one node and no redundancy. Add at least one genuinely separate daemon
|
||||
before relying on failover.
|
||||
|
||||
Behaviour:
|
||||
|
||||
- one logical RPC call tries **every** configured node before it fails;
|
||||
- each node must pass the same chain-identity check before it may answer, so failover can never
|
||||
- each node must pass the same chain-identity check before it may answer, so failover cannot
|
||||
silently serve a different chain's data;
|
||||
- after failing over, the preferred node is re-tested every `RPC_PREFERRED_RETRY_MS` (default 60s,
|
||||
minimum 1s), so recovery does not need a restart;
|
||||
- when **no** node answers, the service reports unhealthy and throws. `getBlockCountStrict()` —
|
||||
used by fairness and match seeding — never substitutes a cached height.
|
||||
- after failing over, the preferred node is re-tested every `RPC_PREFERRED_RETRY_MS` (default
|
||||
60000, minimum 1000), so recovery does not need a restart;
|
||||
- when **no** node answers, the service reports unhealthy and throws. `getBlockCountStrict()`, used
|
||||
by match fairness and seeding, never substitutes a cached height.
|
||||
|
||||
**Entries are refused while no node is reachable.** A run's lifetime is counted in blocks and its
|
||||
entry block comes from the last successful poll; admitting a game during an outage anchors it to an
|
||||
already-past block and it is killed as a timeout on the first recovered poll. The player is told
|
||||
`Can't reach a <CHAIN> node right now…` and nothing is charged. Credits are not consumed, because
|
||||
the gate runs before any spend.
|
||||
entry block comes from the last successful poll, so a game admitted during an outage is anchored to
|
||||
an already-past block and is killed as a timeout on the first recovered poll. The player receives a
|
||||
`chain_unavailable` event reading `Can't reach a <CHAIN> node right now, so entries are paused.
|
||||
Nothing was charged`, and nothing is charged: the gate runs before any spend, so credits are not
|
||||
consumed. The gate fires only when the chain health check explicitly reports false, so simulated
|
||||
blocks and runtimes without the probe are unaffected.
|
||||
|
||||
**The wallet is deliberately NOT failed over.** Two `wallet-rpc` processes serving one wallet file
|
||||
can build transactions from the same outputs and corrupt the wallet cache — a standby wallet is a
|
||||
custody decision for the operator, not something the app may do on its own. Daemons are read-only
|
||||
and interchangeable; wallets are neither. When the wallet is unreachable the server declines paid
|
||||
intake with `Payments are temporarily unavailable…`, and credits and free play keep working.
|
||||
|
||||
### Separate systemd Service
|
||||
|
||||
Install the hash-pinned fleet copy of `monerogue.service`; it is intentionally absent from the
|
||||
runtime artifact. Do not hand-maintain a second, weaker unit. The reviewed template contains the
|
||||
same preflight, graceful-stop, immutable-release path, and systemd sandbox as the mainnet service.
|
||||
**The wallet is deliberately not failed over.** Two `wallet-rpc` processes serving one wallet file
|
||||
can build transactions from the same outputs and corrupt the wallet cache, so a standby wallet is a
|
||||
custody decision for the operator rather than something the application may take on its own.
|
||||
Daemons are read-only and interchangeable; wallets are neither. When the wallet is unreachable the
|
||||
server declines paid intake with a `payment_unavailable` event reading `Payments are temporarily
|
||||
unavailable`, and credits and free play keep working.
|
||||
|
||||
---
|
||||
|
||||
## Wallet Output Management
|
||||
|
||||
Wownero (and Monero) lock change outputs for several blocks after spending. If the wallet has only one large output and multiple payouts fire in quick succession, the second payout will fail because the change output from the first is still locked.
|
||||
Wownero and Monero lock change outputs for several blocks after spending. If the wallet holds one
|
||||
large output and multiple payouts fire in quick succession, the second payout fails because the
|
||||
change output from the first is still locked.
|
||||
|
||||
**Solution**: Pre-split wallet outputs into many smaller ones. The game server includes a CLI tool for this:
|
||||
The remedy is to pre-split wallet outputs into many smaller ones. `scripts/splitOutputs.js` is an
|
||||
operator tool in the source repository, not part of the release artifact; run it from a source
|
||||
checkout whose `src/.env` points at the wallet RPC.
|
||||
|
||||
```bash
|
||||
# Preview what would happen (no transaction sent)
|
||||
node scripts/splitOutputs.js --amount 10 --count 30 --dry-run
|
||||
# Show the current output breakdown, including locked and unlocked status
|
||||
node scripts/splitOutputs.js --status
|
||||
|
||||
# Split into 30 outputs of 10 WOW each (requires ~300 WOW + fees)
|
||||
# Split into 30 outputs of 10 WOW each (requires 300+ WOW unlocked, plus fees)
|
||||
node scripts/splitOutputs.js --amount 10 --count 30
|
||||
```
|
||||
|
||||
This creates 30 independently-spendable outputs, allowing up to 30 concurrent payouts before any output locking becomes an issue.
|
||||
Defaults are 10 WOW and 20 outputs; `--endpoint` overrides `PRIMARY_WALLET_ENDPOINT`. Thirty
|
||||
independently spendable outputs allow up to thirty concurrent payouts before output locking becomes
|
||||
an issue.
|
||||
|
||||
**When to re-split**: As payouts go out, outputs get consumed and consolidated via change. Periodically check the wallet and re-split when the number of spendable outputs drops low. The server also batches payouts that happen within 5 seconds of each other into a single transaction to conserve outputs.
|
||||
**When to re-split.** Outputs are consumed and consolidated back into change as payouts go out.
|
||||
Check the wallet with `--status` periodically and re-split when the number of spendable outputs
|
||||
drops low. The server also debounces payouts by 5 seconds so wins landing close together are batched
|
||||
into one transfer, which conserves outputs. A slower interval sweep
|
||||
(`payouts.processing.batchInterval`, default 300 seconds) picks up anything left pending.
|
||||
|
||||
**Recommended setup**: After initial wallet funding, run the split script. For a game with typical traffic, 20-30 outputs of 10 WOW each provides good concurrency headroom.
|
||||
For typical traffic, 20 to 30 outputs of 10 WOW each provides good concurrency headroom.
|
||||
|
||||
---
|
||||
|
||||
@@ -349,22 +408,28 @@ systemctl status wownerogue
|
||||
journalctl -u wownerogue -f # Follow logs
|
||||
```
|
||||
|
||||
### Health Endpoint
|
||||
### Health Endpoints
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/health/live
|
||||
curl --fail http://localhost:3000/health/ready
|
||||
```
|
||||
|
||||
`/health/live` checks the process. `/health/ready` returns 503 until PostgreSQL, the chain daemon,
|
||||
and (for paid instances) wallet RPC are ready. Public probes do not expose wallet balances or RPC
|
||||
endpoints; those remain in the authenticated admin dashboard.
|
||||
`/health/live` reports process liveness only. `/health` returns the full public snapshot, and
|
||||
`/health/ready` returns the same body with HTTP 503 until PostgreSQL, the chain daemon, and (for
|
||||
paid instances) wallet RPC are ready. All three send `Cache-Control: no-store`.
|
||||
|
||||
Public probes omit wallet balances, RPC addresses, secrets, memory details, and abuse thresholds;
|
||||
those remain in the authenticated admin stats endpoints. An optional accounting sink is reported as
|
||||
sanitized informational data and never makes gameplay readiness fail.
|
||||
|
||||
### Admin Dashboard
|
||||
|
||||
Access `/admin.html` with your `ADMIN_API_KEY` for:
|
||||
The admin page is served at `/admin` and `/admin.html`. Admin API endpoints require an `X-Admin-Key`
|
||||
header matching `ADMIN_API_KEY`, and return 503 when that variable is unset. The dashboard covers:
|
||||
|
||||
- Wallet balance and connection status
|
||||
- Pending/failed payouts
|
||||
- Pending and failed payouts
|
||||
- Game statistics
|
||||
- User search
|
||||
|
||||
@@ -386,6 +451,10 @@ sudo -u wownerogue bash -c 'set -a; . /etc/wownerogue/app.env; set +a; npm run p
|
||||
psql -h localhost -U wownerogue -d wownerogue -c "SELECT 1;"
|
||||
```
|
||||
|
||||
Preflight validates operator configuration without connecting to PostgreSQL or either RPC. It
|
||||
catches missing secrets, ambiguous payout flags, simulated blocks in production, missing wallet or
|
||||
daemon endpoints, and operated-profile scope drift.
|
||||
|
||||
### Wallet RPC Issues
|
||||
|
||||
```bash
|
||||
@@ -403,4 +472,4 @@ If migrations fail, check the `schema_migrations` ledger:
|
||||
SELECT filename, applied_at FROM schema_migrations ORDER BY filename;
|
||||
```
|
||||
|
||||
Migrations are tracked by name to prevent duplicate execution.
|
||||
Migrations are tracked by filename to prevent duplicate execution.
|
||||
|
||||
Reference in New Issue
Block a user