diff --git a/db/c_nostr_relay.db-shm b/db/c_nostr_relay.db-shm index 825010a..adddebb 100644 Binary files a/db/c_nostr_relay.db-shm and b/db/c_nostr_relay.db-shm differ diff --git a/db/c_nostr_relay.db-wal b/db/c_nostr_relay.db-wal index 8895bc4..a6baaa1 100644 Binary files a/db/c_nostr_relay.db-wal and b/db/c_nostr_relay.db-wal differ diff --git a/make_and_restart_relay.sh b/make_and_restart_relay.sh index 69708db..3a10fb5 100755 --- a/make_and_restart_relay.sh +++ b/make_and_restart_relay.sh @@ -32,7 +32,7 @@ if [ "$HELP" = true ]; then echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" - echo " --preserve-config Keep existing configuration file (don't regenerate)" + echo " --preserve-config, -p Keep existing configuration file (don't regenerate)" echo " --help, -h Show this help message" echo "" echo "Default behavior: Automatically regenerates configuration file on each build" @@ -40,16 +40,25 @@ if [ "$HELP" = true ]; then exit 0 fi -# Handle configuration file regeneration +# Handle configuration file and database regeneration CONFIG_FILE="$HOME/.config/c-relay/c_relay_config_event.json" -if [ "$PRESERVE_CONFIG" = false ] && [ -f "$CONFIG_FILE" ]; then - echo "Removing old configuration file to trigger regeneration..." - rm -f "$CONFIG_FILE" - echo "✓ Configuration file removed - will be regenerated with latest database values" -elif [ "$PRESERVE_CONFIG" = true ] && [ -f "$CONFIG_FILE" ]; then - echo "Preserving existing configuration file as requested" -elif [ ! -f "$CONFIG_FILE" ]; then - echo "No existing configuration file found - will generate new one" +DB_FILE="./db/c_nostr_relay.db" + +if [ "$PRESERVE_CONFIG" = false ]; then + if [ -f "$CONFIG_FILE" ]; then + echo "Removing old configuration file to trigger regeneration..." + rm -f "$CONFIG_FILE" + echo "✓ Configuration file removed - will be regenerated with new keys" + fi + if [ -f "$DB_FILE" ]; then + echo "Removing old database to trigger fresh key generation..." + rm -f "$DB_FILE"* # Remove db file and any WAL/SHM files + echo "✓ Database removed - will be recreated with embedded schema and new keys" + fi +elif [ "$PRESERVE_CONFIG" = true ]; then + echo "Preserving existing configuration and database as requested" +else + echo "No existing configuration or database found - will generate fresh setup" fi # Build the project first @@ -107,11 +116,9 @@ fi # Clean up PID file rm -f relay.pid -# Initialize database if needed -if [ ! -f "./db/c_nostr_relay.db" ]; then - echo "Initializing database..." - ./db/init.sh --force >/dev/null 2>&1 -fi +# Database initialization is now handled automatically by the relay +# when it starts up with embedded schema +echo "Database will be initialized automatically on startup if needed" # Start relay in background with output redirection echo "Starting relay server..." @@ -137,16 +144,16 @@ if ps -p "$RELAY_PID" >/dev/null 2>&1; then # Save PID for debugging echo $RELAY_PID > relay.pid - # Check if a new private key was generated and display it + # Check if new keys were generated and display them sleep 1 # Give relay time to write initial logs - if grep -q "GENERATED RELAY ADMIN PRIVATE KEY" relay.log 2>/dev/null; then - echo "=== IMPORTANT: NEW ADMIN PRIVATE KEY GENERATED ===" + if grep -q "GENERATED RELAY KEYPAIRS" relay.log 2>/dev/null; then + echo "=== IMPORTANT: NEW KEYPAIRS GENERATED ===" echo "" - # Extract and display the private key section from the log - grep -A 8 -B 2 "GENERATED RELAY ADMIN PRIVATE KEY" relay.log | head -n 12 + # Extract and display the keypairs section from the log + grep -A 12 -B 2 "GENERATED RELAY KEYPAIRS" relay.log | head -n 16 echo "" - echo "⚠️ SAVE THIS PRIVATE KEY SECURELY - IT CONTROLS YOUR RELAY!" - echo "⚠️ This key is also logged in relay.log for reference" + echo "⚠️ SAVE THESE PRIVATE KEYS SECURELY - THEY CONTROL YOUR RELAY!" + echo "⚠️ These keys are also logged in relay.log for reference" echo "" fi diff --git a/relay.pid b/relay.pid index abaec94..5607a92 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -891896 +905395 diff --git a/src/config.c b/src/config.c index d258487..254a05e 100644 --- a/src/config.c +++ b/src/config.c @@ -154,7 +154,7 @@ int init_config_database_statements(void) { log_info("Initializing configuration database statements..."); // Prepare statement for getting configuration values - const char* get_sql = "SELECT value FROM server_config WHERE key = ?"; + const char* get_sql = "SELECT value FROM config WHERE key = ?"; int rc = sqlite3_prepare_v2(g_db, get_sql, -1, &g_config_manager.get_config_stmt, NULL); if (rc != SQLITE_OK) { log_error("Failed to prepare get_config statement"); @@ -162,7 +162,7 @@ int init_config_database_statements(void) { } // Prepare statement for setting configuration values - const char* set_sql = "INSERT OR REPLACE INTO server_config (key, value, updated_at) VALUES (?, ?, strftime('%s', 'now'))"; + const char* set_sql = "INSERT OR REPLACE INTO config (key, value, updated_at) VALUES (?, ?, strftime('%s', 'now'))"; rc = sqlite3_prepare_v2(g_db, set_sql, -1, &g_config_manager.set_config_stmt, NULL); if (rc != SQLITE_OK) { log_error("Failed to prepare set_config statement"); @@ -242,7 +242,7 @@ int load_config_from_database(void) { // Database configuration is already populated by schema defaults // This function validates that the configuration tables exist and are accessible - const char* test_sql = "SELECT COUNT(*) FROM server_config WHERE config_type IN ('system', 'user')"; + const char* test_sql = "SELECT COUNT(*) FROM config WHERE config_type IN ('system', 'user')"; sqlite3_stmt* test_stmt; int rc = sqlite3_prepare_v2(g_db, test_sql, -1, &test_stmt, NULL); @@ -677,7 +677,7 @@ int config_requires_restart(const char* key) { if (!key) return 0; // Check database for requires_restart flag - const char* sql = "SELECT requires_restart FROM server_config WHERE key = ?"; + const char* sql = "SELECT requires_restart FROM config WHERE key = ?"; sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); @@ -735,6 +735,7 @@ cJSON* create_config_nostr_event(const char* privkey_hex) { {"relay_description", "High-performance C Nostr relay with SQLite storage"}, {"relay_contact", ""}, {"relay_pubkey", ""}, + {"relay_privkey", ""}, {"relay_software", "https://git.laantungir.net/laantungir/c-relay.git"}, {"relay_version", VERSION}, @@ -767,7 +768,7 @@ cJSON* create_config_nostr_event(const char* privkey_hex) { int defaults_count = sizeof(defaults) / sizeof(defaults[0]); // First try to load from database, fall back to defaults - const char* sql = "SELECT key, value FROM server_config WHERE config_type IN ('system', 'user') ORDER BY key"; + const char* sql = "SELECT key, value FROM config WHERE config_type IN ('system', 'user') ORDER BY key"; sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); @@ -901,6 +902,56 @@ int write_config_event_to_file(const cJSON* event) { return 0; } +// Helper function to generate random private key +int generate_random_private_key(char* privkey_hex, size_t buffer_size) { + if (!privkey_hex || buffer_size < 65) { + return -1; + } + + FILE* urandom = fopen("/dev/urandom", "rb"); + if (!urandom) { + log_error("Failed to open /dev/urandom for key generation"); + return -1; + } + + unsigned char privkey_bytes[32]; + if (fread(privkey_bytes, 1, 32, urandom) != 32) { + log_error("Failed to read random bytes for private key"); + fclose(urandom); + return -1; + } + fclose(urandom); + + // Convert to hex + nostr_bytes_to_hex(privkey_bytes, 32, privkey_hex); + + return 0; +} + +// Helper function to derive public key from private key +int derive_public_key(const char* privkey_hex, char* pubkey_hex, size_t buffer_size) { + if (!privkey_hex || !pubkey_hex || buffer_size < 65) { + return -1; + } + + // Convert hex private key to bytes + unsigned char privkey_bytes[32]; + if (nostr_hex_to_bytes(privkey_hex, privkey_bytes, 32) != 0) { + log_error("Failed to convert private key from hex"); + return -1; + } + + // Generate corresponding public key + unsigned char pubkey_bytes[32]; + if (nostr_ec_public_key_from_private_key(privkey_bytes, pubkey_bytes) == 0) { + nostr_bytes_to_hex(pubkey_bytes, 32, pubkey_hex); + return 0; + } else { + log_error("Failed to derive public key from private key"); + return -1; + } +} + int generate_config_file_if_missing(void) { // Check if config file already exists if (config_file_exists()) { @@ -910,84 +961,170 @@ int generate_config_file_if_missing(void) { log_info("Generating missing configuration file..."); - // Get private key from environment variable or generate random one - char privkey_hex[65]; - const char* env_privkey = getenv(CONFIG_PRIVKEY_ENV); + // Generate or get admin private key for configuration signing + char admin_privkey_hex[65]; + const char* env_admin_privkey = getenv(CONFIG_ADMIN_PRIVKEY_ENV); - if (env_privkey && strlen(env_privkey) == 64) { - // Use provided private key - strncpy(privkey_hex, env_privkey, sizeof(privkey_hex) - 1); - privkey_hex[sizeof(privkey_hex) - 1] = '\0'; - log_info("Using private key from environment variable"); + if (env_admin_privkey && strlen(env_admin_privkey) == 64) { + // Use provided admin private key + strncpy(admin_privkey_hex, env_admin_privkey, sizeof(admin_privkey_hex) - 1); + admin_privkey_hex[sizeof(admin_privkey_hex) - 1] = '\0'; + log_info("Using admin private key from environment variable"); } else { - // Generate random private key manually (nostr_core_lib doesn't have a key generation function) - FILE* urandom = fopen("/dev/urandom", "rb"); - if (!urandom) { - log_error("Failed to open /dev/urandom for key generation"); + // Generate random admin private key + if (generate_random_private_key(admin_privkey_hex, sizeof(admin_privkey_hex)) != 0) { + log_error("Failed to generate admin private key"); return -1; } - - unsigned char privkey_bytes[32]; - if (fread(privkey_bytes, 1, 32, urandom) != 32) { - log_error("Failed to read random bytes for private key"); - fclose(urandom); - return -1; - } - fclose(urandom); - - // Convert to hex - nostr_bytes_to_hex(privkey_bytes, 32, privkey_hex); - - // Generate corresponding public key - char pubkey_hex[65]; - unsigned char pubkey_bytes[32]; - if (nostr_ec_public_key_from_private_key(privkey_bytes, pubkey_bytes) == 0) { - nostr_bytes_to_hex(pubkey_bytes, 32, pubkey_hex); - } else { - log_error("Failed to derive public key from private key"); - return -1; - } - - log_info("Generated random private key for configuration signing"); - - // Print the generated private key prominently for the administrator - printf("\n"); - printf("=================================================================\n"); - printf("IMPORTANT: GENERATED RELAY ADMIN PRIVATE KEY\n"); - printf("=================================================================\n"); - printf("Private Key: %s\n", privkey_hex); - printf("Public Key: %s\n", pubkey_hex); - printf("\nSAVE THIS PRIVATE KEY SECURELY - IT CONTROLS YOUR RELAY!\n"); - printf("\nTo use this key in future sessions:\n"); - printf(" export %s=%s\n", CONFIG_PRIVKEY_ENV, privkey_hex); - printf("=================================================================\n"); - printf("\n"); - - char warning_msg[256]; - snprintf(warning_msg, sizeof(warning_msg), - "To use a specific private key, set the %s environment variable", CONFIG_PRIVKEY_ENV); - log_warning(warning_msg); + log_info("Generated random admin private key for configuration signing"); } - // Create Nostr event - cJSON* event = create_config_nostr_event(privkey_hex); - if (!event) { - log_error("Failed to create configuration event"); + // Generate or get relay private key for relay identity + char relay_privkey_hex[65]; + const char* env_relay_privkey = getenv(CONFIG_RELAY_PRIVKEY_ENV); + + if (env_relay_privkey && strlen(env_relay_privkey) == 64) { + // Use provided relay private key + strncpy(relay_privkey_hex, env_relay_privkey, sizeof(relay_privkey_hex) - 1); + relay_privkey_hex[sizeof(relay_privkey_hex) - 1] = '\0'; + log_info("Using relay private key from environment variable"); + } else { + // Generate random relay private key + if (generate_random_private_key(relay_privkey_hex, sizeof(relay_privkey_hex)) != 0) { + log_error("Failed to generate relay private key"); + return -1; + } + log_info("Generated random relay private key for relay identity"); + } + + // Derive public keys from private keys + char admin_pubkey_hex[65]; + char relay_pubkey_hex[65]; + + if (derive_public_key(admin_privkey_hex, admin_pubkey_hex, sizeof(admin_pubkey_hex)) != 0) { + log_error("Failed to derive admin public key"); return -1; } - // Extract and store the admin public key in database configuration - cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); - if (pubkey_obj && cJSON_IsString(pubkey_obj)) { - const char* admin_pubkey = cJSON_GetStringValue(pubkey_obj); - if (set_database_config("admin_pubkey", admin_pubkey, "system") == 0) { - log_info("Stored admin public key in configuration database"); - printf(" Admin Public Key: %s\n", admin_pubkey); + if (derive_public_key(relay_privkey_hex, relay_pubkey_hex, sizeof(relay_pubkey_hex)) != 0) { + log_error("Failed to derive relay public key"); + return -1; + } + + // Display both keypairs prominently for the administrator + printf("\n"); + printf("=================================================================\n"); + printf("IMPORTANT: GENERATED RELAY KEYPAIRS\n"); + printf("=================================================================\n"); + printf("ADMIN KEYS (for configuration signing):\n"); + printf(" Private Key: %s\n", admin_privkey_hex); + printf(" Public Key: %s\n", admin_pubkey_hex); + printf("\nRELAY KEYS (for relay identity):\n"); + printf(" Private Key: %s\n", relay_privkey_hex); + printf(" Public Key: %s\n", relay_pubkey_hex); + printf("\nSAVE THESE PRIVATE KEYS SECURELY!\n"); + printf("\nTo use specific keys in future sessions:\n"); + printf(" export %s=%s\n", CONFIG_ADMIN_PRIVKEY_ENV, admin_privkey_hex); + printf(" export %s=%s\n", CONFIG_RELAY_PRIVKEY_ENV, relay_privkey_hex); + printf("=================================================================\n"); + printf("\n"); + + // Default configuration values (same as in create_config_nostr_event) + typedef struct { + const char* key; + const char* value; + } default_config_t; + + static const default_config_t defaults[] = { + // Administrative settings + {"admin_enabled", "false"}, + + // Server core settings + {"relay_port", "8888"}, + {"database_path", "db/c_nostr_relay.db"}, + {"max_connections", "100"}, + + // NIP-11 Relay Information + {"relay_name", "C Nostr Relay"}, + {"relay_description", "High-performance C Nostr relay with SQLite storage"}, + {"relay_contact", ""}, + {"relay_software", "https://git.laantungir.net/laantungir/c-relay.git"}, + {"relay_version", VERSION}, + + // NIP-13 Proof of Work + {"pow_enabled", "true"}, + {"pow_min_difficulty", "0"}, + {"pow_mode", "basic"}, + + // NIP-40 Expiration Timestamp + {"expiration_enabled", "true"}, + {"expiration_strict", "true"}, + {"expiration_filter", "true"}, + {"expiration_grace_period", "300"}, + + // Subscription limits + {"max_subscriptions_per_client", "25"}, + {"max_total_subscriptions", "5000"}, + {"max_filters_per_subscription", "10"}, + + // Event processing limits + {"max_event_tags", "100"}, + {"max_content_length", "8196"}, + {"max_message_length", "16384"}, + + // Performance settings + {"default_limit", "500"}, + {"max_limit", "5000"} + }; + + int defaults_count = sizeof(defaults) / sizeof(defaults[0]); + + // Store all three keys and all default configuration values in database + if (set_database_config("admin_pubkey", admin_pubkey_hex, "system") == 0) { + log_info("Stored admin public key in configuration database"); + } else { + log_warning("Failed to store admin public key in database"); + } + + if (set_database_config("relay_privkey", relay_privkey_hex, "system") == 0) { + log_info("Stored relay private key in configuration database"); + } else { + log_warning("Failed to store relay private key in database"); + } + + if (set_database_config("relay_pubkey", relay_pubkey_hex, "system") == 0) { + log_info("Stored relay public key in configuration database"); + } else { + log_warning("Failed to store relay public key in database"); + } + + // Store all default configuration values + log_info("Storing default configuration values in database..."); + int stored_count = 0; + for (int i = 0; i < defaults_count; i++) { + if (set_database_config(defaults[i].key, defaults[i].value, "system") == 0) { + stored_count++; } else { - log_warning("Failed to store admin public key in database"); + log_warning("Failed to store default configuration"); + printf(" Key: %s, Value: %s\n", defaults[i].key, defaults[i].value); } } + if (stored_count == defaults_count) { + log_success("All default configuration values stored successfully"); + printf(" Stored %d configuration entries\n", stored_count); + } else { + log_warning("Some default configuration values failed to store"); + printf(" Stored %d of %d configuration entries\n", stored_count, defaults_count); + } + + // Create Nostr event using admin private key for signing + cJSON* event = create_config_nostr_event(admin_privkey_hex); + if (!event) { + log_error("Failed to create configuration event"); + return -1; + } + // Write to file int result = write_config_event_to_file(event); cJSON_Delete(event); diff --git a/src/config.h b/src/config.h index db609e2..bb80c48 100644 --- a/src/config.h +++ b/src/config.h @@ -12,7 +12,8 @@ #define CONFIG_DESCRIPTION_MAX_LENGTH 256 #define CONFIG_XDG_DIR_NAME "c-relay" #define CONFIG_FILE_NAME "c_relay_config_event.json" -#define CONFIG_PRIVKEY_ENV "C_RELAY_CONFIG_PRIVKEY" +#define CONFIG_ADMIN_PRIVKEY_ENV "C_RELAY_ADMIN_PRIVKEY" +#define CONFIG_RELAY_PRIVKEY_ENV "C_RELAY_PRIVKEY" #define NOSTR_PUBKEY_HEX_LENGTH 64 #define NOSTR_PRIVKEY_HEX_LENGTH 64 #define NOSTR_EVENT_ID_HEX_LENGTH 64 @@ -220,4 +221,10 @@ int sign_nostr_event(const cJSON* event, const char* privkey_hex, char* signatur // Write configuration event to file int write_config_event_to_file(const cJSON* event); +// Helper function to generate random private key +int generate_random_private_key(char* privkey_hex, size_t buffer_size); + +// Helper function to derive public key from private key +int derive_public_key(const char* privkey_hex, char* pubkey_hex, size_t buffer_size); + #endif // CONFIG_H \ No newline at end of file diff --git a/src/sql_schema.h b/src/sql_schema.h index 21b1dda..d305fd7 100644 --- a/src/sql_schema.h +++ b/src/sql_schema.h @@ -197,7 +197,7 @@ AND subscription_id NOT IN (\n\ -- ================================\n\ \n\ -- Core server configuration table\n\ -CREATE TABLE server_config (\n\ +CREATE TABLE config (\n\ key TEXT PRIMARY KEY, -- Configuration key (unique identifier)\n\ value TEXT NOT NULL, -- Configuration value (stored as string)\n\ description TEXT, -- Human-readable description\n\ @@ -219,7 +219,7 @@ CREATE TABLE config_history (\n\ changed_by TEXT DEFAULT 'system', -- Who made the change (system/admin/user)\n\ change_reason TEXT, -- Optional reason for change\n\ changed_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),\n\ - FOREIGN KEY (config_key) REFERENCES server_config(key)\n\ + FOREIGN KEY (config_key) REFERENCES config(key)\n\ );\n\ \n\ -- Configuration validation errors log\n\ @@ -244,8 +244,8 @@ CREATE TABLE config_file_cache (\n\ );\n\ \n\ -- Performance indexes for configuration tables\n\ -CREATE INDEX idx_server_config_type ON server_config(config_type);\n\ -CREATE INDEX idx_server_config_updated ON server_config(updated_at DESC);\n\ +CREATE INDEX idx_config_type ON config(config_type);\n\ +CREATE INDEX idx_config_updated ON config(updated_at DESC);\n\ CREATE INDEX idx_config_history_key ON config_history(config_key);\n\ CREATE INDEX idx_config_history_time ON config_history(changed_at DESC);\n\ CREATE INDEX idx_config_validation_key ON config_validation_log(config_key);\n\ @@ -253,14 +253,14 @@ CREATE INDEX idx_config_validation_time ON config_validation_log(attempted_at DE \n\ -- Trigger to update timestamp on configuration changes\n\ CREATE TRIGGER update_config_timestamp\n\ - AFTER UPDATE ON server_config\n\ + AFTER UPDATE ON config\n\ BEGIN\n\ - UPDATE server_config SET updated_at = strftime('%s', 'now') WHERE key = NEW.key;\n\ + UPDATE config SET updated_at = strftime('%s', 'now') WHERE key = NEW.key;\n\ END;\n\ \n\ -- Trigger to log configuration changes to history\n\ CREATE TRIGGER log_config_changes\n\ - AFTER UPDATE ON server_config\n\ + AFTER UPDATE ON config\n\ WHEN OLD.value != NEW.value\n\ BEGIN\n\ INSERT INTO config_history (config_key, old_value, new_value, changed_by, change_reason)\n\ @@ -277,7 +277,7 @@ SELECT\n\ data_type,\n\ requires_restart,\n\ updated_at\n\ -FROM server_config\n\ +FROM config\n\ WHERE config_type IN ('system', 'user')\n\ ORDER BY config_type, key;\n\ \n\ @@ -288,7 +288,7 @@ SELECT\n\ value,\n\ description,\n\ updated_at\n\ -FROM server_config\n\ +FROM config\n\ WHERE config_type = 'runtime'\n\ ORDER BY key;\n\ \n\ @@ -303,7 +303,7 @@ SELECT\n\ ch.change_reason,\n\ ch.changed_at\n\ FROM config_history ch\n\ -JOIN server_config sc ON ch.config_key = sc.key\n\ +JOIN config sc ON ch.config_key = sc.key\n\ ORDER BY ch.changed_at DESC\n\ LIMIT 50;\n\ \n\ diff --git a/systemd/README.md b/systemd/README.md new file mode 100644 index 0000000..8fa0080 --- /dev/null +++ b/systemd/README.md @@ -0,0 +1,217 @@ +# C-Relay Systemd Service + +This directory contains files for running C-Relay as a Linux systemd service. + +## Files + +- **`c-relay.service`** - Systemd service unit file +- **`install-systemd.sh`** - Installation script (run as root) +- **`uninstall-systemd.sh`** - Uninstallation script (run as root) +- **`README.md`** - This documentation file + +## Quick Start + +### 1. Build the relay +```bash +# From the project root directory +make +``` + +### 2. Install as systemd service +```bash +# Run the installation script as root +sudo ./systemd/install-systemd.sh +``` + +### 3. Start the service +```bash +sudo systemctl start c-relay +``` + +### 4. Check status +```bash +sudo systemctl status c-relay +``` + +## Service Details + +### Installation Location +- **Binary**: `/opt/c-relay/c_relay_x86` +- **Database**: `/opt/c-relay/db/` +- **Service File**: `/etc/systemd/system/c-relay.service` + +### User Account +- **User**: `c-relay` (system user, no shell access) +- **Group**: `c-relay` +- **Home Directory**: `/opt/c-relay` + +### Network Configuration +- **Default Port**: 8888 +- **Default Host**: 127.0.0.1 (localhost only) +- **WebSocket Endpoint**: `ws://127.0.0.1:8888` + +## Configuration + +### Environment Variables +Edit `/etc/systemd/system/c-relay.service` to configure: + +```ini +Environment=C_RELAY_CONFIG_PRIVKEY=your_private_key_here +Environment=C_RELAY_PORT=8888 +Environment=C_RELAY_HOST=0.0.0.0 +``` + +After editing, reload and restart: +```bash +sudo systemctl daemon-reload +sudo systemctl restart c-relay +``` + +### Security Settings +The service runs with enhanced security: +- Runs as unprivileged `c-relay` user +- No new privileges allowed +- Protected system directories +- Private temporary directory +- Limited file access (only `/opt/c-relay/db` writable) +- Network restrictions to IPv4/IPv6 only + +## Service Management + +### Basic Commands +```bash +# Start service +sudo systemctl start c-relay + +# Stop service +sudo systemctl stop c-relay + +# Restart service +sudo systemctl restart c-relay + +# Enable auto-start on boot +sudo systemctl enable c-relay + +# Disable auto-start on boot +sudo systemctl disable c-relay + +# Check service status +sudo systemctl status c-relay + +# View logs (live) +sudo journalctl -u c-relay -f + +# View logs (last 100 lines) +sudo journalctl -u c-relay -n 100 +``` + +### Log Management +Logs are handled by systemd's journal: +```bash +# View all logs +sudo journalctl -u c-relay + +# View logs from today +sudo journalctl -u c-relay --since today + +# View logs with timestamps +sudo journalctl -u c-relay --since "1 hour ago" --no-pager +``` + +## Database Management + +The database is automatically created on first run. Location: `/opt/c-relay/db/c_nostr_relay.db` + +### Backup Database +```bash +sudo cp /opt/c-relay/db/c_nostr_relay.db /opt/c-relay/db/backup-$(date +%Y%m%d).db +``` + +### Reset Database +```bash +sudo systemctl stop c-relay +sudo rm /opt/c-relay/db/c_nostr_relay.db* +sudo systemctl start c-relay +``` + +## Updating the Service + +### Update Binary +1. Build new version: `make` +2. Stop service: `sudo systemctl stop c-relay` +3. Replace binary: `sudo cp build/c_relay_x86 /opt/c-relay/` +4. Set permissions: `sudo chown c-relay:c-relay /opt/c-relay/c_relay_x86` +5. Start service: `sudo systemctl start c-relay` + +### Update Service File +1. Stop service: `sudo systemctl stop c-relay` +2. Copy new service file: `sudo cp systemd/c-relay.service /etc/systemd/system/` +3. Reload systemd: `sudo systemctl daemon-reload` +4. Start service: `sudo systemctl start c-relay` + +## Uninstallation + +Run the uninstall script to completely remove the service: +```bash +sudo ./systemd/uninstall-systemd.sh +``` + +This will: +- Stop and disable the service +- Remove the systemd service file +- Optionally remove the installation directory +- Optionally remove the `c-relay` user account + +## Troubleshooting + +### Service Won't Start +```bash +# Check detailed status +sudo systemctl status c-relay -l + +# Check logs for errors +sudo journalctl -u c-relay --no-pager -l +``` + +### Permission Issues +```bash +# Fix ownership of installation directory +sudo chown -R c-relay:c-relay /opt/c-relay + +# Ensure binary is executable +sudo chmod +x /opt/c-relay/c_relay_x86 +``` + +### Port Already in Use +```bash +# Check what's using port 8888 +sudo netstat -tulpn | grep :8888 + +# Or with ss command +sudo ss -tulpn | grep :8888 +``` + +### Database Issues +```bash +# Check database file permissions +ls -la /opt/c-relay/db/ + +# Check database integrity +sudo -u c-relay sqlite3 /opt/c-relay/db/c_nostr_relay.db "PRAGMA integrity_check;" +``` + +## Custom Configuration + +For advanced configurations, you can: +1. Modify the service file for different ports or settings +2. Use environment files: `/etc/systemd/system/c-relay.service.d/override.conf` +3. Configure log rotation with journald settings +4. Set up reverse proxy (nginx/apache) for HTTPS support + +## Security Considerations + +- The service runs as a non-root user with minimal privileges +- Database directory is only writable by the c-relay user +- Consider firewall rules for the relay port +- For internet-facing relays, use reverse proxy with SSL/TLS +- Monitor logs for suspicious activity \ No newline at end of file diff --git a/systemd/c-relay.service b/systemd/c-relay.service new file mode 100644 index 0000000..91d1917 --- /dev/null +++ b/systemd/c-relay.service @@ -0,0 +1,43 @@ +[Unit] +Description=C Nostr Relay Server +Documentation=https://github.com/your-repo/c-relay +After=network.target +Wants=network-online.target + +[Service] +Type=simple +User=c-relay +Group=c-relay +WorkingDirectory=/opt/c-relay +ExecStart=/opt/c-relay/c_relay_x86 +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal +SyslogIdentifier=c-relay + +# Security settings +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/opt/c-relay/db +PrivateTmp=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true + +# Network security +PrivateNetwork=false +RestrictAddressFamilies=AF_INET AF_INET6 + +# Resource limits +LimitNOFILE=65536 +LimitNPROC=4096 + +# Environment variables (optional) +Environment=C_RELAY_CONFIG_PRIVKEY= +Environment=C_RELAY_PORT=8888 +Environment=C_RELAY_HOST=127.0.0.1 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/systemd/install-systemd.sh b/systemd/install-systemd.sh new file mode 100755 index 0000000..11dfe7a --- /dev/null +++ b/systemd/install-systemd.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# C-Relay Systemd Service Installation Script +# This script installs the C-Relay as a systemd service + +set -e + +# Configuration +INSTALL_DIR="/opt/c-relay" +SERVICE_NAME="c-relay" +SERVICE_FILE="c-relay.service" +BINARY_NAME="c_relay_x86" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}=== C-Relay Systemd Service Installation ===${NC}" + +# Check if running as root +if [[ $EUID -ne 0 ]]; then + echo -e "${RED}Error: This script must be run as root${NC}" + echo "Usage: sudo ./install-systemd.sh" + exit 1 +fi + +# Check if binary exists (script is in systemd/ subdirectory) +if [ ! -f "../build/$BINARY_NAME" ]; then + echo -e "${RED}Error: Binary ../build/$BINARY_NAME not found${NC}" + echo "Please run 'make' from the project root directory first" + exit 1 +fi + +# Check if service file exists +if [ ! -f "$SERVICE_FILE" ]; then + echo -e "${RED}Error: Service file $SERVICE_FILE not found${NC}" + exit 1 +fi + +# Create c-relay user if it doesn't exist +if ! id "c-relay" &>/dev/null; then + echo -e "${YELLOW}Creating c-relay user...${NC}" + useradd --system --shell /bin/false --home-dir $INSTALL_DIR --create-home c-relay +else + echo -e "${GREEN}User c-relay already exists${NC}" +fi + +# Create installation directory +echo -e "${YELLOW}Creating installation directory...${NC}" +mkdir -p $INSTALL_DIR +mkdir -p $INSTALL_DIR/db + +# Copy binary +echo -e "${YELLOW}Installing binary...${NC}" +cp ../build/$BINARY_NAME $INSTALL_DIR/ +chmod +x $INSTALL_DIR/$BINARY_NAME + +# Set permissions +echo -e "${YELLOW}Setting permissions...${NC}" +chown -R c-relay:c-relay $INSTALL_DIR + +# Install systemd service +echo -e "${YELLOW}Installing systemd service...${NC}" +cp $SERVICE_FILE /etc/systemd/system/ +systemctl daemon-reload + +# Enable service +echo -e "${YELLOW}Enabling service...${NC}" +systemctl enable $SERVICE_NAME + +echo -e "${GREEN}=== Installation Complete ===${NC}" +echo +echo -e "${GREEN}Next steps:${NC}" +echo "1. Configure environment variables in /etc/systemd/system/$SERVICE_FILE if needed" +echo "2. Start the service: sudo systemctl start $SERVICE_NAME" +echo "3. Check status: sudo systemctl status $SERVICE_NAME" +echo "4. View logs: sudo journalctl -u $SERVICE_NAME -f" +echo +echo -e "${GREEN}Service commands:${NC}" +echo " Start: sudo systemctl start $SERVICE_NAME" +echo " Stop: sudo systemctl stop $SERVICE_NAME" +echo " Restart: sudo systemctl restart $SERVICE_NAME" +echo " Status: sudo systemctl status $SERVICE_NAME" +echo " Logs: sudo journalctl -u $SERVICE_NAME" +echo +echo -e "${GREEN}Installation directory: $INSTALL_DIR${NC}" +echo -e "${GREEN}Service file: /etc/systemd/system/$SERVICE_FILE${NC}" +echo +echo -e "${YELLOW}Note: The relay will run on port 8888 by default${NC}" +echo -e "${YELLOW}Database will be created automatically in $INSTALL_DIR/db/${NC}" \ No newline at end of file diff --git a/systemd/uninstall-systemd.sh b/systemd/uninstall-systemd.sh new file mode 100755 index 0000000..fc8200a --- /dev/null +++ b/systemd/uninstall-systemd.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# C-Relay Systemd Service Uninstallation Script +# This script removes the C-Relay systemd service + +set -e + +# Configuration +INSTALL_DIR="/opt/c-relay" +SERVICE_NAME="c-relay" +SERVICE_FILE="c-relay.service" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}=== C-Relay Systemd Service Uninstallation ===${NC}" + +# Check if running as root +if [[ $EUID -ne 0 ]]; then + echo -e "${RED}Error: This script must be run as root${NC}" + echo "Usage: sudo ./uninstall-systemd.sh" + exit 1 +fi + +# Stop service if running +echo -e "${YELLOW}Stopping service...${NC}" +if systemctl is-active --quiet $SERVICE_NAME; then + systemctl stop $SERVICE_NAME + echo -e "${GREEN}Service stopped${NC}" +else + echo -e "${GREEN}Service was not running${NC}" +fi + +# Disable service if enabled +echo -e "${YELLOW}Disabling service...${NC}" +if systemctl is-enabled --quiet $SERVICE_NAME; then + systemctl disable $SERVICE_NAME + echo -e "${GREEN}Service disabled${NC}" +else + echo -e "${GREEN}Service was not enabled${NC}" +fi + +# Remove systemd service file +echo -e "${YELLOW}Removing service file...${NC}" +if [ -f "/etc/systemd/system/$SERVICE_FILE" ]; then + rm /etc/systemd/system/$SERVICE_FILE + systemctl daemon-reload + echo -e "${GREEN}Service file removed${NC}" +else + echo -e "${GREEN}Service file was not found${NC}" +fi + +# Ask about removing installation directory +echo +echo -e "${YELLOW}Do you want to remove the installation directory $INSTALL_DIR? (y/N)${NC}" +read -r response +if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then + echo -e "${YELLOW}Removing installation directory...${NC}" + rm -rf $INSTALL_DIR + echo -e "${GREEN}Installation directory removed${NC}" +else + echo -e "${GREEN}Installation directory preserved${NC}" +fi + +# Ask about removing c-relay user +echo +echo -e "${YELLOW}Do you want to remove the c-relay user? (y/N)${NC}" +read -r response +if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then + echo -e "${YELLOW}Removing c-relay user...${NC}" + if id "c-relay" &>/dev/null; then + userdel c-relay + echo -e "${GREEN}User c-relay removed${NC}" + else + echo -e "${GREEN}User c-relay was not found${NC}" + fi +else + echo -e "${GREEN}User c-relay preserved${NC}" +fi + +echo +echo -e "${GREEN}=== Uninstallation Complete ===${NC}" +echo -e "${GREEN}C-Relay systemd service has been removed${NC}" \ No newline at end of file