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:
Your Name
2025-09-06 20:19:12 -04:00
parent a02c1204ce
commit c1de1bb480
39 changed files with 6109 additions and 2452 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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 */

View 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 */

View File

@@ -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();

View File

@@ -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 */