v0.3.0 - Complete deployment documentation and examples - Added comprehensive deployment guide, automated deployment scripts, nginx SSL proxy setup, backup automation, and monitoring tools. Includes VPS deployment, cloud platform guides, and practical examples for production deployment of event-based configuration system.
This commit is contained in:
1702
src/config.c
1702
src/config.c
File diff suppressed because it is too large
Load Diff
248
src/config.h
248
src/config.h
@@ -2,231 +2,75 @@
|
||||
#define CONFIG_H
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <time.h>
|
||||
#include <stddef.h>
|
||||
#include <cjson/cJSON.h>
|
||||
#include <time.h>
|
||||
|
||||
// Configuration system constants
|
||||
#define CONFIG_KEY_MAX_LENGTH 64
|
||||
#define CONFIG_VALUE_MAX_LENGTH 512
|
||||
#define CONFIG_DESCRIPTION_MAX_LENGTH 256
|
||||
#define CONFIG_XDG_DIR_NAME "c-relay"
|
||||
#define CONFIG_FILE_NAME "c_relay_config_event.json"
|
||||
#define CONFIG_ADMIN_PRIVKEY_ENV "C_RELAY_ADMIN_PRIVKEY"
|
||||
#define CONFIG_RELAY_PRIVKEY_ENV "C_RELAY_PRIVKEY"
|
||||
#define CONFIG_DIR_OVERRIDE_ENV "C_RELAY_CONFIG_DIR_OVERRIDE"
|
||||
#define CONFIG_FILE_OVERRIDE_ENV "C_RELAY_CONFIG_FILE_OVERRIDE"
|
||||
#define NOSTR_PUBKEY_HEX_LENGTH 64
|
||||
#define NOSTR_PRIVKEY_HEX_LENGTH 64
|
||||
#define NOSTR_EVENT_ID_HEX_LENGTH 64
|
||||
#define NOSTR_SIGNATURE_HEX_LENGTH 128
|
||||
|
||||
// Protocol and implementation constants (hardcoded - should NOT be configurable)
|
||||
#define SUBSCRIPTION_ID_MAX_LENGTH 64
|
||||
#define CLIENT_IP_MAX_LENGTH 64
|
||||
#define RELAY_NAME_MAX_LENGTH 128
|
||||
#define RELAY_DESCRIPTION_MAX_LENGTH 1024
|
||||
#define RELAY_URL_MAX_LENGTH 256
|
||||
#define RELAY_CONTACT_MAX_LENGTH 128
|
||||
// Configuration constants
|
||||
#define CONFIG_VALUE_MAX_LENGTH 1024
|
||||
#define RELAY_NAME_MAX_LENGTH 256
|
||||
#define RELAY_DESCRIPTION_MAX_LENGTH 512
|
||||
#define RELAY_URL_MAX_LENGTH 512
|
||||
#define RELAY_PUBKEY_MAX_LENGTH 65
|
||||
|
||||
// Default configuration values (used as fallbacks if database config fails)
|
||||
#define DEFAULT_DATABASE_PATH "db/c_nostr_relay.db"
|
||||
#define DEFAULT_PORT 8888
|
||||
#define DEFAULT_HOST "127.0.0.1"
|
||||
#define MAX_CLIENTS 100
|
||||
#define MAX_SUBSCRIPTIONS_PER_CLIENT 20
|
||||
#define RELAY_CONTACT_MAX_LENGTH 256
|
||||
#define SUBSCRIPTION_ID_MAX_LENGTH 64
|
||||
#define CLIENT_IP_MAX_LENGTH 46
|
||||
#define MAX_SUBSCRIPTIONS_PER_CLIENT 25
|
||||
#define MAX_TOTAL_SUBSCRIPTIONS 5000
|
||||
#define MAX_FILTERS_PER_SUBSCRIPTION 10
|
||||
#define DEFAULT_PORT 8888
|
||||
#define DEFAULT_DATABASE_PATH "db/c_nostr_relay.db"
|
||||
|
||||
// Configuration types
|
||||
typedef enum {
|
||||
CONFIG_TYPE_SYSTEM = 0,
|
||||
CONFIG_TYPE_USER = 1,
|
||||
CONFIG_TYPE_RUNTIME = 2
|
||||
} config_type_t;
|
||||
// Database path for event-based config
|
||||
extern char g_database_path[512];
|
||||
|
||||
// Configuration data types
|
||||
typedef enum {
|
||||
CONFIG_DATA_STRING = 0,
|
||||
CONFIG_DATA_INTEGER = 1,
|
||||
CONFIG_DATA_BOOLEAN = 2,
|
||||
CONFIG_DATA_JSON = 3
|
||||
} config_data_type_t;
|
||||
|
||||
// Configuration validation result
|
||||
typedef enum {
|
||||
CONFIG_VALID = 0,
|
||||
CONFIG_INVALID_TYPE = 1,
|
||||
CONFIG_INVALID_RANGE = 2,
|
||||
CONFIG_INVALID_FORMAT = 3,
|
||||
CONFIG_MISSING_REQUIRED = 4
|
||||
} config_validation_result_t;
|
||||
|
||||
// Configuration entry structure
|
||||
typedef struct {
|
||||
char key[CONFIG_KEY_MAX_LENGTH];
|
||||
char value[CONFIG_VALUE_MAX_LENGTH];
|
||||
char description[CONFIG_DESCRIPTION_MAX_LENGTH];
|
||||
config_type_t config_type;
|
||||
config_data_type_t data_type;
|
||||
int is_sensitive;
|
||||
int requires_restart;
|
||||
time_t created_at;
|
||||
time_t updated_at;
|
||||
} config_entry_t;
|
||||
|
||||
// Configuration manager state
|
||||
// Configuration manager structure
|
||||
typedef struct {
|
||||
sqlite3* db;
|
||||
sqlite3_stmt* get_config_stmt;
|
||||
sqlite3_stmt* set_config_stmt;
|
||||
sqlite3_stmt* log_change_stmt;
|
||||
|
||||
// Configuration loading status
|
||||
int file_config_loaded;
|
||||
int database_config_loaded;
|
||||
time_t last_reload;
|
||||
|
||||
// XDG configuration directory
|
||||
char config_dir_path[512];
|
||||
char config_file_path[600];
|
||||
char relay_pubkey[65];
|
||||
char admin_pubkey[65];
|
||||
time_t last_config_check;
|
||||
char config_file_path[512]; // Temporary for compatibility
|
||||
} config_manager_t;
|
||||
|
||||
// Global configuration manager instance
|
||||
// Global configuration manager
|
||||
extern config_manager_t g_config_manager;
|
||||
|
||||
// ================================
|
||||
// CORE CONFIGURATION FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Initialize configuration system
|
||||
int init_configuration_system(void);
|
||||
|
||||
// Cleanup configuration system
|
||||
// Core configuration functions (temporary compatibility)
|
||||
int init_configuration_system(const char* config_dir_override, const char* config_file_override);
|
||||
void cleanup_configuration_system(void);
|
||||
|
||||
// Load configuration from all sources (file -> database -> defaults)
|
||||
int load_configuration(void);
|
||||
// Database config functions (temporary compatibility)
|
||||
int set_database_config(const char* key, const char* value, const char* changed_by);
|
||||
|
||||
// Apply loaded configuration to global variables
|
||||
int apply_configuration_to_globals(void);
|
||||
// Database functions
|
||||
char* get_database_name_from_relay_pubkey(const char* relay_pubkey);
|
||||
int create_database_with_relay_pubkey(const char* relay_pubkey);
|
||||
|
||||
// ================================
|
||||
// DATABASE CONFIGURATION FUNCTIONS
|
||||
// ================================
|
||||
// Configuration event functions
|
||||
int store_config_event_in_database(const cJSON* event);
|
||||
cJSON* load_config_event_from_database(const char* relay_pubkey);
|
||||
int process_configuration_event(const cJSON* event);
|
||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Initialize database prepared statements
|
||||
int init_config_database_statements(void);
|
||||
// Retry storing initial config event after database initialization
|
||||
int retry_store_initial_config_event(void);
|
||||
|
||||
// Get configuration value from database
|
||||
int get_database_config(const char* key, char* value, size_t value_size);
|
||||
|
||||
// Set configuration value in database
|
||||
int set_database_config(const char* key, const char* new_value, const char* changed_by);
|
||||
|
||||
// Load all configuration from database
|
||||
int load_config_from_database(void);
|
||||
|
||||
// ================================
|
||||
// FILE CONFIGURATION FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Get XDG configuration directory path
|
||||
int get_xdg_config_dir(char* path, size_t path_size);
|
||||
|
||||
// Check if configuration file exists
|
||||
int config_file_exists(void);
|
||||
|
||||
// Load configuration from file
|
||||
int load_config_from_file(void);
|
||||
|
||||
// Validate and apply Nostr configuration event
|
||||
int validate_and_apply_config_event(const cJSON* event);
|
||||
|
||||
// Validate Nostr event structure
|
||||
int validate_nostr_event_structure(const cJSON* event);
|
||||
|
||||
// Validate configuration tags array
|
||||
int validate_config_tags(const cJSON* tags);
|
||||
|
||||
// Extract and apply configuration tags to database
|
||||
int extract_and_apply_config_tags(const cJSON* tags);
|
||||
|
||||
// ================================
|
||||
// CONFIGURATION ACCESS FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Get configuration value (checks all sources: file -> database -> environment -> defaults)
|
||||
// Configuration access functions
|
||||
const char* get_config_value(const char* key);
|
||||
|
||||
// Get configuration value as integer
|
||||
int get_config_int(const char* key, int default_value);
|
||||
|
||||
// Get configuration value as boolean
|
||||
int get_config_bool(const char* key, int default_value);
|
||||
|
||||
// Set configuration value (updates database)
|
||||
int set_config_value(const char* key, const char* value);
|
||||
// First-time startup functions
|
||||
int is_first_time_startup(void);
|
||||
int first_time_startup_sequence(void);
|
||||
int startup_existing_relay(const char* relay_pubkey);
|
||||
|
||||
// ================================
|
||||
// CONFIGURATION VALIDATION
|
||||
// ================================
|
||||
// Configuration application functions
|
||||
int apply_configuration_from_event(const cJSON* event);
|
||||
int apply_runtime_config_handlers(const cJSON* old_event, const cJSON* new_event);
|
||||
|
||||
// Validate configuration value
|
||||
config_validation_result_t validate_config_value(const char* key, const char* value);
|
||||
// Utility functions
|
||||
char** find_existing_nrdb_files(void);
|
||||
char* extract_pubkey_from_filename(const char* filename);
|
||||
|
||||
// Log validation error
|
||||
void log_config_validation_error(const char* key, const char* value, const char* error);
|
||||
|
||||
// ================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Convert config type enum to string
|
||||
const char* config_type_to_string(config_type_t type);
|
||||
|
||||
// Convert config data type enum to string
|
||||
const char* config_data_type_to_string(config_data_type_t type);
|
||||
|
||||
// Convert string to config type enum
|
||||
config_type_t string_to_config_type(const char* str);
|
||||
|
||||
// Convert string to config data type enum
|
||||
config_data_type_t string_to_config_data_type(const char* str);
|
||||
|
||||
// Check if configuration key requires restart
|
||||
int config_requires_restart(const char* key);
|
||||
|
||||
// ================================
|
||||
// NOSTR EVENT GENERATION FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Generate configuration file with valid Nostr event if it doesn't exist
|
||||
int generate_config_file_if_missing(void);
|
||||
|
||||
// Create a valid Nostr configuration event from database values
|
||||
cJSON* create_config_nostr_event(const char* privkey_hex);
|
||||
|
||||
// Generate a random private key (32 bytes as hex string)
|
||||
int generate_random_privkey(char* privkey_hex, size_t buffer_size);
|
||||
|
||||
// Derive public key from private key (using secp256k1)
|
||||
int derive_pubkey_from_privkey(const char* privkey_hex, char* pubkey_hex, size_t buffer_size);
|
||||
|
||||
// Create Nostr event ID (SHA256 of serialized event data)
|
||||
int create_nostr_event_id(const cJSON* event, char* event_id_hex, size_t buffer_size);
|
||||
|
||||
// Sign Nostr event (using secp256k1 Schnorr signature)
|
||||
int sign_nostr_event(const cJSON* event, const char* privkey_hex, char* signature_hex, size_t buffer_size);
|
||||
|
||||
// Write configuration event to file
|
||||
int write_config_event_to_file(const cJSON* event);
|
||||
|
||||
// Helper function to generate random private key
|
||||
int generate_random_private_key(char* privkey_hex, size_t buffer_size);
|
||||
|
||||
// Helper function to derive public key from private key
|
||||
int derive_public_key(const char* privkey_hex, char* pubkey_hex, size_t buffer_size);
|
||||
|
||||
#endif // CONFIG_H
|
||||
#endif /* CONFIG_H */
|
||||
68
src/default_config_event.h
Normal file
68
src/default_config_event.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef DEFAULT_CONFIG_EVENT_H
|
||||
#define DEFAULT_CONFIG_EVENT_H
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
/*
|
||||
* Default Configuration Event Template
|
||||
*
|
||||
* This header contains the default configuration values for the C Nostr Relay.
|
||||
* These values are used to create the initial kind 33334 configuration event
|
||||
* during first-time startup.
|
||||
*
|
||||
* IMPORTANT: These values should never be accessed directly by other parts
|
||||
* of the program. They are only used during initial configuration event creation.
|
||||
*/
|
||||
|
||||
// Default configuration key-value pairs
|
||||
static const struct {
|
||||
const char* key;
|
||||
const char* value;
|
||||
} DEFAULT_CONFIG_VALUES[] = {
|
||||
// Authentication
|
||||
{"auth_enabled", "false"},
|
||||
|
||||
// Server Core Settings
|
||||
{"relay_port", "8888"},
|
||||
{"max_connections", "100"},
|
||||
|
||||
// NIP-11 Relay Information (relay keys will be populated at runtime)
|
||||
{"relay_description", "High-performance C Nostr relay with SQLite storage"},
|
||||
{"relay_contact", ""},
|
||||
{"relay_software", "https://git.laantungir.net/laantungir/c-relay.git"},
|
||||
{"relay_version", "v1.0.0"},
|
||||
|
||||
// NIP-13 Proof of Work (pow_min_difficulty = 0 means PoW disabled)
|
||||
{"pow_min_difficulty", "0"},
|
||||
{"pow_mode", "basic"},
|
||||
|
||||
// NIP-40 Expiration Timestamp
|
||||
{"nip40_expiration_enabled", "true"},
|
||||
{"nip40_expiration_strict", "true"},
|
||||
{"nip40_expiration_filter", "true"},
|
||||
{"nip40_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"}
|
||||
};
|
||||
|
||||
// Number of default configuration values
|
||||
#define DEFAULT_CONFIG_COUNT (sizeof(DEFAULT_CONFIG_VALUES) / sizeof(DEFAULT_CONFIG_VALUES[0]))
|
||||
|
||||
// Function to create default configuration event
|
||||
cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes,
|
||||
const char* relay_privkey_hex,
|
||||
const char* relay_pubkey_hex);
|
||||
|
||||
#endif /* DEFAULT_CONFIG_EVENT_H */
|
||||
337
src/main.c
337
src/main.c
@@ -198,6 +198,9 @@ int check_and_handle_replaceable_event(int kind, const char* pubkey, long create
|
||||
int check_and_handle_addressable_event(int kind, const char* pubkey, const char* d_tag_value, long created_at);
|
||||
int handle_event_message(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declaration for configuration event handling (kind 33334)
|
||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declaration for NOTICE message support
|
||||
void send_notice_message(struct lws* wsi, const char* message);
|
||||
|
||||
@@ -1940,9 +1943,9 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialize database connection and schema
|
||||
int init_database() {
|
||||
int init_database(const char* database_path_override) {
|
||||
// Priority 1: Command line database path override
|
||||
const char* db_path = getenv("C_RELAY_DATABASE_PATH_OVERRIDE");
|
||||
const char* db_path = database_path_override;
|
||||
|
||||
// Priority 2: Configuration system (if available)
|
||||
if (!db_path) {
|
||||
@@ -2678,6 +2681,11 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size) {
|
||||
return handle_deletion_request(event, error_message, error_size);
|
||||
}
|
||||
|
||||
// Kind 33334: Handle configuration events
|
||||
if (kind == 33334) {
|
||||
return handle_configuration_event(event, error_message, error_size);
|
||||
}
|
||||
|
||||
// Handle replaceable events (NIP-01)
|
||||
event_type_t event_type = classify_event_kind(kind);
|
||||
if (event_type == EVENT_TYPE_REPLACEABLE) {
|
||||
@@ -2949,13 +2957,14 @@ static struct lws_protocols protocols[] = {
|
||||
};
|
||||
|
||||
// Start libwebsockets-based WebSocket Nostr relay server
|
||||
int start_websocket_relay() {
|
||||
int start_websocket_relay(int port_override) {
|
||||
struct lws_context_creation_info info;
|
||||
|
||||
log_info("Starting libwebsockets-based Nostr relay server...");
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.port = get_config_int("relay_port", DEFAULT_PORT);
|
||||
// Use port override if provided, otherwise use configuration
|
||||
info.port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT);
|
||||
info.protocols = protocols;
|
||||
info.gid = -1;
|
||||
info.uid = -1;
|
||||
@@ -3016,211 +3025,185 @@ int start_websocket_relay() {
|
||||
void print_usage(const char* program_name) {
|
||||
printf("Usage: %s [OPTIONS]\n", program_name);
|
||||
printf("\n");
|
||||
printf("C Nostr Relay Server\n");
|
||||
printf("C Nostr Relay Server - Event-Based Configuration\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -p, --port PORT Listen port (default: %d)\n", DEFAULT_PORT);
|
||||
printf(" -c, --config FILE Configuration file path\n");
|
||||
printf(" -d, --config-dir DIR Configuration directory path\n");
|
||||
printf(" -D, --database-path PATH Database file path (default: %s)\n", DEFAULT_DATABASE_PATH);
|
||||
printf(" -h, --help Show this help message\n");
|
||||
printf(" -v, --version Show version information\n");
|
||||
printf("\n");
|
||||
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: <relay_pubkey>.nrdb (created automatically)\n");
|
||||
printf("\n");
|
||||
printf("Examples:\n");
|
||||
printf(" %s --config /path/to/config.json\n", program_name);
|
||||
printf(" %s --config-dir ~/.config/c-relay-dev\n", program_name);
|
||||
printf(" %s --port 9999 --config-dir /etc/c-relay\n", program_name);
|
||||
printf(" %s --database-path /var/lib/c-relay/relay.db\n", program_name);
|
||||
printf(" %s --database-path ./test.db --port 7777\n", program_name);
|
||||
printf(" %s # Start relay (auto-configure on first run)\n", program_name);
|
||||
printf(" %s --help # Show this help\n", program_name);
|
||||
printf(" %s --version # Show version info\n", program_name);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Print version information
|
||||
void print_version() {
|
||||
printf("C Nostr Relay Server v1.0.0\n");
|
||||
printf("Event-based configuration system\n");
|
||||
printf("Built with nostr_core_lib integration\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int port = DEFAULT_PORT;
|
||||
char* config_dir_override = NULL;
|
||||
char* config_file_override = NULL;
|
||||
char* database_path_override = NULL;
|
||||
|
||||
// Parse command line arguments
|
||||
// Parse minimal command line arguments (no configuration overrides)
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
print_usage(argv[0]);
|
||||
return 0;
|
||||
} else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) {
|
||||
if (i + 1 < argc) {
|
||||
port = atoi(argv[++i]);
|
||||
if (port <= 0 || port > 65535) {
|
||||
log_error("Invalid port number");
|
||||
return 1;
|
||||
}
|
||||
// Port will be stored in configuration system after it's initialized
|
||||
} else {
|
||||
log_error("Port argument requires a value");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) {
|
||||
if (i + 1 < argc) {
|
||||
config_file_override = argv[++i];
|
||||
} else {
|
||||
log_error("Config file argument requires a value");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--config-dir") == 0) {
|
||||
if (i + 1 < argc) {
|
||||
config_dir_override = argv[++i];
|
||||
} else {
|
||||
log_error("Config directory argument requires a value");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "-D") == 0 || strcmp(argv[i], "--database-path") == 0) {
|
||||
if (i + 1 < argc) {
|
||||
database_path_override = argv[++i];
|
||||
} else {
|
||||
log_error("Database path argument requires a value");
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
|
||||
print_version();
|
||||
return 0;
|
||||
} else {
|
||||
log_error("Unknown argument");
|
||||
log_error("Unknown argument. Use --help for usage information.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Store config overrides in global variables for configuration system access
|
||||
if (config_dir_override) {
|
||||
setenv("C_RELAY_CONFIG_DIR_OVERRIDE", config_dir_override, 1);
|
||||
}
|
||||
if (config_file_override) {
|
||||
setenv("C_RELAY_CONFIG_FILE_OVERRIDE", config_file_override, 1);
|
||||
}
|
||||
|
||||
// Set up signal handlers
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
printf(BLUE BOLD "=== C Nostr Relay Server ===" RESET "\n");
|
||||
printf("Event-based configuration system\n\n");
|
||||
|
||||
// Apply database path override BEFORE any database operations
|
||||
if (database_path_override) {
|
||||
log_info("Database path override specified from command line");
|
||||
printf(" Override path: %s\n", database_path_override);
|
||||
// Set environment variable so init_database can use the correct path
|
||||
setenv("C_RELAY_DATABASE_PATH_OVERRIDE", database_path_override, 1);
|
||||
}
|
||||
|
||||
// Initialize database FIRST (required for configuration system)
|
||||
if (init_database() != 0) {
|
||||
log_error("Failed to initialize database");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Database path override is applied via environment variable - no need to store in config
|
||||
// (storing database path in database creates circular dependency)
|
||||
|
||||
// Initialize nostr library BEFORE configuration system
|
||||
// (required for Nostr event generation in config files)
|
||||
// Initialize nostr library FIRST (required for key generation and event creation)
|
||||
if (nostr_init() != 0) {
|
||||
log_error("Failed to initialize nostr library");
|
||||
close_database();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize configuration system (loads file + database + applies to globals)
|
||||
if (init_configuration_system() != 0) {
|
||||
log_error("Failed to initialize configuration system");
|
||||
// Check if this is first-time startup or existing relay
|
||||
if (is_first_time_startup()) {
|
||||
log_info("First-time startup detected");
|
||||
|
||||
// Initialize event-based configuration system
|
||||
if (init_configuration_system(NULL, NULL) != 0) {
|
||||
log_error("Failed to initialize event-based configuration system");
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Run first-time startup sequence (generates keys, creates database, etc.)
|
||||
if (first_time_startup_sequence() != 0) {
|
||||
log_error("Failed to complete first-time startup sequence");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize database with the generated relay pubkey
|
||||
if (init_database(g_database_path) != 0) {
|
||||
log_error("Failed to initialize database after first-time setup");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Retry storing the configuration event now that database is initialized
|
||||
if (retry_store_initial_config_event() != 0) {
|
||||
log_warning("Failed to store initial configuration event after database init");
|
||||
}
|
||||
} else {
|
||||
log_info("Existing relay detected");
|
||||
|
||||
// Find existing database file
|
||||
char** existing_files = find_existing_nrdb_files();
|
||||
if (!existing_files || !existing_files[0]) {
|
||||
log_error("No existing relay database found");
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Extract relay pubkey from filename
|
||||
char* relay_pubkey = extract_pubkey_from_filename(existing_files[0]);
|
||||
if (!relay_pubkey) {
|
||||
log_error("Failed to extract relay pubkey from database filename");
|
||||
// Free the files array
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
free(existing_files[i]);
|
||||
}
|
||||
free(existing_files);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize event-based configuration system
|
||||
if (init_configuration_system(NULL, NULL) != 0) {
|
||||
log_error("Failed to initialize event-based configuration system");
|
||||
free(relay_pubkey);
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
free(existing_files[i]);
|
||||
}
|
||||
free(existing_files);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Setup existing relay (sets database path and loads config)
|
||||
if (startup_existing_relay(relay_pubkey) != 0) {
|
||||
log_error("Failed to setup existing relay");
|
||||
cleanup_configuration_system();
|
||||
free(relay_pubkey);
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
free(existing_files[i]);
|
||||
}
|
||||
free(existing_files);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize database with existing database path
|
||||
if (init_database(g_database_path) != 0) {
|
||||
log_error("Failed to initialize existing database");
|
||||
cleanup_configuration_system();
|
||||
free(relay_pubkey);
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
free(existing_files[i]);
|
||||
}
|
||||
free(existing_files);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Load configuration from database
|
||||
cJSON* config_event = load_config_event_from_database(relay_pubkey);
|
||||
if (config_event) {
|
||||
if (apply_configuration_from_event(config_event) != 0) {
|
||||
log_warning("Failed to apply configuration from database");
|
||||
} else {
|
||||
log_success("Configuration loaded from database");
|
||||
}
|
||||
cJSON_Delete(config_event);
|
||||
} else {
|
||||
log_warning("No configuration event found in existing database");
|
||||
}
|
||||
|
||||
// Free memory
|
||||
free(relay_pubkey);
|
||||
for (int i = 0; existing_files[i]; i++) {
|
||||
free(existing_files[i]);
|
||||
}
|
||||
free(existing_files);
|
||||
}
|
||||
|
||||
// Verify database is now available
|
||||
if (!g_db) {
|
||||
log_error("Database not available after initialization");
|
||||
cleanup_configuration_system();
|
||||
nostr_cleanup();
|
||||
close_database();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Update database_path field to reflect actual database path used
|
||||
if (database_path_override) {
|
||||
// Convert to absolute path and normalize
|
||||
char actual_db_path[1024];
|
||||
if (database_path_override[0] == '/') {
|
||||
// Already absolute
|
||||
strncpy(actual_db_path, database_path_override, sizeof(actual_db_path) - 1);
|
||||
} else {
|
||||
// Make absolute by prepending current working directory
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof(cwd))) {
|
||||
// Handle the case where path starts with ./
|
||||
const char* clean_path = database_path_override;
|
||||
if (strncmp(database_path_override, "./", 2) == 0) {
|
||||
clean_path = database_path_override + 2;
|
||||
}
|
||||
|
||||
// Ensure we don't exceed buffer size
|
||||
int written = snprintf(actual_db_path, sizeof(actual_db_path), "%s/%s", cwd, clean_path);
|
||||
if (written >= (int)sizeof(actual_db_path)) {
|
||||
log_warning("Database path too long, using original path");
|
||||
strncpy(actual_db_path, database_path_override, sizeof(actual_db_path) - 1);
|
||||
actual_db_path[sizeof(actual_db_path) - 1] = '\0';
|
||||
}
|
||||
} else {
|
||||
strncpy(actual_db_path, database_path_override, sizeof(actual_db_path) - 1);
|
||||
}
|
||||
}
|
||||
actual_db_path[sizeof(actual_db_path) - 1] = '\0';
|
||||
|
||||
// Update the database_path configuration to reflect actual path used
|
||||
if (set_database_config("database_path", actual_db_path, "system") == 0) {
|
||||
log_info("Updated database_path configuration with actual path used");
|
||||
} else {
|
||||
log_warning("Failed to update database_path configuration");
|
||||
}
|
||||
}
|
||||
|
||||
// Store metadata about configuration file path used
|
||||
if (strlen(g_config_manager.config_file_path) > 0) {
|
||||
// Convert to absolute path and normalize (fix double slash issue)
|
||||
char actual_config_path[1024];
|
||||
if (g_config_manager.config_file_path[0] == '/') {
|
||||
// Already absolute - use as-is
|
||||
strncpy(actual_config_path, g_config_manager.config_file_path, sizeof(actual_config_path) - 1);
|
||||
} else {
|
||||
// Make absolute by prepending current working directory
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof(cwd))) {
|
||||
// Handle the case where path starts with ./
|
||||
const char* clean_path = g_config_manager.config_file_path;
|
||||
if (strncmp(g_config_manager.config_file_path, "./", 2) == 0) {
|
||||
clean_path = g_config_manager.config_file_path + 2;
|
||||
}
|
||||
|
||||
// Remove any trailing slash from cwd to avoid double slash
|
||||
size_t cwd_len = strlen(cwd);
|
||||
if (cwd_len > 0 && cwd[cwd_len - 1] == '/') {
|
||||
cwd[cwd_len - 1] = '\0';
|
||||
}
|
||||
|
||||
// Remove any leading slash from clean_path to avoid double slash
|
||||
if (clean_path[0] == '/') {
|
||||
clean_path++;
|
||||
}
|
||||
|
||||
snprintf(actual_config_path, sizeof(actual_config_path), "%s/%s", cwd, clean_path);
|
||||
} else {
|
||||
strncpy(actual_config_path, g_config_manager.config_file_path, sizeof(actual_config_path) - 1);
|
||||
}
|
||||
}
|
||||
actual_config_path[sizeof(actual_config_path) - 1] = '\0';
|
||||
|
||||
if (set_database_config("config_location", actual_config_path, "system") == 0) {
|
||||
log_info("Stored configuration location metadata");
|
||||
} else {
|
||||
log_warning("Failed to store configuration location metadata");
|
||||
}
|
||||
}
|
||||
|
||||
// Apply command line overrides AFTER configuration system is initialized
|
||||
if (port != DEFAULT_PORT) {
|
||||
log_info("Applying port override from command line");
|
||||
printf(" Port: %d\n", port);
|
||||
// Set environment variable for port override (runtime only, not persisted)
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%d", port);
|
||||
setenv("C_RELAY_PORT_OVERRIDE", port_str, 1);
|
||||
}
|
||||
// Configuration system is now fully initialized with event-based approach
|
||||
// All configuration is loaded from database events
|
||||
|
||||
// Initialize NIP-11 relay information
|
||||
init_relay_info();
|
||||
@@ -3236,8 +3219,8 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
log_info("Starting relay server...");
|
||||
|
||||
// Start WebSocket Nostr relay server
|
||||
int result = start_websocket_relay();
|
||||
// Start WebSocket Nostr relay server (port from configuration)
|
||||
int result = start_websocket_relay(-1); // Let config system determine port
|
||||
|
||||
// Cleanup
|
||||
cleanup_relay_info();
|
||||
|
||||
139
src/sql_schema.h
139
src/sql_schema.h
@@ -1,6 +1,6 @@
|
||||
/* Embedded SQL Schema for C Nostr Relay
|
||||
* Generated from db/schema.sql - Do not edit manually
|
||||
* Schema Version: 3
|
||||
* Schema Version: 4
|
||||
*/
|
||||
#ifndef SQL_SCHEMA_H
|
||||
#define SQL_SCHEMA_H
|
||||
@@ -12,6 +12,7 @@
|
||||
static const char* const EMBEDDED_SCHEMA_SQL =
|
||||
"-- C Nostr Relay Database Schema\n\
|
||||
-- SQLite schema for storing Nostr events with JSON tags support\n\
|
||||
-- Event-based configuration system using kind 33334 Nostr events\n\
|
||||
\n\
|
||||
-- Schema version tracking\n\
|
||||
PRAGMA user_version = 4;\n\
|
||||
@@ -57,8 +58,8 @@ CREATE TABLE schema_info (\n\
|
||||
\n\
|
||||
-- Insert schema metadata\n\
|
||||
INSERT INTO schema_info (key, value) VALUES\n\
|
||||
('version', '3'),\n\
|
||||
('description', 'Hybrid single-table Nostr relay schema with JSON tags and configuration management'),\n\
|
||||
('version', '4'),\n\
|
||||
('description', 'Event-based Nostr relay schema with kind 33334 configuration events'),\n\
|
||||
('created_at', strftime('%s', 'now'));\n\
|
||||
\n\
|
||||
-- Helper views for common queries\n\
|
||||
@@ -79,6 +80,19 @@ SELECT \n\
|
||||
FROM events\n\
|
||||
GROUP BY event_type;\n\
|
||||
\n\
|
||||
-- Configuration events view (kind 33334)\n\
|
||||
CREATE VIEW configuration_events AS\n\
|
||||
SELECT \n\
|
||||
id,\n\
|
||||
pubkey as admin_pubkey,\n\
|
||||
created_at,\n\
|
||||
content,\n\
|
||||
tags,\n\
|
||||
sig\n\
|
||||
FROM events\n\
|
||||
WHERE kind = 33334\n\
|
||||
ORDER BY created_at DESC;\n\
|
||||
\n\
|
||||
-- Optimization: Trigger for automatic cleanup of ephemeral events older than 1 hour\n\
|
||||
CREATE TRIGGER cleanup_ephemeral_events\n\
|
||||
AFTER INSERT ON events\n\
|
||||
@@ -101,6 +115,19 @@ BEGIN\n\
|
||||
AND id != NEW.id;\n\
|
||||
END;\n\
|
||||
\n\
|
||||
-- Addressable event handling trigger (for kind 33334 configuration events)\n\
|
||||
CREATE TRIGGER handle_addressable_events\n\
|
||||
AFTER INSERT ON events\n\
|
||||
WHEN NEW.event_type = 'addressable'\n\
|
||||
BEGIN\n\
|
||||
-- For kind 33334 (configuration), replace previous config from same admin\n\
|
||||
DELETE FROM events \n\
|
||||
WHERE pubkey = NEW.pubkey \n\
|
||||
AND kind = NEW.kind \n\
|
||||
AND event_type = 'addressable'\n\
|
||||
AND id != NEW.id;\n\
|
||||
END;\n\
|
||||
\n\
|
||||
-- Persistent Subscriptions Logging Tables (Phase 2)\n\
|
||||
-- Optional database logging for subscription analytics and debugging\n\
|
||||
\n\
|
||||
@@ -190,110 +217,6 @@ WHERE event_type = 'created'\n\
|
||||
AND subscription_id NOT IN (\n\
|
||||
SELECT subscription_id FROM subscription_events\n\
|
||||
WHERE event_type IN ('closed', 'expired', 'disconnected')\n\
|
||||
);\n\
|
||||
\n\
|
||||
-- ================================\n\
|
||||
-- CONFIGURATION MANAGEMENT TABLES\n\
|
||||
-- ================================\n\
|
||||
\n\
|
||||
-- Core server configuration table\n\
|
||||
CREATE TABLE config (\n\
|
||||
key TEXT PRIMARY KEY, -- Configuration key (unique identifier)\n\
|
||||
value TEXT NOT NULL, -- Configuration value (stored as string)\n\
|
||||
description TEXT, -- Human-readable description\n\
|
||||
config_type TEXT DEFAULT 'user' CHECK (config_type IN ('system', 'user', 'runtime')),\n\
|
||||
data_type TEXT DEFAULT 'string' CHECK (data_type IN ('string', 'integer', 'boolean', 'json')),\n\
|
||||
validation_rules TEXT, -- JSON validation rules (optional)\n\
|
||||
is_sensitive INTEGER DEFAULT 0, -- 1 if value should be masked in logs\n\
|
||||
requires_restart INTEGER DEFAULT 0, -- 1 if change requires server restart\n\
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),\n\
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))\n\
|
||||
);\n\
|
||||
\n\
|
||||
-- Configuration change history table\n\
|
||||
CREATE TABLE config_history (\n\
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,\n\
|
||||
config_key TEXT NOT NULL, -- Key that was changed\n\
|
||||
old_value TEXT, -- Previous value (NULL for new keys)\n\
|
||||
new_value TEXT NOT NULL, -- New value\n\
|
||||
changed_by TEXT DEFAULT 'system', -- Who made the change (system/admin/user)\n\
|
||||
change_reason TEXT, -- Optional reason for change\n\
|
||||
changed_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),\n\
|
||||
FOREIGN KEY (config_key) REFERENCES config(key)\n\
|
||||
);\n\
|
||||
\n\
|
||||
-- Configuration validation errors log\n\
|
||||
CREATE TABLE config_validation_log (\n\
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,\n\
|
||||
config_key TEXT NOT NULL,\n\
|
||||
attempted_value TEXT,\n\
|
||||
validation_error TEXT NOT NULL,\n\
|
||||
error_source TEXT DEFAULT 'validation', -- validation/parsing/constraint\n\
|
||||
attempted_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))\n\
|
||||
);\n\
|
||||
\n\
|
||||
-- Cache for file-based configuration events\n\
|
||||
CREATE TABLE config_file_cache (\n\
|
||||
file_path TEXT PRIMARY KEY, -- Full path to config file\n\
|
||||
file_hash TEXT NOT NULL, -- SHA256 hash of file content\n\
|
||||
event_id TEXT, -- Nostr event ID from file\n\
|
||||
event_pubkey TEXT, -- Admin pubkey that signed event\n\
|
||||
loaded_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),\n\
|
||||
validation_status TEXT CHECK (validation_status IN ('valid', 'invalid', 'unverified')),\n\
|
||||
validation_error TEXT -- Error details if invalid\n\
|
||||
);\n\
|
||||
\n\
|
||||
-- Performance indexes for configuration tables\n\
|
||||
CREATE INDEX idx_config_type ON config(config_type);\n\
|
||||
CREATE INDEX idx_config_updated ON config(updated_at DESC);\n\
|
||||
CREATE INDEX idx_config_history_key ON config_history(config_key);\n\
|
||||
CREATE INDEX idx_config_history_time ON config_history(changed_at DESC);\n\
|
||||
CREATE INDEX idx_config_validation_key ON config_validation_log(config_key);\n\
|
||||
CREATE INDEX idx_config_validation_time ON config_validation_log(attempted_at DESC);\n\
|
||||
\n\
|
||||
-- Trigger to update timestamp on configuration changes\n\
|
||||
CREATE TRIGGER update_config_timestamp\n\
|
||||
AFTER UPDATE ON config\n\
|
||||
BEGIN\n\
|
||||
UPDATE config SET updated_at = strftime('%s', 'now') WHERE key = NEW.key;\n\
|
||||
END;\n\
|
||||
\n\
|
||||
-- Trigger to log configuration changes to history\n\
|
||||
CREATE TRIGGER log_config_changes\n\
|
||||
AFTER UPDATE ON config\n\
|
||||
WHEN OLD.value != NEW.value\n\
|
||||
BEGIN\n\
|
||||
INSERT INTO config_history (config_key, old_value, new_value, changed_by, change_reason)\n\
|
||||
VALUES (NEW.key, OLD.value, NEW.value, 'system', 'configuration update');\n\
|
||||
END;\n\
|
||||
\n\
|
||||
-- Runtime Statistics View\n\
|
||||
CREATE VIEW runtime_stats AS\n\
|
||||
SELECT\n\
|
||||
key,\n\
|
||||
value,\n\
|
||||
description,\n\
|
||||
updated_at\n\
|
||||
FROM config\n\
|
||||
WHERE config_type = 'runtime'\n\
|
||||
ORDER BY key;\n\
|
||||
\n\
|
||||
-- Configuration Change Summary\n\
|
||||
CREATE VIEW recent_config_changes AS\n\
|
||||
SELECT\n\
|
||||
ch.config_key,\n\
|
||||
sc.description,\n\
|
||||
ch.old_value,\n\
|
||||
ch.new_value,\n\
|
||||
ch.changed_by,\n\
|
||||
ch.change_reason,\n\
|
||||
ch.changed_at\n\
|
||||
FROM config_history ch\n\
|
||||
JOIN config sc ON ch.config_key = sc.key\n\
|
||||
ORDER BY ch.changed_at DESC\n\
|
||||
LIMIT 50;\n\
|
||||
\n\
|
||||
-- Runtime Statistics (initialized by server on startup)\n\
|
||||
-- These will be populated when configuration system initializes";
|
||||
);";
|
||||
|
||||
#endif /* SQL_SCHEMA_H */
|
||||
Reference in New Issue
Block a user