feat: harden operated production release

This commit is contained in:
jwinterm
2026-07-22 12:56:00 -04:00
parent dfe4d1ad97
commit 91f139eeaa
44 changed files with 4500 additions and 653 deletions
+3 -3
View File
@@ -25,13 +25,13 @@ the same `DatabaseManager.runMigrations()` used by application startup: lexical
the `schema_migrations` ledger, and one transaction per newly applied file. Finally, it requires the
ledger to equal the complete repository migration manifest, in order, with no missing or extra
entry. The manifest is discovered from the candidate release and must be contiguous from 001; the
current floor is migration 040. A successful current release reports:
current floor is migration 042. A successful current release reports:
```text
clone_migration_status=ok
database=monerogue_restore_20260721t220048z_28d513b29b36
migration_count=40
latest_migration=040_paid_match_entropy_precommit.sql
migration_count=42
latest_migration=042_immutable_financial_event_snapshots.sql
```
## Invocation
+30 -21
View File
@@ -2,6 +2,21 @@
This guide covers deploying Wownerogue to a production environment.
## 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 **NO REAL VALUE** 2×/3× solo test gambling mechanics, with 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.
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.
---
## Prerequisites
@@ -129,6 +144,10 @@ the read-only historical audit and the rollback-only native validation proof des
[`FINANCIAL_CONSTRAINT_VALIDATION.md`](FINANCIAL_CONSTRAINT_VALIDATION.md) for each restored
database. The native `VALIDATE CONSTRAINT` gate deliberately 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.
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
@@ -147,28 +166,18 @@ npm run db:create # Recreate schema
## systemd Service
Install reviewed application/firewall templates through the hash-pinned fleet operations change;
the runtime artifact intentionally has no service units. Wallet-RPC replacement remains subject to
the explicit NO-GO gates in [DEPLOY_INSTANCES.md](DEPLOY_INSTANCES.md). Application units read secrets from
`/etc/wownerogue/app.env` or `/etc/monerogue/app.env`, not 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 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.
Enable and start:
```bash
# Install the correct profile outside the release and replace every placeholder.
sudo install -d -m 0750 -o root -g wownerogue /etc/wownerogue
# Use the separately reviewed source/fleet copy; environment examples are not in the runtime archive.
sudo install -m 0640 -o root -g wownerogue <reviewed-source>/src/.env.mainnet.example /etc/wownerogue/app.env
sudoedit /etc/wownerogue/app.env
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable wownerogue
sudo systemctl start wownerogue
sudo systemctl status wownerogue
```
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,
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.
The systemd hardening options:
- `NoNewPrivileges` - prevents privilege escalation
+65 -36
View File
@@ -4,9 +4,13 @@ Two independent instances of the same secured codebase, behind Nginx Proxy Manag
| Instance | Domain | Port | Network | Money | Payouts | Leaderboards |
|----------|--------|------|---------|-------|---------|--------------|
| **wownerogue** (existing) | play.wowne.ro | 3000 | Wownero **mainnet** | real WOW | **OFF** | Pleb + Hall of Champions |
| **monerogue** (new) | monerogue.app | 3001 | Monero **stagenet** | fake XMR | **ON (target; wallet-gated)** | Pleb + Hall of Champions |
| **wownerogue** (existing) | play.wowne.ro | 3000 | Wownero **mainnet** | purchased service credits | **OFF, always** | Pleb + paid prestige |
| **monerogue** (new) | monerogue.app | 3001 | Monero **stagenet only** | test coins — **NO REAL VALUE** | **ON for 2×/3× solo only (target; wallet-gated)** | Pleb + Hall of Champions |
Such Software (`apps@such.software`) operates only these two domains and these exact scopes.
`play.wowne.ro` is pay-for-credits prestige with no prize/payout/cash-out and is not marketed as
gambling; legal classification depends on applicable law. `monerogue.app` is a conspicuous
no-real-value test gambling surface; never send mainnet XMR and never enable crypto-match payouts.
Stagenet payouts are a technical test surface, not authorization to offer real-money wagering.
`ALLOW_MAINNET_PAYOUTS=true` is only an engineering interlock; obtain jurisdiction-specific legal,
age/access-control, responsible-play, and operational review before attaching redeemable value.
@@ -25,15 +29,16 @@ transfer only that blob into a new root-owned mode-0700 candidate inbox. Compare
with the independently recorded literal digest; the transferred sidecar is not an independent
provenance source.
There is currently no reviewed fleet extraction/activation helper. Blob-only staging is allowed;
manual extraction under `/var/www/<instance>/releases`, dependency installation, clone migration,
preflight, service restart, and `current` changes remain blocked until that helper implements the
complete ownership/path/hash/rollback contract. Never make a release directory writable by either
application service identity.
Such Software production uses only the separate, default-closed fleet playbooks for inert blob
staging, two-dump clone validation, wallet candidate preparation/promotion, and one-instance release
activation. Each operation is bound to literal hashes and exact receipts; none infers a release,
dump, wallet, predecessor, or instance. Do not substitute manual extraction under
`/var/www/<instance>/releases`, dependency installation, service commands, or a hand-written
`current` switch. Never make a release directory writable by either application service identity.
---
## Step 1 — Reconfigure the MAINNET instance: free-to-play, NO payouts
## Step 1 — Reconfigure MAINNET: free + purchased-credit prestige, NO payouts
Install the reviewed source profile `src/.env.mainnet.example` as `/etc/wownerogue/app.env` (mode
0640, `root:wownerogue`), replace every placeholder, and review these money gates. Environment
@@ -41,7 +46,14 @@ examples are intentionally not present in the runtime artifact; provision this f
hash-pinned fleet/source change, never from the application release directory.
```ini
OPERATED_PRODUCT_PROFILE=such-play-wow-prestige
OPERATOR_NAME=Such Software
OPERATOR_CONTACT_URL=mailto:apps@such.software
OPERATOR_CONTACT_LABEL=apps@such.software
FREE_PLAY_ENABLED=true # offer free play as a choice (Pleb board)
PAYMENT_MODES=credits # only purchased-credit prestige is operated here
DIRECT_PAYMENT_ENABLED=false # per-run payment is outside the operated scope
CREDITS_ENABLED=true
PAYOUTS_ENABLED=false # no crypto leaves the house wallet
DIRECT_PAYOUTS_ENABLED=false
CREDITS_PAYOUTS_ENABLED=false
@@ -51,9 +63,10 @@ TRUST_PROXY=true
TRUST_PROXY_HOPS=1 # exactly one NPM hop
```
Players still buy credits / pay entry (→ Hall of Champions prestige); free players → Pleb
board. This profile sends no winnings, but legal classification still depends on the product,
jurisdiction, and access controls; obtain counsel before treating that as a legal conclusion.
Players buy non-redeemable credits for leaderboard/prestige play; free players use the Pleb board.
This operated product offers no winnings, payouts, or cash-out and is not offered or marketed as
gambling. Classification depends on applicable law; this documentation is not legal advice or a
jurisdiction-specific compliance approval.
The following are post-activation verification commands, not authorization to restart the live
service while the wallet/preflight and fleet activation gates remain open:
@@ -61,7 +74,7 @@ service while the wallet/preflight and fleet activation gates remain open:
```bash
sudo systemctl restart wownerogue.service
journalctl -u wownerogue.service -n 50 --no-pager # verify: "Payment system: ENABLED", payouts disabled
curl -s localhost:3000/api/game-modes | jq '{freePlayEnabled, PAID_SINGLE:.PAID_SINGLE.payoutMultiplier}'
curl -s localhost:3000/api/game-modes | jq '{freePlayEnabled, PAID_SINGLE, PAID_CREDITS}'
```
**MAINNET WALLET NO-GO:** this application release does not authorize changing, copying, stopping,
@@ -73,24 +86,27 @@ not files in the runtime artifact and not approval to install them.
---
## Step 2 — MONERO STAGENET wallet boundary: NO-GO
## Step 2 — Prove and promote the MONERO STAGENET wallet boundary
Do **not** install a replacement unit, create wallet identities/directories, copy wallet or key
files, capture a password, provision RPC credentials, stop/restart the wallet, or change the live
application endpoint as part of this release. The current wallet, unit, and application dependency
remain unchanged.
Wallet work is a separate custody operation from application release work. Use only the reviewed
`wowngeon_wallet_candidate` and `wowngeon_wallet_promote` roles from a clean, hash-pinned fleet
revision, select exactly `monerogue`, and preserve every generic receipt. Candidate preparation
must prove the fixed encrypted stagenet snapshot reopens the exact live address, has a spend key,
and enforces the application's strong Digest credentials before it publishes inert dedicated-user
material. Promotion must then stop only the selected legacy wallet, install only the reviewed
loopback-only unit, require unauthenticated `401` plus authenticated address/network/balance probes,
and automatically roll back to the exact legacy unit on a failed gate.
The only retained design is
The earlier non-mutating design record is
[`README-monero-stagenet-parallel-candidate.md`](../scripts/deploy/README-monero-stagenet-parallel-candidate.md).
It currently approves only a disposable synthetic fixture and, after independent review, a fixed
read-only inventory preflight. Even its manual candidate service is not approved for installation.
Before any host mutation, its complete crash/failure-injection gates, stopped-copy restart proof,
offline address match, keyfile/password restore, mnemonic/off-host custody, and isolated unfunded
rehearsal must all pass against an immutable reviewed hash.
It is not an installation procedure and its sample unit is not a promotion artifact.
Until a separate wallet promotion is approved, a stagenet application candidate must use an
isolated database with payments, payouts, and match money disabled. Paid intake and the payout
canary remain blocked; do not bypass application preflight to make them start.
Neither wallet role reads, exports, or proves a mnemonic. A passing keyfile/password proof is not
independent seed custody or a full off-host recovery drill. Record those separately as unproven
until a human-controlled custody inventory and isolated off-host restore establish them. This gap
does not authorize weakening a proof, printing a secret, or applying either role to the funded
Wownero mainnet wallet. Wownero wallet promotion and the corresponding application activation
remain blocked pending that separate review.
## Step 3 — Create the monerogue identity, immutable layout, and DB
@@ -109,8 +125,9 @@ the production host or recursively `chown` `/var/www/monerogue` to the service u
## Step 4 — The monerogue `.env`
This is the eventual paid stagenet profile, not permission to activate it while Step 2 is NO-GO.
Write `/etc/monerogue/app.env` as `root:monerogue` mode `0640`. Amounts are XMR atomic units
This is the exact no-real-value stagenet test profile. Apply its non-secret policy keys through the
fleet runtime-policy role; do not overwrite or log the protected environment. Keep
`/etc/monerogue/app.env` as `root:monerogue` mode `0640`. Amounts are XMR atomic units
(1 XMR = 1e12):
```ini
@@ -118,11 +135,17 @@ NODE_ENV=production
PORT=3001
TRUST_PROXY=true
TRUST_PROXY_HOPS=1
OPERATED_PRODUCT_PROFILE=such-monerogue-stagenet
OPERATOR_NAME=Such Software
OPERATOR_CONTACT_URL=mailto:apps@such.software
OPERATOR_CONTACT_LABEL=apps@such.software
CRYPTO_TYPE=XMR
MONERO_NETWORK=stagenet
HOSTED_BY=https://monerogue.app
DIFFICULTY_PRESET=casino
# NO REAL VALUE: test coins only. Never send mainnet XMR.
# --- payments + free play ---
PAYMENTS_ENABLED=true
PAYMENT_MODES=direct,credits
@@ -150,6 +173,7 @@ PREFER_CREDITS_FIRST=true
# --- payouts --- (verify the unlocked stagenet bankroll before every rollout)
PAYOUTS_ENABLED=true
ALLOW_MAINNET_PAYOUTS=false
PAYOUT_MIN_AMOUNT=1000000000 # 0.001 XMR
PAYOUT_MAX_PER_GAME=50000000000 # 0.05 XMR safety cap (real max is 3x*0.01 = 0.03)
PAYOUT_BATCH_INTERVAL=300
@@ -179,11 +203,15 @@ ADMIN_API_KEY=GENERATE_WITH_openssl_rand_hex_32
# --- smirk: disable for monero stagenet (Smirk doesn't support stagenet) ---
SMIRK_ENABLED=false
# --- PvP financial boundary: free/prestige rulesets may evolve, crypto payouts may not ---
MATCH_CRYPTO_RACE_ENABLED=false
MATCH_PAYOUTS_ENABLED=false
```
The application unit separately requires `/etc/monerogue/wallet-rpc.env`, owned
`root:monerogue` mode `0640`, containing only `WALLET_RPC_USER` and `WALLET_RPC_PASSWORD`. Provision
it through the approved secrets workflow only after the wallet gate clears. Never place either
it only through the reviewed wallet-candidate workflow. Never place either
password in a command argument, unit `ExecStart`, shell history, or this runbook.
Production startup proves that wallet RPC actually enforces Digest authentication: an intentional
@@ -200,10 +228,10 @@ sudo -u monerogue bash -c 'set -a; . /etc/monerogue/app.env; . /etc/monerogue/wa
## Step 5 — Install + start the monerogue instance
The runtime artifact contains no service units. Install or update `monerogue.service` only through
the separately reviewed, hash-pinned `~/src/such-fleet` change after the wallet and application
gates pass. Do not copy a unit from the application release or start this paid profile while Step 2
is NO-GO.
The runtime artifact contains no service units. Promote the wallet and activate `monerogue` only
through the separately reviewed, hash-pinned `~/src/such-fleet` roles after the recovery,
clone-validation, protected-environment, wallet, drain, and financial-audit gates pass. Do not copy
a unit from the application release.
Install the hash-pinned fleet copies of `wowngeon-firewall.sh` and
`wowngeon-firewall.service`, then set the fixed proxy source as `NPM_SOURCE_IPV4=<address>` in
@@ -213,11 +241,12 @@ domains, websocket upgrades, and local readiness probes still work.
## Step 6 — Verify end to end
- `monerogue.app` loads; `play.wowne.ro` still healthy.
- After a separate wallet GO and isolated canary: buy credits / pay entry with a stagenet wallet →
game starts → win → payout arrives. This check is blocked while Step 2 is NO-GO.
- After wallet promotion and the separately approved bounded canary: buy credits / pay entry with a
stagenet wallet → game starts → qualifying solo outcome → 2×/3× test-coin payout arrives.
- After the corresponding gates clear, both boards populate: free game → Pleb, paid game → Hall of
Champions, on each instance.
- Mainnet: confirm a winning paid game does NOT send a payout (no crypto out).
- Mainnet, only after its separate recovery gate and release: confirm purchased-credit play never
creates a payout or cash-out path.
---
+55
View File
@@ -0,0 +1,55 @@
# Financial-event export
Migrations 041 and 042 create a durable outbox for confirmed customer payments and final refund
states. Migration 042 stores an immutable source snapshot before delivery; the exporter never
reconstructs an event from later-mutated payment rows. Payloads contain transaction classification,
atomic amount, asset/network, catalog/provider identifiers, and receipt count, but no player ID,
wallet address, session token, IP address, email address, or authentication material.
Delivery is **at least once**. Every request sets `Idempotency-Key` to the immutable financial event
ID. The receiving system must durably deduplicate that key before acknowledging with a 2xx response.
A timeout or lost acknowledgement can otherwise produce a repeat request. The application marks a
row delivered only after a 2xx response; bounded retries use a durable lease and exponential delay,
then move an exhausted row to `dead_letter` for operator review.
The public health response exposes only aggregate exporter state: enabled/running flags, backlog,
dead-letter and ignored counts, and cumulative delivery counters. It exposes no sink URL,
token, event ID, error text, payload, or source identity. Accounting export is informational and does
not independently make gameplay readiness fail, so production monitoring must alert on backlog age,
dead letters, and the exporter process logs/metrics needed to detect stale delivery.
## Configuration contract
Configure both of these only in the protected Wownero mainnet runtime environment:
```dotenv
FINANCIAL_EVENT_SINK_URL=https://ledger.operator.example/v1/events
FINANCIAL_EVENT_SINK_TOKEN=<strong protected bearer token>
FINANCIAL_EVENT_ACCOUNT_REF=wowngeon:wow-receipts
FINANCIAL_EVENT_POLL_MS=60000
FINANCIAL_EVENT_BATCH_SIZE=20
FINANCIAL_EVENT_MAX_ATTEMPTS=8
```
Production requires HTTPS, forbids URL-embedded credentials, and requires a strong token. The account
reference is a non-PII identifier. URL and token must be set together. Never place the token in Git,
Ansible inventory, an invocation, a receipt, a log, or a public health check.
Monero stagenet deliberately sends nothing to an accounting sink. Its pending, leased, or dead-letter
test rows are moved to `ignored` with reason `non_mainnet_network`; startup rejects sink configuration
on any non-mainnet network. This keeps **NO REAL VALUE** test activity out of real books.
## Operations
Before enabling mainnet export, prove in a disposable environment that the sink:
1. authenticates the protected bearer token over TLS;
2. deduplicates repeated requests by `Idempotency-Key`;
3. durably records the event before returning 2xx;
4. rejects malformed events without echoing secrets; and
5. supports alerting and reconciliation for a deliberately generated dead letter.
Keep the outbox in database backups. Do not delete or rewrite a dead-letter row to make monitoring
green; reconcile it against the receiving ledger, preserve the evidence, and use a separately reviewed
repair procedure. An absent sink is permitted, but confirmed mainnet events then remain pending and
must be included in the production accounting handoff.
+32 -2
View File
@@ -4,6 +4,32 @@ This implementation makes the running server describe its actual free, paid-pres
test-network, and PvP switches. It does **not** claim that the product is licensed or lawful in a
particular place. Obtain jurisdiction-specific advice before accepting assets with real value.
## Operated-product boundary
Such Software (`apps@such.software`) operates only:
| Profile | Public service | Exact scope |
|---|---|---|
| `such-play-wow-prestige` | `play.wowne.ro` | Wownero mainnet free play and pay-for-credits leaderboard/prestige. Credits are non-redeemable; there are no prizes, payouts, cash-out, or crypto racing, and the service is not offered/marketed as gambling. Legal classification depends on applicable law. |
| `such-monerogue-stagenet` | `monerogue.app` | Monero stagenet only. Single-player 2×/3× test gambling mechanics using test coins with **NO REAL VALUE**. Never mainnet XMR and no crypto-match payouts. |
The corresponding `.env.mainnet.example` and `.env.stagenet.example` files opt into a fail-closed
startup contract with `OPERATED_PRODUCT_PROFILE`. Preflight and normal startup reject any drift in
host, operator identity, chain/network, paid modes, multipliers, or payout gates. Independent MIT
self-hosts leave this setting unset and configure a truthful operator identity of their own.
## Open-source and operator disclaimer
MIT permission to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies is
subject to retaining the copyright and permission notice in all copies or substantial portions.
The software is provided “AS IS”, without warranty of any kind, as stated in `LICENSE`. The code,
documentation, examples, and disclosure copy are not legal advice, a licence, or a compliance
approval.
A third-party or self-hosted operator is solely responsible for its deployment, product design,
legal compliance, funds, players, representations, and support. Such Software does not operate,
supervise, endorse, or accept responsibility for third-party deployments.
## Required production settings
Every production instance with `PAYMENTS_ENABLED=true` fails preflight/startup unless all of these
@@ -13,8 +39,8 @@ are explicit and valid:
OPERATOR_NAME=Truthful responsible operator name
OPERATOR_CONTACT_URL=mailto:support@example.com
OPERATOR_CONTACT_LABEL=Support
LEGAL_POLICY_VERSION=2026-07-21-v1
TERMS_EFFECTIVE_DATE=2026-07-21
LEGAL_POLICY_VERSION=2026-07-22-v1
TERMS_EFFECTIVE_DATE=2026-07-22
MINIMUM_AGE=18
PAID_ACKNOWLEDGEMENT_REQUIRED=true
```
@@ -60,6 +86,10 @@ The acknowledgement is a player statement, not proof of identity, age, capacity,
| Crypto PvP on | Paid ticket escrow and configured winner/fee contract; separate from free/prestige PvP |
| XMR stagenet/testnet | Valueless test currency; explicit warning never to send mainnet XMR |
For the operated products, the generic rows narrow further: `play.wowne.ro` exposes purchased
credits only, offers no payout/cash-out, and is not marketed as gambling; `monerogue.app` labels its 2×/3× stagenet mechanics as
**NO REAL VALUE** and keeps crypto-match payouts disabled.
## Unresolved launch decisions
Technical copy and checkboxes do not resolve these. Keep real-value modes gated until the responsible
+7 -4
View File
@@ -77,14 +77,17 @@ activation boundary must then:
4. restore each fresh pre-deploy dump into a separately named clone database, run
`npm run db:migrate:clone` under the exact disposable-target contract in
[CLONE_MIGRATIONS.md](./CLONE_MIGRATIONS.md), require its exact manifest proof (currently through
migration 040), and run the separately reviewed and hash-pinned fleet copy of
migration 042), and run the separately reviewed and hash-pinned fleet copy of
`financial-audit.sql` there—the SQL is intentionally not inside the runtime artifact;
5. run `npm run preflight` from the candidate with each final protected environment. Preflight is
intentionally non-mutating and does not replace database, daemon, or wallet health checks.
The clone runner is deliberately non-activating; there is still no reviewed activation script. Do
not point `current` at a candidate until the clone proof and the complete
drain/audit/symlink/health/rollback sequence in `~/src/such-fleet/RUNBOOK-wowngeon.md` are satisfied.
The clone runner is deliberately non-activating. Such Software production activation must use the
separately reviewed, default-closed `wowngeon_release_activate` role in `~/src/such-fleet`; do not
substitute manual extraction, dependency installation, service commands, or a hand-written
`current` switch. The role still refuses activation until the matching clone-validation receipt,
wallet-promotion receipt, drain/audit gates, literal hashes, and rollback target satisfy the
complete contract in `~/src/such-fleet/RUNBOOK-wowngeon.md`.
## Source and operations boundary
+168
View File
@@ -0,0 +1,168 @@
# Wallet keyfile recovery proof
**Status: source and synthetic tests only. This helper has not been installed on or run against
`deb`, and this document does not authorize a host mutation or a production ceremony.**
This runbook covers one deliberately narrow question: can an audited encrypted wallet snapshot and
the password already exposed in the matching legacy wallet-RPC process open offline and reproduce
the exact public address currently returned by that process? The fixed helper is
`scripts/deploy/wallet-keyfile-recovery-proof.py`.
The answer is emitted as exactly one status line:
```text
result=passed
```
or:
```text
result=refused
```
There is intentionally no diagnostic mode. Never add shell tracing, a debugger, `strace`, command
line capture, wallet-RPC response logging, or exception rendering to investigate a refusal. Those
would defeat the secret-handling boundary this proof is meant to preserve.
## What a pass means
A pass establishes all of the following for the selected profile at that moment:
- the expected legacy systemd unit has a stable MainPID, executable, UID/GID, cgroup, listener,
network flag, RPC port, and exact `--wallet-file` argument;
- its already-exposed inline wallet password could be copied from the NUL-delimited process command
line without rendering it;
- both fixed, root-only pre-production snapshot files were real, single-link regular files with the
audited metadata and were copied byte-for-byte into a newly created private `/run` directory;
- the matching wallet-RPC executable opened that copied pair offline on the fixed loopback proof
port as the exact legacy UID/GID, with an empty supplementary-group list, using a password file and
an ephemeral Digest credential;
- the proof endpoint rejected an unauthenticated request with HTTP 401, accepted an authenticated
`get_address` request with HTTP 200, returned an address shaped for the expected network, and that
address exactly equalled the current live address;
- that restored wallet signed one fixed, non-secret proof message and its own wallet-RPC verified the
signature as a spend-key signature (or, for the legacy response shape, verified the default
spend-key signature), so a watch-only keyfile cannot pass; and
- the candidate stopped, its listener disappeared, and only the exact ephemeral files and directory
were securely cleaned up.
The helper does not stop, start, restart, reload, reconfigure, or write to a live unit or live wallet.
The proof candidate is offline; it cannot refresh, submit transactions, or contact a daemon.
## What a pass does not mean
This is **not seed custody** and it is **not an off-host disaster-recovery test**. In particular, a
pass does not establish:
- that a mnemonic or seed has ever been recorded, is readable, is complete, or is held by the right
custodians;
- that the encrypted keyfile, its password, or a seed exists independently off this host—the password
used here comes only from the already-running process, not from an independent custody source;
- that Restic media, repository credentials, retention, or an actual off-host restore path works;
- that a restore can rescan chain history, discover the expected balance, or spend funds;
- that daemon, database, application, DNS, proxy, Nebula, or fleet recovery works; or
- that payouts, wagering, mainnet money movement, or a production release is authorized.
Do not query, display, export, hash, or automate retrieval of a mnemonic to fill this gap. Seed
custody requires a separate human-controlled inventory and an off-host restore drill with an agreed
handling ceremony. Until that evidence exists, record seed custody and off-host recovery as
**unproven**, even if this helper passes.
## Fixed audited profiles
No path, unit, binary, port, user, or RPC method is accepted from the caller. The only accepted
profile names are `monero-stagenet` and `wownero-mainnet`.
| Profile | Fixed legacy identity | Fixed snapshots | Private proof target |
| --- | --- | --- | --- |
| `monero-stagenet` | `monero-wallet-rpc.service`, `jw:jw`, `/usr/bin/monero-wallet-rpc`, wallet `/home/jw/Programs/monero-x86_64-linux-gnu-v0.18.4.4/test`, `127.0.0.1:38083`, `--stagenet` | `/var/backups/wowngeon/wallets/monero-stagenet-preprod-20260721T1140Z.wallet` and `/var/backups/wowngeon/wallets/monero-stagenet-preprod-20260721T1140Z.keys` | outer `/run/wowngeon-wallet-keyfile-proof-monero-stagenet`, inner `candidate`, `127.0.0.1:39084` |
| `wownero-mainnet` | `wownero-wallet-rpc.service`, `jw:jw`, `/usr/bin/wownero-wallet-rpc`, wallet `/home/jw/Programs/wow-11.3.0/game`, audited legacy listener `0.0.0.0:34570` | `/var/backups/wowngeon/wallets/wownero-mainnet-preprod-20260721T1140Z.wallet` and `/var/backups/wowngeon/wallets/wownero-mainnet-preprod-20260721T1140Z.keys` | outer `/run/wowngeon-wallet-keyfile-proof-wownero-mainnet`, inner `candidate`, `127.0.0.1:39570` |
The live equality observations are unauthenticated because the legacy sources are. Monero is
loopback-only; Wownero's wildcard listener is an insecure legacy source contained by the host
firewall, not a state this proof endorses. `current_live_address` makes one read-only public-address
observation solely for equality; a match is **not** an RPC-hardening or network-security pass. The
helper neither changes nor resolves the wildcard/unauthenticated service. Its proof candidate always
binds loopback and requires an ephemeral Digest credential.
Each fixed proof path is a root-owned outer directory, `root:jw` mode `0710`, which prevents the
candidate identity from listing or writing its parent. Root builds the inner `candidate` subtree and
files before publishing only that subtree as `jw:jw` mode `0700` with `0600` files. Immediately before
exec, the child clears supplementary groups, sets all real/effective/saved GIDs and UIDs to `jw:jw`,
clears retained capabilities, then reapplies no-dump, no-new-privileges, and zero-core limits. The
parent verifies empty inheritable/permitted/effective/ambient capability sets. Wallet-RPC never runs
as root.
The audited snapshot directory is `/var/backups/wowngeon/wallets`, `root:root` mode `0700`; each
snapshot is `root:root` mode `0600` with one link. Its parent is `/var/backups/wowngeon`,
`root:postgres` mode `0710`. Any deviation refuses the proof.
## Review and synthetic verification
Before any future host ceremony, review the helper and run the disposable fixture locally:
```bash
python3 -m py_compile scripts/deploy/wallet-keyfile-recovery-proof.py \
scripts/deploy/wallet-keyfile-recovery-fixture-test.py
python3 scripts/deploy/wallet-keyfile-recovery-fixture-test.py
```
The fixture uses only generated temporary files and synthetic credentials. It covers exact argument
selection and wiping, indirect or duplicate password refusal, network mismatch, link and metadata
failures, source mutation during copy, byte equality, cleanup containment, Digest authentication
shape, candidate config shape, privilege dropping, fixed-message spend signing/verification,
watch-only refusal, and proof-port collision. It never opens a real wallet.
Review must also confirm that the executable still contains only read-only `systemctl is-active` and
`systemctl show` calls; only the `get_address`, `sign`, and `verify` wallet RPC methods; no
caller-controlled paths, ports, or messages; and only the two status strings as runtime output. The
address and signature are never output or retained as ceremony evidence.
## Future authorized ceremony
Run Monero stagenet first. Wownero mainnet remains an offline keyfile-open proof only and must not be
treated as approval to enable payouts or submit a transaction.
Only after a reviewed revision is explicitly approved for installation should an operator install
that exact file to a root-owned, non-writable path such as
`/usr/local/sbin/wowngeon-wallet-keyfile-recovery-proof` with owner `root:root` and mode `0700`.
Do not run it from a mutable checkout. Record the reviewed source revision, but do not record wallet
bytes, addresses, passwords, command lines, Digest material, wallet-derived hashes, or RPC bodies.
Before each profile:
1. Confirm no debugger, tracing wrapper, shell xtrace, terminal recorder, or broad process collector
will observe the root helper.
2. Confirm the expected proof port is not reserved for another operation and the exact fixed `/run`
proof directory does not exist.
3. Confirm the legacy unit is expected to remain online and unchanged for the short proof window.
4. Invoke the installed helper as root with exactly one fixed profile argument and capture only its
one-line status.
The future invocations are:
```bash
sudo /usr/local/sbin/wowngeon-wallet-keyfile-recovery-proof monero-stagenet
sudo /usr/local/sbin/wowngeon-wallet-keyfile-recovery-proof wownero-mainnet
```
These commands are examples for a later authorized ceremony, not evidence they have been run.
After each invocation, verify only that its fixed proof port is closed and its fixed proof directory
is absent. A pass is invalid if cleanup did not complete; the helper reports refusal in that case.
## Refusal or interrupted execution
`result=refused` is intentionally non-diagnostic. Do not weaken the helper or print an exception to
learn why. Review the fixed-path metadata, expected unit state, listener ownership, port availability,
and synthetic suite through non-secret checks, then arrange a new reviewed ceremony.
If a fixed proof directory remains after interruption, **do not blindly remove it**. Treat it as
potentially containing wallet material. First establish through metadata-only checks that no proof
wallet-RPC process or proof-port listener remains, keep the incident off shared logs, and have an
authorized operator review cleanup of that exact path. Never use a recursive command against `/run`
or a variable/glob target.
The next independent backup milestone is a human-approved seed-custody inventory followed by a
restore from genuinely off-host media into an isolated environment. That is the evidence needed to
answer “do we have recoverable wallet seeds?”; this helper deliberately cannot answer it.