This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
alexpaynex cdd97922d5 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

64 lines
1.5 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
##
## strfry.conf — Timmy's sovereign Nostr relay
##
## All events pass through the relay-policy sidecar before being accepted.
## No event is stored without explicit approval from the API server.
##
db = "/data/strfry-db"
relay {
bind = "0.0.0.0"
port = 7777
info {
name = "Timmy Relay"
description = "Timmy's sovereign Nostr relay — whitelist-only"
pubkey = ""
contact = ""
icon = ""
}
# Maximum WebSocket payload size (bytes).
# 131072 = 128 KiB — generous for NIP-09 deletions but not abusable.
maxWebsocketPayloadSize = 131072
autoPingSeconds = 55
enableTcpKeepalive = false
queryTimesliceBudgetMicroseconds = 10000
maxFilterLimit = 500
maxSubsPerConnection = 20
writePolicy {
# Plugin receives JSON lines on stdin, writes decision lines to stdout.
# The plugin script bridges each event to the relay-policy HTTP sidecar.
plugin = "/usr/local/bin/relay-policy-plugin"
}
compression {
enabled = true
slidingWindow = true
}
logging {
dumpInAll = false
dumpInEvents = false
dumpInReqs = false
dbScanPerf = false
quiet = false
}
numThreads {
ingester = 3
reqWorker = 3
reqMonitor = 1
negentropy = 2
}
# Accept events up to 64 KiB — NIP-01 compliant maximum.
maxEventSize = 65536
# Allow ephemeral events (kinds 2000029999) — needed for NIP-04 DMs.
rejectEphemeral = false
}