476 lines
15 KiB
Markdown
476 lines
15 KiB
Markdown
# Configuration Management Guide
|
||
|
||
Comprehensive guide for managing the C Nostr Relay's event-based configuration system.
|
||
|
||
## Table of Contents
|
||
|
||
- [Overview](#overview)
|
||
- [Configuration Events](#configuration-events)
|
||
- [Parameter Reference](#parameter-reference)
|
||
- [Configuration Examples](#configuration-examples)
|
||
- [Security Considerations](#security-considerations)
|
||
- [Troubleshooting](#troubleshooting)
|
||
|
||
## Overview
|
||
|
||
The C Nostr Relay uses a revolutionary **event-based configuration system** where all settings are stored as kind 33334 Nostr events in the database. This provides several advantages:
|
||
|
||
### Benefits
|
||
- **Real-time updates**: Configuration changes applied instantly without restart
|
||
- **Cryptographic security**: All changes must be cryptographically signed by admin
|
||
- **Audit trail**: Complete history of all configuration changes
|
||
- **Version control**: Each configuration change is timestamped and signed
|
||
- **Zero files**: No configuration files to manage, backup, or version control
|
||
|
||
### How It Works
|
||
1. **Admin keypair**: Generated on first startup, used to sign configuration events
|
||
2. **Configuration events**: Kind 33334 Nostr events with relay settings in tags
|
||
3. **Real-time processing**: New configuration events processed via WebSocket
|
||
4. **Immediate application**: Changes applied to running system without restart
|
||
|
||
## Configuration Events
|
||
|
||
### Event Structure
|
||
|
||
Configuration events follow the standard Nostr event format with kind 33334:
|
||
|
||
```json
|
||
{
|
||
"id": "event_id_computed_from_content",
|
||
"kind": 33334,
|
||
"pubkey": "admin_public_key_hex",
|
||
"created_at": 1699123456,
|
||
"content": "C Nostr Relay Configuration",
|
||
"tags": [
|
||
["d", "relay_public_key_hex"],
|
||
["relay_description", "My Nostr Relay"],
|
||
["max_subscriptions_per_client", "25"],
|
||
["pow_min_difficulty", "16"]
|
||
],
|
||
"sig": "signature_computed_with_admin_private_key"
|
||
}
|
||
```
|
||
|
||
### Required Tags
|
||
- **`d` tag**: Must contain the relay's public key (identifies which relay this config is for)
|
||
|
||
### Event Properties
|
||
- **Kind**: Must be exactly `33334`
|
||
- **Content**: Should be descriptive (e.g., "C Nostr Relay Configuration")
|
||
- **Pubkey**: Must be the admin public key generated at first startup
|
||
- **Signature**: Must be valid signature from admin private key
|
||
|
||
## Parameter Reference
|
||
|
||
### Basic Relay Information
|
||
|
||
#### `relay_description`
|
||
- **Description**: Human-readable relay description (shown in NIP-11)
|
||
- **Default**: `"C Nostr Relay"`
|
||
- **Format**: String, max 512 characters
|
||
- **Example**: `"My awesome Nostr relay for the community"`
|
||
|
||
#### `relay_contact`
|
||
- **Description**: Admin contact information (email, npub, etc.)
|
||
- **Default**: `""` (empty)
|
||
- **Format**: String, max 256 characters
|
||
- **Example**: `"admin@example.com"` or `"npub1..."`
|
||
|
||
#### `relay_software`
|
||
- **Description**: Software identifier for NIP-11
|
||
- **Default**: `"c-relay-pg"`
|
||
- **Format**: String, max 64 characters
|
||
- **Example**: `"c-relay-pg v1.0.0"`
|
||
|
||
#### `relay_version`
|
||
- **Description**: Software version string
|
||
- **Default**: Auto-detected from build
|
||
- **Format**: Semantic version string
|
||
- **Example**: `"1.0.0"`
|
||
|
||
### Client Connection Limits
|
||
|
||
#### `max_subscriptions_per_client`
|
||
- **Description**: Maximum subscriptions allowed per WebSocket connection
|
||
- **Default**: `"25"`
|
||
- **Range**: `1` to `100`
|
||
- **Impact**: Prevents individual clients from overwhelming the relay
|
||
- **Example**: `"50"` (allows up to 50 subscriptions per client)
|
||
|
||
#### `max_total_subscriptions`
|
||
- **Description**: Maximum total subscriptions across all clients
|
||
- **Default**: `"5000"`
|
||
- **Range**: `100` to `50000`
|
||
- **Impact**: Global limit to protect server resources
|
||
- **Example**: `"10000"` (allows up to 10,000 total subscriptions)
|
||
|
||
### Message and Event Limits
|
||
|
||
#### `max_message_length`
|
||
- **Description**: Maximum WebSocket message size in bytes
|
||
- **Default**: `"65536"` (64KB)
|
||
- **Range**: `1024` to `1048576` (1MB)
|
||
- **Impact**: Prevents large messages from consuming resources
|
||
- **Example**: `"131072"` (128KB)
|
||
|
||
#### `max_event_tags`
|
||
- **Description**: Maximum number of tags allowed per event
|
||
- **Default**: `"2000"`
|
||
- **Range**: `10` to `10000`
|
||
- **Impact**: Prevents events with excessive tags
|
||
- **Example**: `"5000"`
|
||
|
||
#### `max_content_length`
|
||
- **Description**: Maximum event content length in bytes
|
||
- **Default**: `"65536"` (64KB)
|
||
- **Range**: `1` to `1048576` (1MB)
|
||
- **Impact**: Limits event content size
|
||
- **Example**: `"131072"` (128KB for longer content)
|
||
|
||
### Proof of Work (NIP-13)
|
||
|
||
#### `pow_min_difficulty`
|
||
- **Description**: Minimum proof-of-work difficulty required for events
|
||
- **Default**: `"0"` (no PoW required)
|
||
- **Range**: `0` to `40`
|
||
- **Impact**: Higher values require more computational work from clients
|
||
- **Example**: `"20"` (requires significant PoW)
|
||
|
||
#### `pow_mode`
|
||
- **Description**: How proof-of-work is handled
|
||
- **Default**: `"optional"`
|
||
- **Values**:
|
||
- `"disabled"`: PoW completely ignored
|
||
- `"optional"`: PoW verified if present but not required
|
||
- `"required"`: All events must meet minimum difficulty
|
||
- **Example**: `"required"` (enforce PoW for all events)
|
||
|
||
### Event Expiration (NIP-40)
|
||
|
||
#### `nip40_expiration_enabled`
|
||
- **Description**: Enable NIP-40 expiration timestamp support
|
||
- **Default**: `"true"`
|
||
- **Values**: `"true"` or `"false"`
|
||
- **Impact**: When enabled, processes expiration tags and removes expired events
|
||
- **Example**: `"false"` (disable expiration processing)
|
||
|
||
#### `nip40_expiration_strict`
|
||
- **Description**: Strict mode for expiration handling
|
||
- **Default**: `"false"`
|
||
- **Values**: `"true"` or `"false"`
|
||
- **Impact**: In strict mode, expired events are immediately rejected
|
||
- **Example**: `"true"` (reject expired events immediately)
|
||
|
||
#### `nip40_expiration_filter`
|
||
- **Description**: Filter expired events from query results
|
||
- **Default**: `"true"`
|
||
- **Values**: `"true"` or `"false"`
|
||
- **Impact**: When enabled, expired events are filtered from responses
|
||
- **Example**: `"false"` (include expired events in results)
|
||
|
||
#### `nip40_expiration_grace_period`
|
||
- **Description**: Grace period in seconds before expiration takes effect
|
||
- **Default**: `"300"` (5 minutes)
|
||
- **Range**: `0` to `86400` (24 hours)
|
||
- **Impact**: Allows some flexibility in expiration timing
|
||
- **Example**: `"600"` (10 minute grace period)
|
||
|
||
### UDP Nostr Ingress (No-Handshake Event Reception)
|
||
|
||
The relay can receive Nostr events as single UDP datagrams — no TCP connection,
|
||
no WebSocket handshake, no TLS negotiation. The event carries its own Schnorr
|
||
signature, which replaces the handshake. This provides censorship resistance:
|
||
a passive observer cannot distinguish a Nostr event from random UDP noise, and
|
||
active probing cannot detect the relay (invalid events are silently dropped).
|
||
|
||
#### `udp_ingress_enabled`
|
||
- **Description**: Enable the UDP event listener
|
||
- **Default**: `"false"`
|
||
- **Values**: `"true"` or `"false"`
|
||
- **Impact**: When enabled, the relay binds a UDP socket and accepts Nostr events
|
||
as single datagrams. Events are validated through the same pipeline as WebSocket
|
||
events (signature, structure, expiration, PoW) and broadcast to subscribers.
|
||
- **Note**: This is a dynamic config key — the listener starts/stops without
|
||
restart when the value changes (via `udp_ingress_tick()` in the main loop).
|
||
|
||
#### `udp_ingress_port`
|
||
- **Description**: UDP port for the event listener
|
||
- **Default**: `"443"`
|
||
- **Range**: `1` to `65535`
|
||
- **Impact**: Ports below 1024 require `CAP_NET_BIND_SERVICE` capability (see
|
||
deployment guide). TCP and UDP port namespaces are separate — UDP 443 can
|
||
coexist with an HTTPS server on TCP 443.
|
||
- **Recommendation**: Use port 443 to blend with QUIC/HTTP3 traffic for maximum
|
||
censorship resistance. Use port 8888 for local testing.
|
||
|
||
#### `udp_ingress_bind_addr`
|
||
- **Description**: IP address to bind the UDP socket to
|
||
- **Default**: `"0.0.0.0"`
|
||
- **Format**: IPv4 address string
|
||
- **Example**: `"127.0.0.1"` (listen only on localhost)
|
||
|
||
#### `udp_ingress_max_datagram_size`
|
||
- **Description**: Maximum allowed UDP datagram size in bytes
|
||
- **Default**: `"1472"`
|
||
- **Range**: `512` to `65535`
|
||
- **Impact**: 1472 bytes is the single-packet guarantee (1500 Ethernet MTU − 20
|
||
IP header − 8 UDP header). Datagrams larger than this are silently dropped.
|
||
This ensures the event fits in a single unfragmented IP packet, which is the
|
||
foundation of the censorship-resistance property.
|
||
|
||
### NIP-59 Gift Wrap Timestamp Configuration
|
||
|
||
#### `nip59_timestamp_max_delay_sec`
|
||
- **Description**: Controls timestamp randomization for NIP-59 gift wraps
|
||
- **Default**: `"0"` (no randomization)
|
||
- **Range**: `0` to `604800` (7 days)
|
||
- **Impact**: Affects compatibility with other Nostr clients for direct messaging
|
||
- **Values**:
|
||
- `"0"`: No randomization (maximum compatibility)
|
||
- `"1-604800"`: Random timestamp between now and N seconds ago
|
||
- **Example**: `"172800"` (2 days randomization for privacy)
|
||
|
||
## Configuration Examples
|
||
|
||
### Basic Relay Setup
|
||
```json
|
||
{
|
||
"kind": 33334,
|
||
"content": "Basic Relay Configuration",
|
||
"tags": [
|
||
["d", "relay_pubkey_here"],
|
||
["relay_description", "Community Nostr Relay"],
|
||
["relay_contact", "admin@community-relay.com"],
|
||
["max_subscriptions_per_client", "30"],
|
||
["max_total_subscriptions", "8000"]
|
||
]
|
||
}
|
||
```
|
||
|
||
### High-Security Relay
|
||
```json
|
||
{
|
||
"kind": 33334,
|
||
"content": "High Security Configuration",
|
||
"tags": [
|
||
["d", "relay_pubkey_here"],
|
||
["relay_description", "High-Security Nostr Relay"],
|
||
["pow_min_difficulty", "24"],
|
||
["pow_mode", "required"],
|
||
["max_subscriptions_per_client", "10"],
|
||
["max_total_subscriptions", "1000"],
|
||
["max_message_length", "32768"],
|
||
["nip40_expiration_strict", "true"]
|
||
]
|
||
}
|
||
```
|
||
|
||
### Public Community Relay
|
||
```json
|
||
{
|
||
"kind": 33334,
|
||
"content": "Public Community Relay Configuration",
|
||
"tags": [
|
||
["d", "relay_pubkey_here"],
|
||
["relay_description", "Open Community Relay - Welcome Everyone!"],
|
||
["relay_contact", "community@relay.example"],
|
||
["max_subscriptions_per_client", "50"],
|
||
["max_total_subscriptions", "25000"],
|
||
["max_content_length", "131072"],
|
||
["pow_mode", "optional"],
|
||
["pow_min_difficulty", "8"],
|
||
["nip40_expiration_enabled", "true"],
|
||
["nip40_expiration_grace_period", "900"]
|
||
]
|
||
}
|
||
```
|
||
|
||
### Private/Corporate Relay
|
||
```json
|
||
{
|
||
"kind": 33334,
|
||
"content": "Corporate Internal Relay",
|
||
"tags": [
|
||
["d", "relay_pubkey_here"],
|
||
["relay_description", "Corporate Internal Communications"],
|
||
["relay_contact", "it-admin@company.com"],
|
||
["max_subscriptions_per_client", "20"],
|
||
["max_total_subscriptions", "2000"],
|
||
["max_message_length", "262144"],
|
||
["nip40_expiration_enabled", "false"],
|
||
["pow_mode", "disabled"]
|
||
]
|
||
}
|
||
```
|
||
|
||
## Security Considerations
|
||
|
||
### Admin Key Management
|
||
|
||
#### Secure Storage
|
||
```bash
|
||
# Store admin private key securely
|
||
echo "ADMIN_PRIVKEY=your_admin_private_key_here" > .env
|
||
chmod 600 .env
|
||
|
||
# Or use a password manager
|
||
# Never store in version control
|
||
echo ".env" >> .gitignore
|
||
```
|
||
|
||
#### Key Rotation
|
||
Currently, admin key rotation requires:
|
||
1. Stopping the relay
|
||
2. Removing the database (loses all events)
|
||
3. Restarting (generates new keys)
|
||
|
||
Future versions will support admin key rotation while preserving events.
|
||
|
||
### Event Validation
|
||
|
||
The relay performs comprehensive validation on configuration events:
|
||
|
||
#### Cryptographic Validation
|
||
- **Signature verification**: Uses `nostr_verify_event_signature()`
|
||
- **Event structure**: Validates JSON structure with `nostr_validate_event_structure()`
|
||
- **Admin authorization**: Ensures events are signed by the authorized admin pubkey
|
||
|
||
#### Content Validation
|
||
- **Parameter bounds checking**: Validates numeric ranges
|
||
- **String length limits**: Enforces maximum lengths
|
||
- **Enum validation**: Validates allowed values for mode parameters
|
||
|
||
### Network Security
|
||
|
||
#### Access Control
|
||
```bash
|
||
# Limit access with firewall
|
||
sudo ufw allow from 192.168.1.0/24 to any port 8888
|
||
|
||
# Or use specific IPs
|
||
sudo ufw allow from 203.0.113.10 to any port 8888
|
||
```
|
||
|
||
#### TLS/SSL Termination
|
||
```nginx
|
||
# nginx configuration for HTTPS termination
|
||
server {
|
||
listen 443 ssl;
|
||
server_name relay.example.com;
|
||
|
||
ssl_certificate /path/to/cert.pem;
|
||
ssl_certificate_key /path/to/key.pem;
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:8888;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection "Upgrade";
|
||
proxy_set_header Host $host;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
### Configuration Not Applied
|
||
|
||
#### Check Event Signature
|
||
```javascript
|
||
// Verify event signature with nostrtool or similar
|
||
const event = { /* your configuration event */ };
|
||
const isValid = nostrTools.verifySignature(event);
|
||
```
|
||
|
||
#### Verify Admin Pubkey
|
||
```bash
|
||
# Check current admin pubkey in database
|
||
sqlite3 relay.nrdb "SELECT DISTINCT pubkey FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1;"
|
||
|
||
# Compare with expected admin pubkey from first startup
|
||
grep "Admin Public Key" relay.log
|
||
```
|
||
|
||
#### Check Event Structure
|
||
```bash
|
||
# View the exact event stored in database
|
||
sqlite3 relay.nrdb "SELECT json_pretty(json_object(
|
||
'kind', kind,
|
||
'pubkey', pubkey,
|
||
'created_at', created_at,
|
||
'content', content,
|
||
'tags', json(tags),
|
||
'sig', sig
|
||
)) FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1;"
|
||
```
|
||
|
||
### Configuration Validation Errors
|
||
|
||
#### Invalid Parameter Values
|
||
```bash
|
||
# Check relay logs for validation errors
|
||
journalctl -u c-relay-pg | grep "Configuration.*invalid\|Invalid.*configuration"
|
||
|
||
# Common issues:
|
||
# - Numeric values outside valid ranges
|
||
# - Invalid enum values (e.g., pow_mode)
|
||
# - String values exceeding length limits
|
||
```
|
||
|
||
#### Missing Required Tags
|
||
```bash
|
||
# Ensure 'd' tag is present with relay pubkey
|
||
sqlite3 relay.nrdb "SELECT tags FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1;" | grep '"d"'
|
||
```
|
||
|
||
### Performance Impact
|
||
|
||
#### Monitor Configuration Changes
|
||
```bash
|
||
# Track configuration update frequency
|
||
sqlite3 relay.nrdb "SELECT datetime(created_at, 'unixepoch') as date,
|
||
COUNT(*) as config_updates
|
||
FROM events WHERE kind = 33334
|
||
GROUP BY date(created_at, 'unixepoch')
|
||
ORDER BY date DESC;"
|
||
```
|
||
|
||
#### Resource Usage After Changes
|
||
```bash
|
||
# Monitor system resources after configuration updates
|
||
top -p $(pgrep c_relay_pg)
|
||
|
||
# Check for memory leaks
|
||
ps aux | grep c_relay_pg | awk '{print $6}' # RSS memory
|
||
```
|
||
|
||
### Emergency Recovery
|
||
|
||
#### Reset to Default Configuration
|
||
If configuration becomes corrupted or causes issues:
|
||
|
||
```bash
|
||
# Create emergency configuration event
|
||
nostrtool event \
|
||
--kind 33334 \
|
||
--content "Emergency Reset Configuration" \
|
||
--tag d YOUR_RELAY_PUBKEY \
|
||
--tag max_subscriptions_per_client 25 \
|
||
--tag max_total_subscriptions 5000 \
|
||
--tag pow_mode optional \
|
||
--tag pow_min_difficulty 0 \
|
||
--private-key YOUR_ADMIN_PRIVKEY \
|
||
| nostrtool send ws://localhost:8888
|
||
```
|
||
|
||
#### Database Recovery
|
||
```bash
|
||
# If database is corrupted, backup and recreate
|
||
cp relay.nrdb relay.nrdb.backup
|
||
rm relay.nrdb*
|
||
./build/c_relay_pg_x86 # Creates fresh database with new keys
|
||
```
|
||
|
||
---
|
||
|
||
This configuration guide covers all aspects of managing the C Nostr Relay's event-based configuration system. The system provides unprecedented flexibility and security for Nostr relay administration while maintaining simplicity and real-time responsiveness. |