#include "config.h" #include "default_config_event.h" #include "../nostr_core_lib/nostr_core/nostr_core.h" #include #include #include #include #include #include #include #include // External database connection (from main.c) extern sqlite3* g_db; // Global configuration manager instance config_manager_t g_config_manager = {0}; char g_database_path[512] = {0}; // Logging functions (defined in main.c) extern void log_info(const char* message); extern void log_success(const char* message); extern void log_warning(const char* message); extern void log_error(const char* message); // Current configuration cache static cJSON* g_current_config = NULL; // Cache for initial configuration event (before database is initialized) static cJSON* g_pending_config_event = NULL; // ================================ // UTILITY FUNCTIONS // ================================ char** find_existing_nrdb_files(void) { DIR *dir; struct dirent *entry; char **files = NULL; int count = 0; dir = opendir("."); if (dir == NULL) { return NULL; } // Count .nrdb files while ((entry = readdir(dir)) != NULL) { if (strstr(entry->d_name, ".nrdb") != NULL) { count++; } } rewinddir(dir); if (count == 0) { closedir(dir); return NULL; } // Allocate array for filenames files = malloc((count + 1) * sizeof(char*)); if (!files) { closedir(dir); return NULL; } // Store filenames int i = 0; while ((entry = readdir(dir)) != NULL && i < count) { if (strstr(entry->d_name, ".nrdb") != NULL) { files[i] = malloc(strlen(entry->d_name) + 1); if (files[i]) { strcpy(files[i], entry->d_name); i++; } } } files[i] = NULL; // Null terminate closedir(dir); return files; } char* extract_pubkey_from_filename(const char* filename) { if (!filename) return NULL; // Find .nrdb extension const char* dot = strstr(filename, ".nrdb"); if (!dot) return NULL; // Calculate pubkey length size_t pubkey_len = dot - filename; if (pubkey_len != 64) return NULL; // Invalid pubkey length // Allocate and copy pubkey char* pubkey = malloc(pubkey_len + 1); if (!pubkey) return NULL; strncpy(pubkey, filename, pubkey_len); pubkey[pubkey_len] = '\0'; return pubkey; } char* get_database_name_from_relay_pubkey(const char* relay_pubkey) { if (!relay_pubkey || strlen(relay_pubkey) != 64) { return NULL; } char* db_name = malloc(strlen(relay_pubkey) + 6); // +6 for ".nrdb\0" if (!db_name) return NULL; sprintf(db_name, "%s.nrdb", relay_pubkey); return db_name; } // ================================ // DATABASE FUNCTIONS // ================================ int create_database_with_relay_pubkey(const char* relay_pubkey) { char* db_name = get_database_name_from_relay_pubkey(relay_pubkey); if (!db_name) { log_error("Failed to generate database name"); return -1; } strncpy(g_database_path, db_name, sizeof(g_database_path) - 1); g_database_path[sizeof(g_database_path) - 1] = '\0'; log_info("Creating database with relay pubkey"); printf(" Database: %s\n", db_name); free(db_name); return 0; } // ================================ // CONFIGURATION EVENT FUNCTIONS // ================================ int store_config_event_in_database(const cJSON* event) { if (!event || !g_db) { return -1; } // Get event fields cJSON* id_obj = cJSON_GetObjectItem(event, "id"); cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at"); cJSON* kind_obj = cJSON_GetObjectItem(event, "kind"); cJSON* content_obj = cJSON_GetObjectItem(event, "content"); cJSON* sig_obj = cJSON_GetObjectItem(event, "sig"); cJSON* tags_obj = cJSON_GetObjectItem(event, "tags"); if (!id_obj || !pubkey_obj || !created_at_obj || !kind_obj || !content_obj || !sig_obj || !tags_obj) { return -1; } // Convert tags to JSON string char* tags_str = cJSON_Print(tags_obj); if (!tags_str) { return -1; } // Insert or replace the configuration event (kind 33334 is replaceable) const char* sql = "INSERT OR REPLACE INTO events (id, pubkey, created_at, kind, event_type, content, sig, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { log_error("Failed to prepare configuration event insert"); free(tags_str); return -1; } sqlite3_bind_text(stmt, 1, cJSON_GetStringValue(id_obj), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(pubkey_obj), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 3, (sqlite3_int64)cJSON_GetNumberValue(created_at_obj)); sqlite3_bind_int(stmt, 4, (int)cJSON_GetNumberValue(kind_obj)); sqlite3_bind_text(stmt, 5, "addressable", -1, SQLITE_STATIC); // kind 33334 is addressable sqlite3_bind_text(stmt, 6, cJSON_GetStringValue(content_obj), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 7, cJSON_GetStringValue(sig_obj), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 8, tags_str, -1, SQLITE_TRANSIENT); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); free(tags_str); if (rc == SQLITE_DONE) { log_success("Configuration event stored in database"); return 0; } else { log_error("Failed to store configuration event"); return -1; } } cJSON* load_config_event_from_database(const char* relay_pubkey) { if (!g_db || !relay_pubkey) { return NULL; } const char* sql; sqlite3_stmt* stmt; int rc; // If we have admin pubkey, query by it; otherwise find the most recent kind 33334 event if (strlen(g_config_manager.admin_pubkey) > 0) { sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 AND pubkey = ? ORDER BY created_at DESC LIMIT 1"; rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { log_error("Failed to prepare configuration event query"); return NULL; } sqlite3_bind_text(stmt, 1, g_config_manager.admin_pubkey, -1, SQLITE_STATIC); } else { // During existing relay startup, we don't know the admin pubkey yet // Look for any kind 33334 configuration event (should only be one per relay) sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1"; rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { log_error("Failed to prepare configuration event query"); return NULL; } } cJSON* event = NULL; if (sqlite3_step(stmt) == SQLITE_ROW) { // Reconstruct the event JSON from database columns event = cJSON_CreateObject(); if (event) { const char* event_pubkey = (const char*)sqlite3_column_text(stmt, 1); cJSON_AddStringToObject(event, "id", (const char*)sqlite3_column_text(stmt, 0)); cJSON_AddStringToObject(event, "pubkey", event_pubkey); cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2)); cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3)); cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4)); cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5)); // If we didn't have admin pubkey, store it now if (strlen(g_config_manager.admin_pubkey) == 0) { strncpy(g_config_manager.admin_pubkey, event_pubkey, sizeof(g_config_manager.admin_pubkey) - 1); g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0'; } // Parse tags JSON const char* tags_str = (const char*)sqlite3_column_text(stmt, 6); if (tags_str) { cJSON* tags = cJSON_Parse(tags_str); if (tags) { cJSON_AddItemToObject(event, "tags", tags); } else { cJSON_AddItemToObject(event, "tags", cJSON_CreateArray()); } } else { cJSON_AddItemToObject(event, "tags", cJSON_CreateArray()); } } } sqlite3_finalize(stmt); return event; } // ================================ // CONFIGURATION ACCESS FUNCTIONS // ================================ const char* get_config_value(const char* key) { static char buffer[CONFIG_VALUE_MAX_LENGTH]; if (!key || !g_current_config) { return NULL; } // Look for key in current configuration tags cJSON* tags = cJSON_GetObjectItem(g_current_config, "tags"); if (!tags || !cJSON_IsArray(tags)) { return NULL; } 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)) { if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) { strncpy(buffer, cJSON_GetStringValue(tag_value), sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; return buffer; } } } } return NULL; } int get_config_int(const char* key, int default_value) { const char* str_value = get_config_value(key); if (!str_value) { return default_value; } char* endptr; long val = strtol(str_value, &endptr, 10); if (endptr == str_value || *endptr != '\0') { return default_value; } return (int)val; } int get_config_bool(const char* key, int default_value) { const char* str_value = get_config_value(key); if (!str_value) { return default_value; } if (strcasecmp(str_value, "true") == 0 || strcasecmp(str_value, "yes") == 0 || strcasecmp(str_value, "1") == 0) { return 1; } else if (strcasecmp(str_value, "false") == 0 || strcasecmp(str_value, "no") == 0 || strcasecmp(str_value, "0") == 0) { return 0; } return default_value; } // ================================ // FIRST-TIME STARTUP FUNCTIONS // ================================ int is_first_time_startup(void) { char** existing_files = find_existing_nrdb_files(); if (existing_files) { // Free the array for (int i = 0; existing_files[i]; i++) { free(existing_files[i]); } free(existing_files); return 0; // Not first time } return 1; // First time } // ================================ // COMPATIBILITY FUNCTIONS // ================================ int init_configuration_system(const char* config_dir_override, const char* config_file_override) { // Suppress unused parameter warnings for compatibility function (void)config_dir_override; (void)config_file_override; log_info("Initializing event-based configuration system..."); // Clear configuration manager state memset(&g_config_manager, 0, sizeof(config_manager_t)); g_config_manager.db = g_db; // For now, set empty paths for compatibility g_config_manager.config_file_path[0] = '\0'; log_success("Event-based configuration system initialized"); return 0; } void cleanup_configuration_system(void) { log_info("Cleaning up configuration system..."); if (g_current_config) { cJSON_Delete(g_current_config); g_current_config = NULL; } if (g_pending_config_event) { cJSON_Delete(g_pending_config_event); g_pending_config_event = NULL; } memset(&g_config_manager, 0, sizeof(config_manager_t)); log_success("Configuration system cleaned up"); } int set_database_config(const char* key, const char* value, const char* changed_by) { // Suppress unused parameter warnings for compatibility function (void)key; (void)value; (void)changed_by; // Temporary compatibility function - does nothing for now // In the new system, configuration is only updated via events log_warning("set_database_config called - not supported in event-based config"); return 0; } // ================================ // KEY GENERATION FUNCTIONS // ================================ // Helper function to generate random private key int generate_random_private_key_bytes(unsigned char* privkey_bytes) { if (!privkey_bytes) { return -1; } FILE* urandom = fopen("/dev/urandom", "rb"); if (!urandom) { log_error("Failed to open /dev/urandom for key generation"); return -1; } if (fread(privkey_bytes, 1, 32, urandom) != 32) { log_error("Failed to read random bytes for private key"); fclose(urandom); return -1; } fclose(urandom); // Verify the private key is valid using nostr_core_lib if (nostr_ec_private_key_verify(privkey_bytes) != NOSTR_SUCCESS) { log_error("Generated private key failed validation"); return -1; } return 0; } // ================================ // DEFAULT CONFIG EVENT CREATION // ================================ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes, const char* relay_privkey_hex, const char* relay_pubkey_hex) { if (!admin_privkey_bytes || !relay_privkey_hex || !relay_pubkey_hex) { log_error("Invalid parameters for creating default config event"); return NULL; } log_info("Creating default configuration event..."); // Create tags array with default configuration values cJSON* tags = cJSON_CreateArray(); if (!tags) { log_error("Failed to create tags array"); return NULL; } // Add d tag with relay pubkey cJSON* d_tag = cJSON_CreateArray(); cJSON_AddItemToArray(d_tag, cJSON_CreateString("d")); cJSON_AddItemToArray(d_tag, cJSON_CreateString(relay_pubkey_hex)); cJSON_AddItemToArray(tags, d_tag); // Add relay keys cJSON* relay_pubkey_tag = cJSON_CreateArray(); cJSON_AddItemToArray(relay_pubkey_tag, cJSON_CreateString("relay_pubkey")); 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); // Add all default configuration values for (size_t i = 0; i < DEFAULT_CONFIG_COUNT; i++) { cJSON* tag = cJSON_CreateArray(); cJSON_AddItemToArray(tag, cJSON_CreateString(DEFAULT_CONFIG_VALUES[i].key)); cJSON_AddItemToArray(tag, cJSON_CreateString(DEFAULT_CONFIG_VALUES[i].value)); cJSON_AddItemToArray(tags, tag); } // Create and sign event using nostr_core_lib cJSON* event = nostr_create_and_sign_event( 33334, // kind "C Nostr Relay Configuration", // content tags, // tags admin_privkey_bytes, // private key bytes for signing time(NULL) // created_at timestamp ); cJSON_Delete(tags); // Clean up tags as they were duplicated in nostr_create_and_sign_event if (!event) { log_error("Failed to create and sign configuration event"); return NULL; } // Log success information cJSON* id_obj = cJSON_GetObjectItem(event, "id"); cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); if (id_obj && pubkey_obj) { log_success("Default configuration event created successfully"); printf(" Event ID: %s\n", cJSON_GetStringValue(id_obj)); printf(" Admin Public Key: %s\n", cJSON_GetStringValue(pubkey_obj)); } return event; } // ================================ // IMPLEMENTED FUNCTIONS // ================================ int first_time_startup_sequence(void) { log_info("Starting first-time startup sequence..."); // 1. Generate admin keypair using /dev/urandom + nostr_core_lib unsigned char admin_privkey_bytes[32]; char admin_privkey[65], admin_pubkey[65]; if (generate_random_private_key_bytes(admin_privkey_bytes) != 0) { log_error("Failed to generate admin private key"); return -1; } nostr_bytes_to_hex(admin_privkey_bytes, 32, admin_privkey); unsigned char admin_pubkey_bytes[32]; if (nostr_ec_public_key_from_private_key(admin_privkey_bytes, admin_pubkey_bytes) != NOSTR_SUCCESS) { log_error("Failed to derive admin public key"); return -1; } nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey); // 2. Generate relay keypair using /dev/urandom + nostr_core_lib unsigned char relay_privkey_bytes[32]; char relay_privkey[65], relay_pubkey[65]; if (generate_random_private_key_bytes(relay_privkey_bytes) != 0) { log_error("Failed to generate relay private key"); return -1; } nostr_bytes_to_hex(relay_privkey_bytes, 32, relay_privkey); unsigned char relay_pubkey_bytes[32]; if (nostr_ec_public_key_from_private_key(relay_privkey_bytes, relay_pubkey_bytes) != NOSTR_SUCCESS) { log_error("Failed to derive relay public key"); return -1; } nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey); // 3. Store keys in global config manager strncpy(g_config_manager.admin_pubkey, admin_pubkey, sizeof(g_config_manager.admin_pubkey) - 1); g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0'; strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1); g_config_manager.relay_pubkey[sizeof(g_config_manager.relay_pubkey) - 1] = '\0'; // 4. Create database with relay pubkey name if (create_database_with_relay_pubkey(relay_pubkey) != 0) { log_error("Failed to create database with relay pubkey"); return -1; } // 5. Create initial configuration event using defaults cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey); 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 if (store_config_event_in_database(config_event) == 0) { log_success("Initial configuration event stored successfully"); } else { log_warning("Failed to store initial configuration event - will retry after database init"); // Cache the event for later storage if (g_pending_config_event) { cJSON_Delete(g_pending_config_event); } g_pending_config_event = cJSON_Duplicate(config_event, 1); } // 7. Cache the current config if (g_current_config) { cJSON_Delete(g_current_config); } g_current_config = cJSON_Duplicate(config_event, 1); // 8. Clean up cJSON_Delete(config_event); // 9. Print admin private key for user to save printf("\n"); printf("=================================================================\n"); printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n"); printf("=================================================================\n"); printf("Admin Private Key: %s\n", admin_privkey); printf("Admin Public Key: %s\n", admin_pubkey); printf("\nRelay Private Key: %s\n", relay_privkey); printf("Relay Public Key: %s\n", relay_pubkey); printf("\nDatabase: %s\n", g_database_path); printf("\nThis admin private key is needed to update configuration!\n"); printf("Store it safely - it will not be displayed again.\n"); printf("=================================================================\n"); printf("\n"); log_success("First-time startup sequence completed"); return 0; } int startup_existing_relay(const char* relay_pubkey) { if (!relay_pubkey) { log_error("Invalid relay pubkey for existing relay startup"); return -1; } log_info("Starting existing relay..."); printf(" Relay pubkey: %s\n", relay_pubkey); // Store relay pubkey in global config manager strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1); // Set database path char* db_name = get_database_name_from_relay_pubkey(relay_pubkey); if (!db_name) { log_error("Failed to generate database name"); return -1; } strncpy(g_database_path, db_name, sizeof(g_database_path) - 1); g_database_path[sizeof(g_database_path) - 1] = '\0'; free(db_name); // Load configuration event from database (after database is initialized) // This will be done in apply_configuration_from_database() log_success("Existing relay startup prepared"); return 0; } int process_configuration_event(const cJSON* event) { if (!event) { log_error("Invalid configuration event"); return -1; } log_info("Processing configuration event..."); // Validate event structure cJSON* kind_obj = cJSON_GetObjectItem(event, "kind"); cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) { log_error("Invalid event kind for configuration"); return -1; } if (!pubkey_obj) { log_error("Missing pubkey in configuration event"); return -1; } // Verify it's from the admin const char* event_pubkey = cJSON_GetStringValue(pubkey_obj); if (strlen(g_config_manager.admin_pubkey) > 0) { if (strcmp(event_pubkey, g_config_manager.admin_pubkey) != 0) { log_error("Configuration event not from authorized admin"); return -1; } } // Comprehensive event validation using nostr_core_lib log_info("Validating configuration event structure and signature..."); // First validate the event structure (fields, format, etc.) if (nostr_validate_event_structure((cJSON*)event) != NOSTR_SUCCESS) { log_error("Configuration event has invalid structure"); return -1; } // Then validate the cryptographic signature if (nostr_verify_event_signature((cJSON*)event) != NOSTR_SUCCESS) { log_error("Configuration event has invalid signature"); return -1; } log_success("Configuration event structure and signature validated successfully"); // Store in database if (store_config_event_in_database(event) != 0) { log_error("Failed to store configuration event"); return -1; } // Apply configuration if (apply_configuration_from_event(event) != 0) { log_error("Failed to apply configuration from event"); return -1; } log_success("Configuration event processed successfully"); return 0; } // ================================ // RUNTIME CONFIGURATION HANDLERS // ================================ // External functions and globals from main.c that need to be updated extern void update_subscription_manager_config(void); extern void init_pow_config(void); extern void init_expiration_config(void); extern void init_relay_info(void); // Compare configuration values between old and new config static const char* get_config_value_from_event(const cJSON* event, const char* key) { if (!event || !key) return NULL; cJSON* tags = cJSON_GetObjectItem(event, "tags"); if (!tags || !cJSON_IsArray(tags)) return NULL; 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)) { if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) { return cJSON_GetStringValue(tag_value); } } } } return NULL; } // Check if a configuration value has changed static int config_value_changed(const cJSON* old_config, const cJSON* new_config, const char* key) { const char* old_value = get_config_value_from_event(old_config, key); const char* new_value = get_config_value_from_event(new_config, key); // Both NULL - no change if (!old_value && !new_value) return 0; // One is NULL, other isn't - changed if (!old_value || !new_value) return 1; // Compare string values return strcmp(old_value, new_value) != 0; } // Apply runtime configuration changes by calling appropriate handlers int apply_runtime_config_handlers(const cJSON* old_config, const cJSON* new_config) { if (!new_config) return 0; int handlers_applied = 0; log_info("Checking for runtime configuration changes..."); // Subscription Manager Configuration if (config_value_changed(old_config, new_config, "max_subscriptions_per_client") || config_value_changed(old_config, new_config, "max_total_subscriptions")) { log_info("Subscription limits changed - updating subscription manager"); update_subscription_manager_config(); handlers_applied++; } // NIP-13 Proof of Work Configuration if (config_value_changed(old_config, new_config, "pow_min_difficulty") || config_value_changed(old_config, new_config, "pow_mode")) { log_info("PoW configuration changed - reinitializing PoW system"); init_pow_config(); handlers_applied++; } // NIP-40 Expiration Configuration if (config_value_changed(old_config, new_config, "nip40_expiration_enabled") || config_value_changed(old_config, new_config, "nip40_expiration_strict") || config_value_changed(old_config, new_config, "nip40_expiration_filter") || config_value_changed(old_config, new_config, "nip40_expiration_grace_period")) { log_info("Expiration configuration changed - reinitializing expiration system"); init_expiration_config(); handlers_applied++; } // NIP-11 Relay Information if (config_value_changed(old_config, new_config, "relay_description") || config_value_changed(old_config, new_config, "relay_contact") || config_value_changed(old_config, new_config, "relay_software") || config_value_changed(old_config, new_config, "relay_version") || config_value_changed(old_config, new_config, "max_message_length") || config_value_changed(old_config, new_config, "max_event_tags") || config_value_changed(old_config, new_config, "max_content_length")) { log_info("Relay information changed - reinitializing relay info"); init_relay_info(); handlers_applied++; } // Log configuration changes for audit if (handlers_applied > 0) { char audit_msg[512]; snprintf(audit_msg, sizeof(audit_msg), "Configuration updated via kind 33334 event - %d system components reinitialized", handlers_applied); log_success(audit_msg); } else { log_info("No runtime configuration changes detected"); } return handlers_applied; } int apply_configuration_from_event(const cJSON* event) { if (!event) { log_error("Invalid event for configuration application"); return -1; } log_info("Applying configuration from event..."); // Store previous config for comparison cJSON* old_config = g_current_config; // Update cached configuration g_current_config = cJSON_Duplicate(event, 1); // Extract admin pubkey if not already set cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); if (pubkey_obj && strlen(g_config_manager.admin_pubkey) == 0) { strncpy(g_config_manager.admin_pubkey, cJSON_GetStringValue(pubkey_obj), sizeof(g_config_manager.admin_pubkey) - 1); } // Apply runtime configuration changes int handlers_applied = apply_runtime_config_handlers(old_config, g_current_config); // Clean up old config if (old_config) { cJSON_Delete(old_config); } char success_msg[256]; snprintf(success_msg, sizeof(success_msg), "Configuration applied from event (%d handlers executed)", handlers_applied); log_success(success_msg); return 0; } // ================================ // REAL-TIME EVENT HANDLER (called from main.c) // ================================ // Handle kind 33334 configuration events received via WebSocket int handle_configuration_event(cJSON* event, char* error_message, size_t error_size) { if (!event) { snprintf(error_message, error_size, "invalid: null configuration event"); return -1; } log_info("Handling configuration event from WebSocket"); // Use existing process_configuration_event function if (process_configuration_event(event) == 0) { // Success error_message[0] = '\0'; // Empty error message indicates success return 0; } else { // Failed to process snprintf(error_message, error_size, "error: failed to process configuration event"); return -1; } } // ================================ // RETRY INITIAL CONFIG EVENT STORAGE // ================================ int retry_store_initial_config_event(void) { if (!g_pending_config_event) { // No pending event to store return 0; } log_info("Retrying storage of initial configuration event..."); // Try to store the cached configuration event if (store_config_event_in_database(g_pending_config_event) == 0) { log_success("Initial configuration event stored successfully on retry"); // Clean up the pending event cJSON_Delete(g_pending_config_event); g_pending_config_event = NULL; return 0; } else { log_error("Failed to store initial configuration event on retry"); return -1; } }