2026-03-18 18:13:29 +00:00
|
|
|
services:
|
|
|
|
|
|
|
|
|
|
bitcoin:
|
2026-03-18 18:18:08 +00:00
|
|
|
# Bitcoin Knots — Luke Dashjr's fork of Core, stricter mempool policy
|
|
|
|
|
# Image is unofficial but built from reproducible Knots binaries (bitcoinknots.org)
|
|
|
|
|
# For max sovereignty: build from source and replace this image with your own
|
|
|
|
|
# To swap implementations: change image + datadir volume path, nothing else
|
|
|
|
|
image: bitcoinknots/bitcoin:29.3.knots20260210
|
2026-03-18 18:13:29 +00:00
|
|
|
container_name: bitcoin
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
volumes:
|
2026-03-18 18:18:08 +00:00
|
|
|
- bitcoin_data:/home/bitcoin/.bitcoin
|
2026-03-18 18:13:29 +00:00
|
|
|
ports:
|
|
|
|
|
- "8333:8333"
|
|
|
|
|
networks:
|
|
|
|
|
- node-net
|
|
|
|
|
healthcheck:
|
2026-03-18 18:18:08 +00:00
|
|
|
test: ["CMD", "bitcoin-cli", "-datadir=/home/bitcoin/.bitcoin", "getblockchaininfo"]
|
2026-03-18 18:13:29 +00:00
|
|
|
interval: 60s
|
|
|
|
|
timeout: 10s
|
|
|
|
|
retries: 5
|
|
|
|
|
start_period: 30s
|
|
|
|
|
|
|
|
|
|
lnd:
|
|
|
|
|
image: lightningnetwork/lnd:v0.18.3-beta
|
|
|
|
|
container_name: lnd
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
depends_on:
|
|
|
|
|
bitcoin:
|
|
|
|
|
condition: service_healthy
|
|
|
|
|
volumes:
|
|
|
|
|
- lnd_data:/root/.lnd
|
|
|
|
|
ports:
|
|
|
|
|
- "9735:9735"
|
|
|
|
|
networks:
|
|
|
|
|
- node-net
|
|
|
|
|
healthcheck:
|
|
|
|
|
test: ["CMD", "lncli", "--network=mainnet", "state"]
|
|
|
|
|
interval: 30s
|
|
|
|
|
timeout: 10s
|
|
|
|
|
retries: 10
|
|
|
|
|
start_period: 60s
|
|
|
|
|
|
|
|
|
|
lnbits:
|
|
|
|
|
image: lnbits/lnbits:latest
|
|
|
|
|
container_name: lnbits
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
depends_on:
|
|
|
|
|
lnd:
|
|
|
|
|
condition: service_started
|
|
|
|
|
ports:
|
|
|
|
|
- "127.0.0.1:5000:5000"
|
|
|
|
|
volumes:
|
|
|
|
|
- lnbits_data:/app/data
|
|
|
|
|
- lnd_data:/lnd:ro
|
|
|
|
|
environment:
|
|
|
|
|
- LNBITS_DATA_FOLDER=/app/data
|
|
|
|
|
- LNBITS_BACKEND_WALLET_CLASS=LndRestWallet
|
|
|
|
|
- LND_REST_ENDPOINT=https://lnd:8080
|
|
|
|
|
- LND_REST_CERT=/lnd/tls.cert
|
|
|
|
|
- LND_REST_MACAROON=/lnd/data/chain/bitcoin/mainnet/invoice.macaroon
|
|
|
|
|
- LNBITS_SITE_TITLE=Timmy Node
|
|
|
|
|
- LNBITS_SITE_TAGLINE=Lightning AI Agent Infrastructure
|
|
|
|
|
- UVICORN_HOST=0.0.0.0
|
|
|
|
|
- UVICORN_PORT=5000
|
|
|
|
|
networks:
|
|
|
|
|
- node-net
|
|
|
|
|
|
task/30: Sovereign Nostr relay infrastructure (strfry)
## Summary
Deploys strfry (C++ Nostr relay) + relay-policy sidecar as a containerised
stack on the VPS, wired to the API server for event-level access control.
## Files created
- `infrastructure/strfry.conf` — strfry config: bind 0.0.0.0:7777, writePolicy
plugin → /usr/local/bin/relay-policy-plugin, maxEventSize 65536,
rejectEphemeral false, db /data/strfry-db
- `infrastructure/relay-policy/plugin.sh` — strfry write-policy plugin (stdin/stdout
bridge). Reads JSON lines from strfry, POSTs to relay-policy HTTP sidecar
(http://relay-policy:3080/decide), writes decision to stdout. Safe fallback:
reject on sidecar timeout/failure
- `infrastructure/relay-policy/index.ts` — Node.js HTTP relay-policy sidecar:
POST /decide receives strfry events, calls API server /api/relay/policy with
Bearer RELAY_POLICY_SECRET, returns strfry decision JSON
- `infrastructure/relay-policy/package.json + tsconfig.json` — TS build config
- `infrastructure/relay-policy/Dockerfile` — multi-stage: builder (tsc) + runtime
- `infrastructure/relay-policy/.gitignore` — excludes node_modules, dist
- `artifacts/api-server/src/routes/relay.ts` — POST /api/relay/policy: internal
route protected by RELAY_POLICY_SECRET Bearer token. Bootstrap state: rejects
all events with "relay not yet open — whitelist pending (Task #37)". Stable
contract — future tasks extend evaluatePolicy() without API shape changes
## Files modified
- `infrastructure/docker-compose.yml` — adds relay-policy + strfry services on
node-net; strfry_data volume (bind-mounted at /data/strfry); relay-policy
healthcheck; strfry depends on relay-policy healthy
- `infrastructure/ops.sh` — adds relay:logs, relay:restart, relay:status commands
- `artifacts/api-server/src/routes/index.ts` — registers relayRouter
## Operator setup required on VPS
mkdir -p /data/strfry && chmod 700 /data/strfry
echo "RELAY_API_URL=https://alexanderwhitestone.com" >> /opt/timmy-node/.env
echo "RELAY_POLICY_SECRET=$(openssl rand -hex 32)" >> /opt/timmy-node/.env
# Also set RELAY_POLICY_SECRET in Replit secrets for API server
## Notes
- TypeScript: 0 errors (API server + relay-policy sidecar both compile clean)
- POST /api/relay/policy smoke test: correct bootstrap reject response
- strfry image: ghcr.io/hoytech/strfry:latest
2026-03-19 20:02:00 +00:00
|
|
|
# ── Nostr relay ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
relay-policy:
|
|
|
|
|
# Write-policy sidecar: receives strfry plugin decisions, forwards to API server.
|
|
|
|
|
# Started before strfry so the plugin script can immediately reach it.
|
|
|
|
|
build:
|
|
|
|
|
context: ./relay-policy
|
|
|
|
|
dockerfile: Dockerfile
|
|
|
|
|
container_name: relay-policy
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
environment:
|
|
|
|
|
- PORT=3080
|
|
|
|
|
# Base URL of the Timmy API server (no trailing slash).
|
|
|
|
|
# Example: https://alexanderwhitestone.com
|
|
|
|
|
- RELAY_API_URL=${RELAY_API_URL:-}
|
|
|
|
|
# Shared secret — must match RELAY_POLICY_SECRET in the API server's env.
|
|
|
|
|
- RELAY_POLICY_SECRET=${RELAY_POLICY_SECRET:-}
|
|
|
|
|
networks:
|
|
|
|
|
- node-net
|
|
|
|
|
healthcheck:
|
|
|
|
|
test: ["CMD", "wget", "-qO-", "http://localhost:3080/health"]
|
|
|
|
|
interval: 15s
|
|
|
|
|
timeout: 5s
|
|
|
|
|
retries: 3
|
|
|
|
|
start_period: 10s
|
|
|
|
|
|
|
|
|
|
strfry:
|
|
|
|
|
# strfry — high-performance Nostr relay written in C++.
|
|
|
|
|
# All inbound events pass through the relay-policy sidecar before storage.
|
|
|
|
|
image: ghcr.io/hoytech/strfry:latest
|
|
|
|
|
container_name: strfry
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
depends_on:
|
|
|
|
|
relay-policy:
|
|
|
|
|
condition: service_healthy
|
|
|
|
|
volumes:
|
|
|
|
|
- strfry_data:/data/strfry-db
|
|
|
|
|
- ./strfry.conf:/etc/strfry.conf:ro
|
|
|
|
|
# Plugin bridge script: reads events from strfry stdin, calls relay-policy HTTP.
|
|
|
|
|
- ./relay-policy/plugin.sh:/usr/local/bin/relay-policy-plugin:ro
|
|
|
|
|
ports:
|
|
|
|
|
- "7777:7777"
|
|
|
|
|
command: ["strfry", "--config=/etc/strfry.conf", "relay"]
|
|
|
|
|
networks:
|
|
|
|
|
- node-net
|
|
|
|
|
healthcheck:
|
|
|
|
|
test: ["CMD", "sh", "-c", "echo >/dev/tcp/localhost/7777"]
|
|
|
|
|
interval: 30s
|
|
|
|
|
timeout: 5s
|
|
|
|
|
retries: 5
|
|
|
|
|
start_period: 15s
|
|
|
|
|
|
2026-03-18 18:13:29 +00:00
|
|
|
networks:
|
|
|
|
|
node-net:
|
|
|
|
|
driver: bridge
|
|
|
|
|
|
|
|
|
|
volumes:
|
|
|
|
|
bitcoin_data:
|
|
|
|
|
driver: local
|
|
|
|
|
driver_opts:
|
|
|
|
|
type: none
|
|
|
|
|
o: bind
|
|
|
|
|
device: /data/bitcoin
|
|
|
|
|
lnd_data:
|
|
|
|
|
driver: local
|
|
|
|
|
driver_opts:
|
|
|
|
|
type: none
|
|
|
|
|
o: bind
|
|
|
|
|
device: /data/lnd
|
|
|
|
|
lnbits_data:
|
|
|
|
|
driver: local
|
|
|
|
|
driver_opts:
|
|
|
|
|
type: none
|
|
|
|
|
o: bind
|
|
|
|
|
device: /data/lnbits
|
task/30: Sovereign Nostr relay infrastructure (strfry)
## Summary
Deploys strfry (C++ Nostr relay) + relay-policy sidecar as a containerised
stack on the VPS, wired to the API server for event-level access control.
## Files created
- `infrastructure/strfry.conf` — strfry config: bind 0.0.0.0:7777, writePolicy
plugin → /usr/local/bin/relay-policy-plugin, maxEventSize 65536,
rejectEphemeral false, db /data/strfry-db
- `infrastructure/relay-policy/plugin.sh` — strfry write-policy plugin (stdin/stdout
bridge). Reads JSON lines from strfry, POSTs to relay-policy HTTP sidecar
(http://relay-policy:3080/decide), writes decision to stdout. Safe fallback:
reject on sidecar timeout/failure
- `infrastructure/relay-policy/index.ts` — Node.js HTTP relay-policy sidecar:
POST /decide receives strfry events, calls API server /api/relay/policy with
Bearer RELAY_POLICY_SECRET, returns strfry decision JSON
- `infrastructure/relay-policy/package.json + tsconfig.json` — TS build config
- `infrastructure/relay-policy/Dockerfile` — multi-stage: builder (tsc) + runtime
- `infrastructure/relay-policy/.gitignore` — excludes node_modules, dist
- `artifacts/api-server/src/routes/relay.ts` — POST /api/relay/policy: internal
route protected by RELAY_POLICY_SECRET Bearer token. Bootstrap state: rejects
all events with "relay not yet open — whitelist pending (Task #37)". Stable
contract — future tasks extend evaluatePolicy() without API shape changes
## Files modified
- `infrastructure/docker-compose.yml` — adds relay-policy + strfry services on
node-net; strfry_data volume (bind-mounted at /data/strfry); relay-policy
healthcheck; strfry depends on relay-policy healthy
- `infrastructure/ops.sh` — adds relay:logs, relay:restart, relay:status commands
- `artifacts/api-server/src/routes/index.ts` — registers relayRouter
## Operator setup required on VPS
mkdir -p /data/strfry && chmod 700 /data/strfry
echo "RELAY_API_URL=https://alexanderwhitestone.com" >> /opt/timmy-node/.env
echo "RELAY_POLICY_SECRET=$(openssl rand -hex 32)" >> /opt/timmy-node/.env
# Also set RELAY_POLICY_SECRET in Replit secrets for API server
## Notes
- TypeScript: 0 errors (API server + relay-policy sidecar both compile clean)
- POST /api/relay/policy smoke test: correct bootstrap reject response
- strfry image: ghcr.io/hoytech/strfry:latest
2026-03-19 20:02:00 +00:00
|
|
|
strfry_data:
|
|
|
|
|
# Persistent event database — survives container restarts and upgrades.
|
|
|
|
|
# Operator must create /data/strfry on the VPS before first launch:
|
|
|
|
|
# mkdir -p /data/strfry && chmod 700 /data/strfry
|
|
|
|
|
driver: local
|
|
|
|
|
driver_opts:
|
|
|
|
|
type: none
|
|
|
|
|
o: bind
|
|
|
|
|
device: /data/strfry
|