@bitmacro/relay-agent
v0.2.9
Published
REST API agent for managing Nostr relays without touching the terminal
Maintainers
Readme
@bitmacro/relay-agent

→ Web UI: relay-panel.bitmacro.io
→ BitMacro Ecosystem: bitmacro.io
Manage your Nostr relay without touching the terminal.
relay-agent is a REST API agent that runs on your relay server and translates HTTP requests into strfry CLI commands. It is part of the BitMacro Relay Manager ecosystem.
| Project | Description | License |
|---------|-------------|---------|
| relay-agent | This repo — REST API for strfry | MIT |
| @bitmacro/relay-connect | BitMacro Connect SDK (NIP-46 / NIP-07) | MIT |
| relay-connect-web | Connect UI + /signer proxy (Next.js) | MIT |
| relay-api | Central hub (Supabase, proxy) | Private |
| relay-panel | Relay management UI | BSL 1.1 |
Quick Start
Via npx
npx @bitmacro/relay-agent --port 7800 --token your-secret-tokenUse --version or --help to check version or CLI options.
Via Docker
Multi-arch image (amd64, arm64) at ghcr.io/bitmacro/relay-agent. Includes strfry binary. Mount your strfry data volume:
docker pull ghcr.io/bitmacro/relay-agent:latest
docker run -p 7800:7800 \
-e RELAY_AGENT_TOKEN=your-secret-token \
-v /path/to/strfry-db:/app/strfry-db \
-v /path/to/whitelist.txt:/app/whitelist.txt \
ghcr.io/bitmacro/relay-agent:latestOr build locally: docker build -t relay-agent .
Multiple relays (v0.2): One agent, N relays via RELAY_INSTANCES. Use docker-compose.relay-agent.yml (fragment) or docker-compose.yml (standalone).
Server deployment (v0.2 multi-relay)
# 1. Clone relay-agent into a subdir next to your docker-compose.yml
git clone https://github.com/bitmacro/relay-agent.git relay-agent
# 2. Configure .env (single token for all relays)
echo "RELAY_AGENT_TOKEN=your-secret-token" >> .env
# 3. Build and start (requires relay_private, relay_public, relay_paid, network bitmacro in parent compose)
docker compose -f docker-compose.yml -f relay-agent/docker-compose.relay-agent.yml build relay-agent
docker compose -f docker-compose.yml -f relay-agent/docker-compose.relay-agent.yml up -d relay-agentPrerequisites: nostr/{public,private,paid}/ must have strfry.conf, whitelist.txt, data/.
Operational Commands
v0.2 multi-relay
# Build and start
docker compose -f docker-compose.yml -f relay-agent/docker-compose.relay-agent.yml up -d --build relay-agent
# View logs
docker compose -f docker-compose.yml -f relay-agent/docker-compose.relay-agent.yml logs -f relay-agent
# Stop
docker compose -f docker-compose.yml -f relay-agent/docker-compose.relay-agent.yml stop relay-agentStandalone (from relay-agent dir)
cd relay-agent && docker compose up -dSmoke Test
# v0.2: health lists relay IDs and version
curl http://localhost:7810/health
# {"status":"ok","version":"0.2.7","relayIds":["public","private","paid"],...}
# Per-relay: GET /:relayId/health includes the same package version field.
# v0.2: stats for a specific relay (replace TOKEN and relay id)
curl -H "Authorization: Bearer TOKEN" http://localhost:7810/private/statsREST API Endpoints
v0.2 multi-relay (RELAY_INSTANCES set)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | /health | List active relay IDs | no |
| GET | /:relayId/health | Health for relay | no |
| GET | /:relayId/stats | Relay statistics | Bearer |
| GET | /:relayId/events | List events | Bearer |
| DELETE | /:relayId/events/:id | Delete event | Bearer |
| GET | /:relayId/policy | Policy entries | Bearer |
| GET | /:relayId/policy/blocked | List blocked pubkeys (! lines in whitelist) | Bearer |
| POST | /:relayId/policy/block | Block pubkey | Bearer |
| POST | /:relayId/policy/allow | Allow pubkey ({ "pubkey", "label"? }) | Bearer |
| DELETE | /:relayId/policy/allow/:pubkey | Remove pubkey from allow list (plain line) | Bearer |
| DELETE | /:relayId/policy/block/:pubkey | Remove !pubkey block line | Bearer |
| GET | /:relayId/users | List pubkeys | Bearer |
relayId = logical ID from RELAY_INSTANCES (e.g. public, private, paid). Must match agent_relay_id in relay_configs.
v0.1 single-relay (no RELAY_INSTANCES)
| Method | Path | Description |
|--------|------|-------------|
| GET | /health | Health check |
| GET | /stats, /events, /policy, /policy/blocked, /users | Same as above, no prefix |
| DELETE | /policy/allow/:pubkey, /policy/block/:pubkey | Remove allow / block line (same whitelist file) |
Query parameters for GET /events
| Param | Type | Description |
|-------|------|-------------|
| kinds | comma-separated | e.g. 1,3 |
| authors | comma-separated | pubkeys |
| since | unix timestamp | |
| until | unix timestamp | |
| limit | number | max events to return |
Authentication
All endpoints except /health require:
Authorization: Bearer <your-token>API Roadmap
Alta prioridade (esta PR)
- [x]
DELETE /:relayId/policy/allow/:pubkey - [x]
DELETE /:relayId/policy/block/:pubkey - [x]
GET /:relayId/policy/blocked
Média prioridade
- [ ]
DELETE /:relayId/events— apagar eventos por filtro{ kinds?, authors?, since?, until? } - [ ]
GET /:relayId/events/count— contagem sem payload completo - [ ]
POST /:relayId/policy/allow/batch— body{ pubkeys: string[] }, máx. 100
v1.0 Lightning
- [ ]
GET /:relayId/subscribers— lista pubkeys com expiração de acesso pago - [ ]
POST /:relayId/invoice— gerar invoice LNbits para nova subscrição
Environment Variables
v0.2 multi-relay
| Variable | Default | Description |
|----------|---------|-------------|
| RELAY_INSTANCES | — | JSON array of {id, token, strfryConfig, strfryDb, whitelistPath?} — see strfry.conf vs relay-agent |
| RELAY_AGENT_TOKEN | — | Not used when RELAY_INSTANCES is set |
| STRFRY_BIN | strfry | Path to strfry binary |
| PORT | 7800 | HTTP server port |
| ALLOWED_ORIGINS | — | Comma-separated extra CORS origins |
strfry.conf vs relay-agent (v0.2)
The agent runs strfry with cwd = dirname(strfryDb). The db = … line in strfry.conf is resolved relative to that directory.
- In each relay’s
strfry.conf, use:db = "./data/" - In
RELAY_INSTANCES, setstrfryDbto the LMDB directory inside the agent container, e.g.:
where"/app/nostr/<relayId>/data"<relayId>matches your logical id (public,private,paid). Mount the host folder to that path (seedocker-compose.relay-agent.yml).
Then ./data/ resolves to /app/nostr/<relayId>/data — the same files the relay uses if the host nostr/<relayId>/data is mounted consistently.
Relay containers (strfry relay) often use working_dir: /app and mount the same host data/ at /app/data, still with db = "./data/". That is a different in-container path but the same host directory as the agent mount.
Breaking change: Older setups used db = "./strfry-db/" and strfryDb ending in strfry-db. Migrate to db = "./data/" and strfryDb ending in /data, and align Docker volume targets on both the agent and relay services.
v0.1 single-relay
| Variable | Default | Description |
|----------|---------|-------------|
| RELAY_AGENT_TOKEN | — | Required. Bearer token for API auth |
| STRFRY_BIN | strfry | Path to strfry binary |
| STRFRY_DB_PATH | ./strfry-db | Path to strfry database directory |
| STRFRY_CONFIG | — | Path to strfry config file |
| WHITELIST_PATH | /etc/strfry/whitelist.txt | Path to whitelist file |
| PORT | 7800 | HTTP server port |
| ALLOWED_ORIGINS | — | Comma-separated extra CORS origins |
Compatibility
| relay-agent | strfry | Mode | |-------------|--------|------| | 0.1.x | 1.0.x | Single-relay | | 0.2.x | 1.0.x | Multi-relay (RELAY_INSTANCES) |
Architecture
relay-panel
│ HTTP + JWT
▼
relay-api (Vercel)
│ HTTP REST + Bearer JWT
▼
relay-agent (this repo)
│ child_process spawn()
▼
strfry (local C++ process / LMDB)The relay-agent is stateless — it has no database. State lives in Supabase, managed by relay-api. The relay-agent only translates HTTP calls into strfry CLI commands.
Troubleshooting
503 "relay unavailable"
Capture the error — run logs in one terminal, then curl in another:
# v0.2 docker compose -f docker-compose.yml -f relay-agent/docker-compose.relay-agent.yml logs -f relay-agent curl -H "Authorization: Bearer TOKEN" "http://localhost:7810/private/events?limit=3"The strfry stderr will appear in the logs.
LMDB "Resource temporarily unavailable" — relay and relay-agent share the same db. Increase
maxreadersin your relay's strfry.conf (e.g../nostr/private/strfry.conf):dbParams { maxreaders = 512 }Then restart the relay:
docker restart relay_privateStale reader slots (after crashes / SIGABRT) can still wedge the relay until cleared. Stop every container that mounts that LMDB directory (relay_*+relay-agent), then on the host (path = host dir that containsdata.mdb):# Debian/Ubuntu: lmdb-utils provides mdb_stat — not mdb_reader_check sudo mdb_stat -e -rr /home/server/nostr/public/dataDouble
-rchecks the reader table and removes stale entries. Then start the relay container(s), thenrelay-agent.Verify db path — relay-agent mounts
./nostr/private/data:/app/nostr/private/dataso it matches productionstrfry.confwithdb="./data/"(same layout asrelay_private). If you seemdb_env_open: No such file or directory, the mount path ordb=instrfry.confdoes not match. Check your maindocker-compose.yml:grep -A5 relay_private docker-compose.ymlTest strfry inside container (v0.2):
docker compose -f docker-compose.yml -f relay-agent/docker-compose.relay-agent.yml run --rm relay-agent sh -c 'ls -la /app/nostr/private/data && /app/strfry --config /app/nostr/private/strfry.conf scan "{}" | head -3'If
data.mdbis missing or strfry fails, fix the volume path.
Security
- Run on a private network. The relay-agent should run on the operator's server and never be exposed directly to the internet.
- Access is controlled by the relay-api, which proxies requests with a shared Bearer token.
- Use a strong, random token in production. Rotate it if compromised.
Contributing
See CONTRIBUTING.md for setup and PR guidelines.
Issues and PRs are welcome — bug fixes, UI improvements, and new relay-agent integrations.
This project is maintained by BitMacro.
Contributors
License
MIT. See LICENSE.
