v0.3.7 - working on cinfig api
This commit is contained in:
797
src/config.c
797
src/config.c
@@ -18,12 +18,57 @@ extern sqlite3* g_db;
|
||||
config_manager_t g_config_manager = {0};
|
||||
char g_database_path[512] = {0};
|
||||
|
||||
// ================================
|
||||
// NEW ADMIN API STRUCTURES
|
||||
// ================================
|
||||
|
||||
// Migration state management
|
||||
typedef enum {
|
||||
MIGRATION_NOT_NEEDED,
|
||||
MIGRATION_NEEDED,
|
||||
MIGRATION_IN_PROGRESS,
|
||||
MIGRATION_COMPLETED,
|
||||
MIGRATION_FAILED
|
||||
} migration_state_t;
|
||||
|
||||
typedef struct {
|
||||
migration_state_t state;
|
||||
int event_config_count;
|
||||
int table_config_count;
|
||||
int migration_errors;
|
||||
time_t migration_started;
|
||||
time_t migration_completed;
|
||||
char error_message[512];
|
||||
} migration_status_t;
|
||||
|
||||
static migration_status_t g_migration_status = {0};
|
||||
|
||||
// Configuration source type
|
||||
typedef enum {
|
||||
CONFIG_SOURCE_EVENT, // Current event-based system
|
||||
CONFIG_SOURCE_TABLE, // New table-based system
|
||||
CONFIG_SOURCE_HYBRID // During migration
|
||||
} config_source_t;
|
||||
|
||||
// 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);
|
||||
|
||||
// Forward declarations for new admin API functions
|
||||
int populate_default_config_values(void);
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size);
|
||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size);
|
||||
void invalidate_config_cache(void);
|
||||
int add_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
const char* pattern_value, const char* action);
|
||||
int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
const char* pattern_value);
|
||||
int is_config_table_ready(void);
|
||||
int migrate_config_from_events_to_table(void);
|
||||
int populate_config_table_from_event(const cJSON* event);
|
||||
|
||||
// Current configuration cache
|
||||
static cJSON* g_current_config = NULL;
|
||||
|
||||
@@ -811,12 +856,12 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 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");
|
||||
// 7. Process configuration through admin API instead of storing in events table
|
||||
if (process_startup_config_event_with_fallback(config_event) == 0) {
|
||||
log_success("Initial configuration processed successfully through admin API");
|
||||
} else {
|
||||
log_warning("Failed to store initial configuration event - will retry after database init");
|
||||
// Cache the event for later storage
|
||||
log_warning("Failed to process initial configuration - will retry after database init");
|
||||
// Cache the event for later processing
|
||||
if (g_pending_config_event) {
|
||||
cJSON_Delete(g_pending_config_event);
|
||||
}
|
||||
@@ -873,6 +918,9 @@ int startup_existing_relay(const char* relay_pubkey) {
|
||||
g_database_path[sizeof(g_database_path) - 1] = '\0';
|
||||
free(db_name);
|
||||
|
||||
// Configuration will be migrated from events to table after database initialization
|
||||
log_info("Configuration migration will be performed after database is available");
|
||||
|
||||
// Load configuration event from database (after database is initialized)
|
||||
// This will be done in apply_configuration_from_database()
|
||||
|
||||
@@ -1579,6 +1627,490 @@ int handle_configuration_event(cJSON* event, char* error_message, size_t error_s
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// NEW ADMIN API IMPLEMENTATION
|
||||
// ================================
|
||||
|
||||
// ================================
|
||||
// CONFIG TABLE MANAGEMENT FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Note: Config table is now created via embedded schema in sql_schema.h
|
||||
|
||||
// Get value from config table
|
||||
const char* get_config_value_from_table(const char* key) {
|
||||
if (!g_db || !key) return NULL;
|
||||
|
||||
const char* sql = "SELECT value FROM config WHERE key = ?";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
||||
|
||||
static char config_value_buffer[CONFIG_VALUE_MAX_LENGTH];
|
||||
const char* result = NULL;
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* value = (char*)sqlite3_column_text(stmt, 0);
|
||||
if (value) {
|
||||
strncpy(config_value_buffer, value, sizeof(config_value_buffer) - 1);
|
||||
config_value_buffer[sizeof(config_value_buffer) - 1] = '\0';
|
||||
result = config_value_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set value in config table
|
||||
int set_config_value_in_table(const char* key, const char* value, const char* data_type,
|
||||
const char* description, const char* category, int requires_restart) {
|
||||
if (!g_db || !key || !value || !data_type) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* sql = "INSERT OR REPLACE INTO config (key, value, data_type, description, category, requires_restart) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?)";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 3, data_type, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 4, description ? description : "", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 5, category ? category : "general", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int(stmt, 6, requires_restart);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return (rc == SQLITE_DONE) ? 0 : -1;
|
||||
}
|
||||
|
||||
// Update config in table (simpler version of set_config_value_in_table)
|
||||
int update_config_in_table(const char* key, const char* value) {
|
||||
if (!g_db || !key || !value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* sql = "UPDATE config SET value = ?, updated_at = strftime('%s', 'now') WHERE key = ?";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, value, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, key, -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return (rc == SQLITE_DONE) ? 0 : -1;
|
||||
}
|
||||
|
||||
// Populate default config values
|
||||
int populate_default_config_values(void) {
|
||||
log_info("Populating default configuration values in table...");
|
||||
|
||||
// Add all default configuration values to the table
|
||||
for (size_t i = 0; i < DEFAULT_CONFIG_COUNT; i++) {
|
||||
const char* key = DEFAULT_CONFIG_VALUES[i].key;
|
||||
const char* value = DEFAULT_CONFIG_VALUES[i].value;
|
||||
|
||||
// Determine data type
|
||||
const char* data_type = "string";
|
||||
if (strcmp(key, "relay_port") == 0 ||
|
||||
strcmp(key, "max_connections") == 0 ||
|
||||
strcmp(key, "pow_min_difficulty") == 0 ||
|
||||
strcmp(key, "max_subscriptions_per_client") == 0 ||
|
||||
strcmp(key, "max_total_subscriptions") == 0 ||
|
||||
strcmp(key, "max_filters_per_subscription") == 0 ||
|
||||
strcmp(key, "max_event_tags") == 0 ||
|
||||
strcmp(key, "max_content_length") == 0 ||
|
||||
strcmp(key, "max_message_length") == 0 ||
|
||||
strcmp(key, "default_limit") == 0 ||
|
||||
strcmp(key, "max_limit") == 0 ||
|
||||
strcmp(key, "nip42_challenge_expiration") == 0 ||
|
||||
strcmp(key, "nip40_expiration_grace_period") == 0) {
|
||||
data_type = "integer";
|
||||
} else if (strcmp(key, "auth_enabled") == 0 ||
|
||||
strcmp(key, "nip40_expiration_enabled") == 0 ||
|
||||
strcmp(key, "nip40_expiration_strict") == 0 ||
|
||||
strcmp(key, "nip40_expiration_filter") == 0 ||
|
||||
strcmp(key, "nip42_auth_required") == 0) {
|
||||
data_type = "boolean";
|
||||
}
|
||||
|
||||
// Set category
|
||||
const char* category = "general";
|
||||
if (strstr(key, "relay_")) {
|
||||
category = "relay";
|
||||
} else if (strstr(key, "nip40_")) {
|
||||
category = "expiration";
|
||||
} else if (strstr(key, "nip42_") || strstr(key, "auth_")) {
|
||||
category = "authentication";
|
||||
} else if (strstr(key, "pow_")) {
|
||||
category = "proof_of_work";
|
||||
} else if (strstr(key, "max_")) {
|
||||
category = "limits";
|
||||
}
|
||||
|
||||
// Determine if requires restart
|
||||
int requires_restart = 0;
|
||||
if (strcmp(key, "relay_port") == 0) {
|
||||
requires_restart = 1;
|
||||
}
|
||||
|
||||
if (set_config_value_in_table(key, value, data_type, NULL, category, requires_restart) != 0) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Failed to set default config: %s = %s", key, value);
|
||||
log_error(error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
log_success("Default configuration values populated");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// ADMIN EVENT PROCESSING FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Process admin events (moved from main.c)
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (!kind_obj || !cJSON_IsNumber(kind_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: missing or invalid kind");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify admin authorization
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
if (!pubkey_obj || !cJSON_IsString(pubkey_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: missing pubkey");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
|
||||
if (!admin_pubkey || strcmp(event_pubkey, admin_pubkey) != 0) {
|
||||
snprintf(error_message, error_size, "auth-required: not authorized admin");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
|
||||
switch (kind) {
|
||||
case 33334:
|
||||
return process_admin_config_event(event, error_message, error_size);
|
||||
case 33335:
|
||||
return process_admin_auth_event(event, error_message, error_size);
|
||||
default:
|
||||
snprintf(error_message, error_size, "invalid: unsupported admin event kind");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle kind 33334 config events
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: configuration event must have tags");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Config table should already exist from embedded schema
|
||||
|
||||
// Begin transaction for atomic config updates
|
||||
int rc = sqlite3_exec(g_db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to begin config transaction");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int updates_applied = 0;
|
||||
|
||||
// Process each tag as a configuration parameter
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_obj) {
|
||||
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
|
||||
if (!cJSON_IsString(tag_name) || !cJSON_IsString(tag_value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* key = cJSON_GetStringValue(tag_name);
|
||||
const char* value = cJSON_GetStringValue(tag_value);
|
||||
|
||||
// Skip relay identifier tag
|
||||
if (strcmp(key, "d") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update configuration in table
|
||||
if (update_config_in_table(key, value) == 0) {
|
||||
updates_applied++;
|
||||
}
|
||||
}
|
||||
|
||||
if (updates_applied > 0) {
|
||||
sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL);
|
||||
invalidate_config_cache();
|
||||
|
||||
char success_msg[256];
|
||||
snprintf(success_msg, sizeof(success_msg), "Applied %d configuration updates", updates_applied);
|
||||
log_success(success_msg);
|
||||
} else {
|
||||
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
|
||||
snprintf(error_message, error_size, "no valid configuration parameters found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle kind 33335 auth rule events
|
||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: auth rule event must have tags");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Extract action from content or tags
|
||||
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
||||
const char* content = content_obj ? cJSON_GetStringValue(content_obj) : "";
|
||||
|
||||
// Parse the action from content (should be "add" or "remove")
|
||||
cJSON* content_json = cJSON_Parse(content);
|
||||
const char* action = "add"; // default
|
||||
if (content_json) {
|
||||
cJSON* action_obj = cJSON_GetObjectItem(content_json, "action");
|
||||
if (action_obj && cJSON_IsString(action_obj)) {
|
||||
action = cJSON_GetStringValue(action_obj);
|
||||
}
|
||||
cJSON_Delete(content_json);
|
||||
}
|
||||
|
||||
// Begin transaction for atomic auth rule updates
|
||||
int rc = sqlite3_exec(g_db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to begin auth rule transaction");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rules_processed = 0;
|
||||
|
||||
// Process each tag as an auth rule specification
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_obj) {
|
||||
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* rule_type_obj = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* pattern_type_obj = cJSON_GetArrayItem(tag, 1);
|
||||
cJSON* pattern_value_obj = cJSON_GetArrayItem(tag, 2);
|
||||
|
||||
if (!cJSON_IsString(rule_type_obj) ||
|
||||
!cJSON_IsString(pattern_type_obj) ||
|
||||
!cJSON_IsString(pattern_value_obj)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* rule_type = cJSON_GetStringValue(rule_type_obj);
|
||||
const char* pattern_type = cJSON_GetStringValue(pattern_type_obj);
|
||||
const char* pattern_value = cJSON_GetStringValue(pattern_value_obj);
|
||||
|
||||
// Process the auth rule based on action
|
||||
if (strcmp(action, "add") == 0) {
|
||||
if (add_auth_rule_from_config(rule_type, pattern_type, pattern_value, "allow") == 0) {
|
||||
rules_processed++;
|
||||
}
|
||||
} else if (strcmp(action, "remove") == 0) {
|
||||
if (remove_auth_rule_from_config(rule_type, pattern_type, pattern_value) == 0) {
|
||||
rules_processed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rules_processed > 0) {
|
||||
sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL);
|
||||
|
||||
char success_msg[256];
|
||||
snprintf(success_msg, sizeof(success_msg), "Processed %d auth rule updates", rules_processed);
|
||||
log_success(success_msg);
|
||||
} else {
|
||||
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
|
||||
snprintf(error_message, error_size, "no valid auth rules found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// AUTH RULES MANAGEMENT FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Add auth rule from configuration
|
||||
int add_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
const char* pattern_value, const char* action) {
|
||||
if (!g_db || !rule_type || !pattern_type || !pattern_value || !action) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* sql = "INSERT INTO auth_rules (rule_type, pattern_type, pattern_value, action) "
|
||||
"VALUES (?, ?, ?, ?)";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, rule_type, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, pattern_type, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 3, pattern_value, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 4, action, -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return (rc == SQLITE_DONE) ? 0 : -1;
|
||||
}
|
||||
|
||||
// Remove auth rule from configuration
|
||||
int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
const char* pattern_value) {
|
||||
if (!g_db || !rule_type || !pattern_type || !pattern_value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* sql = "DELETE FROM auth_rules WHERE rule_type = ? AND pattern_type = ? AND pattern_value = ?";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, rule_type, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, pattern_type, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 3, pattern_value, -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return (rc == SQLITE_DONE) ? 0 : -1;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// CONFIGURATION CACHE MANAGEMENT
|
||||
// ================================
|
||||
|
||||
// Invalidate configuration cache
|
||||
void invalidate_config_cache(void) {
|
||||
// For now, just log that cache was invalidated
|
||||
// In a full implementation, this would clear any cached config values
|
||||
log_info("Configuration cache invalidated");
|
||||
}
|
||||
|
||||
// Reload configuration from table
|
||||
int reload_config_from_table(void) {
|
||||
// For now, just log that config was reloaded
|
||||
// In a full implementation, this would reload all cached values from the table
|
||||
log_info("Configuration reloaded from table");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// HYBRID CONFIG ACCESS FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Hybrid config getter (tries table first, falls back to event)
|
||||
const char* get_config_value_hybrid(const char* key) {
|
||||
// Try table-based config first if available
|
||||
if (is_config_table_ready()) {
|
||||
const char* table_value = get_config_value_from_table(key);
|
||||
if (table_value) {
|
||||
return table_value;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to event-based config
|
||||
return get_config_value(key);
|
||||
}
|
||||
|
||||
// Check if config table is ready
|
||||
int is_config_table_ready(void) {
|
||||
if (!g_db) return 0;
|
||||
|
||||
const char* sql = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='config'";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int table_exists = 0;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
table_exists = sqlite3_column_int(stmt, 0) > 0;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (!table_exists) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if table has configuration data
|
||||
const char* count_sql = "SELECT COUNT(*) FROM config";
|
||||
rc = sqlite3_prepare_v2(g_db, count_sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_count = 0;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
config_count = sqlite3_column_int(stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return config_count > 0;
|
||||
}
|
||||
|
||||
// Initialize configuration system with migration support
|
||||
int initialize_config_system_with_migration(void) {
|
||||
log_info("Initializing configuration system with migration support...");
|
||||
|
||||
// Initialize config manager
|
||||
memset(&g_config_manager, 0, sizeof(g_config_manager));
|
||||
memset(&g_migration_status, 0, sizeof(g_migration_status));
|
||||
|
||||
// For new installations, config table should already exist from embedded schema
|
||||
log_success("Configuration system initialized with table support");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// RETRY INITIAL CONFIG EVENT STORAGE
|
||||
// ================================
|
||||
@@ -1591,16 +2123,263 @@ int retry_store_initial_config_event(void) {
|
||||
|
||||
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");
|
||||
// Try to process the cached configuration event through admin API
|
||||
if (process_startup_config_event_with_fallback(g_pending_config_event) == 0) {
|
||||
log_success("Initial configuration processed successfully through admin API 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");
|
||||
log_error("Failed to process initial configuration through admin API on retry");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// CONFIG MIGRATION FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Populate config table from a configuration event
|
||||
int populate_config_table_from_event(const cJSON* event) {
|
||||
if (!event || !g_db) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Populating config table from configuration event...");
|
||||
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
log_error("Configuration event missing tags array");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int configs_populated = 0;
|
||||
|
||||
// Process each tag as a configuration parameter
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags) {
|
||||
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
|
||||
if (!cJSON_IsString(tag_name) || !cJSON_IsString(tag_value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* key = cJSON_GetStringValue(tag_name);
|
||||
const char* value = cJSON_GetStringValue(tag_value);
|
||||
|
||||
// Skip relay identifier tag
|
||||
if (strcmp(key, "d") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine data type for the config value
|
||||
const char* data_type = "string";
|
||||
if (strcmp(key, "relay_port") == 0 ||
|
||||
strcmp(key, "max_connections") == 0 ||
|
||||
strcmp(key, "pow_min_difficulty") == 0 ||
|
||||
strcmp(key, "max_subscriptions_per_client") == 0 ||
|
||||
strcmp(key, "max_total_subscriptions") == 0 ||
|
||||
strcmp(key, "max_filters_per_subscription") == 0 ||
|
||||
strcmp(key, "max_event_tags") == 0 ||
|
||||
strcmp(key, "max_content_length") == 0 ||
|
||||
strcmp(key, "max_message_length") == 0 ||
|
||||
strcmp(key, "default_limit") == 0 ||
|
||||
strcmp(key, "max_limit") == 0 ||
|
||||
strcmp(key, "nip42_challenge_expiration") == 0 ||
|
||||
strcmp(key, "nip40_expiration_grace_period") == 0) {
|
||||
data_type = "integer";
|
||||
} else if (strcmp(key, "auth_enabled") == 0 ||
|
||||
strcmp(key, "nip40_expiration_enabled") == 0 ||
|
||||
strcmp(key, "nip40_expiration_strict") == 0 ||
|
||||
strcmp(key, "nip40_expiration_filter") == 0 ||
|
||||
strcmp(key, "nip42_auth_required") == 0) {
|
||||
data_type = "boolean";
|
||||
}
|
||||
|
||||
// Set category
|
||||
const char* category = "general";
|
||||
if (strstr(key, "relay_")) {
|
||||
category = "relay";
|
||||
} else if (strstr(key, "nip40_")) {
|
||||
category = "expiration";
|
||||
} else if (strstr(key, "nip42_") || strstr(key, "auth_")) {
|
||||
category = "authentication";
|
||||
} else if (strstr(key, "pow_")) {
|
||||
category = "proof_of_work";
|
||||
} else if (strstr(key, "max_")) {
|
||||
category = "limits";
|
||||
}
|
||||
|
||||
// Determine if requires restart
|
||||
int requires_restart = 0;
|
||||
if (strcmp(key, "relay_port") == 0) {
|
||||
requires_restart = 1;
|
||||
}
|
||||
|
||||
// Insert into config table
|
||||
if (set_config_value_in_table(key, value, data_type, NULL, category, requires_restart) == 0) {
|
||||
configs_populated++;
|
||||
} else {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Failed to populate config: %s = %s", key, value);
|
||||
log_error(error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (configs_populated > 0) {
|
||||
char success_msg[256];
|
||||
snprintf(success_msg, sizeof(success_msg), "Populated %d configuration values from event", configs_populated);
|
||||
log_success(success_msg);
|
||||
return 0;
|
||||
} else {
|
||||
log_error("No configuration values were populated from event");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate configuration from existing events to config table
|
||||
int migrate_config_from_events_to_table(void) {
|
||||
if (!g_db) {
|
||||
log_error("Database not available for configuration migration");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Migrating configuration from events to config table...");
|
||||
|
||||
// Load the most recent configuration event from database
|
||||
cJSON* config_event = load_config_event_from_database(g_config_manager.relay_pubkey);
|
||||
if (!config_event) {
|
||||
log_info("No existing configuration event found - migration not needed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Populate config table from the event
|
||||
int result = populate_config_table_from_event(config_event);
|
||||
|
||||
// Clean up
|
||||
cJSON_Delete(config_event);
|
||||
|
||||
if (result == 0) {
|
||||
log_success("Configuration migration from events to table completed successfully");
|
||||
} else {
|
||||
log_error("Configuration migration from events to table failed");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// STARTUP CONFIGURATION PROCESSING
|
||||
// ================================
|
||||
|
||||
// Process startup configuration event - bypasses auth and updates config table
|
||||
int process_startup_config_event(const cJSON* event) {
|
||||
if (!event || !g_db) {
|
||||
log_error("Invalid parameters for startup config processing");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing startup configuration event through admin API...");
|
||||
|
||||
// Validate event structure first
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
|
||||
log_error("Invalid event kind for startup configuration");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
log_error("Startup configuration event missing tags");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Begin transaction for atomic config updates
|
||||
int rc = sqlite3_exec(g_db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("Failed to begin startup config transaction");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int updates_applied = 0;
|
||||
|
||||
// Process each tag as a configuration parameter (same logic as process_admin_config_event)
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_obj) {
|
||||
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
|
||||
if (!cJSON_IsString(tag_name) || !cJSON_IsString(tag_value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* key = cJSON_GetStringValue(tag_name);
|
||||
const char* value = cJSON_GetStringValue(tag_value);
|
||||
|
||||
// Skip relay identifier tag and relay_pubkey (already in table)
|
||||
if (strcmp(key, "d") == 0 || strcmp(key, "relay_pubkey") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update configuration in table
|
||||
if (update_config_in_table(key, value) == 0) {
|
||||
updates_applied++;
|
||||
}
|
||||
}
|
||||
|
||||
if (updates_applied > 0) {
|
||||
sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL);
|
||||
invalidate_config_cache();
|
||||
|
||||
char success_msg[256];
|
||||
snprintf(success_msg, sizeof(success_msg),
|
||||
"Processed startup configuration: %d values updated in config table", updates_applied);
|
||||
log_success(success_msg);
|
||||
return 0;
|
||||
} else {
|
||||
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
|
||||
log_error("No valid configuration parameters found in startup event");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Process startup configuration event with fallback - for retry scenarios
|
||||
int process_startup_config_event_with_fallback(const cJSON* event) {
|
||||
if (!event) {
|
||||
log_error("Invalid event for startup config processing with fallback");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Try to process through admin API first
|
||||
if (process_startup_config_event(event) == 0) {
|
||||
log_success("Startup configuration processed successfully through admin API");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If that fails, populate defaults and try again
|
||||
log_warning("Startup config processing failed - ensuring defaults are populated");
|
||||
if (populate_default_config_values() != 0) {
|
||||
log_error("Failed to populate default config values");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Retry processing
|
||||
if (process_startup_config_event(event) == 0) {
|
||||
log_success("Startup configuration processed successfully after populating defaults");
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_error("Startup configuration processing failed even after populating defaults");
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user