Operations
monitoring, backups, updates.
Day-to-day work after go-live: what to monitor, backups and restore, key rotation, the sanctions list, updating to a new version and what to do in an incident. Paths follow the bare-metal layout; with Docker, prefix commands with docker compose -f deploy/docker-compose.yml exec server and use tools/ instead of scripts/.
01Monitoring
| What | How | Alert when |
|---|---|---|
| Liveness | GET https://pay.example.com/health from any uptime monitor | non-200, or a chain missing from chains |
| Service | systemctl status mikodes-checkout, journalctl -u mikodes-checkout | restarts, error lines |
| Start-up warnings | The first log lines list what is disabled (chains, operator fee, sanctions screening) | any warning you did not expect |
| Checks | Console → Status | any blocker; RPC, watcher lag or dead-letter warnings |
| Audit ledger | GET /v1/operator/audit → chainIntact | chainIntact: false |
| Disk, certificates | Free space on the data and backup volumes; Caddy's log | < 20 % free; renewal errors |
Do not paste the operator key into third-party monitoring services. Monitor /health and use the console for the rest.
02Backups and restore
SQLite holds everything: merchants, invoices, the fee-credit ledger, webhook log and the audit ledger. scripts/backup.mjs uses SQLite's online backup API (safe while the server runs), verifies each copy with PRAGMA integrity_check, writes it with mode 0600 and rotates old files. It also backs up the console's admin.sqlite.
pnpm run backup # uses apps/server/.env
node --env-file=/etc/mikodes-checkout/server.env scripts/backup.mjs --dir /var/backups/mikodes-checkout --keep 14
The systemd timer mikodes-backup.timer runs it daily at 03:17; the PM2 file includes a daily backup. Also keep: a copy off the machine, the env files (in a password manager, not next to the backups) and, with the console, ADMIN_SECRET_KEY together with admin.sqlite.
sudo systemctl stop mikodes-checkout
sudo -u mikodes cp /var/backups/mikodes-checkout/checkout-<stamp>.sqlite /var/lib/mikodes-checkout/checkout.sqlite
sudo rm -f /var/lib/mikodes-checkout/checkout.sqlite-wal /var/lib/mikodes-checkout/checkout.sqlite-shm
sudo systemctl start mikodes-checkout
After a restore, invoices paid after the backup are re-detected only while they are in their watch window (pending, or expired less than 24 hours ago). Reconcile older ones from the on-chain evidence. Practise a restore once before you need it.
03Rotating keys and secrets
| Secret | How | Effect |
|---|---|---|
OPERATOR_ADMIN_KEY | sudo node scripts/setup.mjs --target bare --force --rotate-admin-key --server-env /etc/mikodes-checkout/server.env --caddy-env /etc/mikodes-checkout/caddy.env, then restart | The old key stops working at restart. --force keeps every other value. |
| Merchant API key, webhook secret | Console → Merchants → merchant → rotate; or the merchant in the portal (Developers) | New value shown once; the old one stops working |
| RPC provider keys | Edit SOLANA_RPC_URL / BASE_RPC_URL, restart | None, if the new endpoint serves the same network |
| Fee addresses | Edit OPERATOR_SOLANA_ADDRESS / OPERATOR_BASE_ADDRESS, restart | Applies to new invoices only |
ADMIN_SECRET_KEY | Do not change it casually: a new key makes the stored console secrets unreadable. The setup script keeps it across re-runs. | Re-enter the Telegram token and SMTP URL after a change |
04Sanctions list
Screening is disabled until SANCTIONS_LIST_PATH points at a list; the server warns at every start. With a list, the payer is screened when a payment is first seen; a match marks the invoice flagged; a screener error fails closed (the invoice does not advance).
- Download the SDN XML yourselfFrom OFAC's Sanctions List Service (public domain). The converter never downloads anything.
- Convert
Check its exact arguments in the script header first, and review the first converted file by hand.node packages/core/scripts/ofac-sdn-to-json.mjs <sdn.xml> <YYYY-MM-DD> - Point the server at itSet
SANCTIONS_LIST_PATH(readable by themikodesuser) and restart; the list is loaded at start. - Refresh on a schedule
deploy/sanctions/refresh.shdownloads from the URL you set inSDN_XML_URL, converts, refuses an empty result, swaps the file atomically and restarts the service. Run it from root's crontab.
The OFAC list is a starting point. Wire your own screening provider if your jurisdiction requires more. Not verified refresh.sh was not run on a schedule by MIKODES.
05Updating to a new version
- Read the changelogChangelog.
- Back up
node --env-file=/etc/mikodes-checkout/server.env scripts/backup.mjs --dir /var/backups/mikodes-checkout - Replace the code and rebuildUnpack the new package over a copy of the old folder, keeping your env files, then:
cd /opt/mikodes-checkout sudo -u mikodes -H pnpm install --frozen-lockfile sudo -u mikodes -H pnpm --filter "@mikodes-checkout/server..." build sudo -u mikodes -H pnpm --filter @mikodes-checkout/ops build sudo -u mikodes -H pnpm run build:web sudo systemctl restart mikodes-checkout curl -s http://127.0.0.1:8787/health - MigrationsRun automatically at start and are forward-only. To roll back, restore the backup from step 2 and the previous code, never just the code.
Docker: back up the server-data volume, then docker compose -f deploy/docker-compose.yml up -d --build.
06Incident checklist
RPC down or rate-limited
Invoices stay pending or confirming; wallet pay fails at "prepare" (/public/solana/prep/:id returns 502 rpc_error). Switch to a second provider and restart. Nothing is lost: payments land on-chain regardless, and the watcher finds them when the RPC is back, including invoices that expired less than 24 hours ago (recorded as late).
Webhook dead letters
Console → Webhook deliveries. Look at the recorded status or error (expired TLS certificate at the merchant, firewall, old secret). After the merchant fixes it, retry; receivers deduplicate on event.id.
Flagged payment
The funds are already at the merchant's address; the system is non-custodial and cannot reverse or freeze them. Tell the merchant not to deliver the order, keep the evidence (timeline, audit entry, explorer link) and follow your compliance procedure and legal advice.
Exchange pay missing for a merchant
Its fee credit is below the invoice fee. Top up (portal Fee credit, or Console → Merchants → merchant). Wallet pay keeps working meanwhile.
Audit chain broken
chainIntact: false means an audit row was changed outside the application. Copy the database as evidence, compare with the last intact backup, rotate the operator key and review host access.
Operator key leaked
Rotate it, restart, review the audit ledger and check every merchant's payout addresses and fees for changes you did not make.