Deno-based relay health monitor with NIP-66 event publishing.
@nostrwatch/relaymon monitors Nostr relay (WebSocket server) health through automated check cycles. It runs configurable check suites — DNS, connectivity, SSL, info document retrieval, and geo-location — against a seeded relay list, then publishes results as NIP-66 relay status events to the Nostr network. It can also publish opt-in Trusted Relay Assertions (kind 30385) using the draft TRA NIP shape from Letdown2491/trustedrelays. RelayMon TRA events are monitor-local assertions derived only from that monitor's own observation history and are signed by the same monitor key used for NIP-66 publishing. RelayMon does not ingest report/appeal events or manage trusted-provider declarations for this feature. Relays are discovered from multiple seed sources including static configuration, API endpoints, and existing NIP-66 events. Supports clearnet, Tor (.onion), and I2P (.i2p) relay monitoring via configurable network routing.
- Deno >=1.40
- SQLite is created automatically at the path specified in
config.yaml
Environment variables:
| Variable | Required | Description | Example |
|---|---|---|---|
RELAYMON_NSEC |
Yes | Private key for signing NIP-66 events and monitor announcements | nsec1... |
@nostrwatch/relaymon is a Deno application — no npm install step is required. Clone the monorepo and navigate to the app directory.
# Ensure Deno is installed: https://deno.land
cd apps/relaymon# Copy and edit the configuration file
cp config.sample.yaml config.yaml
# Edit config.yaml: set monitor.slug, publisher.relays, and relaymon network options
# Start monitoring
RELAYMON_NSEC=nsec1... deno task startAdditional tasks:
| Task | Description |
|---|---|
deno task start |
Start the monitoring loop |
deno task interactive |
Launch the interactive CLI |
deno task status |
Print a status report |
deno task dbcheck |
Run the database integrity check utility |
deno task compile:linux-x64 |
Compile a standalone binary for Linux (x64) |
Relaymon ships two Docker image variants:
| Image | Description |
|---|---|
nostrwatch/relaymon:clearnet |
Monitors clearnet relays only |
nostrwatch/relaymon:multinet |
Monitors clearnet, Tor (.onion), and I2P (.i2p) via Gluetun routing |
nostrwatch/relaymon:latest |
Alias for clearnet |
Production Docker Compose configurations are available in apps/docker-stacks.
# Clearnet (from Docker Hub)
docker compose -f apps/relaymon/.docker/docker-compose.yml up -d
# Multinet (from Docker Hub)
docker compose -f apps/relaymon/.docker/docker-compose.multinet.yml up -dThe multinet variant includes supplementary containers for network routing:
tor-proxy— Tor SOCKS proxy for.onionaddressesi2pd— I2P router for.i2paddresses
Traffic is routed transparently based on domain type with no application code changes needed.
Relaymon is configured via config.yaml. Copy config.sample.yaml as a starting point.
monitor:
slug: tor-i2p-clearnet-monitor
info:
name: "tor i2p clearnet relay monitor"
about: ""
nip05: ""
owner: ""
geo:
city: "Frankfurt am Main"
country: "Germany"
countryCode: "DE"
lat: 50.1169
lon: 8.6821
region: "Hesse"
continent: "Europe"
publisher:
relays:
- "wss://relay.nostr.watch"
- "wss://relaypag.es"
relaymon:
networks:
- clearnet
- tor
- i2p
retry:
expiry:
- { max: 3, delay: "1m" }
- { max: 5, delay: "20m" }
- { max: 7, delay: "1h" }
- { max: 22, delay: "24h" }
- { max: 107, delay: "7d" }
seed:
interval: "1m"
sources:
- config
- static
- api
- events
- db
options:
db:
path: "./relay.db"
enableWAL: true
static:
path: "./seed.yaml"
api:
remote: ""
checks:
enabled:
- open
- read
options:
expires: "6h"
interval: "5m"
timeout:
open: 30000
read: 5000
max: "100"
statusInterval: 20
trustedRelayAssertions:
enabled: false
relays: []
min_observations: 10
material_change_threshold: 3
refresh_interval: "1h"
history_retention: "30d"
max_observations_per_relay: 1000
publish_unreachable: true
publish_blocked: false
algorithm:
version: relaymon-local-v2
url: https://github.com/Letdown2491/trustedrelays/blob/main/ALGORITHM.md
queue:
workerConcurrency: 10Key configuration sections:
| Key | Description |
|---|---|
monitor.slug |
Unique identifier for this monitor instance |
publisher.relays |
Array of relay URLs for publishing NIP-66 events |
relaymon.networks |
Network types to monitor: clearnet, tor, i2p |
relaymon.retry.expiry |
Stepped retry backoff rules (max retries + delay per step) |
relaymon.seed.sources |
Where to discover relays: config, static, api, events, db |
relaymon.checks.enabled |
Which check types to run (e.g., open, read) |
relaymon.trustedRelayAssertions |
Optional kind 30385 Trusted Relay Assertion publishing controls |
queue.workerConcurrency |
Maximum parallel relay checks |
-
Relay URL filtering with pipe character:
daemon.ts(lines 234–239) logs relay URLs containing the|character but does not properly clean or reject them. This is a hotfix-level workaround for a structural URL validation gap at ingestion. As a workaround, remove malformed relay entries from the database directly via the SQLite CLI. See CONCERNS.md — Relay URL Filtering with Pipe Character. -
Database inspection function incomplete:
interactive/index.ts(lines 31–88) has adebugInspectDatabasefunction that logs start and end markers but contains no actual SQL inspection queries — they are missing or commented out. Use the SQLite CLI directly to inspect the database. See CONCERNS.md — Database Inspection Function Incomplete.
No agent skills defined yet for this package.
@nostrwatch/nocap— relay check primitives used for all health checks@nostrwatch/db— SQLite database abstraction layer@nostrwatch/logger— structured logging@nostrwatch/announce— monitor profile and relay list announcement@nostrwatch/publisher— NIP-66 and Trusted Relay Assertion event publishingapps/docker-stacks— pre-configured Docker Compose stacks for relaymon