This commit is contained in:
Your Name
2025-10-09 10:43:42 -04:00
parent dcf421ff93
commit 5a916cc221
7 changed files with 149 additions and 37 deletions

View File

@@ -58,7 +58,7 @@
<div class="inline-buttons">
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
<button type="button" id="disconnect-relay-btn" disabled>DISCONNECT</button>
<button type="button" id="test-websocket-btn" disabled>TEST WEBSOCKET</button>
<button type="button" id="restart-relay-btn" disabled>RESTART RELAY</button>
</div>
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>

View File

@@ -41,7 +41,7 @@
const relayConnectionStatus = document.getElementById('relay-connection-status');
const connectRelayBtn = document.getElementById('connect-relay-btn');
const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
const testWebSocketBtn = document.getElementById('test-websocket-btn');
const restartRelayBtn = document.getElementById('restart-relay-btn');
const configDisplay = document.getElementById('config-display');
const configTableBody = document.getElementById('config-table-body');
@@ -369,28 +369,28 @@
relayConnectionStatus.className = 'status connected';
connectRelayBtn.disabled = true;
disconnectRelayBtn.disabled = true;
testWebSocketBtn.disabled = true;
restartRelayBtn.disabled = true;
break;
case 'connected':
relayConnectionStatus.textContent = 'CONNECTED';
relayConnectionStatus.className = 'status connected';
connectRelayBtn.disabled = true;
disconnectRelayBtn.disabled = false;
testWebSocketBtn.disabled = false;
restartRelayBtn.disabled = false;
break;
case 'disconnected':
relayConnectionStatus.textContent = 'NOT CONNECTED';
relayConnectionStatus.className = 'status disconnected';
connectRelayBtn.disabled = false;
disconnectRelayBtn.disabled = true;
testWebSocketBtn.disabled = true;
restartRelayBtn.disabled = true;
break;
case 'error':
relayConnectionStatus.textContent = 'CONNECTION FAILED';
relayConnectionStatus.className = 'status error';
connectRelayBtn.disabled = false;
disconnectRelayBtn.disabled = true;
testWebSocketBtn.disabled = true;
restartRelayBtn.disabled = true;
break;
}
}
@@ -1670,22 +1670,12 @@
disconnectFromRelay();
});
testWebSocketBtn.addEventListener('click', function (e) {
restartRelayBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
const url = relayConnectionUrl.value.trim();
if (!url) {
log('Please enter a relay URL first', 'ERROR');
return;
}
testWebSocketConnection(url)
.then(() => {
log('WebSocket test successful', 'INFO');
})
.catch(error => {
log(`WebSocket test failed: ${error.message}`, 'ERROR');
});
sendRestartCommand().catch(error => {
log(`Restart command failed: ${error.message}`, 'ERROR');
});
});
// ================================
@@ -3148,6 +3138,83 @@
// DATABASE STATISTICS FUNCTIONS
// ================================
// Send restart command to restart the relay using Administrator API
async function sendRestartCommand() {
if (!isLoggedIn || !userPubkey) {
log('Must be logged in to restart relay', 'ERROR');
return;
}
if (!relayPool) {
log('SimplePool connection not available', 'ERROR');
return;
}
try {
log('Sending restart command to relay...', 'INFO');
// Create command array for restart
const command_array = ["system_command", "restart"];
// Encrypt the command array directly using NIP-44
const encrypted_content = await encryptForRelay(JSON.stringify(command_array));
if (!encrypted_content) {
throw new Error('Failed to encrypt command array');
}
// Create single kind 23456 admin event
const restartEvent = {
kind: 23456,
pubkey: userPubkey,
created_at: Math.floor(Date.now() / 1000),
tags: [["p", getRelayPubkey()]],
content: encrypted_content
};
// Sign the event
const signedEvent = await window.nostr.signEvent(restartEvent);
if (!signedEvent || !signedEvent.sig) {
throw new Error('Event signing failed');
}
// Publish via SimplePool
const url = relayConnectionUrl.value.trim();
const publishPromises = relayPool.publish([url], signedEvent);
// Use Promise.allSettled to capture per-relay outcomes
const results = await Promise.allSettled(publishPromises);
// Check if any relay accepted the event
let successCount = 0;
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
successCount++;
log(`Restart command published successfully to relay ${index}`, 'INFO');
} else {
log(`Restart command failed on relay ${index}: ${result.reason?.message || result.reason}`, 'ERROR');
}
});
if (successCount === 0) {
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
throw new Error(`All relays rejected restart command. Details: ${errorDetails}`);
}
log('Restart command sent successfully - relay should restart shortly...', 'INFO');
// Update connection status to indicate restart is in progress
updateRelayConnectionStatus('connecting');
relayConnectionStatus.textContent = 'RESTARTING...';
// The relay will disconnect and need to be reconnected after restart
// This will be handled by the WebSocket disconnection event
} catch (error) {
log(`Failed to send restart command: ${error.message}`, 'ERROR');
updateRelayConnectionStatus('error');
}
}
// Send stats_query command to get database statistics using Administrator API (inner events)
async function sendStatsQuery() {
if (!isLoggedIn || !userPubkey) {

View File

@@ -1 +1 @@
1523367
1567707

View File

@@ -16,6 +16,9 @@
// External database connection (from main.c)
extern sqlite3* g_db;
// External shutdown flag (from main.c)
extern volatile sig_atomic_t g_shutdown_flag;
// Global unified configuration cache instance
unified_config_cache_t g_unified_cache = {
.cache_lock = PTHREAD_MUTEX_INITIALIZER,
@@ -3673,19 +3676,19 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "command", "system_status");
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
cJSON* status_data = cJSON_CreateObject();
cJSON_AddStringToObject(status_data, "database", g_db ? "connected" : "not_available");
cJSON_AddStringToObject(status_data, "cache_status", g_unified_cache.cache_valid ? "valid" : "invalid");
if (strlen(g_database_path) > 0) {
cJSON_AddStringToObject(status_data, "database_path", g_database_path);
}
// Count configuration items and auth rules
if (g_db) {
sqlite3_stmt* stmt;
// Config count
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
@@ -3693,7 +3696,7 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
}
sqlite3_finalize(stmt);
}
// Auth rules count
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM auth_rules", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
@@ -3702,34 +3705,72 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
sqlite3_finalize(stmt);
}
}
cJSON_AddItemToObject(response, "data", status_data);
printf("=== System Status ===\n");
printf("Database: %s\n", g_db ? "Connected" : "Not available");
printf("Cache status: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid");
// Get admin pubkey from event for response
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
if (!admin_pubkey) {
cJSON_Delete(response);
snprintf(error_message, error_size, "missing admin pubkey for response");
return -1;
}
// Send response as signed kind 23457 event
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
log_success("System status query completed successfully with signed response");
cJSON_Delete(response);
return 0;
}
cJSON_Delete(response);
snprintf(error_message, error_size, "failed to send system status response");
return -1;
}
else if (strcmp(command, "restart") == 0) {
// Build restart acknowledgment response
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "command", "restart");
cJSON_AddStringToObject(response, "status", "initiating_restart");
cJSON_AddStringToObject(response, "message", "Relay restart initiated - shutting down gracefully");
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
printf("=== Relay Restart Initiated ===\n");
printf("Admin requested system restart\n");
printf("Sending acknowledgment and initiating shutdown...\n");
// Get admin pubkey from event for response
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
if (!admin_pubkey) {
cJSON_Delete(response);
snprintf(error_message, error_size, "missing admin pubkey for response");
return -1;
}
// Send acknowledgment response as signed kind 23457 event
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
log_success("Restart acknowledgment sent successfully - initiating shutdown");
// Trigger graceful shutdown by setting the global shutdown flag
g_shutdown_flag = 1;
log_info("Shutdown flag set - relay will restart gracefully");
cJSON_Delete(response);
return 0;
}
cJSON_Delete(response);
snprintf(error_message, error_size, "failed to send restart acknowledgment");
return -1;
}
else {
snprintf(error_message, error_size, "invalid: unknown system command '%s'", command);
return -1;

File diff suppressed because one or more lines are too long

View File

@@ -45,6 +45,8 @@ int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
// Global state
sqlite3* g_db = NULL; // Non-static so config.c can access it
int g_server_running = 1; // Non-static so websockets.c can access it
volatile sig_atomic_t g_shutdown_flag = 0; // Non-static so config.c can access it for restart functionality
int g_restart_requested = 0; // Non-static so config.c can access it for restart functionality
struct lws_context *ws_context = NULL; // Non-static so websockets.c can access it
// NIP-11 relay information structure

View File

@@ -95,6 +95,8 @@ extern unified_config_cache_t g_unified_cache;
// Forward declarations for global state
extern sqlite3* g_db;
extern int g_server_running;
extern volatile sig_atomic_t g_shutdown_flag;
extern int g_restart_requested;
extern struct lws_context *ws_context;
// Global subscription manager
@@ -1153,7 +1155,7 @@ int start_websocket_relay(int port_override, int strict_port) {
log_success(startup_msg);
// Main event loop with proper signal handling
while (g_server_running) {
while (g_server_running && !g_shutdown_flag) {
int result = lws_service(ws_context, 1000);
if (result < 0) {