v0.2.2 - Working on config setup

This commit is contained in:
Your Name
2025-09-05 19:48:49 -04:00
parent 6c10713e18
commit 9a29ea51e3
19 changed files with 2378 additions and 574 deletions

View File

@@ -15,26 +15,7 @@
#include "../nostr_core_lib/cjson/cJSON.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "../nostr_core_lib/nostr_core/nip013.h" // NIP-13: Proof of Work
// Server Configuration
#define DEFAULT_PORT 8888
#define DEFAULT_HOST "127.0.0.1"
#define DATABASE_PATH "db/c_nostr_relay.db"
#define MAX_CLIENTS 100
// Persistent subscription system configuration
#define MAX_SUBSCRIPTIONS_PER_CLIENT 20
#define MAX_TOTAL_SUBSCRIPTIONS 5000
#define MAX_FILTERS_PER_SUBSCRIPTION 10
#define SUBSCRIPTION_ID_MAX_LENGTH 64
#define CLIENT_IP_MAX_LENGTH 64
// NIP-11 relay information configuration
#define RELAY_NAME_MAX_LENGTH 128
#define RELAY_DESCRIPTION_MAX_LENGTH 1024
#define RELAY_URL_MAX_LENGTH 256
#define RELAY_CONTACT_MAX_LENGTH 128
#define RELAY_PUBKEY_MAX_LENGTH 65 // 64 hex chars + null terminator
#include "config.h" // Configuration management system
// Color constants for logging
#define RED "\033[31m"
@@ -45,7 +26,7 @@
#define RESET "\033[0m"
// Global state
static sqlite3* g_db = NULL;
sqlite3* g_db = NULL; // Non-static so config.c can access it
static int g_server_running = 1;
static struct lws_context *ws_context = NULL;
@@ -188,8 +169,8 @@ static subscription_manager_t g_subscription_manager = {
.active_subscriptions = NULL,
.subscriptions_lock = PTHREAD_MUTEX_INITIALIZER,
.total_subscriptions = 0,
.max_subscriptions_per_client = MAX_SUBSCRIPTIONS_PER_CLIENT,
.max_total_subscriptions = MAX_TOTAL_SUBSCRIPTIONS,
.max_subscriptions_per_client = MAX_SUBSCRIPTIONS_PER_CLIENT, // Will be updated from config
.max_total_subscriptions = MAX_TOTAL_SUBSCRIPTIONS, // Will be updated from config
.total_created = 0,
.total_events_broadcast = 0
};
@@ -200,6 +181,9 @@ void log_success(const char* message);
void log_error(const char* message);
void log_warning(const char* message);
// Forward declaration for subscription manager configuration
void update_subscription_manager_config(void);
// Forward declarations for subscription database logging
void log_subscription_created(const subscription_t* sub);
void log_subscription_closed(const char* sub_id, const char* client_ip, const char* reason);
@@ -955,6 +939,19 @@ void log_warning(const char* message) {
fflush(stdout);
}
// Update subscription manager configuration from config system
void update_subscription_manager_config(void) {
g_subscription_manager.max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", MAX_SUBSCRIPTIONS_PER_CLIENT);
g_subscription_manager.max_total_subscriptions = get_config_int("max_total_subscriptions", MAX_TOTAL_SUBSCRIPTIONS);
char config_msg[256];
snprintf(config_msg, sizeof(config_msg),
"Subscription limits: max_per_client=%d, max_total=%d",
g_subscription_manager.max_subscriptions_per_client,
g_subscription_manager.max_total_subscriptions);
log_info(config_msg);
}
// Signal handler for graceful shutdown
void signal_handler(int sig) {
if (sig == SIGINT || sig == SIGTERM) {
@@ -1288,13 +1285,47 @@ int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, c
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Initialize relay information with default values
// Initialize relay information using configuration system
void init_relay_info() {
// Set default relay information
strncpy(g_relay_info.name, "C Nostr Relay", sizeof(g_relay_info.name) - 1);
strncpy(g_relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_relay_info.description) - 1);
strncpy(g_relay_info.software, "https://github.com/teknari/c-relay", sizeof(g_relay_info.software) - 1);
strncpy(g_relay_info.version, "0.1.0", sizeof(g_relay_info.version) - 1);
// Load relay information from configuration system
const char* relay_name = get_config_value("relay_name");
if (relay_name) {
strncpy(g_relay_info.name, relay_name, sizeof(g_relay_info.name) - 1);
} else {
strncpy(g_relay_info.name, "C Nostr Relay", sizeof(g_relay_info.name) - 1);
}
const char* relay_description = get_config_value("relay_description");
if (relay_description) {
strncpy(g_relay_info.description, relay_description, sizeof(g_relay_info.description) - 1);
} else {
strncpy(g_relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_relay_info.description) - 1);
}
const char* relay_software = get_config_value("relay_software");
if (relay_software) {
strncpy(g_relay_info.software, relay_software, sizeof(g_relay_info.software) - 1);
} else {
strncpy(g_relay_info.software, "https://github.com/laantungir/c-relay", sizeof(g_relay_info.software) - 1);
}
const char* relay_version = get_config_value("relay_version");
if (relay_version) {
strncpy(g_relay_info.version, relay_version, sizeof(g_relay_info.version) - 1);
} else {
strncpy(g_relay_info.version, "0.2.0", sizeof(g_relay_info.version) - 1);
}
// Load optional fields
const char* relay_contact = get_config_value("relay_contact");
if (relay_contact) {
strncpy(g_relay_info.contact, relay_contact, sizeof(g_relay_info.contact) - 1);
}
const char* relay_pubkey = get_config_value("relay_pubkey");
if (relay_pubkey) {
strncpy(g_relay_info.pubkey, relay_pubkey, sizeof(g_relay_info.pubkey) - 1);
}
// Initialize supported NIPs array
g_relay_info.supported_nips = cJSON_CreateArray();
@@ -1308,22 +1339,22 @@ void init_relay_info() {
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp
}
// Initialize server limitations
// Initialize server limitations using configuration
g_relay_info.limitation = cJSON_CreateObject();
if (g_relay_info.limitation) {
cJSON_AddNumberToObject(g_relay_info.limitation, "max_message_length", 16384);
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subscriptions", MAX_SUBSCRIPTIONS_PER_CLIENT);
cJSON_AddNumberToObject(g_relay_info.limitation, "max_limit", 5000);
cJSON_AddNumberToObject(g_relay_info.limitation, "max_message_length", get_config_int("max_message_length", 16384));
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subscriptions", get_config_int("max_subscriptions_per_client", 20));
cJSON_AddNumberToObject(g_relay_info.limitation, "max_limit", get_config_int("max_limit", 5000));
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", 100);
cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", 8196);
cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", get_config_int("max_event_tags", 100));
cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", get_config_int("max_content_length", 8196));
cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", g_pow_config.min_pow_difficulty);
cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", cJSON_False);
cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", get_config_bool("admin_enabled", 0) ? cJSON_True : cJSON_False);
cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False);
cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False);
cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_lower_limit", 0);
cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_upper_limit", 2147483647);
cJSON_AddNumberToObject(g_relay_info.limitation, "default_limit", 500);
cJSON_AddNumberToObject(g_relay_info.limitation, "default_limit", get_config_int("default_limit", 500));
}
// Initialize empty retention policies (can be configured later)
@@ -1636,48 +1667,39 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Initialize PoW configuration with environment variables and defaults
// Initialize PoW configuration using configuration system
void init_pow_config() {
log_info("Initializing NIP-13 Proof of Work configuration");
// Initialize with defaults (already set in struct initialization)
// Load PoW settings from configuration system
g_pow_config.enabled = get_config_bool("pow_enabled", 1);
g_pow_config.min_pow_difficulty = get_config_int("pow_min_difficulty", 0);
// Check environment variables for configuration
const char* pow_enabled_env = getenv("RELAY_POW_ENABLED");
if (pow_enabled_env) {
g_pow_config.enabled = (strcmp(pow_enabled_env, "1") == 0 ||
strcmp(pow_enabled_env, "true") == 0 ||
strcmp(pow_enabled_env, "yes") == 0);
}
const char* min_diff_env = getenv("RELAY_MIN_POW_DIFFICULTY");
if (min_diff_env) {
int min_diff = atoi(min_diff_env);
if (min_diff >= 0 && min_diff <= 64) { // Reasonable bounds
g_pow_config.min_pow_difficulty = min_diff;
}
}
const char* pow_mode_env = getenv("RELAY_POW_MODE");
if (pow_mode_env) {
if (strcmp(pow_mode_env, "strict") == 0) {
// Get PoW mode from configuration
const char* pow_mode = get_config_value("pow_mode");
if (pow_mode) {
if (strcmp(pow_mode, "strict") == 0) {
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
g_pow_config.require_nonce_tag = 1;
g_pow_config.reject_lower_targets = 1;
g_pow_config.strict_format = 1;
g_pow_config.anti_spam_mode = 1;
log_info("PoW configured in strict anti-spam mode");
} else if (strcmp(pow_mode_env, "full") == 0) {
} else if (strcmp(pow_mode, "full") == 0) {
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
g_pow_config.require_nonce_tag = 1;
log_info("PoW configured in full validation mode");
} else if (strcmp(pow_mode_env, "basic") == 0) {
} else if (strcmp(pow_mode, "basic") == 0) {
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
log_info("PoW configured in basic validation mode");
} else if (strcmp(pow_mode_env, "disabled") == 0) {
} else if (strcmp(pow_mode, "disabled") == 0) {
g_pow_config.enabled = 0;
log_info("PoW validation disabled via RELAY_POW_MODE");
log_info("PoW validation disabled via configuration");
}
} else {
// Default to basic mode
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
log_info("PoW configured in basic validation mode (default)");
}
// Log final configuration
@@ -1801,45 +1823,21 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// Initialize expiration configuration with environment variables and defaults
// Initialize expiration configuration using configuration system
void init_expiration_config() {
log_info("Initializing NIP-40 Expiration Timestamp configuration");
// Check environment variables for configuration
const char* exp_enabled_env = getenv("RELAY_EXPIRATION_ENABLED");
if (exp_enabled_env) {
g_expiration_config.enabled = (strcmp(exp_enabled_env, "1") == 0 ||
strcmp(exp_enabled_env, "true") == 0 ||
strcmp(exp_enabled_env, "yes") == 0);
}
// Load expiration settings from configuration system
g_expiration_config.enabled = get_config_bool("expiration_enabled", 1);
g_expiration_config.strict_mode = get_config_bool("expiration_strict", 1);
g_expiration_config.filter_responses = get_config_bool("expiration_filter", 1);
g_expiration_config.delete_expired = get_config_bool("expiration_delete", 0);
g_expiration_config.grace_period = get_config_int("expiration_grace_period", 300);
const char* exp_strict_env = getenv("RELAY_EXPIRATION_STRICT");
if (exp_strict_env) {
g_expiration_config.strict_mode = (strcmp(exp_strict_env, "1") == 0 ||
strcmp(exp_strict_env, "true") == 0 ||
strcmp(exp_strict_env, "yes") == 0);
}
const char* exp_filter_env = getenv("RELAY_EXPIRATION_FILTER");
if (exp_filter_env) {
g_expiration_config.filter_responses = (strcmp(exp_filter_env, "1") == 0 ||
strcmp(exp_filter_env, "true") == 0 ||
strcmp(exp_filter_env, "yes") == 0);
}
const char* exp_delete_env = getenv("RELAY_EXPIRATION_DELETE");
if (exp_delete_env) {
g_expiration_config.delete_expired = (strcmp(exp_delete_env, "1") == 0 ||
strcmp(exp_delete_env, "true") == 0 ||
strcmp(exp_delete_env, "yes") == 0);
}
const char* exp_grace_env = getenv("RELAY_EXPIRATION_GRACE_PERIOD");
if (exp_grace_env) {
long grace_period = atol(exp_grace_env);
if (grace_period >= 0 && grace_period <= 86400) { // Max 24 hours
g_expiration_config.grace_period = grace_period;
}
// Validate grace period bounds
if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) {
log_warning("Invalid grace period, using default of 300 seconds");
g_expiration_config.grace_period = 300;
}
// Log final configuration
@@ -2259,7 +2257,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
}
// Check session subscription limits
if (pss && pss->subscription_count >= MAX_SUBSCRIPTIONS_PER_CLIENT) {
if (pss && pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
log_error("Maximum subscriptions per client exceeded");
// Send CLOSED notice
@@ -2883,7 +2881,7 @@ int start_websocket_relay() {
log_info("Starting libwebsockets-based Nostr relay server...");
memset(&info, 0, sizeof(info));
info.port = DEFAULT_PORT;
info.port = get_config_int("relay_port", DEFAULT_PORT);
info.protocols = protocols;
info.gid = -1;
info.uid = -1;
@@ -2909,7 +2907,9 @@ int start_websocket_relay() {
return -1;
}
log_success("WebSocket relay started on ws://127.0.0.1:8888");
char startup_msg[256];
snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", info.port);
log_success(startup_msg);
// Main event loop with proper signal handling
while (g_server_running) {
@@ -2965,6 +2965,12 @@ int main(int argc, char* argv[]) {
log_error("Invalid port number");
return 1;
}
// Store port in configuration system
char port_str[16];
snprintf(port_str, sizeof(port_str), "%d", port);
set_database_config("relay_port", port_str, "command_line");
// Re-apply configuration to make sure global variables are updated
apply_configuration_to_globals();
} else {
log_error("Port argument requires a value");
return 1;
@@ -2982,19 +2988,28 @@ int main(int argc, char* argv[]) {
printf(BLUE BOLD "=== C Nostr Relay Server ===" RESET "\n");
// Initialize database
// Initialize database FIRST (required for configuration system)
if (init_database() != 0) {
log_error("Failed to initialize database");
return 1;
}
// Initialize nostr library
// Initialize nostr library BEFORE configuration system
// (required for Nostr event generation in config files)
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");
nostr_cleanup();
close_database();
return 1;
}
// Initialize NIP-11 relay information
init_relay_info();
@@ -3004,6 +3019,9 @@ int main(int argc, char* argv[]) {
// Initialize NIP-40 expiration configuration
init_expiration_config();
// Update subscription manager configuration
update_subscription_manager_config();
log_info("Starting relay server...");
// Start WebSocket Nostr relay server
@@ -3011,6 +3029,7 @@ int main(int argc, char* argv[]) {
// Cleanup
cleanup_relay_info();
cleanup_configuration_system();
nostr_cleanup();
close_database();