diff --git a/.roo/commands/push.md b/.roo/commands/push.md new file mode 100644 index 0000000..f01dca2 --- /dev/null +++ b/.roo/commands/push.md @@ -0,0 +1,5 @@ +--- +description: "Brief description of what this command does" +--- + +Run build_and_push.sh, and supply a good git commit message. \ No newline at end of file diff --git a/c-relay-x86_64 b/c-relay-x86_64 deleted file mode 100755 index 1c73c76..0000000 Binary files a/c-relay-x86_64 and /dev/null differ diff --git a/make_and_restart_relay.sh b/make_and_restart_relay.sh index 4b798e1..fa3511d 100755 --- a/make_and_restart_relay.sh +++ b/make_and_restart_relay.sh @@ -38,7 +38,7 @@ if [ "$HELP" = true ]; then echo "Event-Based Configuration:" echo " This relay now uses event-based configuration stored directly in the database." echo " On first startup, keys are automatically generated and printed once." - echo " Database file: .nrdb (created automatically)" + echo " Database file: .db (created automatically)" echo "" echo "Examples:" echo " $0 # Fresh start with new keys (default)" @@ -51,15 +51,22 @@ fi # Handle database file cleanup for fresh start if [ "$PRESERVE_DATABASE" = false ]; then - if ls *.nrdb >/dev/null 2>&1; then + if ls *.db >/dev/null 2>&1 || ls build/*.db >/dev/null 2>&1; then echo "Removing existing database files to trigger fresh key generation..." - rm -f *.nrdb + rm -f *.db build/*.db echo "✓ Database files removed - will generate new keys and database" else echo "No existing database found - will generate fresh setup" fi else echo "Preserving existing database files as requested" + # Back up database files before clean build + if ls build/*.db >/dev/null 2>&1; then + echo "Backing up existing database files..." + mkdir -p /tmp/relay_backup_$$ + cp build/*.db* /tmp/relay_backup_$$/ 2>/dev/null || true + echo "Database files backed up to temporary location" + fi fi # Clean up legacy files that are no longer used @@ -70,6 +77,14 @@ rm -f db/c_nostr_relay.db* 2>/dev/null echo "Building project..." make clean all +# Restore database files if preserving +if [ "$PRESERVE_DATABASE" = true ] && [ -d "/tmp/relay_backup_$$" ]; then + echo "Restoring preserved database files..." + cp /tmp/relay_backup_$$/*.db* build/ 2>/dev/null || true + rm -rf /tmp/relay_backup_$$ + echo "Database files restored to build directory" +fi + # Check if build was successful if [ $? -ne 0 ]; then echo "ERROR: Build failed. Cannot restart relay." @@ -129,9 +144,13 @@ echo "Database will be initialized automatically on startup if needed" echo "Starting relay server..." echo "Debug: Current processes: $(ps aux | grep 'c_relay_' | grep -v grep || echo 'None')" +# Change to build directory before starting relay so database files are created there +cd build # Start relay in background and capture its PID (no command line arguments needed) -$BINARY_PATH > relay.log 2>&1 & +./$(basename $BINARY_PATH) > ../relay.log 2>&1 & RELAY_PID=$! +# Change back to original directory +cd .. echo "Started with PID: $RELAY_PID" diff --git a/relay.pid b/relay.pid index bc1ea67..eda1436 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -1073070 +1177520 diff --git a/relay2.log b/relay2.log new file mode 100644 index 0000000..c32587d --- /dev/null +++ b/relay2.log @@ -0,0 +1,35 @@ +=== 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 new file mode 100644 index 0000000..9936ca1 --- /dev/null +++ b/relay3.log @@ -0,0 +1,37 @@ +=== 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 c45074f..4651982 100644 --- a/src/config.c +++ b/src/config.c @@ -44,9 +44,9 @@ char** find_existing_nrdb_files(void) { return NULL; } - // Count .nrdb files + // Count .db files while ((entry = readdir(dir)) != NULL) { - if (strstr(entry->d_name, ".nrdb") != NULL) { + if (strstr(entry->d_name, ".db") != NULL) { count++; } } @@ -67,7 +67,7 @@ char** find_existing_nrdb_files(void) { // Store filenames int i = 0; while ((entry = readdir(dir)) != NULL && i < count) { - if (strstr(entry->d_name, ".nrdb") != NULL) { + if (strstr(entry->d_name, ".db") != NULL) { files[i] = malloc(strlen(entry->d_name) + 1); if (files[i]) { strcpy(files[i], entry->d_name); @@ -84,8 +84,8 @@ char** find_existing_nrdb_files(void) { char* extract_pubkey_from_filename(const char* filename) { if (!filename) return NULL; - // Find .nrdb extension - const char* dot = strstr(filename, ".nrdb"); + // Find .db extension + const char* dot = strstr(filename, ".db"); if (!dot) return NULL; // Calculate pubkey length @@ -107,10 +107,10 @@ char* get_database_name_from_relay_pubkey(const char* relay_pubkey) { return NULL; } - char* db_name = malloc(strlen(relay_pubkey) + 6); // +6 for ".nrdb\0" + char* db_name = malloc(strlen(relay_pubkey) + 4); // +4 for ".db\0" if (!db_name) return NULL; - - sprintf(db_name, "%s.nrdb", relay_pubkey); + + sprintf(db_name, "%s.db", relay_pubkey); return db_name; } @@ -642,6 +642,373 @@ int startup_existing_relay(const char* relay_pubkey) { return 0; } +// ================================ +// CONFIGURATION FIELD VALIDATION +// ================================ + +// Validation helper functions +static int is_valid_port(const char* port_str) { + if (!port_str) return 0; + + char* endptr; + long port = strtol(port_str, &endptr, 10); + + // Must be valid number and in valid port range + return (endptr != port_str && *endptr == '\0' && port >= 1 && port <= 65535); +} + +static int is_valid_boolean(const char* bool_str) { + if (!bool_str) return 0; + + return (strcasecmp(bool_str, "true") == 0 || + strcasecmp(bool_str, "false") == 0 || + strcasecmp(bool_str, "yes") == 0 || + strcasecmp(bool_str, "no") == 0 || + strcasecmp(bool_str, "1") == 0 || + strcasecmp(bool_str, "0") == 0); +} + +static int is_valid_positive_integer(const char* int_str) { + if (!int_str) return 0; + + char* endptr; + long val = strtol(int_str, &endptr, 10); + + return (endptr != int_str && *endptr == '\0' && val >= 0); +} + +static int is_valid_non_negative_integer(const char* int_str) { + if (!int_str) return 0; + + char* endptr; + long val = strtol(int_str, &endptr, 10); + + return (endptr != int_str && *endptr == '\0' && val >= 0); +} + +static int is_valid_string_length(const char* str, size_t max_length) { + if (!str) return 1; // NULL strings are valid (use defaults) + return strlen(str) <= max_length; +} + +static int is_valid_pow_mode(const char* mode_str) { + if (!mode_str) return 0; + + return (strcasecmp(mode_str, "basic") == 0 || + strcasecmp(mode_str, "strict") == 0 || + strcasecmp(mode_str, "disabled") == 0); +} + +static int is_valid_hex_key(const char* key_str) { + if (!key_str) return 0; + + // Must be exactly 64 hex characters + if (strlen(key_str) != 64) return 0; + + // Must contain only hex characters + for (int i = 0; i < 64; i++) { + char c = key_str[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + return 0; + } + } + return 1; +} + +// Main validation function for configuration fields +static int validate_config_field(const char* key, const char* value, char* error_msg, size_t error_size) { + if (!key || !value) { + snprintf(error_msg, error_size, "key or value is NULL"); + return -1; + } + + // Port validation + if (strcmp(key, "relay_port") == 0) { + if (!is_valid_port(value)) { + snprintf(error_msg, error_size, "invalid port number '%s' (must be 1-65535)", value); + return -1; + } + return 0; + } + + // Connection limits + if (strcmp(key, "max_connections") == 0) { + if (!is_valid_positive_integer(value)) { + snprintf(error_msg, error_size, "invalid max_connections '%s' (must be positive integer)", value); + return -1; + } + int val = atoi(value); + if (val < 1 || val > 10000) { + snprintf(error_msg, error_size, "max_connections '%s' out of range (1-10000)", value); + return -1; + } + return 0; + } + + // Boolean fields + if (strcmp(key, "auth_enabled") == 0 || + strcmp(key, "nip40_expiration_enabled") == 0 || + strcmp(key, "nip40_expiration_strict") == 0 || + strcmp(key, "nip40_expiration_filter") == 0) { + if (!is_valid_boolean(value)) { + snprintf(error_msg, error_size, "invalid boolean value '%s' for %s", value, key); + return -1; + } + return 0; + } + + // String length validation + if (strcmp(key, "relay_description") == 0) { + if (!is_valid_string_length(value, RELAY_DESCRIPTION_MAX_LENGTH)) { + snprintf(error_msg, error_size, "relay_description too long (max %d characters)", RELAY_DESCRIPTION_MAX_LENGTH); + return -1; + } + return 0; + } + + if (strcmp(key, "relay_contact") == 0) { + if (!is_valid_string_length(value, RELAY_CONTACT_MAX_LENGTH)) { + snprintf(error_msg, error_size, "relay_contact too long (max %d characters)", RELAY_CONTACT_MAX_LENGTH); + return -1; + } + return 0; + } + + if (strcmp(key, "relay_software") == 0 || + strcmp(key, "relay_version") == 0) { + if (!is_valid_string_length(value, 256)) { + snprintf(error_msg, error_size, "%s too long (max 256 characters)", key); + return -1; + } + return 0; + } + + // PoW difficulty validation + if (strcmp(key, "pow_min_difficulty") == 0) { + if (!is_valid_non_negative_integer(value)) { + snprintf(error_msg, error_size, "invalid pow_min_difficulty '%s' (must be non-negative integer)", value); + return -1; + } + int val = atoi(value); + if (val > 32) { // 32 is practically impossible + snprintf(error_msg, error_size, "pow_min_difficulty '%s' too high (max 32)", value); + return -1; + } + return 0; + } + + // PoW mode validation + if (strcmp(key, "pow_mode") == 0) { + if (!is_valid_pow_mode(value)) { + snprintf(error_msg, error_size, "invalid pow_mode '%s' (must be basic, strict, or disabled)", value); + return -1; + } + return 0; + } + + // Time-based validation + if (strcmp(key, "nip40_expiration_grace_period") == 0) { + if (!is_valid_non_negative_integer(value)) { + snprintf(error_msg, error_size, "invalid grace period '%s' (must be non-negative integer)", value); + return -1; + } + int val = atoi(value); + if (val > 86400) { // Max 1 day + snprintf(error_msg, error_size, "grace period '%s' too long (max 86400 seconds)", value); + return -1; + } + return 0; + } + + // Subscription limits + if (strcmp(key, "max_subscriptions_per_client") == 0) { + if (!is_valid_positive_integer(value)) { + snprintf(error_msg, error_size, "invalid max_subscriptions_per_client '%s'", value); + return -1; + } + int val = atoi(value); + if (val < 1 || val > 1000) { + snprintf(error_msg, error_size, "max_subscriptions_per_client '%s' out of range (1-1000)", value); + return -1; + } + return 0; + } + + if (strcmp(key, "max_total_subscriptions") == 0) { + if (!is_valid_positive_integer(value)) { + snprintf(error_msg, error_size, "invalid max_total_subscriptions '%s'", value); + return -1; + } + int val = atoi(value); + if (val < 1 || val > 100000) { + snprintf(error_msg, error_size, "max_total_subscriptions '%s' out of range (1-100000)", value); + return -1; + } + return 0; + } + + if (strcmp(key, "max_filters_per_subscription") == 0) { + if (!is_valid_positive_integer(value)) { + snprintf(error_msg, error_size, "invalid max_filters_per_subscription '%s'", value); + return -1; + } + int val = atoi(value); + if (val < 1 || val > 100) { + snprintf(error_msg, error_size, "max_filters_per_subscription '%s' out of range (1-100)", value); + return -1; + } + return 0; + } + + // Event limits + if (strcmp(key, "max_event_tags") == 0) { + if (!is_valid_positive_integer(value)) { + snprintf(error_msg, error_size, "invalid max_event_tags '%s'", value); + return -1; + } + int val = atoi(value); + if (val < 1 || val > 1000) { + snprintf(error_msg, error_size, "max_event_tags '%s' out of range (1-1000)", value); + return -1; + } + return 0; + } + + if (strcmp(key, "max_content_length") == 0) { + if (!is_valid_positive_integer(value)) { + snprintf(error_msg, error_size, "invalid max_content_length '%s'", value); + return -1; + } + int val = atoi(value); + if (val < 100 || val > 1048576) { // 1MB max + snprintf(error_msg, error_size, "max_content_length '%s' out of range (100-1048576)", value); + return -1; + } + return 0; + } + + if (strcmp(key, "max_message_length") == 0) { + if (!is_valid_positive_integer(value)) { + snprintf(error_msg, error_size, "invalid max_message_length '%s'", value); + return -1; + } + int val = atoi(value); + if (val < 1024 || val > 1048576) { // 1KB to 1MB + snprintf(error_msg, error_size, "max_message_length '%s' out of range (1024-1048576)", value); + return -1; + } + return 0; + } + + // Performance limits + if (strcmp(key, "default_limit") == 0 || strcmp(key, "max_limit") == 0) { + if (!is_valid_positive_integer(value)) { + snprintf(error_msg, error_size, "invalid %s '%s'", key, value); + return -1; + } + int val = atoi(value); + if (val < 1 || val > 50000) { + snprintf(error_msg, error_size, "%s '%s' out of range (1-50000)", key, value); + return -1; + } + return 0; + } + + // Key validation for relay keys + if (strcmp(key, "relay_pubkey") == 0 || strcmp(key, "relay_privkey") == 0) { + if (!is_valid_hex_key(value)) { + snprintf(error_msg, error_size, "invalid %s format (must be 64-character hex)", key); + return -1; + } + return 0; + } + + // Special validation for d tag (relay identifier) + if (strcmp(key, "d") == 0) { + if (!is_valid_hex_key(value)) { + snprintf(error_msg, error_size, "invalid relay identifier 'd' format (must be 64-character hex)"); + return -1; + } + return 0; + } + + // Unknown field - log warning but allow + log_warning("Unknown configuration field"); + printf(" Field: %s = %s\n", key, value); + return 0; +} + +// Validate all fields in a configuration event +static int validate_configuration_event_fields(const cJSON* event, char* error_msg, size_t error_size) { + if (!event) { + snprintf(error_msg, error_size, "null configuration event"); + return -1; + } + + log_info("Validating configuration event fields..."); + + cJSON* tags = cJSON_GetObjectItem(event, "tags"); + if (!tags || !cJSON_IsArray(tags)) { + snprintf(error_msg, error_size, "missing or invalid tags array"); + return -1; + } + + int validated_fields = 0; + int validation_errors = 0; + char field_error[512]; + + cJSON* tag = NULL; + cJSON_ArrayForEach(tag, tags) { + if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) { + cJSON* tag_key = cJSON_GetArrayItem(tag, 0); + cJSON* tag_value = cJSON_GetArrayItem(tag, 1); + + if (tag_key && tag_value && + cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) { + + const char* key = cJSON_GetStringValue(tag_key); + const char* value = cJSON_GetStringValue(tag_value); + + if (validate_config_field(key, value, field_error, sizeof(field_error)) != 0) { + // Safely truncate the error message if needed + size_t prefix_len = strlen("field validation failed: "); + size_t available_space = error_size > prefix_len ? error_size - prefix_len - 1 : 0; + + if (available_space > 0) { + snprintf(error_msg, error_size, "field validation failed: %.*s", + (int)available_space, field_error); + } else { + strncpy(error_msg, "field validation failed", error_size - 1); + error_msg[error_size - 1] = '\0'; + } + + log_error("Configuration field validation failed"); + printf(" Field: %s = %s\n", key, value); + printf(" Error: %s\n", field_error); + validation_errors++; + return -1; // Stop on first error + } else { + validated_fields++; + } + } + } + } + + if (validation_errors > 0) { + char summary[256]; + snprintf(summary, sizeof(summary), "%d configuration fields failed validation", validation_errors); + log_error(summary); + return -1; + } + + char success_msg[256]; + snprintf(success_msg, sizeof(success_msg), "%d configuration fields validated successfully", validated_fields); + log_success(success_msg); + return 0; +} + int process_configuration_event(const cJSON* event) { if (!event) { log_error("Invalid configuration event"); @@ -690,6 +1057,14 @@ int process_configuration_event(const cJSON* event) { log_success("Configuration event structure and signature validated successfully"); + // NEW: Validate configuration field values + char validation_error[512]; + if (validate_configuration_event_fields(event, validation_error, sizeof(validation_error)) != 0) { + log_error("Configuration field validation failed"); + printf(" Validation error: %s\n", validation_error); + return -1; + } + // Store in database if (store_config_event_in_database(event) != 0) { log_error("Failed to store configuration event"); @@ -702,7 +1077,7 @@ int process_configuration_event(const cJSON* event) { return -1; } - log_success("Configuration event processed successfully"); + log_success("Configuration event processed successfully with field validation"); return 0; } diff --git a/src/main.c b/src/main.c index e30b4c3..d3b3e41 100644 --- a/src/main.c +++ b/src/main.c @@ -10,6 +10,10 @@ #include #include #include +#include +#include +#include +#include // Include nostr_core_lib for Nostr functionality #include "../nostr_core_lib/cjson/cJSON.h" @@ -2956,6 +2960,34 @@ static struct lws_protocols protocols[] = { { NULL, NULL, 0, 0, 0, NULL, 0 } // terminator }; +// Check if a port is available for binding +int check_port_available(int port) { + int sockfd; + struct sockaddr_in addr; + int result; + + // Create a socket + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + return 0; // Cannot create socket, assume port unavailable + } + + // Set up the address structure + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + // Try to bind to the port + result = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); + + // Close the socket + close(sockfd); + + // Return 1 if bind succeeded (port available), 0 if failed (port in use) + return (result == 0) ? 1 : 0; +} + // Start libwebsockets-based WebSocket Nostr relay server int start_websocket_relay(int port_override) { struct lws_context_creation_info info; @@ -2964,12 +2996,15 @@ int start_websocket_relay(int port_override) { memset(&info, 0, sizeof(info)); // Use port override if provided, otherwise use configuration - info.port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT); + int configured_port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT); + int actual_port = configured_port; + int port_attempts = 0; + const int max_port_attempts = 5; + + // Minimal libwebsockets configuration info.protocols = protocols; info.gid = -1; info.uid = -1; - - // Minimal libwebsockets configuration info.options = LWS_SERVER_OPTION_VALIDATE_UTF8; // Remove interface restrictions - let system choose @@ -2983,15 +3018,82 @@ int start_websocket_relay(int port_override) { // Max payload size for Nostr events info.max_http_header_data = 4096; - ws_context = lws_create_context(&info); + // Find an available port with pre-checking + while (port_attempts < max_port_attempts) { + char attempt_msg[256]; + snprintf(attempt_msg, sizeof(attempt_msg), "Checking port availability: %d", actual_port); + log_info(attempt_msg); + + // Pre-check if port is available + if (!check_port_available(actual_port)) { + port_attempts++; + if (port_attempts < max_port_attempts) { + char retry_msg[256]; + snprintf(retry_msg, sizeof(retry_msg), "Port %d is in use, trying port %d (attempt %d/%d)", + actual_port, actual_port + 1, port_attempts + 1, max_port_attempts); + log_warning(retry_msg); + actual_port++; + continue; + } else { + char error_msg[512]; + snprintf(error_msg, sizeof(error_msg), + "Failed to find available port after %d attempts (tried ports %d-%d)", + max_port_attempts, configured_port, actual_port); + log_error(error_msg); + return -1; + } + } + + // Port appears available, try creating libwebsockets context + info.port = actual_port; + + char binding_msg[256]; + snprintf(binding_msg, sizeof(binding_msg), "Attempting to bind libwebsockets to port %d", actual_port); + log_info(binding_msg); + + ws_context = lws_create_context(&info); + if (ws_context) { + // Success! Port binding worked + break; + } + + // libwebsockets failed even though port check passed + // This could be due to timing or different socket options + int errno_saved = errno; + char lws_error_msg[256]; + snprintf(lws_error_msg, sizeof(lws_error_msg), + "libwebsockets failed to bind to port %d (errno: %d)", actual_port, errno_saved); + log_warning(lws_error_msg); + + port_attempts++; + if (port_attempts < max_port_attempts) { + actual_port++; + continue; + } + + // If we get here, we've exhausted attempts + break; + } + if (!ws_context) { - log_error("Failed to create libwebsockets context"); + char error_msg[512]; + snprintf(error_msg, sizeof(error_msg), + "Failed to create libwebsockets context after %d attempts. Last attempted port: %d", + port_attempts, actual_port); + log_error(error_msg); perror("libwebsockets creation error"); return -1; } char startup_msg[256]; - snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", info.port); + if (actual_port != configured_port) { + snprintf(startup_msg, sizeof(startup_msg), + "WebSocket relay started on ws://127.0.0.1:%d (configured port %d was unavailable)", + actual_port, configured_port); + log_warning(startup_msg); + } else { + snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", actual_port); + } log_success(startup_msg); // Main event loop with proper signal handling @@ -3034,7 +3136,7 @@ void print_usage(const char* program_name) { printf("Configuration:\n"); printf(" This relay uses event-based configuration stored in the database.\n"); printf(" On first startup, keys are automatically generated and printed once.\n"); - printf(" Database file: .nrdb (created automatically)\n"); + printf(" Database file: .db (created automatically)\n"); printf("\n"); printf("Examples:\n"); printf(" %s # Start relay (auto-configure on first run)\n", program_name); diff --git a/test_port_increment.log b/test_port_increment.log new file mode 100644 index 0000000..9a24255 --- /dev/null +++ b/test_port_increment.log @@ -0,0 +1,47 @@ +=== 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