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

37 lines
1.3 KiB
Bash
Executable File

#!/usr/bin/env bash
##
## relay-policy-plugin — strfry write-policy plugin
##
## strfry starts this script once and feeds it JSON lines on stdin (one per
## event). The script forwards each line to the relay-policy HTTP sidecar and
## echoes the sidecar's JSON decision to stdout. If the sidecar is unavailable
## the event is rejected with a safe fallback so the relay does not accept
## unapproved events during a transient outage.
##
## stdin format: {"event":{...},"receivedAt":N,"sourceType":"...","sourceInfo":"..."}
## stdout format: {"id":"<event-id>","action":"accept|reject|shadowReject","msg":"..."}
##
RELAY_POLICY_URL="${RELAY_POLICY_URL:-http://relay-policy:3080/decide}"
while IFS= read -r line; do
# Extract event id for the fallback response — pure bash, no external tools.
event_id=$(printf '%s' "$line" \
| grep -o '"id":"[^"]*"' \
| head -1 \
| sed 's/"id":"//; s/"//')
decision=$(printf '%s' "$line" \
| curl -sf --max-time 5 \
-X POST "$RELAY_POLICY_URL" \
-H "Content-Type: application/json" \
--data-binary @- 2>/dev/null)
if [[ -z "$decision" ]]; then
printf '{"id":"%s","action":"reject","msg":"policy service unavailable"}\n' \
"${event_id:-unknown}"
else
printf '%s\n' "$decision"
fi
done