Hardened systemd deployment contract for read-only Monero observer
This commit is contained in:
Executable
+50
@@ -0,0 +1,50 @@
|
||||
# Monero observer service (single Linux host)
|
||||
|
||||
This is a supervised **observation-only** process for a dedicated Linux host. It is not a public API, deposit-address service, credit worker, withdrawal signer, or production-launch approval. It requires an independently deployed PostgreSQL instance and a private TLS 1.3 mTLS Wallet-RPC gateway. Do not point it at public Wallet RPC or expose either dependency to the Internet.
|
||||
|
||||
## Deployment prerequisites
|
||||
|
||||
- Linux x86-64 with systemd, Node.js 24, a dedicated `wikideck-observer` user/group, and a reviewed immutable application release installed read-only at `/opt/wikideck/current`.
|
||||
- A production PostgreSQL database reachable only on a private network, with verified TLS, encryption at rest, restricted administrative access, encrypted backups and a tested restore. Do not reuse the local sandbox database or runtime credential.
|
||||
- A mainnet Monero full node and watch-only Wallet RPC behind the already-tested mTLS method-allowlisting gateway. Network policy/firewall must permit the observer host to reach only the intended PostgreSQL and gateway addresses/ports. The gateway must not expose Wallet RPC directly.
|
||||
- Protected wallet observer/mTLS JSON and credential files owned by `wikideck-observer`, mode `0600`. Keep the mTLS private key and PostgreSQL URL out of shell history, process arguments, environment values, source control and logs. Store host disks and backups encrypted; define and test certificate rotation before use.
|
||||
- The chosen wallet is independently verified as the intended mainnet watch-only wallet; its spending key is not present on this host. A config endpoint match is not proof that the remote service is the intended wallet.
|
||||
|
||||
The example JSON files in this directory are templates only. Copy them to `/etc/wikideck/monero-observer/`, replace the gateway hostname, and install the matching client certificate/key and gateway CA there. The endpoint in both configs must be identical and the certificate chain must be checked for validity dates, key strength and SHA-2 signatures. The application's certificate loader requires owner-only POSIX file permissions.
|
||||
|
||||
## One-time database preparation
|
||||
|
||||
Use the dedicated schema-owner PostgreSQL credential file and CA file in a restricted operator session. The operation below is explicit, transactional, digest-checked and idempotent; it does **not** create the observer login, add a runtime grant, or enable deposits. Do not run it against a database with unrelated or valuable data before reviewing its schema and backup.
|
||||
|
||||
```sh
|
||||
WIKIDECK_NETWORK=mainnet \
|
||||
WIKIDECK_CUSTODY_PG_URL_FILE=/secure/operator/schema-owner-url \
|
||||
WIKIDECK_CUSTODY_PG_CA_FILE=/secure/operator/postgres-ca.crt \
|
||||
WIKIDECK_MAINNET_SCHEMA_MIGRATION=explicit-schema-migration-only \
|
||||
node --experimental-strip-types scripts/custody-db.ts migrate-mainnet-schema
|
||||
```
|
||||
|
||||
Have the database administrator create a separate `LOGIN` role with no administrative attributes and no role memberships, and place its TLS PostgreSQL URL in `postgres-url`. Then issue only the observer grants using the schema-owner credential:
|
||||
|
||||
```sh
|
||||
WIKIDECK_NETWORK=mainnet \
|
||||
WIKIDECK_CUSTODY_PG_URL_FILE=/secure/operator/schema-owner-url \
|
||||
WIKIDECK_CUSTODY_PG_CA_FILE=/secure/operator/postgres-ca.crt \
|
||||
node --experimental-strip-types scripts/custody-db.ts grant-monero-observer-runtime wikideck_observer
|
||||
```
|
||||
|
||||
The role name in the command must exactly match the separately provisioned login. The worker role cannot read journals or balances, allocate addresses, post credits/releases, or submit transfers. Restrict the schema-owner credential to this one-time operation and remove it from the host afterward. `custody-db` continues to refuse all other mainnet financial actions.
|
||||
|
||||
## Install and operate
|
||||
|
||||
Install the service unit from this directory at `/etc/systemd/system/wikideck-monero-observer.service`. Create `/etc/wikideck/monero-observer/` with owner `wikideck-observer` and mode `0700`. Install the two runtime JSON files and all referenced certificates/keys plus `postgres-url` and `postgres-ca.crt`, each owned by that user and mode `0600`. The PostgreSQL URL must use the observer role and must not contain TLS downgrade parameters. Ensure the `current` application release is root-owned and not writable by the service account.
|
||||
|
||||
After reviewing the unit against the host's systemd version, reload systemd and start the unit. Confirm the process is running and that logs contain only bounded scan aggregates; logs intentionally omit wallet addresses, transaction IDs, amounts and credentials. Configure an alert outside this host for service exits, sustained scan lag, database/gateway unavailability, WAL/disk pressure and unexpected worker restarts. The worker itself does not deliver alerts.
|
||||
|
||||
The service uses a PostgreSQL session advisory lock so a second instance cannot scan concurrently. SIGTERM/SIGINT stops its polling loop and releases the lock. A failed RPC/database batch is retried; a failed scan does not advance the durable cursor. Logs are available through the system journal under identifier `wikideck-monero-observer`.
|
||||
|
||||
## Release boundary
|
||||
|
||||
This deployment only persists read-only chain observations and scan progress. It does **not** issue customer deposit addresses, expose an address to a user, calculate customer credit, release pending liabilities, enable marketplace checkout, or move XMR. There is no public endpoint or UI connected to these rows. A scan is not proof of solvency or authorization to accept deposits.
|
||||
|
||||
The repository still lacks a production Monero daemon/Wallet-RPC/gateway deployment, certificate and secret lifecycle automation, nonempty owned-output validation against the deployed topology, external monitoring/restore evidence, a reviewed customer deposit/withdrawal policy, and live settlement integration. The mainnet candidate is independently marked NO-GO and its current candidate genesis time is 2027-01-01; deploying this worker does not clear any launch gate. The service file is a starting deployment contract, not a certification or instruction to accept funds.
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"url": "https://monero-rpc.internal.example/json_rpc",
|
||||
"clientCertificateFile": "/etc/wikideck/monero-observer/client.crt",
|
||||
"clientPrivateKeyFile": "/etc/wikideck/monero-observer/client.key",
|
||||
"serverCaFile": "/etc/wikideck/monero-observer/gateway-ca.crt"
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"network": "mainnet",
|
||||
"url": "https://monero-rpc.internal.example/json_rpc"
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
[Unit]
|
||||
Description=Wikideck read-only Monero mainnet observation worker
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=wikideck-observer
|
||||
Group=wikideck-observer
|
||||
WorkingDirectory=/opt/wikideck/current
|
||||
Environment=WIKIDECK_MONERO_OBSERVER_WORKER=1
|
||||
Environment=WIKIDECK_MONERO_OBSERVER=1
|
||||
Environment=WIKIDECK_MONERO_WALLET_RPC_CONFIG_FILE=/etc/wikideck/monero-observer/wallet-rpc.json
|
||||
Environment=WIKIDECK_MONERO_MTLS_CONFIG_FILE=/etc/wikideck/monero-observer/mtls.json
|
||||
Environment=WIKIDECK_CUSTODY_PG_URL_FILE=/etc/wikideck/monero-observer/postgres-url
|
||||
Environment=WIKIDECK_CUSTODY_PG_CA_FILE=/etc/wikideck/monero-observer/postgres-ca.crt
|
||||
Environment=WIKIDECK_MONERO_OBSERVER_POLL_MS=30000
|
||||
ExecStart=/usr/bin/node --experimental-strip-types /opt/wikideck/current/scripts/monero-observer-worker.ts
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
TimeoutStopSec=40s
|
||||
KillSignal=SIGTERM
|
||||
UMask=0077
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=wikideck-monero-observer
|
||||
|
||||
NoNewPrivileges=true
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectControlGroups=true
|
||||
ProtectClock=true
|
||||
ProtectHostname=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
RestrictNamespaces=true
|
||||
LockPersonality=true
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user