Remove markets
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
FROM node:24-alpine
|
||||
RUN apk add --no-cache openssl
|
||||
COPY deploy/postgres/setup.mjs /setup.mjs
|
||||
CMD ["node", "/setup.mjs"]
|
||||
@@ -1,2 +0,0 @@
|
||||
FROM postgres:17-alpine
|
||||
COPY deploy/postgres/pg_hba.conf /etc/wikideck/pg_hba.conf
|
||||
@@ -1,8 +0,0 @@
|
||||
# Unix socket is for the official local bootstrap only; no database host port is published.
|
||||
local all all trust
|
||||
hostnossl all all all reject
|
||||
hostssl wikideck_custody postgres all scram-sha-256
|
||||
hostssl wikideck_custody custody_owner all scram-sha-256
|
||||
hostssl wikideck_custody custody_runtime all scram-sha-256
|
||||
hostssl wikideck_custody custody_backup all scram-sha-256
|
||||
host all all all reject
|
||||
@@ -1,61 +0,0 @@
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync, chownSync, openSync, fsyncSync, closeSync, renameSync } from 'node:fs';
|
||||
|
||||
// Local SANDBOX provisioning only. No secrets pass through logs, command arguments or source control.
|
||||
const roots = ['/secrets/admin', '/secrets/owner', '/secrets/runtime', '/secrets/backup', '/secrets/tls'];
|
||||
for (const root of roots) mkdirSync(root, { recursive: true, mode: 0o755 });
|
||||
const files = ['/secrets/admin/password', '/secrets/admin/url', '/secrets/owner/url', '/secrets/runtime/url',
|
||||
'/secrets/tls/server.key', '/secrets/tls/server.crt', '/secrets/owner/ca.crt', '/secrets/runtime/ca.crt', '/secrets/admin/ca.crt'];
|
||||
const backupFiles = ['/secrets/backup/url', '/secrets/backup/ca.crt'];
|
||||
const marker = '/secrets/admin/complete';
|
||||
const put = (file, text, mode = 0o600) => {
|
||||
writeFileSync(file, text, { flag: 'wx', mode }); chownSync(file, 1000, 1000);
|
||||
const fd = openSync(file, 'r'); try { fsyncSync(fd); } finally { closeSync(fd); }
|
||||
};
|
||||
const provisionBackupRole = () => {
|
||||
if (backupFiles.some(existsSync)) throw new Error('Partial backup-role secret initialization; preserve volumes for inspection');
|
||||
const backup = randomBytes(32).toString('hex');
|
||||
put('/secrets/backup/url', `postgresql://custody_backup:${backup}@database:5432/wikideck_custody`);
|
||||
put('/secrets/backup/ca.crt', readFileSync('/secrets/admin/ca.crt'), 0o644);
|
||||
};
|
||||
if (existsSync(marker)) {
|
||||
const version = readFileSync(marker, 'utf8');
|
||||
if (!['wikideck-local-pg-v1', 'wikideck-local-pg-v2'].includes(version) || files.some(f => !existsSync(f))) {
|
||||
throw new Error('Incomplete secret volumes; restore the matching volumes, never regenerate over an existing database');
|
||||
}
|
||||
execFileSync('openssl', ['x509', '-checkend', '86400', '-noout', '-in', '/secrets/tls/server.crt'], { stdio: 'ignore' });
|
||||
if (version === 'wikideck-local-pg-v1') {
|
||||
provisionBackupRole();
|
||||
const tempMarker = `${marker}.upgrade`;
|
||||
writeFileSync(tempMarker, 'wikideck-local-pg-v2', { flag: 'wx', mode: 0o644 });
|
||||
renameSync(tempMarker, marker);
|
||||
} else if (backupFiles.some(f => !existsSync(f))) {
|
||||
throw new Error('Backup-role secret volumes are incomplete; preserve existing database and inspect volumes');
|
||||
}
|
||||
console.log('Existing local sandbox credentials/certificate retained; read-only backup role available.');
|
||||
} else {
|
||||
if (files.some(existsSync) || backupFiles.some(existsSync)) throw new Error('Partial secret initialization; inspect preserved volumes before retrying');
|
||||
const admin = randomBytes(32).toString('hex'), owner = randomBytes(32).toString('hex'), runtime = randomBytes(32).toString('hex'), backup = randomBytes(32).toString('hex');
|
||||
// PostgreSQL's unprivileged entrypoint also reads POSTGRES_PASSWORD_FILE. This separate scoped
|
||||
// admin volume is never mounted into the application; the password file must be readable there.
|
||||
put('/secrets/admin/password', admin, 0o644);
|
||||
put('/secrets/admin/url', `postgresql://postgres:${admin}@database:5432/wikideck_custody`);
|
||||
put('/secrets/owner/url', `postgresql://custody_owner:${owner}@database:5432/wikideck_custody`);
|
||||
put('/secrets/runtime/url', `postgresql://custody_runtime:${runtime}@database:5432/wikideck_custody`);
|
||||
put('/secrets/backup/url', `postgresql://custody_backup:${backup}@database:5432/wikideck_custody`);
|
||||
execFileSync('openssl', ['req', '-x509', '-newkey', 'rsa:3072', '-sha256', '-nodes', '-days', '30',
|
||||
'-subj', '/CN=Wikideck local sandbox CA', '-keyout', '/tmp/ca.key', '-out', '/tmp/ca.crt',
|
||||
'-addext', 'basicConstraints=critical,CA:TRUE', '-addext', 'keyUsage=critical,keyCertSign,cRLSign'], { stdio: 'ignore' });
|
||||
execFileSync('openssl', ['req', '-new', '-newkey', 'rsa:3072', '-nodes', '-subj', '/CN=database',
|
||||
'-keyout', '/secrets/tls/server.key', '-out', '/tmp/server.csr'], { stdio: 'ignore' });
|
||||
writeFileSync('/tmp/extensions', 'subjectAltName=DNS:database\nbasicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth\n');
|
||||
execFileSync('openssl', ['x509', '-req', '-in', '/tmp/server.csr', '-CA', '/tmp/ca.crt', '-CAkey', '/tmp/ca.key',
|
||||
'-CAcreateserial', '-days', '30', '-sha256', '-extfile', '/tmp/extensions', '-out', '/secrets/tls/server.crt'], { stdio: 'ignore' });
|
||||
for (const root of ['admin', 'owner', 'runtime', 'backup']) put(`/secrets/${root}/ca.crt`, readFileSync('/tmp/ca.crt'), 0o644);
|
||||
// postgres:17-alpine runs as UID/GID 70. Private key never mounted into Wikideck or migrator.
|
||||
chownSync('/secrets/tls/server.key', 70, 70); chmodSync('/secrets/tls/server.key', 0o600);
|
||||
chmodSync('/secrets/tls/server.crt', 0o644);
|
||||
put(marker, 'wikideck-local-pg-v2', 0o644);
|
||||
console.log('Provisioned fresh local sandbox credentials and verified-TLS certificate; no secrets logged.');
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"network": "mainnet",
|
||||
"url": "https://monero-rpc.internal.example/json_rpc"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
[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