High-performance Nostr relay built with Rust and OpenSearch.
- NIP-01, NIP-09, NIP-11, NIP-16, NIP-33, NIP-45, NIP-50, NIP-86
- Multi-process architecture for connection scaling
- OpenSearch backend with full-text search
- Realtime streaming via Redis pub/sub (multi-node support)
- Advanced NIP-50 search extensions (top, hot, controversial, rising)
- Event counting via NIP-45 COUNT
- Relay management API (NIP-86) with NIP-98 authentication
- Relay-to-relay sync via Negentropy
- Prometheus metrics
# Start OpenSearch
docker run -d -p 9200:9200 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Admin123!" \
opensearchproject/opensearch:latest
# Build and run
cargo build --release
./target/release/umbraSet via environment variables:
UMBRA_BIND- WebSocket address (default: 0.0.0.0:7777)UMBRA_OPENSEARCH_URL- OpenSearch URL(s), comma-separated for multiple nodes (default: http://localhost:9200)UMBRA_REDIS_URL- Redis URL for cross-node streaming (optional, e.g., redis://localhost:6379)UMBRA_WORKERS- Worker processes (default: CPU cores)UMBRA_ADMIN_PUBKEYS- Comma-separated list of admin pubkeys for NIP-86UMBRA_RELAY_URL- Public relay URL for NIP-98 verification (default: http://localhost:7777)UMBRA_POOL_MAX_IDLE_PER_HOST- Max idle connections to OpenSearch per host (default: 100)UMBRA_POOL_IDLE_TIMEOUT_SECS- Idle connection timeout in seconds (default: 300)RUST_LOG- Log level (default: info)
Umbra supports realtime event streaming to connected clients. When a client sends a REQ message, Umbra returns matching stored events, sends EOSE, then keeps the subscription open for new events.
Local streaming works out of the box - events received by a node are immediately broadcast to matching local subscriptions using in-memory inverted indexes for efficient O(1) lookups by author, kind, and tags.
Cross-node streaming requires Redis. When configured, events are published to a nostr:events Redis pub/sub channel, allowing multiple Umbra nodes to share events in realtime:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Node A │────▶│ Redis │◀────│ Node B │
│ (events) │◀────│ pub/sub │────▶│ (events) │
└─────────────┘ └─────────────┘ └─────────────┘
Umbra supports powerful NIP-50 search extensions for content discovery:
{"kinds": [1], "search": "sort:hot bitcoin", "limit": 50}Sort Modes:
sort:top- Most referenced events (all-time best)sort:hot- Trending now (recent + popular)sort:controversial- Mixed positive/negative reactionssort:rising- Rapidly gaining traction
Prometheus metrics available at http://localhost:9090/metrics
Run the test suite:
# Run all tests (single-threaded for env var tests)
cargo test -- --test-threads=1
# Run clippy with pedantic checks
cargo clippy -- -D warnings -D clippy::all -D clippy::pedanticNote: Some tests manipulate environment variables and require single-threaded execution to avoid race conditions.
AGPLv3