Reupload
This commit is contained in:
@@ -58,7 +58,7 @@
|
|||||||
<div class="inline-buttons">
|
<div class="inline-buttons">
|
||||||
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
|
<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="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>
|
||||||
|
|
||||||
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
|
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
|
||||||
|
|||||||
105
api/index.js
105
api/index.js
@@ -41,7 +41,7 @@
|
|||||||
const relayConnectionStatus = document.getElementById('relay-connection-status');
|
const relayConnectionStatus = document.getElementById('relay-connection-status');
|
||||||
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
||||||
const disconnectRelayBtn = document.getElementById('disconnect-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 configDisplay = document.getElementById('config-display');
|
||||||
const configTableBody = document.getElementById('config-table-body');
|
const configTableBody = document.getElementById('config-table-body');
|
||||||
|
|
||||||
@@ -369,28 +369,28 @@
|
|||||||
relayConnectionStatus.className = 'status connected';
|
relayConnectionStatus.className = 'status connected';
|
||||||
connectRelayBtn.disabled = true;
|
connectRelayBtn.disabled = true;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
relayConnectionStatus.textContent = 'CONNECTED';
|
relayConnectionStatus.textContent = 'CONNECTED';
|
||||||
relayConnectionStatus.className = 'status connected';
|
relayConnectionStatus.className = 'status connected';
|
||||||
connectRelayBtn.disabled = true;
|
connectRelayBtn.disabled = true;
|
||||||
disconnectRelayBtn.disabled = false;
|
disconnectRelayBtn.disabled = false;
|
||||||
testWebSocketBtn.disabled = false;
|
restartRelayBtn.disabled = false;
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
relayConnectionStatus.textContent = 'NOT CONNECTED';
|
relayConnectionStatus.textContent = 'NOT CONNECTED';
|
||||||
relayConnectionStatus.className = 'status disconnected';
|
relayConnectionStatus.className = 'status disconnected';
|
||||||
connectRelayBtn.disabled = false;
|
connectRelayBtn.disabled = false;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
relayConnectionStatus.textContent = 'CONNECTION FAILED';
|
relayConnectionStatus.textContent = 'CONNECTION FAILED';
|
||||||
relayConnectionStatus.className = 'status error';
|
relayConnectionStatus.className = 'status error';
|
||||||
connectRelayBtn.disabled = false;
|
connectRelayBtn.disabled = false;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1670,22 +1670,12 @@
|
|||||||
disconnectFromRelay();
|
disconnectFromRelay();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWebSocketBtn.addEventListener('click', function (e) {
|
restartRelayBtn.addEventListener('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const url = relayConnectionUrl.value.trim();
|
sendRestartCommand().catch(error => {
|
||||||
if (!url) {
|
log(`Restart command failed: ${error.message}`, 'ERROR');
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
@@ -3148,6 +3138,83 @@
|
|||||||
// DATABASE STATISTICS FUNCTIONS
|
// 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)
|
// Send stats_query command to get database statistics using Administrator API (inner events)
|
||||||
async function sendStatsQuery() {
|
async function sendStatsQuery() {
|
||||||
if (!isLoggedIn || !userPubkey) {
|
if (!isLoggedIn || !userPubkey) {
|
||||||
|
|||||||
63
src/config.c
63
src/config.c
@@ -16,6 +16,9 @@
|
|||||||
// External database connection (from main.c)
|
// External database connection (from main.c)
|
||||||
extern sqlite3* g_db;
|
extern sqlite3* g_db;
|
||||||
|
|
||||||
|
// External shutdown flag (from main.c)
|
||||||
|
extern volatile sig_atomic_t g_shutdown_flag;
|
||||||
|
|
||||||
// Global unified configuration cache instance
|
// Global unified configuration cache instance
|
||||||
unified_config_cache_t g_unified_cache = {
|
unified_config_cache_t g_unified_cache = {
|
||||||
.cache_lock = PTHREAD_MUTEX_INITIALIZER,
|
.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* response = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(response, "command", "system_status");
|
cJSON_AddStringToObject(response, "command", "system_status");
|
||||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||||
|
|
||||||
cJSON* status_data = cJSON_CreateObject();
|
cJSON* status_data = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(status_data, "database", g_db ? "connected" : "not_available");
|
cJSON_AddStringToObject(status_data, "database", g_db ? "connected" : "not_available");
|
||||||
cJSON_AddStringToObject(status_data, "cache_status", g_unified_cache.cache_valid ? "valid" : "invalid");
|
cJSON_AddStringToObject(status_data, "cache_status", g_unified_cache.cache_valid ? "valid" : "invalid");
|
||||||
|
|
||||||
if (strlen(g_database_path) > 0) {
|
if (strlen(g_database_path) > 0) {
|
||||||
cJSON_AddStringToObject(status_data, "database_path", g_database_path);
|
cJSON_AddStringToObject(status_data, "database_path", g_database_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count configuration items and auth rules
|
// Count configuration items and auth rules
|
||||||
if (g_db) {
|
if (g_db) {
|
||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
|
|
||||||
// Config count
|
// Config count
|
||||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
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);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth rules count
|
// Auth rules count
|
||||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM auth_rules", -1, &stmt, NULL) == SQLITE_OK) {
|
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM auth_rules", -1, &stmt, NULL) == SQLITE_OK) {
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
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);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_AddItemToObject(response, "data", status_data);
|
cJSON_AddItemToObject(response, "data", status_data);
|
||||||
|
|
||||||
printf("=== System Status ===\n");
|
printf("=== System Status ===\n");
|
||||||
printf("Database: %s\n", g_db ? "Connected" : "Not available");
|
printf("Database: %s\n", g_db ? "Connected" : "Not available");
|
||||||
printf("Cache status: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid");
|
printf("Cache status: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid");
|
||||||
|
|
||||||
// Get admin pubkey from event for response
|
// Get admin pubkey from event for response
|
||||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||||
|
|
||||||
if (!admin_pubkey) {
|
if (!admin_pubkey) {
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send response as signed kind 23457 event
|
// Send response as signed kind 23457 event
|
||||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||||
log_success("System status query completed successfully with signed response");
|
log_success("System status query completed successfully with signed response");
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
snprintf(error_message, error_size, "failed to send system status response");
|
snprintf(error_message, error_size, "failed to send system status response");
|
||||||
return -1;
|
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 {
|
else {
|
||||||
snprintf(error_message, error_size, "invalid: unknown system command '%s'", command);
|
snprintf(error_message, error_size, "invalid: unknown system command '%s'", command);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -45,6 +45,8 @@ int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
|
|||||||
// Global state
|
// Global state
|
||||||
sqlite3* g_db = NULL; // Non-static so config.c can access it
|
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
|
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
|
struct lws_context *ws_context = NULL; // Non-static so websockets.c can access it
|
||||||
|
|
||||||
// NIP-11 relay information structure
|
// NIP-11 relay information structure
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ extern unified_config_cache_t g_unified_cache;
|
|||||||
// Forward declarations for global state
|
// Forward declarations for global state
|
||||||
extern sqlite3* g_db;
|
extern sqlite3* g_db;
|
||||||
extern int g_server_running;
|
extern int g_server_running;
|
||||||
|
extern volatile sig_atomic_t g_shutdown_flag;
|
||||||
|
extern int g_restart_requested;
|
||||||
extern struct lws_context *ws_context;
|
extern struct lws_context *ws_context;
|
||||||
|
|
||||||
// Global subscription manager
|
// Global subscription manager
|
||||||
@@ -1153,7 +1155,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
|||||||
log_success(startup_msg);
|
log_success(startup_msg);
|
||||||
|
|
||||||
// Main event loop with proper signal handling
|
// 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);
|
int result = lws_service(ws_context, 1000);
|
||||||
|
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user