diff --git a/api/index.html b/api/index.html
index d474cec..e37e414 100644
--- a/api/index.html
+++ b/api/index.html
@@ -932,7 +932,7 @@
description: 'C-Relay instance - pubkey provided manually',
pubkey: manualPubkey,
contact: 'admin@manual.config.relay',
- supported_nips: [1, 2, 4, 9, 11, 12, 15, 16, 20, 22],
+ supported_nips: [1, 9, 11, 13, 15, 20, 33, 40, 42],
software: 'https://github.com/0xtrr/c-relay',
version: '1.0.0'
};
@@ -958,7 +958,7 @@
description: 'C-Relay instance - pubkey provided manually',
pubkey: manualPubkey,
contact: 'admin@manual.config.relay',
- supported_nips: [1, 2, 4, 9, 11, 12, 15, 16, 20, 22],
+ supported_nips: [1, 9, 11, 13, 15, 20, 33, 40, 42],
software: 'https://github.com/0xtrr/c-relay',
version: '1.0.0'
};
@@ -1286,18 +1286,6 @@
console.log('Logout event handled successfully');
}
- // Disconnect from relay and clean up connections
- function disconnectFromRelay() {
- if (relayPool) {
- console.log('Cleaning up relay pool connection...');
- const url = relayConnectionUrl.value.trim();
- if (url) {
- relayPool.close([url]);
- }
- relayPool = null;
- subscriptionId = null;
- }
- }
// Update visibility of admin sections based on login and relay connection status
function updateAdminSectionsVisibility() {
@@ -2030,56 +2018,33 @@
configForm.innerHTML = '';
- // Define field types and validation for different config parameters
+ // Define field types and validation for different config parameters (aligned with README.md)
const fieldTypes = {
'auth_enabled': 'boolean',
- 'nip42_auth_required_events': 'boolean',
- 'nip42_auth_required_subscriptions': 'boolean',
+ 'nip42_auth_required': 'boolean',
'nip40_expiration_enabled': 'boolean',
- 'nip40_expiration_strict': 'boolean',
- 'nip40_expiration_filter': 'boolean',
- 'relay_port': 'number',
'max_connections': 'number',
'pow_min_difficulty': 'number',
- 'nip42_challenge_expiration': 'number',
- 'nip40_expiration_grace_period': 'number',
+ 'nip42_challenge_timeout': 'number',
'max_subscriptions_per_client': 'number',
- 'max_total_subscriptions': 'number',
- 'max_filters_per_subscription': 'number',
'max_event_tags': 'number',
- 'max_content_length': 'number',
- 'max_message_length': 'number',
- 'default_limit': 'number',
- 'max_limit': 'number'
+ 'max_content_length': 'number'
};
const descriptions = {
'relay_pubkey': 'Relay Public Key (Read-only)',
'auth_enabled': 'Enable Authentication',
- 'nip42_auth_required_events': 'Require Auth for Events',
- 'nip42_auth_required_subscriptions': 'Require Auth for Subscriptions',
- 'nip42_auth_required_kinds': 'Auth Required Event Kinds',
- 'nip42_challenge_expiration': 'Auth Challenge Expiration (seconds)',
- 'relay_port': 'Relay Port',
+ 'nip42_auth_required': 'Enable NIP-42 Cryptographic Authentication',
+ 'nip42_auth_required_kinds': 'Event Kinds Requiring NIP-42 Auth',
+ 'nip42_challenge_timeout': 'NIP-42 Challenge Expiration Seconds',
'max_connections': 'Maximum Connections',
'relay_description': 'Relay Description',
'relay_contact': 'Relay Contact',
- 'relay_software': 'Relay Software URL',
- 'relay_version': 'Relay Version',
- 'pow_min_difficulty': 'Minimum PoW Difficulty',
- 'pow_mode': 'PoW Mode',
+ 'pow_min_difficulty': 'Minimum Proof-of-Work Difficulty',
'nip40_expiration_enabled': 'Enable Event Expiration',
- 'nip40_expiration_strict': 'Strict Expiration Mode',
- 'nip40_expiration_filter': 'Filter Expired Events',
- 'nip40_expiration_grace_period': 'Expiration Grace Period (seconds)',
'max_subscriptions_per_client': 'Max Subscriptions per Client',
- 'max_total_subscriptions': 'Max Total Subscriptions',
- 'max_filters_per_subscription': 'Max Filters per Subscription',
- 'max_event_tags': 'Max Event Tags',
- 'max_content_length': 'Max Content Length',
- 'max_message_length': 'Max Message Length',
- 'default_limit': 'Default Query Limit',
- 'max_limit': 'Maximum Query Limit'
+ 'max_event_tags': 'Maximum Tags per Event',
+ 'max_content_length': 'Maximum Event Content Length'
};
// Process configuration tags (no d tag filtering for ephemeral events)
@@ -3452,7 +3417,7 @@
logTestEvent('SENT', `Add Whitelist event: ${JSON.stringify(signedEvent)}`, 'EVENT');
// Publish via SimplePool
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
@@ -3594,7 +3559,7 @@
logTestEvent('SENT', `Signed test event: ${JSON.stringify(signedEvent)}`, 'EVENT');
// Publish via SimplePool to the same relay with detailed error diagnostics
- const url = relayUrl.value.trim();
+ const url = relayConnectionUrl.value.trim();
logTestEvent('INFO', `Publishing to relay: ${url}`, 'INFO');
const publishPromises = relayPool.publish([url], signedEvent);
diff --git a/e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db b/e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db
new file mode 100644
index 0000000..d1c5ad4
Binary files /dev/null and b/e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db differ
diff --git a/nip11_relay_connection_implementation_plan.md b/nip11_relay_connection_implementation_plan.md
deleted file mode 100644
index a860df3..0000000
--- a/nip11_relay_connection_implementation_plan.md
+++ /dev/null
@@ -1,455 +0,0 @@
-# NIP-11 Relay Connection Implementation Plan
-
-## Overview
-Implement NIP-11 relay information fetching in the web admin interface to replace hardcoded relay pubkey and provide proper relay connection flow.
-
-## Current Issues
-1. **Hardcoded Relay Pubkey**: `getRelayPubkey()` returns hardcoded value `'4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa'`
-2. **Relay URL in Debug Section**: Currently in "DEBUG - TEST FETCH WITHOUT LOGIN" section (lines 336-385)
-3. **No Relay Verification**: Users can attempt admin operations without verifying relay identity
-4. **Missing NIP-11 Support**: No fetching of relay information document
-
-## Implementation Plan
-
-### 1. New Relay Connection Section (HTML Structure)
-
-Add after User Info section (around line 332):
-
-```html
-
-
-
RELAY CONNECTION
-
-
-
-
-
-
-
-
-
NOT CONNECTED
-
-
-
-
Relay Information
-
-
Name: -
-
Description: -
-
-
Software: -
-
Version: -
-
Contact: -
-
Supported NIPs: -
-
-
-
-```
-
-### 2. JavaScript Implementation
-
-#### Global State Variables
-Add to global state section (around line 535):
-
-```javascript
-// Relay connection state
-let relayInfo = null;
-let isRelayConnected = false;
-let relayWebSocket = null;
-```
-
-#### NIP-11 Fetching Function
-Add new function:
-
-```javascript
-// Fetch relay information using NIP-11
-async function fetchRelayInfo(relayUrl) {
- try {
- console.log('=== FETCHING RELAY INFO VIA NIP-11 ===');
- console.log('Relay URL:', relayUrl);
-
- // Convert WebSocket URL to HTTP URL for NIP-11
- let httpUrl = relayUrl;
- if (relayUrl.startsWith('ws://')) {
- httpUrl = relayUrl.replace('ws://', 'http://');
- } else if (relayUrl.startsWith('wss://')) {
- httpUrl = relayUrl.replace('wss://', 'https://');
- }
-
- console.log('HTTP URL for NIP-11:', httpUrl);
-
- // Fetch relay information document
- const response = await fetch(httpUrl, {
- method: 'GET',
- headers: {
- 'Accept': 'application/nostr+json'
- },
- // Add timeout
- signal: AbortSignal.timeout(10000) // 10 second timeout
- });
-
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
- }
-
- const contentType = response.headers.get('content-type');
- if (!contentType || !contentType.includes('application/json')) {
- throw new Error(`Invalid content type: ${contentType}. Expected application/json or application/nostr+json`);
- }
-
- const relayInfoData = await response.json();
- console.log('Fetched relay info:', relayInfoData);
-
- // Validate required fields
- if (!relayInfoData.pubkey) {
- throw new Error('Relay information missing required pubkey field');
- }
-
- // Validate pubkey format (64 hex characters)
- if (!/^[0-9a-fA-F]{64}$/.test(relayInfoData.pubkey)) {
- throw new Error(`Invalid relay pubkey format: ${relayInfoData.pubkey}`);
- }
-
- return relayInfoData;
-
- } catch (error) {
- console.error('Failed to fetch relay info:', error);
- throw error;
- }
-}
-```
-
-#### Relay Connection Function
-Add new function:
-
-```javascript
-// Connect to relay and fetch information
-async function connectToRelay() {
- try {
- const relayUrlInput = document.getElementById('relay-url-input');
- const connectBtn = document.getElementById('connect-relay-btn');
- const disconnectBtn = document.getElementById('disconnect-relay-btn');
- const statusDiv = document.getElementById('relay-connection-status');
- const infoDisplay = document.getElementById('relay-info-display');
-
- const url = relayUrlInput.value.trim();
- if (!url) {
- throw new Error('Please enter a relay URL');
- }
-
- // Update UI to show connecting state
- connectBtn.disabled = true;
- statusDiv.textContent = 'CONNECTING...';
- statusDiv.className = 'status connected';
-
- console.log('Connecting to relay:', url);
-
- // Fetch relay information via NIP-11
- console.log('Fetching relay information...');
- const fetchedRelayInfo = await fetchRelayInfo(url);
-
- // Test WebSocket connection
- console.log('Testing WebSocket connection...');
- await testWebSocketConnection(url);
-
- // Store relay information
- relayInfo = fetchedRelayInfo;
- isRelayConnected = true;
-
- // Update UI with relay information
- displayRelayInfo(relayInfo);
-
- // Update connection status
- statusDiv.textContent = 'CONNECTED';
- statusDiv.className = 'status connected';
-
- // Update button states
- connectBtn.style.display = 'none';
- disconnectBtn.style.display = 'inline-block';
- relayUrlInput.disabled = true;
-
- // Show relay info
- infoDisplay.classList.remove('hidden');
-
- console.log('Successfully connected to relay:', relayInfo.name || url);
- log(`Connected to relay: ${relayInfo.name || url}`, 'INFO');
-
- } catch (error) {
- console.error('Failed to connect to relay:', error);
-
- // Reset UI state
- const connectBtn = document.getElementById('connect-relay-btn');
- const statusDiv = document.getElementById('relay-connection-status');
-
- connectBtn.disabled = false;
- statusDiv.textContent = `CONNECTION FAILED: ${error.message}`;
- statusDiv.className = 'status error';
-
- // Clear any partial state
- relayInfo = null;
- isRelayConnected = false;
-
- log(`Failed to connect to relay: ${error.message}`, 'ERROR');
- }
-}
-```
-
-#### WebSocket Connection Test
-Add new function:
-
-```javascript
-// Test WebSocket connection to relay
-async function testWebSocketConnection(url) {
- return new Promise((resolve, reject) => {
- const timeout = setTimeout(() => {
- ws.close();
- reject(new Error('WebSocket connection timeout'));
- }, 5000);
-
- const ws = new WebSocket(url);
-
- ws.onopen = () => {
- clearTimeout(timeout);
- console.log('WebSocket connection successful');
- ws.close();
- resolve();
- };
-
- ws.onerror = (error) => {
- clearTimeout(timeout);
- console.error('WebSocket connection failed:', error);
- reject(new Error('WebSocket connection failed'));
- };
-
- ws.onclose = (event) => {
- if (event.code !== 1000) {
- clearTimeout(timeout);
- reject(new Error(`WebSocket closed with code ${event.code}: ${event.reason}`));
- }
- };
- });
-}
-```
-
-#### Display Relay Information
-Add new function:
-
-```javascript
-// Display relay information in the UI
-function displayRelayInfo(info) {
- document.getElementById('relay-name').textContent = info.name || 'Unknown';
- document.getElementById('relay-description').textContent = info.description || 'No description';
- document.getElementById('relay-pubkey-display').textContent = info.pubkey || 'Unknown';
- document.getElementById('relay-software').textContent = info.software || 'Unknown';
- document.getElementById('relay-version').textContent = info.version || 'Unknown';
- document.getElementById('relay-contact').textContent = info.contact || 'No contact info';
-
- // Format supported NIPs
- let nipsText = 'None specified';
- if (info.supported_nips && Array.isArray(info.supported_nips) && info.supported_nips.length > 0) {
- nipsText = info.supported_nips.map(nip => `NIP-${nip.toString().padStart(2, '0')}`).join(', ');
- }
- document.getElementById('relay-nips').textContent = nipsText;
-}
-```
-
-#### Disconnect Function
-Add new function:
-
-```javascript
-// Disconnect from relay
-function disconnectFromRelay() {
- console.log('Disconnecting from relay...');
-
- // Clear relay state
- relayInfo = null;
- isRelayConnected = false;
-
- // Close any existing connections
- if (relayPool) {
- const url = document.getElementById('relay-url-input').value.trim();
- if (url) {
- relayPool.close([url]);
- }
- relayPool = null;
- subscriptionId = null;
- }
-
- // Reset UI
- const connectBtn = document.getElementById('connect-relay-btn');
- const disconnectBtn = document.getElementById('disconnect-relay-btn');
- const statusDiv = document.getElementById('relay-connection-status');
- const infoDisplay = document.getElementById('relay-info-display');
- const relayUrlInput = document.getElementById('relay-url-input');
-
- connectBtn.style.display = 'inline-block';
- disconnectBtn.style.display = 'none';
- connectBtn.disabled = false;
- relayUrlInput.disabled = false;
-
- statusDiv.textContent = 'NOT CONNECTED';
- statusDiv.className = 'status disconnected';
-
- infoDisplay.classList.add('hidden');
-
- // Reset configuration status
- updateConfigStatus(false);
-
- log('Disconnected from relay', 'INFO');
-}
-```
-
-#### Update getRelayPubkey Function
-Replace existing function (around line 3142):
-
-```javascript
-// Helper function to get relay pubkey from connected relay info
-function getRelayPubkey() {
- if (relayInfo && relayInfo.pubkey) {
- return relayInfo.pubkey;
- }
-
- // Fallback to hardcoded value if no relay connected (for testing)
- console.warn('No relay connected, using fallback pubkey');
- return '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa';
-}
-```
-
-### 3. Event Handlers
-
-Add event handlers in the DOMContentLoaded section:
-
-```javascript
-// Relay connection event handlers
-const connectRelayBtn = document.getElementById('connect-relay-btn');
-const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
-
-if (connectRelayBtn) {
- connectRelayBtn.addEventListener('click', function(e) {
- e.preventDefault();
- connectToRelay().catch(error => {
- console.error('Connect to relay failed:', error);
- });
- });
-}
-
-if (disconnectRelayBtn) {
- disconnectRelayBtn.addEventListener('click', function(e) {
- e.preventDefault();
- disconnectFromRelay();
- });
-}
-```
-
-### 4. Update Existing Functions
-
-#### Update fetchConfiguration Function
-Add relay connection check at the beginning:
-
-```javascript
-async function fetchConfiguration() {
- try {
- console.log('=== FETCHING CONFIGURATION VIA ADMIN API ===');
-
- // Check if relay is connected
- if (!isRelayConnected || !relayInfo) {
- throw new Error('Must be connected to relay first. Please connect to relay in the Relay Connection section.');
- }
-
- // ... rest of existing function
- } catch (error) {
- // ... existing error handling
- }
-}
-```
-
-#### Update subscribeToConfiguration Function
-Add relay connection check:
-
-```javascript
-async function subscribeToConfiguration() {
- try {
- console.log('=== STARTING SIMPLEPOOL CONFIGURATION SUBSCRIPTION ===');
-
- if (!isRelayConnected || !relayInfo) {
- console.error('Must be connected to relay first');
- return false;
- }
-
- // Use the relay URL from the connection section instead of the debug section
- const url = document.getElementById('relay-url-input').value.trim();
-
- // ... rest of existing function
- } catch (error) {
- // ... existing error handling
- }
-}
-```
-
-### 5. Update UI Flow
-
-#### Modify showMainInterface Function
-Update to show relay connection requirement:
-
-```javascript
-function showMainInterface() {
- loginSection.classList.add('hidden');
- mainInterface.classList.remove('hidden');
- userPubkeyDisplay.textContent = userPubkey;
-
- // Show message about relay connection requirement
- if (!isRelayConnected) {
- log('Please connect to a relay to access admin functions', 'INFO');
- }
-}
-```
-
-### 6. Remove/Update Debug Section
-
-#### Option 1: Remove Debug Section Entirely
-Remove the "DEBUG - TEST FETCH WITHOUT LOGIN" section (lines 335-385) since relay URL is now in the proper connection section.
-
-#### Option 2: Keep Debug Section for Testing
-Update the debug section to use the connected relay URL and add a note that it's for testing purposes.
-
-### 7. Error Handling
-
-Add comprehensive error handling for:
-- Network timeouts
-- Invalid relay URLs
-- Missing NIP-11 support
-- Invalid relay pubkey format
-- WebSocket connection failures
-- CORS issues
-
-### 8. Security Considerations
-
-- Validate relay pubkey format (64 hex characters)
-- Verify relay identity before admin operations
-- Handle CORS properly for NIP-11 requests
-- Sanitize relay information display
-- Warn users about connecting to untrusted relays
-
-## Testing Plan
-
-1. **NIP-11 Fetching**: Test with various relay URLs (localhost, remote relays)
-2. **Error Handling**: Test with invalid URLs, non-Nostr servers, network failures
-3. **WebSocket Connection**: Verify WebSocket connectivity after NIP-11 fetch
-4. **Admin API Integration**: Ensure admin commands use correct relay pubkey
-5. **UI Flow**: Test complete user journey from login → relay connection → admin operations
-
-## Benefits
-
-1. **Proper Relay Identification**: Uses actual relay pubkey instead of hardcoded value
-2. **Better UX**: Clear connection flow and relay information display
-3. **Protocol Compliance**: Implements NIP-11 standard for relay discovery
-4. **Security**: Verifies relay identity before admin operations
-5. **Flexibility**: Works with any NIP-11 compliant relay
-
-## Migration Notes
-
-- Existing users will need to connect to relay after this update
-- Debug section can be kept for development/testing purposes
-- All admin functions will require relay connection
-- Relay pubkey will be dynamically fetched instead of hardcoded
\ No newline at end of file
diff --git a/relay.pid b/relay.pid
index 7ccf87c..4a842f6 100644
--- a/relay.pid
+++ b/relay.pid
@@ -1 +1 @@
-1796483
+1878384
diff --git a/src/config.c b/src/config.c
index f7c69be..1706337 100644
--- a/src/config.c
+++ b/src/config.c
@@ -917,10 +917,11 @@ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes,
int first_time_startup_sequence(const cli_options_t* cli_options) {
log_info("Starting first-time startup sequence...");
-
+
// 1. Generate or use provided admin keypair
unsigned char admin_privkey_bytes[32];
char admin_privkey[65], admin_pubkey[65];
+ int generated_admin_key = 0; // Track if we generated a new admin key
if (cli_options && strlen(cli_options->admin_pubkey_override) == 64) {
// Use provided admin public key directly - skip private key generation entirely
@@ -943,6 +944,7 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
// Set a dummy private key that will never be used (not displayed or stored)
memset(admin_privkey_bytes, 0, 32); // Zero out for security
memset(admin_privkey, 0, sizeof(admin_privkey)); // Zero out the hex string
+ generated_admin_key = 0; // Did not generate a new key
} else {
// Generate random admin keypair using /dev/urandom + nostr_core_lib
log_info("Generating random admin keypair");
@@ -959,6 +961,7 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
return -1;
}
nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey);
+ generated_admin_key = 1; // Generated a new key
}
// 2. Generate or use provided relay keypair
@@ -1017,57 +1020,40 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0';
log_info("Relay private key cached for secure storage after database initialization");
- // 6. Handle configuration setup based on admin key availability
- if (cli_options && strlen(cli_options->admin_pubkey_override) == 64) {
- // Admin pubkey provided - will populate config table after database initialization
- log_info("Admin pubkey provided - config table will be populated after database initialization");
+ // 6. Handle configuration setup - defaults will be populated after database initialization
+ log_info("Configuration setup prepared - defaults will be populated after database initialization");
+
+
+ // CLI overrides will be applied after database initialization in main.c
+ // This prevents "g_db is NULL" errors during first-time startup
+
+ // 10. Print admin private key for user to save (only if we generated a new key)
+ if (generated_admin_key) {
+ printf("\n");
+ printf("=================================================================\n");
+ printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n");
+ printf("=================================================================\n");
+ printf("Admin Private Key: %s\n", admin_privkey);
+ printf("Admin Public Key: %s\n", admin_pubkey);
+ printf("Relay Public Key: %s\n", relay_pubkey);
+ printf("\nDatabase: %s\n", g_database_path);
+ printf("\nThis admin private key is needed to update configuration!\n");
+ printf("Store it safely - it will not be displayed again.\n");
+ printf("=================================================================\n");
+ printf("\n");
} else {
- // Admin private key available - create signed configuration event
- log_info("Admin private key available - creating signed configuration event");
-
- // Create initial configuration event using defaults
- cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey, cli_options);
- if (!config_event) {
- log_error("Failed to create default configuration event");
- return -1;
- }
-
- // 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 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);
- }
- g_pending_config_event = cJSON_Duplicate(config_event, 1);
- }
-
- // Cache the current config
- if (g_current_config) {
- cJSON_Delete(g_current_config);
- }
- g_current_config = cJSON_Duplicate(config_event, 1);
-
- // Clean up
- cJSON_Delete(config_event);
+ printf("\n");
+ printf("=================================================================\n");
+ printf("RELAY STARTUP COMPLETE\n");
+ printf("=================================================================\n");
+ printf("Using provided admin public key for authentication\n");
+ printf("Admin Public Key: %s\n", admin_pubkey);
+ printf("Relay Public Key: %s\n", relay_pubkey);
+ printf("\nDatabase: %s\n", g_database_path);
+ printf("=================================================================\n");
+ printf("\n");
}
- // 10. Print admin private key for user to save
- printf("\n");
- printf("=================================================================\n");
- printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n");
- printf("=================================================================\n");
- printf("Admin Private Key: %s\n", admin_privkey);
- printf("Admin Public Key: %s\n", admin_pubkey);
- printf("Relay Public Key: %s\n", relay_pubkey);
- printf("\nDatabase: %s\n", g_database_path);
- printf("\nThis admin private key is needed to update configuration!\n");
- printf("Store it safely - it will not be displayed again.\n");
- printf("=================================================================\n");
- printf("\n");
-
log_success("First-time startup sequence completed");
return 0;
}
diff --git a/src/main.c b/src/main.c
index eb57682..669dc55 100644
--- a/src/main.c
+++ b/src/main.c
@@ -348,18 +348,18 @@ int init_database(const char* database_path_override) {
}
if (!has_auth_rules) {
- // Add auth_rules table
+ // Add auth_rules table matching sql_schema.h
const char* create_auth_rules_sql =
"CREATE TABLE IF NOT EXISTS auth_rules ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
- " rule_type TEXT NOT NULL," // 'pubkey_whitelist', 'pubkey_blacklist', 'hash_blacklist'
- " operation TEXT NOT NULL," // 'event', 'event_kind_1', etc.
- " rule_target TEXT NOT NULL," // pubkey, hash, or other identifier
- " enabled INTEGER DEFAULT 1," // 0 = disabled, 1 = enabled
- " priority INTEGER DEFAULT 1000," // Lower numbers = higher priority
- " description TEXT," // Optional description
- " created_at INTEGER DEFAULT (strftime('%s', 'now')),"
- " UNIQUE(rule_type, operation, rule_target)"
+ " rule_type TEXT NOT NULL CHECK (rule_type IN ('whitelist', 'blacklist', 'rate_limit', 'auth_required')),"
+ " pattern_type TEXT NOT NULL CHECK (pattern_type IN ('pubkey', 'kind', 'ip', 'global')),"
+ " pattern_value TEXT,"
+ " action TEXT NOT NULL CHECK (action IN ('allow', 'deny', 'require_auth', 'rate_limit')),"
+ " parameters TEXT,"
+ " active INTEGER NOT NULL DEFAULT 1,"
+ " created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),"
+ " updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))"
");";
char* error_msg = NULL;
@@ -373,6 +373,24 @@ int init_database(const char* database_path_override) {
return -1;
}
log_success("Created auth_rules table");
+
+ // Add indexes for auth_rules table
+ const char* create_auth_rules_indexes_sql =
+ "CREATE INDEX IF NOT EXISTS idx_auth_rules_pattern ON auth_rules(pattern_type, pattern_value);"
+ "CREATE INDEX IF NOT EXISTS idx_auth_rules_type ON auth_rules(rule_type);"
+ "CREATE INDEX IF NOT EXISTS idx_auth_rules_active ON auth_rules(active);";
+
+ char* index_error_msg = NULL;
+ int index_rc = sqlite3_exec(g_db, create_auth_rules_indexes_sql, NULL, NULL, &index_error_msg);
+ if (index_rc != SQLITE_OK) {
+ char index_error_log[512];
+ snprintf(index_error_log, sizeof(index_error_log), "Failed to create auth_rules indexes: %s",
+ index_error_msg ? index_error_msg : "unknown error");
+ log_error(index_error_log);
+ if (index_error_msg) sqlite3_free(index_error_msg);
+ return -1;
+ }
+ log_success("Created auth_rules indexes");
} else {
log_info("auth_rules table already exists, skipping creation");
}
@@ -1408,35 +1426,43 @@ int main(int argc, char* argv[]) {
}
// Handle configuration setup after database is initialized
- if (cli_options.admin_pubkey_override && strlen(cli_options.admin_pubkey_override) == 64) {
- // Admin pubkey provided - populate config table directly
- log_info("Populating config table for admin pubkey override after database initialization");
+ // Always populate defaults directly in config table (abandoning legacy event signing)
+ log_info("Populating config table with defaults after database initialization");
- // Populate default config values in table
- if (populate_default_config_values() != 0) {
- log_error("Failed to populate default config values");
- cleanup_configuration_system();
- nostr_cleanup();
- close_database();
- return 1;
- }
-
- // Add pubkeys to config table
- if (add_pubkeys_to_config_table() != 0) {
- log_error("Failed to add pubkeys to config table");
- cleanup_configuration_system();
- nostr_cleanup();
- close_database();
- return 1;
- }
-
- log_success("Configuration populated directly in config table after database initialization");
- } else {
- // Admin private key available - retry storing initial config event
- if (retry_store_initial_config_event() != 0) {
- log_warning("Failed to store initial config event - will retry later");
- }
+ // Populate default config values in table
+ if (populate_default_config_values() != 0) {
+ log_error("Failed to populate default config values");
+ cleanup_configuration_system();
+ nostr_cleanup();
+ close_database();
+ return 1;
}
+
+ // Apply CLI overrides now that database is available
+ if (cli_options.port_override > 0) {
+ char port_str[16];
+ snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override);
+ if (update_config_in_table("relay_port", port_str) != 0) {
+ log_error("Failed to update relay port override in config table");
+ cleanup_configuration_system();
+ nostr_cleanup();
+ close_database();
+ return 1;
+ }
+ log_info("Applied port override from command line");
+ printf(" Port: %d (overriding default)\n", cli_options.port_override);
+ }
+
+ // Add pubkeys to config table
+ if (add_pubkeys_to_config_table() != 0) {
+ log_error("Failed to add pubkeys to config table");
+ cleanup_configuration_system();
+ nostr_cleanup();
+ close_database();
+ return 1;
+ }
+
+ log_success("Configuration populated directly in config table after database initialization");
// Now store the pubkeys in config table since database is available
const char* admin_pubkey = get_admin_pubkey_cached();
@@ -1539,6 +1565,21 @@ int main(int argc, char* argv[]) {
log_warning("No configuration event found in existing database");
}
+ // Apply CLI overrides for existing relay (port override should work even for existing relays)
+ if (cli_options.port_override > 0) {
+ char port_str[16];
+ snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override);
+ if (update_config_in_table("relay_port", port_str) != 0) {
+ log_error("Failed to update relay port override in config table for existing relay");
+ cleanup_configuration_system();
+ nostr_cleanup();
+ close_database();
+ return 1;
+ }
+ log_info("Applied port override from command line for existing relay");
+ printf(" Port: %d (overriding configured port)\n", cli_options.port_override);
+ }
+
// Free memory
free(relay_pubkey);
for (int i = 0; existing_files[i]; i++) {
diff --git a/tests/event_config_tests.sh b/tests/event_config_tests.sh
index 7f64c91..11e4bdc 100755
--- a/tests/event_config_tests.sh
+++ b/tests/event_config_tests.sh
@@ -310,8 +310,51 @@ else
print_failure "Relay failed to start for network test"
fi
-# TEST 10: Multiple Startup Attempts (Port Conflict)
-print_test_header "Test 10: Port Conflict Handling"
+# TEST 10: Port Override with Admin/Relay Key Overrides
+print_test_header "Test 10: Port Override with -a/-r Flags"
+
+cleanup_test_files
+
+# Generate test keys (64 hex chars each)
+TEST_ADMIN_PUBKEY="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
+TEST_RELAY_PRIVKEY="abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
+
+print_info "Testing port override with -p 9999 -a $TEST_ADMIN_PUBKEY -r $TEST_RELAY_PRIVKEY"
+
+# Start relay with port override and key overrides
+timeout 15 $RELAY_BINARY -p 9999 -a $TEST_ADMIN_PUBKEY -r $TEST_RELAY_PRIVKEY > "test_port_override.log" 2>&1 &
+relay_pid=$!
+sleep 5
+
+if kill -0 $relay_pid 2>/dev/null; then
+ # Check if relay bound to port 9999 (not default 8888)
+ if netstat -tln 2>/dev/null | grep -q ":9999"; then
+ print_success "Relay successfully bound to overridden port 9999"
+ else
+ print_failure "Relay not bound to overridden port 9999"
+ fi
+
+ # Check that relay started successfully
+ if check_relay_startup "test_port_override.log"; then
+ print_success "Relay startup completed with overrides"
+ else
+ print_failure "Relay failed to complete startup with overrides"
+ fi
+
+ # Check that admin keys were NOT generated (since -a was provided)
+ if ! check_admin_keys "test_port_override.log"; then
+ print_success "Admin keys not generated (correctly using provided -a key)"
+ else
+ print_failure "Admin keys generated despite -a override"
+ fi
+
+ stop_relay_test $relay_pid
+else
+ print_failure "Relay failed to start with port/key overrides"
+fi
+
+# TEST 11: Multiple Startup Attempts (Port Conflict)
+print_test_header "Test 11: Port Conflict Handling"
relay_pid1=$(start_relay_test "port_conflict_1" 10)
sleep 2
@@ -320,14 +363,14 @@ if kill -0 $relay_pid1 2>/dev/null; then
# Try to start a second relay (should fail due to port conflict)
relay_pid2=$(start_relay_test "port_conflict_2" 5)
sleep 1
-
+
if [ "$relay_pid2" = "0" ] || ! kill -0 $relay_pid2 2>/dev/null; then
print_success "Port conflict properly handled (second instance failed to start)"
else
print_failure "Multiple relay instances started (port conflict not handled)"
stop_relay_test $relay_pid2
fi
-
+
stop_relay_test $relay_pid1
else
print_failure "First relay instance failed to start"