#include "config.h" #include "version.h" #include #include #include #include #include #include #include #include #include "../nostr_core_lib/nostr_core/nostr_core.h" // External database connection (from main.c) extern sqlite3* g_db; // Global configuration manager instance config_manager_t g_config_manager = {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); // ================================ // CORE CONFIGURATION FUNCTIONS // ================================ // int init_configuration_system(void) { log_info("Initializing configuration system..."); // Clear configuration manager state memset(&g_config_manager, 0, sizeof(config_manager_t)); g_config_manager.db = g_db; // Get XDG configuration directory if (get_xdg_config_dir(g_config_manager.config_dir_path, sizeof(g_config_manager.config_dir_path)) != 0) { log_error("Failed to determine XDG configuration directory"); return -1; } // Build configuration file path snprintf(g_config_manager.config_file_path, sizeof(g_config_manager.config_file_path), "%s/%s", g_config_manager.config_dir_path, CONFIG_FILE_NAME); log_info("Configuration directory: %s"); printf(" %s\n", g_config_manager.config_dir_path); log_info("Configuration file: %s"); printf(" %s\n", g_config_manager.config_file_path); // Initialize database prepared statements if (init_config_database_statements() != 0) { log_error("Failed to initialize configuration database statements"); return -1; } // Generate configuration file if missing if (generate_config_file_if_missing() != 0) { log_warning("Failed to generate configuration file, continuing with database configuration"); } // Load configuration from all sources if (load_configuration() != 0) { log_error("Failed to load configuration"); return -1; } // Apply configuration to global variables if (apply_configuration_to_globals() != 0) { log_error("Failed to apply configuration to global variables"); return -1; } log_success("Configuration system initialized successfully"); return 0; } void cleanup_configuration_system(void) { log_info("Cleaning up configuration system..."); // Finalize prepared statements if (g_config_manager.get_config_stmt) { sqlite3_finalize(g_config_manager.get_config_stmt); g_config_manager.get_config_stmt = NULL; } if (g_config_manager.set_config_stmt) { sqlite3_finalize(g_config_manager.set_config_stmt); g_config_manager.set_config_stmt = NULL; } if (g_config_manager.log_change_stmt) { sqlite3_finalize(g_config_manager.log_change_stmt); g_config_manager.log_change_stmt = NULL; } // Clear manager state memset(&g_config_manager, 0, sizeof(config_manager_t)); log_success("Configuration system cleaned up"); } int load_configuration(void) { log_info("Loading configuration from all sources..."); // Try to load configuration from file first if (config_file_exists()) { log_info("Configuration file found, attempting to load..."); if (load_config_from_file() == 0) { g_config_manager.file_config_loaded = 1; log_success("File configuration loaded successfully"); } else { log_warning("Failed to load file configuration, falling back to database"); } } else { log_info("No configuration file found, checking database"); } // Load configuration from database (either as primary or fallback) if (load_config_from_database() == 0) { g_config_manager.database_config_loaded = 1; log_success("Database configuration loaded"); } else { log_error("Failed to load database configuration"); return -1; } g_config_manager.last_reload = time(NULL); return 0; } int apply_configuration_to_globals(void) { log_info("Applying configuration to global variables..."); // Apply configuration values to existing global variables // This would update the existing hardcoded values with database values // For now, this is a placeholder - in Phase 4 we'll implement // the actual mapping to existing global variables log_success("Configuration applied to global variables"); return 0; } // ================================ // DATABASE CONFIGURATION FUNCTIONS // ================================ int init_config_database_statements(void) { if (!g_db) { log_error("Database connection not available for configuration"); return -1; } log_info("Initializing configuration database statements..."); // Prepare statement for getting configuration values const char* get_sql = "SELECT value FROM server_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"); return -1; } // Prepare statement for setting configuration values const char* set_sql = "INSERT OR REPLACE INTO server_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"); return -1; } // Prepare statement for logging configuration changes const char* log_sql = "INSERT INTO config_history (config_key, old_value, new_value, changed_by) VALUES (?, ?, ?, ?)"; rc = sqlite3_prepare_v2(g_db, log_sql, -1, &g_config_manager.log_change_stmt, NULL); if (rc != SQLITE_OK) { log_error("Failed to prepare log_change statement"); return -1; } log_success("Configuration database statements initialized"); return 0; } int get_database_config(const char* key, char* value, size_t value_size) { if (!key || !value || !g_config_manager.get_config_stmt) { return -1; } // Reset and bind parameters sqlite3_reset(g_config_manager.get_config_stmt); sqlite3_bind_text(g_config_manager.get_config_stmt, 1, key, -1, SQLITE_STATIC); int result = -1; if (sqlite3_step(g_config_manager.get_config_stmt) == SQLITE_ROW) { const char* db_value = (const char*)sqlite3_column_text(g_config_manager.get_config_stmt, 0); if (db_value) { strncpy(value, db_value, value_size - 1); value[value_size - 1] = '\0'; result = 0; } } return result; } int set_database_config(const char* key, const char* new_value, const char* changed_by) { if (!key || !new_value || !g_config_manager.set_config_stmt) { return -1; } // Get old value for logging char old_value[CONFIG_VALUE_MAX_LENGTH] = {0}; get_database_config(key, old_value, sizeof(old_value)); // Set new value sqlite3_reset(g_config_manager.set_config_stmt); sqlite3_bind_text(g_config_manager.set_config_stmt, 1, key, -1, SQLITE_STATIC); sqlite3_bind_text(g_config_manager.set_config_stmt, 2, new_value, -1, SQLITE_STATIC); int result = 0; if (sqlite3_step(g_config_manager.set_config_stmt) != SQLITE_DONE) { log_error("Failed to set configuration value"); result = -1; } else { // Log the change if (g_config_manager.log_change_stmt) { sqlite3_reset(g_config_manager.log_change_stmt); sqlite3_bind_text(g_config_manager.log_change_stmt, 1, key, -1, SQLITE_STATIC); sqlite3_bind_text(g_config_manager.log_change_stmt, 2, strlen(old_value) > 0 ? old_value : NULL, -1, SQLITE_STATIC); sqlite3_bind_text(g_config_manager.log_change_stmt, 3, new_value, -1, SQLITE_STATIC); sqlite3_bind_text(g_config_manager.log_change_stmt, 4, changed_by ? changed_by : "system", -1, SQLITE_STATIC); sqlite3_step(g_config_manager.log_change_stmt); } } return result; } int load_config_from_database(void) { log_info("Loading configuration from database..."); // 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')"; sqlite3_stmt* test_stmt; int rc = sqlite3_prepare_v2(g_db, test_sql, -1, &test_stmt, NULL); if (rc != SQLITE_OK) { log_error("Failed to prepare database configuration test query"); return -1; } int config_count = 0; if (sqlite3_step(test_stmt) == SQLITE_ROW) { config_count = sqlite3_column_int(test_stmt, 0); } sqlite3_finalize(test_stmt); if (config_count > 0) { log_success("Database configuration validated (%d entries)"); printf(" Found %d configuration entries\n", config_count); return 0; } else { log_error("No configuration entries found in database"); return -1; } } // ================================ // FILE CONFIGURATION FUNCTIONS // ================================ int get_xdg_config_dir(char* path, size_t path_size) { const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); if (xdg_config_home && strlen(xdg_config_home) > 0) { // Use XDG_CONFIG_HOME if set snprintf(path, path_size, "%s/%s", xdg_config_home, CONFIG_XDG_DIR_NAME); } else { // Fall back to ~/.config const char* home = getenv("HOME"); if (!home) { log_error("Neither XDG_CONFIG_HOME nor HOME environment variable is set"); return -1; } snprintf(path, path_size, "%s/.config/%s", home, CONFIG_XDG_DIR_NAME); } return 0; } int config_file_exists(void) { struct stat st; return (stat(g_config_manager.config_file_path, &st) == 0); } int load_config_from_file(void) { log_info("Loading configuration from file..."); FILE* file = fopen(g_config_manager.config_file_path, "r"); if (!file) { log_error("Failed to open configuration file"); return -1; } // Read file contents fseek(file, 0, SEEK_END); long file_size = ftell(file); fseek(file, 0, SEEK_SET); char* file_content = malloc(file_size + 1); if (!file_content) { log_error("Failed to allocate memory for configuration file"); fclose(file); return -1; } size_t read_size = fread(file_content, 1, file_size, file); file_content[read_size] = '\0'; fclose(file); // Parse JSON cJSON* json = cJSON_Parse(file_content); free(file_content); if (!json) { log_error("Failed to parse configuration file as JSON"); return -1; } // Validate Nostr event structure int result = validate_and_apply_config_event(json); cJSON_Delete(json); if (result == 0) { log_success("Configuration loaded from file successfully"); } else { log_error("Configuration file validation failed"); } return result; } // ================================ // NOSTR EVENT VALIDATION FUNCTIONS // ================================ int validate_nostr_event_structure(const cJSON* event) { if (!event || !cJSON_IsObject(event)) { log_error("Configuration event is not a valid JSON object"); return -1; } // Check required fields cJSON* kind = cJSON_GetObjectItem(event, "kind"); cJSON* created_at = cJSON_GetObjectItem(event, "created_at"); cJSON* tags = cJSON_GetObjectItem(event, "tags"); cJSON* content = cJSON_GetObjectItem(event, "content"); cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey"); cJSON* id = cJSON_GetObjectItem(event, "id"); cJSON* sig = cJSON_GetObjectItem(event, "sig"); if (!kind || !cJSON_IsNumber(kind)) { log_error("Configuration event missing or invalid 'kind' field"); return -1; } if (cJSON_GetNumberValue(kind) != 33334) { log_error("Configuration event has wrong kind (expected 33334)"); return -1; } if (!created_at || !cJSON_IsNumber(created_at)) { log_error("Configuration event missing or invalid 'created_at' field"); return -1; } if (!tags || !cJSON_IsArray(tags)) { log_error("Configuration event missing or invalid 'tags' field"); return -1; } if (!content || !cJSON_IsString(content)) { log_error("Configuration event missing or invalid 'content' field"); return -1; } if (!pubkey || !cJSON_IsString(pubkey)) { log_error("Configuration event missing or invalid 'pubkey' field"); return -1; } if (!id || !cJSON_IsString(id)) { log_error("Configuration event missing or invalid 'id' field"); return -1; } if (!sig || !cJSON_IsString(sig)) { log_error("Configuration event missing or invalid 'sig' field"); return -1; } // Validate pubkey format (64 hex characters) const char* pubkey_str = cJSON_GetStringValue(pubkey); if (strlen(pubkey_str) != 64) { log_error("Configuration event pubkey has invalid length"); return -1; } // Validate id format (64 hex characters) const char* id_str = cJSON_GetStringValue(id); if (strlen(id_str) != 64) { log_error("Configuration event id has invalid length"); return -1; } // Validate signature format (128 hex characters) const char* sig_str = cJSON_GetStringValue(sig); if (strlen(sig_str) != 128) { log_error("Configuration event signature has invalid length"); return -1; } log_info("Configuration event structure validation passed"); return 0; } int validate_config_tags(const cJSON* tags) { if (!tags || !cJSON_IsArray(tags)) { return -1; } int tag_count = 0; const cJSON* tag = NULL; cJSON_ArrayForEach(tag, tags) { if (!cJSON_IsArray(tag)) { log_error("Configuration tag is not an array"); return -1; } int tag_size = cJSON_GetArraySize(tag); if (tag_size < 2) { log_error("Configuration tag has insufficient elements"); return -1; } cJSON* key = cJSON_GetArrayItem(tag, 0); cJSON* value = cJSON_GetArrayItem(tag, 1); if (!key || !cJSON_IsString(key) || !value || !cJSON_IsString(value)) { log_error("Configuration tag key or value is not a string"); return -1; } tag_count++; } log_info("Configuration tags validation passed (%d tags)"); printf(" Found %d configuration tags\n", tag_count); return 0; } int extract_and_apply_config_tags(const cJSON* tags) { if (!tags || !cJSON_IsArray(tags)) { return -1; } int applied_count = 0; const cJSON* tag = NULL; cJSON_ArrayForEach(tag, tags) { cJSON* key = cJSON_GetArrayItem(tag, 0); cJSON* value = cJSON_GetArrayItem(tag, 1); if (!key || !value) continue; const char* key_str = cJSON_GetStringValue(key); const char* value_str = cJSON_GetStringValue(value); if (!key_str || !value_str) continue; // Validate configuration value config_validation_result_t validation = validate_config_value(key_str, value_str); if (validation != CONFIG_VALID) { log_config_validation_error(key_str, value_str, "Value failed validation"); continue; } // Apply configuration to database if (set_database_config(key_str, value_str, "file") == 0) { applied_count++; } else { log_error("Failed to apply configuration"); printf(" Key: %s, Value: %s\n", key_str, value_str); } } if (applied_count > 0) { log_success("Applied configuration from file"); printf(" Applied %d configuration values\n", applied_count); return 0; } else { log_warning("No valid configuration values found in file"); return -1; } } int validate_and_apply_config_event(const cJSON* event) { log_info("Validating configuration event..."); // Step 1: Validate event structure if (validate_nostr_event_structure(event) != 0) { return -1; } // Step 2: Extract and validate tags cJSON* tags = cJSON_GetObjectItem(event, "tags"); if (validate_config_tags(tags) != 0) { return -1; } // Step 3: For now, skip signature verification (would require Nostr crypto library) // In production, this would verify the event signature against admin pubkeys log_warning("Signature verification not yet implemented - accepting event"); // Step 4: Extract and apply configuration if (extract_and_apply_config_tags(tags) != 0) { return -1; } log_success("Configuration event validation and application completed"); return 0; } // ================================ // CONFIGURATION ACCESS FUNCTIONS // ================================ const char* get_config_value(const char* key) { static char buffer[CONFIG_VALUE_MAX_LENGTH]; if (!key) { return NULL; } // Priority 1: Database configuration (updated from file) if (get_database_config(key, buffer, sizeof(buffer)) == 0) { return buffer; } // Priority 2: Environment variables (fallback) const char* env_value = getenv(key); if (env_value) { return env_value; } // No value found 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') { // Invalid integer format 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; } // Check for boolean values 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; } int set_config_value(const char* key, const char* value) { if (!key || !value) { return -1; } return set_database_config(key, value, "api"); } // ================================ // CONFIGURATION VALIDATION // ================================ config_validation_result_t validate_config_value(const char* key, const char* value) { // Placeholder for validation logic // Will implement full validation in Phase 3 if (!key || !value) { return CONFIG_MISSING_REQUIRED; } // Basic validation - all values are valid for now return CONFIG_VALID; } void log_config_validation_error(const char* key, const char* value, const char* error) { log_error("Configuration validation error"); printf(" Key: %s\n", key ? key : "NULL"); printf(" Value: %s\n", value ? value : "NULL"); printf(" Error: %s\n", error ? error : "Unknown error"); } // ================================ // UTILITY FUNCTIONS // ================================ const char* config_type_to_string(config_type_t type) { switch (type) { case CONFIG_TYPE_SYSTEM: return "system"; case CONFIG_TYPE_USER: return "user"; case CONFIG_TYPE_RUNTIME: return "runtime"; default: return "unknown"; } } const char* config_data_type_to_string(config_data_type_t type) { switch (type) { case CONFIG_DATA_STRING: return "string"; case CONFIG_DATA_INTEGER: return "integer"; case CONFIG_DATA_BOOLEAN: return "boolean"; case CONFIG_DATA_JSON: return "json"; default: return "unknown"; } } config_type_t string_to_config_type(const char* str) { if (!str) return CONFIG_TYPE_USER; if (strcmp(str, "system") == 0) return CONFIG_TYPE_SYSTEM; if (strcmp(str, "user") == 0) return CONFIG_TYPE_USER; if (strcmp(str, "runtime") == 0) return CONFIG_TYPE_RUNTIME; return CONFIG_TYPE_USER; } config_data_type_t string_to_config_data_type(const char* str) { if (!str) return CONFIG_DATA_STRING; if (strcmp(str, "string") == 0) return CONFIG_DATA_STRING; if (strcmp(str, "integer") == 0) return CONFIG_DATA_INTEGER; if (strcmp(str, "boolean") == 0) return CONFIG_DATA_BOOLEAN; if (strcmp(str, "json") == 0) return CONFIG_DATA_JSON; return CONFIG_DATA_STRING; } 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 = ?"; sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { return 0; } sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); int requires_restart = 0; if (sqlite3_step(stmt) == SQLITE_ROW) { requires_restart = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); return requires_restart; } // ================================ // NOSTR EVENT GENERATION FUNCTIONS // ================================ #include cJSON* create_config_nostr_event(const char* privkey_hex) { log_info("Creating configuration Nostr event..."); // 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 NULL; } // Create tags array with default configuration values cJSON* tags = cJSON_CreateArray(); // Default configuration values (moved from schema.sql) 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_pubkey", ""}, {"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]); // 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"; sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); if (rc == SQLITE_OK) { // Load existing values from database while (sqlite3_step(stmt) == SQLITE_ROW) { const char* key = (const char*)sqlite3_column_text(stmt, 0); const char* value = (const char*)sqlite3_column_text(stmt, 1); // Skip admin_pubkey since it's redundant (already in event.pubkey) if (key && value && strcmp(key, "admin_pubkey") != 0) { cJSON* tag = cJSON_CreateArray(); cJSON_AddItemToArray(tag, cJSON_CreateString(key)); cJSON_AddItemToArray(tag, cJSON_CreateString(value)); cJSON_AddItemToArray(tags, tag); } } sqlite3_finalize(stmt); } // If database is empty, use defaults if (cJSON_GetArraySize(tags) == 0) { log_info("Database empty, using default configuration values"); for (int i = 0; i < defaults_count; i++) { cJSON* tag = cJSON_CreateArray(); cJSON_AddItemToArray(tag, cJSON_CreateString(defaults[i].key)); cJSON_AddItemToArray(tag, cJSON_CreateString(defaults[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 event", // content tags, // tags 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("Configuration Nostr event created successfully"); printf(" Event ID: %s\n", cJSON_GetStringValue(id_obj)); printf(" Public Key: %s\n", cJSON_GetStringValue(pubkey_obj)); } return event; } int write_config_event_to_file(const cJSON* event) { if (!event) { return -1; } // Ensure config directory exists struct stat st = {0}; if (stat(g_config_manager.config_dir_path, &st) == -1) { if (mkdir(g_config_manager.config_dir_path, 0700) != 0) { log_error("Failed to create configuration directory"); return -1; } log_info("Created configuration directory: %s"); printf(" %s\n", g_config_manager.config_dir_path); } // Write to file with custom formatting for better readability FILE* file = fopen(g_config_manager.config_file_path, "w"); if (!file) { log_error("Failed to open configuration file for writing"); return -1; } // Custom formatting: tags first, then other fields fprintf(file, "{\n"); // First, write tags array with each tag on its own line cJSON* tags = cJSON_GetObjectItem(event, "tags"); if (tags && cJSON_IsArray(tags)) { fprintf(file, " \"tags\": [\n"); int tag_count = cJSON_GetArraySize(tags); for (int i = 0; i < tag_count; i++) { cJSON* tag = cJSON_GetArrayItem(tags, i); if (tag && cJSON_IsArray(tag)) { char* tag_str = cJSON_Print(tag); if (tag_str) { fprintf(file, " %s%s\n", tag_str, (i < tag_count - 1) ? "," : ""); free(tag_str); } } } fprintf(file, " ],\n"); } // Then write other fields in order const char* field_order[] = {"id", "pubkey", "created_at", "kind", "content", "sig"}; int field_count = sizeof(field_order) / sizeof(field_order[0]); for (int i = 0; i < field_count; i++) { cJSON* field = cJSON_GetObjectItem(event, field_order[i]); if (field) { fprintf(file, " \"%s\": ", field_order[i]); if (cJSON_IsString(field)) { fprintf(file, "\"%s\"", cJSON_GetStringValue(field)); } else if (cJSON_IsNumber(field)) { fprintf(file, "%ld", (long)cJSON_GetNumberValue(field)); } if (i < field_count - 1) { fprintf(file, ","); } fprintf(file, "\n"); } } fprintf(file, "}\n"); fclose(file); log_success("Configuration file written successfully"); printf(" File: %s\n", g_config_manager.config_file_path); return 0; } int generate_config_file_if_missing(void) { // Check if config file already exists if (config_file_exists()) { log_info("Configuration file already exists, skipping generation"); return 0; } 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); 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"); } 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"); 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); } // Create Nostr event cJSON* event = create_config_nostr_event(privkey_hex); if (!event) { log_error("Failed to create configuration event"); 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); } else { log_warning("Failed to store admin public key in database"); } } // Write to file int result = write_config_event_to_file(event); cJSON_Delete(event); if (result == 0) { log_success("Configuration file generated successfully"); } else { log_error("Failed to write configuration file"); } return result; }