diff --git a/CLI_PORT_OVERRIDE_IMPLEMENTATION.md b/CLI_PORT_OVERRIDE_IMPLEMENTATION.md deleted file mode 100644 index 9fb76ca..0000000 --- a/CLI_PORT_OVERRIDE_IMPLEMENTATION.md +++ /dev/null @@ -1,145 +0,0 @@ -# CLI Port Override Implementation - -## Overview - -This document describes the implementation of the `-p ` command line option for the C Nostr Relay, which allows overriding the default relay port during first-time startup only. - -## Design Principles - -1. **First-time startup only**: Command line options only affect the initial configuration event creation -2. **Event-based persistence**: After first startup, all configuration is managed through database events -3. **Proper encapsulation**: All configuration logic is contained within `config.c` -4. **Extensible design**: The CLI options structure can easily accommodate future command line options - -## Implementation Details - -### Files Modified - -#### `src/config.h` -- Added `cli_options_t` structure to encapsulate command line options -- Updated `first_time_startup_sequence()` function signature - -#### `src/config.c` -- Updated `first_time_startup_sequence()` to accept CLI options parameter -- Updated `create_default_config_event()` to accept CLI options parameter -- Implemented port override logic in DEFAULT_CONFIG_VALUES array processing - -#### `src/default_config_event.h` -- Updated function signature for `create_default_config_event()` -- Added proper header include for `cli_options_t` definition - -#### `src/main.c` -- Added command line parsing for `-p ` and `--port ` -- Updated help text to document the new option -- Added proper error handling for invalid port values -- Updated function call to pass CLI options to configuration system - -### CLI Options Structure - -```c -typedef struct { - int port_override; // -1 = not set, >0 = port value - // Future CLI options can be added here -} cli_options_t; -``` - -### Command Line Usage - -```bash -# First-time startup with port override -./c_relay_x86 -p 9090 -./c_relay_x86 --port 9090 - -# Show help (includes new option) -./c_relay_x86 --help - -# Show version -./c_relay_x86 --version -``` - -### Error Handling - -The implementation includes robust error handling for: -- Missing port argument: `./c_relay_x86 -p` -- Invalid port format: `./c_relay_x86 -p invalid_port` -- Out-of-range ports: `./c_relay_x86 -p 0` or `./c_relay_x86 -p 99999` - -## Behavior Verification - -### First-Time Startup -When no database exists: -1. Command line is parsed and `-p ` is processed -2. CLI options are passed to `first_time_startup_sequence()` -3. Port override is applied in `create_default_config_event()` -4. Configuration event is created with overridden port value -5. Relay starts on the specified port -6. Port setting is persisted in database for future startups - -### Subsequent Startups -When database already exists: -1. Command line is still parsed (for consistency) -2. Existing relay path is taken -3. Configuration is loaded from database events -4. CLI options are ignored -5. Relay starts on port from database configuration - -## Testing Results - -### Test 1: First-time startup with port override -```bash -./c_relay_x86 -p 9090 -``` -**Result**: ✅ Relay starts on port 9090, configuration stored in database - -### Test 2: Subsequent startup ignores CLI options -```bash -./c_relay_x86 -p 7777 -``` -**Result**: ✅ Relay starts on port 9090 (from database), ignores `-p 7777` - -### Test 3: Error handling -```bash -./c_relay_x86 -p invalid_port -./c_relay_x86 -p -``` -**Result**: ✅ Proper error messages and help text displayed - -### Test 4: Help text -```bash -./c_relay_x86 --help -``` -**Result**: ✅ Displays updated help with `-p, --port PORT` option - -## Database Verification - -The port setting is correctly stored in the database: -```sql -SELECT json_extract(tags, '$') FROM events WHERE kind = 33334; -``` -Shows the overridden port value in the configuration event tags. - -## Future Extensions - -The `cli_options_t` structure is designed to be easily extended: - -```c -typedef struct { - int port_override; // -1 = not set, >0 = port value - char* description_override; // Future: relay description override - int max_connections_override; // Future: connection limit override - // Add more options as needed -} cli_options_t; -``` - -## Key Design Benefits - -1. **Separation of Concerns**: Main function handles CLI parsing, config system handles application -2. **First-time Only**: Prevents confusion about configuration precedence -3. **Event-based Architecture**: Maintains consistency with the relay's event-based configuration system -4. **Extensible**: Easy to add new command line options in the future -5. **Robust**: Comprehensive error handling and validation -6. **Documented**: Clear help text explains behavior to users - -## Summary - -The `-p ` command line option implementation successfully provides a way to override the default relay port during first-time startup while maintaining the event-based configuration architecture for ongoing operation. The implementation is robust, well-tested, and ready for production use. \ No newline at end of file diff --git a/a3aa7add56a3d4f742c904c2b7bfb6b4197013249f3b822f2cdd1f51591cf1e6.db b/a3aa7add56a3d4f742c904c2b7bfb6b4197013249f3b822f2cdd1f51591cf1e6.db new file mode 100644 index 0000000..e69de29 diff --git a/relay.pid b/relay.pid index 7bfb774..d647459 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -1190476 +1198669 diff --git a/relay2.log b/relay2.log deleted file mode 100644 index c32587d..0000000 --- a/relay2.log +++ /dev/null @@ -1,35 +0,0 @@ -=== C Nostr Relay Server === -Event-based configuration system - -[INFO] Existing relay detected -[INFO] Initializing event-based configuration system... -[SUCCESS] Event-based configuration system initialized -[INFO] Starting existing relay... - Relay pubkey: 6df436471c7965d6473e89998162e6b87cc3547d71a2db12f559a39f4596059a -[SUCCESS] Existing relay startup prepared -[SUCCESS] Database connection established: 6df436471c7965d6473e89998162e6b87cc3547d71a2db12f559a39f4596059a.nrdb -[INFO] Database schema already exists, skipping initialization -[INFO] Existing database schema version: 4 -[WARNING] No configuration event found in existing database -[SUCCESS] Relay information initialized with default values -[INFO] Initializing NIP-13 Proof of Work configuration -[INFO] PoW configured in basic validation mode (default) -[INFO] PoW Configuration: enabled=true, min_difficulty=0, validation_flags=0x1, mode=full -[INFO] Initializing NIP-40 Expiration Timestamp configuration -[INFO] Expiration Configuration: enabled=true, strict_mode=true, filter_responses=true, grace_period=300 seconds -[INFO] Subscription limits: max_per_client=25, max_total=5000 -[INFO] Starting relay server... -[INFO] Starting libwebsockets-based Nostr relay server... -[INFO] Checking port availability: 8888 -[WARNING] Port 8888 is in use, trying port 8889 (attempt 2/5) -[INFO] Checking port availability: 8889 -[INFO] Attempting to bind libwebsockets to port 8889 -[WARNING] WebSocket relay started on ws://127.0.0.1:8889 (configured port 8888 was unavailable) -[SUCCESS] WebSocket relay started on ws://127.0.0.1:8889 (configured port 8888 was unavailable) -[INFO] Received shutdown signal -[INFO] Shutting down WebSocket server... -[SUCCESS] WebSocket relay shut down cleanly -[INFO] Cleaning up configuration system... -[SUCCESS] Configuration system cleaned up -[INFO] Database connection closed -[SUCCESS] Server shutdown complete diff --git a/relay3.log b/relay3.log deleted file mode 100644 index 9936ca1..0000000 --- a/relay3.log +++ /dev/null @@ -1,37 +0,0 @@ -=== C Nostr Relay Server === -Event-based configuration system - -[INFO] Existing relay detected -[INFO] Initializing event-based configuration system... -[SUCCESS] Event-based configuration system initialized -[INFO] Starting existing relay... - Relay pubkey: 6df436471c7965d6473e89998162e6b87cc3547d71a2db12f559a39f4596059a -[SUCCESS] Existing relay startup prepared -[SUCCESS] Database connection established: 6df436471c7965d6473e89998162e6b87cc3547d71a2db12f559a39f4596059a.nrdb -[INFO] Database schema already exists, skipping initialization -[INFO] Existing database schema version: 4 -[WARNING] No configuration event found in existing database -[SUCCESS] Relay information initialized with default values -[INFO] Initializing NIP-13 Proof of Work configuration -[INFO] PoW configured in basic validation mode (default) -[INFO] PoW Configuration: enabled=true, min_difficulty=0, validation_flags=0x1, mode=full -[INFO] Initializing NIP-40 Expiration Timestamp configuration -[INFO] Expiration Configuration: enabled=true, strict_mode=true, filter_responses=true, grace_period=300 seconds -[INFO] Subscription limits: max_per_client=25, max_total=5000 -[INFO] Starting relay server... -[INFO] Starting libwebsockets-based Nostr relay server... -[INFO] Checking port availability: 8888 -[WARNING] Port 8888 is in use, trying port 8889 (attempt 2/5) -[INFO] Checking port availability: 8889 -[WARNING] Port 8889 is in use, trying port 8890 (attempt 3/5) -[INFO] Checking port availability: 8890 -[INFO] Attempting to bind libwebsockets to port 8890 -[WARNING] WebSocket relay started on ws://127.0.0.1:8890 (configured port 8888 was unavailable) -[SUCCESS] WebSocket relay started on ws://127.0.0.1:8890 (configured port 8888 was unavailable) -[INFO] Received shutdown signal -[INFO] Shutting down WebSocket server... -[SUCCESS] WebSocket relay shut down cleanly -[INFO] Cleaning up configuration system... -[SUCCESS] Configuration system cleaned up -[INFO] Database connection closed -[SUCCESS] Server shutdown complete diff --git a/src/config.c b/src/config.c index bd7731e..7ee921f 100644 --- a/src/config.c +++ b/src/config.c @@ -29,6 +29,9 @@ static cJSON* g_current_config = NULL; // Cache for initial configuration event (before database is initialized) static cJSON* g_pending_config_event = NULL; +// Temporary storage for relay private key during first-time setup +static char g_temp_relay_privkey[65] = {0}; + // ================================ // UTILITY FUNCTIONS // ================================ @@ -437,6 +440,103 @@ int generate_random_private_key_bytes(unsigned char* privkey_bytes) { return 0; } +// ================================ +// SECURE RELAY PRIVATE KEY STORAGE +// ================================ + +int store_relay_private_key(const char* relay_privkey_hex) { + if (!relay_privkey_hex) { + log_error("Invalid relay private key for storage"); + return -1; + } + + // Validate private key format (must be 64 hex characters) + if (strlen(relay_privkey_hex) != 64) { + log_error("Invalid relay private key length (must be 64 hex characters)"); + return -1; + } + + // Validate hex format + for (int i = 0; i < 64; i++) { + char c = relay_privkey_hex[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + log_error("Invalid relay private key format (must be hex characters only)"); + return -1; + } + } + + if (!g_db) { + log_error("Database not available for relay private key storage"); + return -1; + } + + const char* sql = "INSERT OR REPLACE INTO relay_seckey (private_key_hex) VALUES (?)"; + sqlite3_stmt* stmt; + + int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + log_error("Failed to prepare relay private key storage query"); + return -1; + } + + sqlite3_bind_text(stmt, 1, relay_privkey_hex, -1, SQLITE_STATIC); + + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + + if (rc == SQLITE_DONE) { + log_success("Relay private key stored securely in database"); + return 0; + } else { + log_error("Failed to store relay private key in database"); + return -1; + } +} + +char* get_relay_private_key(void) { + if (!g_db) { + log_error("Database not available for relay private key retrieval"); + return NULL; + } + + const char* sql = "SELECT private_key_hex FROM relay_seckey"; + sqlite3_stmt* stmt; + + int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + log_error("Failed to prepare relay private key retrieval query"); + return NULL; + } + + char* private_key = NULL; + if (sqlite3_step(stmt) == SQLITE_ROW) { + const char* key_from_db = (const char*)sqlite3_column_text(stmt, 0); + if (key_from_db && strlen(key_from_db) == 64) { + private_key = malloc(65); // 64 chars + null terminator + if (private_key) { + strcpy(private_key, key_from_db); + } + } + } + + sqlite3_finalize(stmt); + + if (!private_key) { + log_error("Relay private key not found in secure storage"); + } + + return private_key; +} + +const char* get_temp_relay_private_key(void) { + if (strlen(g_temp_relay_privkey) == 64) { + return g_temp_relay_privkey; + } + return NULL; +} + // ================================ // DEFAULT CONFIG EVENT CREATION // ================================ @@ -471,10 +571,8 @@ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes, cJSON_AddItemToArray(relay_pubkey_tag, cJSON_CreateString(relay_pubkey_hex)); cJSON_AddItemToArray(tags, relay_pubkey_tag); - cJSON* relay_privkey_tag = cJSON_CreateArray(); - cJSON_AddItemToArray(relay_privkey_tag, cJSON_CreateString("relay_privkey")); - cJSON_AddItemToArray(relay_privkey_tag, cJSON_CreateString(relay_privkey_hex)); - cJSON_AddItemToArray(tags, relay_privkey_tag); + // Note: relay_privkey is now stored securely in relay_seckey table + // It is no longer included in the public configuration event // Add all default configuration values with command line overrides for (size_t i = 0; i < DEFAULT_CONFIG_COUNT; i++) { @@ -583,14 +681,19 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { return -1; } - // 5. Create initial configuration event using defaults + // 5. Store relay private key in temporary storage for later secure storage + strncpy(g_temp_relay_privkey, relay_privkey, sizeof(g_temp_relay_privkey) - 1); + g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0'; + log_info("Relay private key cached for secure storage after database initialization"); + + // 6. Create initial configuration event using defaults (without private key) cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey, cli_options); if (!config_event) { log_error("Failed to create default configuration event"); return -1; } - // 6. Try to store configuration event in database, but cache it if database isn't ready + // 7. Try to store configuration event in database, but cache it if database isn't ready if (store_config_event_in_database(config_event) == 0) { log_success("Initial configuration event stored successfully"); } else { @@ -602,16 +705,16 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { g_pending_config_event = cJSON_Duplicate(config_event, 1); } - // 7. Cache the current config + // 8. Cache the current config if (g_current_config) { cJSON_Delete(g_current_config); } g_current_config = cJSON_Duplicate(config_event, 1); - // 8. Clean up + // 9. Clean up cJSON_Delete(config_event); - // 9. Print admin private key for user to save + // 10. Print admin private key for user to save printf("\n"); printf("=================================================================\n"); printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n"); diff --git a/src/config.h b/src/config.h index 17ebcaf..e743095 100644 --- a/src/config.h +++ b/src/config.h @@ -79,4 +79,9 @@ int apply_runtime_config_handlers(const cJSON* old_event, const cJSON* new_event char** find_existing_db_files(void); char* extract_pubkey_from_filename(const char* filename); +// Secure relay private key storage functions +int store_relay_private_key(const char* relay_privkey_hex); +char* get_relay_private_key(void); +const char* get_temp_relay_private_key(void); // For first-time startup only + #endif /* CONFIG_H */ \ No newline at end of file diff --git a/src/main.c b/src/main.c index 6c51345..16b8944 100644 --- a/src/main.c +++ b/src/main.c @@ -3227,7 +3227,7 @@ int main(int argc, char* argv[]) { return 1; } - // Run first-time startup sequence (generates keys, creates database, etc.) + // Run first-time startup sequence (generates keys, sets up database path, but doesn't store private key yet) if (first_time_startup_sequence(&cli_options) != 0) { log_error("Failed to complete first-time startup sequence"); cleanup_configuration_system(); @@ -3243,6 +3243,23 @@ int main(int argc, char* argv[]) { return 1; } + // Now that database is available, store the relay private key securely + const char* relay_privkey = get_temp_relay_private_key(); + if (relay_privkey) { + if (store_relay_private_key(relay_privkey) != 0) { + log_error("Failed to store relay private key securely after database initialization"); + cleanup_configuration_system(); + nostr_cleanup(); + return 1; + } + log_success("Relay private key stored securely in database"); + } else { + log_error("Relay private key not available from first-time startup"); + cleanup_configuration_system(); + nostr_cleanup(); + return 1; + } + // Retry storing the configuration event now that database is initialized if (retry_store_initial_config_event() != 0) { log_warning("Failed to store initial configuration event after database init"); diff --git a/src/sql_schema.h b/src/sql_schema.h index 1844a12..0738868 100644 --- a/src/sql_schema.h +++ b/src/sql_schema.h @@ -1,12 +1,12 @@ /* Embedded SQL Schema for C Nostr Relay * Generated from db/schema.sql - Do not edit manually - * Schema Version: 4 + * Schema Version: 5 */ #ifndef SQL_SCHEMA_H #define SQL_SCHEMA_H /* Schema version constant */ -#define EMBEDDED_SCHEMA_VERSION "4" +#define EMBEDDED_SCHEMA_VERSION "5" /* Embedded SQL schema as C string literal */ static const char* const EMBEDDED_SCHEMA_SQL = @@ -15,7 +15,7 @@ static const char* const EMBEDDED_SCHEMA_SQL = -- Event-based configuration system using kind 33334 Nostr events\n\ \n\ -- Schema version tracking\n\ -PRAGMA user_version = 4;\n\ +PRAGMA user_version = 5;\n\ \n\ -- Enable foreign key support\n\ PRAGMA foreign_keys = ON;\n\ @@ -58,8 +58,8 @@ CREATE TABLE schema_info (\n\ \n\ -- Insert schema metadata\n\ INSERT INTO schema_info (key, value) VALUES\n\ - ('version', '4'),\n\ - ('description', 'Event-based Nostr relay schema with kind 33334 configuration events'),\n\ + ('version', '5'),\n\ + ('description', 'Event-based Nostr relay schema with secure relay private key storage'),\n\ ('created_at', strftime('%s', 'now'));\n\ \n\ -- Helper views for common queries\n\ @@ -128,6 +128,13 @@ BEGIN\n\ AND id != NEW.id;\n\ END;\n\ \n\ +-- Relay Private Key Secure Storage\n\ +-- Stores the relay's private key separately from public configuration\n\ +CREATE TABLE relay_seckey (\n\ + private_key_hex TEXT NOT NULL CHECK (length(private_key_hex) = 64),\n\ + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))\n\ +);\n\ +\n\ -- Persistent Subscriptions Logging Tables (Phase 2)\n\ -- Optional database logging for subscription analytics and debugging\n\ \n\ diff --git a/test_port_increment.log b/test_port_increment.log deleted file mode 100644 index 9a24255..0000000 --- a/test_port_increment.log +++ /dev/null @@ -1,47 +0,0 @@ -=== C Nostr Relay Server === -Event-based configuration system - -[INFO] Existing relay detected -[INFO] Initializing event-based configuration system... -[SUCCESS] Event-based configuration system initialized -[INFO] Starting existing relay... - Relay pubkey: 6df436471c7965d6473e89998162e6b87cc3547d71a2db12f559a39f4596059a -[SUCCESS] Existing relay startup prepared -[SUCCESS] Database connection established: 6df436471c7965d6473e89998162e6b87cc3547d71a2db12f559a39f4596059a.nrdb -[INFO] Database schema already exists, skipping initialization -[INFO] Existing database schema version: 4 -[INFO] Applying configuration from event... -[INFO] Checking for runtime configuration changes... -[INFO] Subscription limits changed - updating subscription manager -[INFO] Subscription limits: max_per_client=25, max_total=5000 -[INFO] PoW configuration changed - reinitializing PoW system -[INFO] Initializing NIP-13 Proof of Work configuration -[INFO] PoW configured in basic validation mode -[INFO] PoW Configuration: enabled=true, min_difficulty=0, validation_flags=0x1, mode=full -[INFO] Expiration configuration changed - reinitializing expiration system -[INFO] Initializing NIP-40 Expiration Timestamp configuration -[INFO] Expiration Configuration: enabled=true, strict_mode=true, filter_responses=true, grace_period=300 seconds -[INFO] Relay information changed - reinitializing relay info -[SUCCESS] Relay information initialized with default values -[SUCCESS] Configuration updated via kind 33334 event - 4 system components reinitialized -[SUCCESS] Configuration applied from event (4 handlers executed) -[SUCCESS] Configuration loaded from database -[SUCCESS] Relay information initialized with default values -[INFO] Initializing NIP-13 Proof of Work configuration -[INFO] PoW configured in basic validation mode -[INFO] PoW Configuration: enabled=true, min_difficulty=0, validation_flags=0x1, mode=full -[INFO] Initializing NIP-40 Expiration Timestamp configuration -[INFO] Expiration Configuration: enabled=true, strict_mode=true, filter_responses=true, grace_period=300 seconds -[INFO] Subscription limits: max_per_client=25, max_total=5000 -[INFO] Starting relay server... -[INFO] Starting libwebsockets-based Nostr relay server... -[INFO] Attempting to bind to port 8888 -[2025/09/06 20:34:16:8170] E: ERROR on binding fd 8 to port 8888 (-1 98) -[2025/09/06 20:34:16:8172] E: init server failed -[2025/09/06 20:34:16:8172] E: Failed to create default vhost -[ERROR] Failed to create libwebsockets context after 1 attempts. Last attempted port: 8888 -libwebsockets creation error: Inappropriate ioctl for device -[INFO] Cleaning up configuration system... -[SUCCESS] Configuration system cleaned up -[INFO] Database connection closed -[ERROR] Server shutdown with errors