Files
c-relay-pg/docs/deployment_guide.md
T

18 KiB

Deployment Guide - C Nostr Relay

Complete deployment guide for the C Nostr Relay with event-based configuration system across different environments and platforms.

Table of Contents

Deployment Overview

The C Nostr Relay's event-based configuration system simplifies deployment:

Key Deployment Benefits

  • Zero Configuration: No config files to manage or transfer
  • Self-Contained: Single binary + auto-generated database
  • Portable: Database contains all relay state and configuration
  • Secure: Admin keys generated locally, never transmitted
  • Scalable: Efficient SQLite backend with WAL mode

Deployment Requirements

  • CPU: 1 vCPU minimum, 2+ recommended
  • RAM: 512MB minimum, 2GB+ recommended
  • Storage: 100MB for binary + database growth (varies by usage)
  • Network: Port 8888 (configurable via events)
  • OS: Linux (recommended), macOS, Windows (WSL)

Production Deployment

Server Preparation

System Updates

# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y

# CentOS/RHEL
sudo yum update -y

# Install required packages
sudo apt install -y build-essential git sqlite3 libsqlite3-dev \
    libwebsockets-dev libssl-dev libsecp256k1-dev libcurl4-openssl-dev \
    zlib1g-dev systemd

User and Directory Setup

# Create dedicated system user
sudo useradd --system --home-dir /opt/c-relay-pg --shell /bin/false c-relay-pg

# Create application directory
sudo mkdir -p /opt/c-relay-pg
sudo chown c-relay-pg:c-relay-pg /opt/c-relay-pg

Build and Installation

# Clone repository
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
git submodule update --init --recursive

# Build
make clean && make

# Install as systemd service
sudo systemd/install-service.sh

Manual Installation

# Build relay
make clean && make

# Install binary
sudo cp build/c_relay_pg_x86 /opt/c-relay-pg/
sudo chown c-relay-pg:c-relay-pg /opt/c-relay-pg/c_relay_pg_x86
sudo chmod +x /opt/c-relay-pg/c_relay_pg_x86

# Install systemd service
sudo cp systemd/c-relay-pg.service /etc/systemd/system/
sudo systemctl daemon-reload

UDP Nostr Ingress (Privileged Port Binding)

If UDP ingress is enabled on a port below 1024 (e.g., the default port 443), the relay needs the CAP_NET_BIND_SERVICE capability to bind the port as a non-root user. Add this to the [Service] section of the systemd unit file:

AmbientCapabilities=CAP_NET_BIND_SERVICE

This grants the c-relay-pg service user permission to bind privileged ports without running as root. The capability is automatically inherited by the forked relay process and does not require any changes to the binary.

Note: TCP and UDP port namespaces are separate. The UDP listener on port 443 coexists with an HTTPS server (nginx, caddy) on TCP port 443 without conflict. This is the same mechanism used by QUIC/HTTP3.

Service Management

Start and Enable Service

# Start the service
sudo systemctl start c-relay-pg

# Enable auto-start on boot
sudo systemctl enable c-relay-pg

# Check status
sudo systemctl status c-relay-pg

Capture Admin Keys (CRITICAL)

# View startup logs to get admin keys
sudo journalctl -u c-relay-pg --since "5 minutes ago" | grep -A 10 "IMPORTANT: SAVE THIS ADMIN PRIVATE KEY"

# Or check the full log
sudo journalctl -u c-relay-pg --no-pager | grep "Admin Private Key"

⚠️ CRITICAL: Save the admin private key immediately - it's only shown once and is needed for all configuration updates!

Firewall Configuration

UFW (Ubuntu)

# Allow relay WebSocket port
sudo ufw allow 8888/tcp

# Allow UDP Nostr ingress (if enabled)
sudo ufw allow 443/udp

# Allow SSH (ensure you don't lock yourself out)
sudo ufw allow 22/tcp

# Enable firewall
sudo ufw enable

iptables

# Allow relay WebSocket port
sudo iptables -A INPUT -p tcp --dport 8888 -j ACCEPT

# Allow UDP Nostr ingress (if enabled)
sudo iptables -A INPUT -p udp --dport 443 -j ACCEPT

# Save rules (Ubuntu/Debian)
sudo iptables-save > /etc/iptables/rules.v4

Cloud Deployments

AWS EC2

Instance Setup

# Launch Ubuntu 22.04 LTS instance (t3.micro or larger)
# Security Group: Allow port 8888 from 0.0.0.0/0 (or restricted IPs)

# Connect via SSH
ssh -i your-key.pem ubuntu@your-instance-ip

# Use the simple deployment script
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
sudo examples/deployment/simple-vps/deploy.sh
# Associate Elastic IP to ensure consistent public IP
# Configure DNS A record to point to Elastic IP

EBS Volume for Data

# Attach EBS volume for persistent storage
sudo mkfs.ext4 /dev/xvdf
sudo mkdir /data
sudo mount /dev/xvdf /data
sudo chown c-relay-pg:c-relay-pg /data

# Update systemd service to use /data
sudo sed -i 's/WorkingDirectory=\/opt\/c-relay-pg/WorkingDirectory=\/data/' /etc/systemd/system/c-relay-pg.service
sudo systemctl daemon-reload

Google Cloud Platform

Compute Engine Setup

# Create VM instance (e2-micro or larger)
gcloud compute instances create c-relay-pg-instance \
    --image-family=ubuntu-2204-lts \
    --image-project=ubuntu-os-cloud \
    --machine-type=e2-micro \
    --tags=nostr-relay

# Configure firewall
gcloud compute firewall-rules create allow-nostr-relay \
    --allow tcp:8888 \
    --source-ranges 0.0.0.0/0 \
    --target-tags nostr-relay

# SSH and deploy
gcloud compute ssh c-relay-pg-instance
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
sudo examples/deployment/simple-vps/deploy.sh

Persistent Disk

# Create and attach persistent disk
gcloud compute disks create relay-data --size=50GB
gcloud compute instances attach-disk c-relay-pg-instance --disk=relay-data

# Format and mount
sudo mkfs.ext4 /dev/sdb
sudo mkdir /data
sudo mount /dev/sdb /data
sudo chown c-relay-pg:c-relay-pg /data

DigitalOcean

Droplet Creation

# Create Ubuntu 22.04 droplet (Basic plan, $6/month minimum)
# Enable monitoring and backups

# SSH into droplet
ssh root@your-droplet-ip

# Deploy relay
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
examples/deployment/simple-vps/deploy.sh

Block Storage

# Attach block storage volume
# Format and mount as /data
sudo mkfs.ext4 /dev/sda
sudo mkdir /data
sudo mount /dev/sda /data
echo '/dev/sda /data ext4 defaults,nofail,discard 0 2' >> /etc/fstab

Automated Deployment Examples

The examples/deployment/ directory contains ready-to-use scripts:

Simple VPS Deployment

# Clone repository and run automated deployment
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
sudo examples/deployment/simple-vps/deploy.sh

SSL Proxy Setup

# Set up nginx reverse proxy with SSL
sudo examples/deployment/nginx-proxy/setup-ssl-proxy.sh \
    -d relay.example.com -e admin@example.com

Monitoring Setup

# Set up continuous monitoring
sudo examples/deployment/monitoring/monitor-relay.sh \
    -c -i 60 -e admin@example.com

Backup Setup

# Set up automated backups
sudo examples/deployment/backup/backup-relay.sh \
    -s my-backup-bucket -e admin@example.com

Reverse Proxy Setup

Nginx Configuration

Basic WebSocket Proxy

# /etc/nginx/sites-available/nostr-relay
server {
    listen 80;
    server_name relay.yourdomain.com;
    
    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;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # WebSocket timeouts
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

HTTPS with Let's Encrypt

# Install certbot
sudo apt install -y certbot python3-certbot-nginx

# Obtain certificate
sudo certbot --nginx -d relay.yourdomain.com

# Auto-renewal (crontab)
echo "0 12 * * * /usr/bin/certbot renew --quiet" | sudo crontab -

Enhanced HTTPS Configuration

server {
    listen 443 ssl http2;
    server_name relay.yourdomain.com;

    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/relay.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/relay.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";

    # Rate limiting (optional)
    limit_req_zone $remote_addr zone=relay:10m rate=10r/s;
    limit_req zone=relay burst=20 nodelay;

    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;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # WebSocket timeouts
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
        
        # Buffer settings
        proxy_buffering off;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name relay.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

PHP Admin UI: Public Stats vs Authenticated Admin

The relay ships with a PHP admin UI (in admin/) that talks directly to PostgreSQL. It is served by nginx + php-fpm and is split into two URL paths with different access controls:

URL Auth Purpose
https://<domain>/relay/ None (public) Public landing page: relay info + event-rate chart
https://<domain>/relay/api/chart.php?range=day None (public) Read-only ASCII chart endpoint (aggregate COUNT)
https://<domain>/relay/admin/ HTTP Basic Auth Full admin UI: config, auth rules, IP bans, DMs, SQL query

Why Basic Auth is required for /relay/admin/: the PHP endpoints have no server-side authentication of their own. The nostr_login_lite modal in the admin UI is a client-side only gate (it only toggles UI visibility in the browser); it does not send a signed event or session token to the backend. Without Basic Auth, anyone could hit api/query.php to run SELECT queries against your database, api/config.php to change relay config, api/auth.php to edit blacklist/whitelist rules, or api/dm.php to read DMs.

Only admin/api/chart.php is safe to expose publicly — it runs a fixed aggregate COUNT(*) query with the only user input being the range selector, which is validated against a whitelist (hour/day/month/year).

A complete, ready-to-use nginx config including this split is in examples/deployment/nginx-proxy/nginx.conf. The relevant blocks:

# Public landing page + chart (no auth)
location /relay/ {
    alias /opt/c-relay-pg/admin/public/;
    index index.php;
    location ~ \.php$ { fastcgi_pass unix:/run/php/php8.2-fpm.sock; ... }
}
location ^~ /relay/api/chart.php { alias /opt/c-relay-pg/admin/api/chart.php; ... }
location ^~ /relay/assets/       { alias /opt/c-relay-pg/admin/assets/; }

# Authenticated admin UI (Basic Auth)
location /relay/admin/ {
    alias /opt/c-relay-pg/admin/;
    auth_basic "Relay Admin";
    auth_basic_user_file /opt/c-relay-pg/admin/.htpasswd;
    location ~ \.php$ { fastcgi_pass unix:/run/php/php8.2-fpm.sock; ... }
    location ^~ /relay/admin/lib/    { deny all; }   # DB credentials
    location ^~ /relay/admin/public/ { deny all; }   # served via /relay/
}

Create the Basic Auth credentials on the server:

sudo htpasswd -c /opt/c-relay-pg/admin/.htpasswd <username>
sudo systemctl reload nginx

Apache Configuration

WebSocket Proxy with mod_proxy_wstunnel

# Enable required modules
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
sudo a2enmod ssl

# /etc/apache2/sites-available/nostr-relay.conf
<VirtualHost *:443>
    ServerName relay.yourdomain.com
    
    # SSL configuration
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/relay.yourdomain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/relay.yourdomain.com/privkey.pem
    
    # WebSocket proxy
    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass / ws://127.0.0.1:8888/
    ProxyPassReverse / ws://127.0.0.1:8888/
    
    # Fallback for HTTP requests
    RewriteEngine on
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/?(.*) "ws://127.0.0.1:8888/$1" [P,L]
    
    # Security headers
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    Header always set X-Content-Type-Options nosniff
    Header always set X-Frame-Options DENY
</VirtualHost>

<VirtualHost *:80>
    ServerName relay.yourdomain.com
    Redirect permanent / https://relay.yourdomain.com/
</VirtualHost>

Monitoring Setup

System Monitoring

Basic Monitoring Script

#!/bin/bash
# /usr/local/bin/relay-monitor.sh

LOG_FILE="/var/log/relay-monitor.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')

# Check if relay is running
if ! pgrep -f "c_relay_pg_x86" > /dev/null; then
    echo "[$DATE] ERROR: Relay process not running" >> $LOG_FILE
    systemctl restart c-relay-pg
fi

# Check port availability
if ! netstat -tln | grep -q ":8888"; then
    echo "[$DATE] ERROR: Port 8888 not listening" >> $LOG_FILE
fi

# Check database file
RELAY_DB=$(find /opt/c-relay-pg -name "*.nrdb" | head -1)
if [[ -n "$RELAY_DB" ]]; then
    DB_SIZE=$(du -h "$RELAY_DB" | cut -f1)
    echo "[$DATE] INFO: Database size: $DB_SIZE" >> $LOG_FILE
fi

# Check memory usage
MEM_USAGE=$(ps aux | grep c_relay_pg_x86 | grep -v grep | awk '{print $6}')
if [[ -n "$MEM_USAGE" ]]; then
    echo "[$DATE] INFO: Memory usage: ${MEM_USAGE}KB" >> $LOG_FILE
fi

Cron Job Setup

# Add to crontab
echo "*/5 * * * * /usr/local/bin/relay-monitor.sh" | sudo crontab -

# Make script executable
sudo chmod +x /usr/local/bin/relay-monitor.sh

Log Aggregation

Centralized Logging with rsyslog

# /etc/rsyslog.d/50-c-relay-pg.conf
if $programname == 'c-relay-pg' then /var/log/c-relay-pg.log
& stop

External Monitoring

Prometheus Integration

# /etc/prometheus/prometheus.yml
scrape_configs:
  - job_name: 'c-relay-pg'
    static_configs:
      - targets: ['localhost:8888']
    metrics_path: '/metrics'  # If implemented
    scrape_interval: 30s

Security Hardening

System Hardening

Service User Restrictions

# Restrict service user
sudo usermod -s /bin/false c-relay-pg
sudo usermod -d /opt/c-relay-pg c-relay-pg

# Set proper permissions
sudo chmod 700 /opt/c-relay-pg
sudo chown -R c-relay-pg:c-relay-pg /opt/c-relay-pg

File System Restrictions

# Mount data directory with appropriate options
echo "/dev/sdb /opt/c-relay-pg ext4 defaults,noexec,nosuid,nodev 0 2" >> /etc/fstab

Network Security

Fail2Ban Configuration

# /etc/fail2ban/jail.d/c-relay-pg.conf
[c-relay-pg-dos]
enabled = true
port = 8888
filter = c-relay-pg-dos
logpath = /var/log/c-relay-pg.log
maxretry = 10
findtime = 60
bantime = 300

DDoS Protection

# iptables rate limiting
sudo iptables -A INPUT -p tcp --dport 8888 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8888 -j DROP

Database Security

Encryption at Rest

# Use encrypted filesystem
sudo cryptsetup luksFormat /dev/sdb
sudo cryptsetup luksOpen /dev/sdb relay-data
sudo mkfs.ext4 /dev/mapper/relay-data

Backup and Recovery

Automated Backup

Database Backup Script

#!/bin/bash
# /usr/local/bin/backup-relay.sh

BACKUP_DIR="/backup/c-relay-pg"
DATE=$(date +%Y%m%d_%H%M%S)
RELAY_DB=$(find /opt/c-relay-pg -name "*.nrdb" | head -1)

mkdir -p "$BACKUP_DIR"

if [[ -n "$RELAY_DB" ]]; then
    # SQLite backup
    sqlite3 "$RELAY_DB" ".backup $BACKUP_DIR/relay_backup_$DATE.nrdb"
    
    # Compress backup
    gzip "$BACKUP_DIR/relay_backup_$DATE.nrdb"
    
    # Cleanup old backups (keep 30 days)
    find "$BACKUP_DIR" -name "relay_backup_*.nrdb.gz" -mtime +30 -delete
    
    echo "Backup completed: relay_backup_$DATE.nrdb.gz"
else
    echo "No relay database found!"
    exit 1
fi

Cron Schedule

# Daily backup at 2 AM
echo "0 2 * * * /usr/local/bin/backup-relay.sh" | sudo crontab -

Cloud Backup

AWS S3 Sync

# Install AWS CLI
sudo apt install -y awscli

# Configure AWS credentials
aws configure

# Sync backups to S3
aws s3 sync /backup/c-relay-pg/ s3://your-backup-bucket/c-relay-pg/ --delete

Disaster Recovery

Recovery Procedures

# 1. Restore from backup
gunzip backup/relay_backup_20231201_020000.nrdb.gz
cp backup/relay_backup_20231201_020000.nrdb /opt/c-relay-pg/

# 2. Fix permissions
sudo chown c-relay-pg:c-relay-pg /opt/c-relay-pg/*.nrdb

# 3. Restart service
sudo systemctl restart c-relay-pg

# 4. Verify recovery
sudo journalctl -u c-relay-pg --since "1 minute ago"

This deployment guide provides comprehensive coverage for deploying the C Nostr Relay across various environments while taking full advantage of the event-based configuration system's simplicity and security features.