v0.1.11 - Last push before changing logging system

This commit is contained in:
Your Name
2025-12-07 12:50:12 -04:00
parent db7621a293
commit deec021933
13 changed files with 1042 additions and 159 deletions

View File

@@ -1,163 +1,541 @@
/*
* Ginxsom Admin WebSocket Module
* Ginxsom Admin WebSocket Server
* Handles WebSocket connections for Kind 23456/23457 admin commands
* Based on c-relay's WebSocket implementation
* Based on c-relay's WebSocket implementation using libwebsockets
*/
#include "ginxsom.h"
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <cjson/cJSON.h>
#include <sqlite3.h>
#include <libwebsockets.h>
#include "ginxsom.h"
// Forward declarations from admin_auth.c
int process_admin_command(cJSON *event, char ***command_array_out, int *command_count_out, char **admin_pubkey_out);
void free_command_array(char **command_array, int command_count);
int create_admin_response(const char *response_json, const char *admin_pubkey, const char *original_event_id, cJSON **response_event_out);
// Forward declarations from admin_event.c
extern char g_db_path[];
extern int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t bytes_len);
extern int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size);
extern int nostr_nip44_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size);
extern cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags,
const unsigned char* private_key, time_t created_at);
// Forward declarations from admin_handlers.c (to be created)
int execute_admin_command(char **command_array, int command_count, const char *admin_pubkey, char **response_json_out);
// Per-session data for each WebSocket connection
struct per_session_data {
char admin_pubkey[65];
int authenticated;
unsigned char pending_response[LWS_PRE + 131072];
size_t pending_response_len;
};
// Handle WebSocket admin command endpoint (/api/admin)
void handle_admin_websocket_request(void) {
// For now, this is a placeholder for WebSocket implementation
// In a full implementation, this would:
// 1. Upgrade HTTP connection to WebSocket
// 2. Handle WebSocket frames
// 3. Process Kind 23456 events
// 4. Send Kind 23457 responses
// Global WebSocket context
static struct lws_context *ws_context = NULL;
static volatile int force_exit = 0;
printf("Status: 501 Not Implemented\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"websocket_not_implemented\",\n");
printf(" \"message\": \"WebSocket admin endpoint not yet implemented\",\n");
printf(" \"note\": \"Use HTTP POST to /api/admin for now\"\n");
printf("}\n");
// Function prototypes
static int get_server_privkey(unsigned char* privkey_bytes);
static int get_server_pubkey(char* pubkey_hex, size_t size);
static int handle_config_query_command(cJSON* response_data);
static int process_admin_event(struct lws *wsi, struct per_session_data *pss, const char *json_str);
/**
* WebSocket protocol callback
*/
static int callback_admin_protocol(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
struct per_session_data *pss = (struct per_session_data *)user;
switch (reason) {
case LWS_CALLBACK_ESTABLISHED:
fprintf(stderr, "[WebSocket] New connection established\n");
fflush(stderr);
memset(pss, 0, sizeof(*pss));
pss->authenticated = 0;
break;
case LWS_CALLBACK_RECEIVE:
fprintf(stderr, "[WebSocket] Received %zu bytes\n", len);
fflush(stderr);
// Null-terminate the received data
char *json_str = malloc(len + 1);
if (!json_str) {
fprintf(stderr, "[WebSocket] Memory allocation failed\n");
fflush(stderr);
return -1;
}
memcpy(json_str, in, len);
json_str[len] = '\0';
// Process the admin event
int result = process_admin_event(wsi, pss, json_str);
free(json_str);
if (result == 0 && pss->pending_response_len > 0) {
// Request callback to send response
lws_callback_on_writable(wsi);
}
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
if (pss->pending_response_len > 0) {
fprintf(stderr, "[WebSocket] Sending %zu bytes\n", pss->pending_response_len - LWS_PRE);
fflush(stderr);
int written = lws_write(wsi,
&pss->pending_response[LWS_PRE],
pss->pending_response_len - LWS_PRE,
LWS_WRITE_TEXT);
if (written < 0) {
fprintf(stderr, "[WebSocket] Write failed\n");
fflush(stderr);
return -1;
}
pss->pending_response_len = 0;
}
break;
case LWS_CALLBACK_CLOSED:
fprintf(stderr, "[WebSocket] Connection closed\n");
fflush(stderr);
break;
default:
break;
}
return 0;
}
// Handle HTTP POST admin command endpoint (/api/admin)
void handle_admin_command_post_request(void) {
// Read the request body (should contain Kind 23456 event JSON)
const char *content_length_str = getenv("CONTENT_LENGTH");
if (!content_length_str) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"missing_content_length\",\n");
printf(" \"message\": \"Content-Length header required\"\n");
printf("}\n");
return;
}
long content_length = atol(content_length_str);
if (content_length <= 0 || content_length > 1024 * 1024) { // 1MB limit
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"invalid_content_length\",\n");
printf(" \"message\": \"Content-Length must be between 1 and 1MB\"\n");
printf("}\n");
return;
}
// Read the request body
char *request_body = malloc(content_length + 1);
if (!request_body) {
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"memory_allocation_failed\",\n");
printf(" \"message\": \"Failed to allocate memory for request body\"\n");
printf("}\n");
return;
}
size_t bytes_read = fread(request_body, 1, content_length, stdin);
if (bytes_read != (size_t)content_length) {
free(request_body);
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"incomplete_request_body\",\n");
printf(" \"message\": \"Failed to read complete request body\"\n");
printf("}\n");
return;
}
request_body[content_length] = '\0';
// Parse the JSON event
cJSON *event = cJSON_Parse(request_body);
free(request_body);
/**
* WebSocket protocols
*/
static struct lws_protocols protocols[] = {
{
"nostr-admin",
callback_admin_protocol,
sizeof(struct per_session_data),
131072, // rx buffer size
0, NULL, 0
},
{ NULL, NULL, 0, 0, 0, NULL, 0 } // terminator
};
/**
* Process Kind 23456 admin event received via WebSocket
*/
static int process_admin_event(struct lws *wsi __attribute__((unused)), struct per_session_data *pss, const char *json_str) {
// Parse event JSON
cJSON *event = cJSON_Parse(json_str);
if (!event) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"invalid_json\",\n");
printf(" \"message\": \"Request body is not valid JSON\"\n");
printf("}\n");
return;
fprintf(stderr, "[WebSocket] Invalid JSON\n");
fflush(stderr);
return -1;
}
// Process the admin command
char **command_array = NULL;
int command_count = 0;
char *admin_pubkey = NULL;
int result = process_admin_command(event, &command_array, &command_count, &admin_pubkey);
// Verify it's Kind 23456
cJSON *kind_obj = cJSON_GetObjectItem(event, "kind");
if (!kind_obj || !cJSON_IsNumber(kind_obj) ||
(int)cJSON_GetNumberValue(kind_obj) != 23456) {
fprintf(stderr, "[WebSocket] Not a Kind 23456 event\n");
fflush(stderr);
cJSON_Delete(event);
return -1;
}
// Get event ID for response correlation
cJSON *id_obj = cJSON_GetObjectItem(event, "id");
if (!id_obj || !cJSON_IsString(id_obj)) {
fprintf(stderr, "[WebSocket] Event missing id\n");
fflush(stderr);
cJSON_Delete(event);
return -1;
}
const char *request_id = cJSON_GetStringValue(id_obj);
// Get admin pubkey from event
cJSON *pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (!pubkey_obj || !cJSON_IsString(pubkey_obj)) {
fprintf(stderr, "[WebSocket] Event missing pubkey\n");
fflush(stderr);
cJSON_Delete(event);
return -1;
}
const char *admin_pubkey = cJSON_GetStringValue(pubkey_obj);
// Verify admin pubkey
if (!verify_admin_pubkey(admin_pubkey)) {
fprintf(stderr, "[WebSocket] Not authorized as admin: %s\n", admin_pubkey);
fflush(stderr);
cJSON_Delete(event);
return -1;
}
// Store admin pubkey in session
strncpy(pss->admin_pubkey, admin_pubkey, sizeof(pss->admin_pubkey) - 1);
pss->authenticated = 1;
// Get encrypted content
cJSON *content_obj = cJSON_GetObjectItem(event, "content");
if (!content_obj || !cJSON_IsString(content_obj)) {
fprintf(stderr, "[WebSocket] Event missing content\n");
fflush(stderr);
cJSON_Delete(event);
return -1;
}
const char *encrypted_content = cJSON_GetStringValue(content_obj);
// Get server private key for decryption
unsigned char server_privkey[32];
if (get_server_privkey(server_privkey) != 0) {
fprintf(stderr, "[WebSocket] Failed to get server private key\n");
fflush(stderr);
cJSON_Delete(event);
return -1;
}
// Convert admin pubkey to bytes
unsigned char admin_pubkey_bytes[32];
if (nostr_hex_to_bytes(admin_pubkey, admin_pubkey_bytes, 32) != 0) {
fprintf(stderr, "[WebSocket] Invalid admin pubkey format\n");
fflush(stderr);
cJSON_Delete(event);
return -1;
}
// Decrypt content using NIP-44
char decrypted_content[8192];
const char *content_to_parse = encrypted_content;
// Check if content is already plaintext JSON (starts with '[')
if (encrypted_content[0] != '[') {
int decrypt_result = nostr_nip44_decrypt(
server_privkey,
admin_pubkey_bytes,
encrypted_content,
decrypted_content,
sizeof(decrypted_content)
);
if (decrypt_result != 0) {
fprintf(stderr, "[WebSocket] Failed to decrypt content\n");
fflush(stderr);
cJSON_Delete(event);
return -1;
}
content_to_parse = decrypted_content;
}
// Parse command array
cJSON *command_array = cJSON_Parse(content_to_parse);
if (!command_array || !cJSON_IsArray(command_array)) {
fprintf(stderr, "[WebSocket] Decrypted content is not a valid command array\n");
fflush(stderr);
cJSON_Delete(event);
return -1;
}
// Get command type
cJSON *command_type = cJSON_GetArrayItem(command_array, 0);
if (!command_type || !cJSON_IsString(command_type)) {
fprintf(stderr, "[WebSocket] Invalid command format\n");
fflush(stderr);
cJSON_Delete(command_array);
cJSON_Delete(event);
return -1;
}
const char *cmd = cJSON_GetStringValue(command_type);
fprintf(stderr, "[WebSocket] Processing command: %s\n", cmd);
fflush(stderr);
// Create response data object
cJSON *response_data = cJSON_CreateObject();
cJSON_AddStringToObject(response_data, "query_type", cmd);
cJSON_AddNumberToObject(response_data, "timestamp", (double)time(NULL));
// Handle command
int result = -1;
if (strcmp(cmd, "config_query") == 0) {
result = handle_config_query_command(response_data);
} else {
cJSON_AddStringToObject(response_data, "status", "error");
cJSON_AddStringToObject(response_data, "error", "Unknown command");
}
cJSON_Delete(command_array);
cJSON_Delete(event);
if (result == 0) {
// Get server keys
char server_pubkey[65];
if (get_server_pubkey(server_pubkey, sizeof(server_pubkey)) != 0) {
fprintf(stderr, "[WebSocket] Failed to get server pubkey\n");
fflush(stderr);
cJSON_Delete(response_data);
return -1;
}
// Convert response data to JSON string
char *response_json = cJSON_PrintUnformatted(response_data);
cJSON_Delete(response_data);
if (!response_json) {
fprintf(stderr, "[WebSocket] Failed to serialize response\n");
fflush(stderr);
return -1;
}
// Encrypt response using NIP-44
char encrypted_response[131072];
int encrypt_result = nostr_nip44_encrypt(
server_privkey,
admin_pubkey_bytes,
response_json,
encrypted_response,
sizeof(encrypted_response)
);
free(response_json);
if (encrypt_result != 0) {
fprintf(stderr, "[WebSocket] Failed to encrypt response\n");
fflush(stderr);
return -1;
}
// Create Kind 23457 response event
cJSON *tags = cJSON_CreateArray();
// p tag for admin
cJSON *p_tag = cJSON_CreateArray();
cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
cJSON_AddItemToArray(p_tag, cJSON_CreateString(admin_pubkey));
cJSON_AddItemToArray(tags, p_tag);
// e tag for request correlation
cJSON *e_tag = cJSON_CreateArray();
cJSON_AddItemToArray(e_tag, cJSON_CreateString("e"));
cJSON_AddItemToArray(e_tag, cJSON_CreateString(request_id));
cJSON_AddItemToArray(tags, e_tag);
// Sign the event
cJSON *signed_event = nostr_create_and_sign_event(
23457,
encrypted_response,
tags,
server_privkey,
time(NULL)
);
if (!signed_event) {
fprintf(stderr, "[WebSocket] Failed to sign response event\n");
fflush(stderr);
return -1;
}
// Serialize event to JSON
char *event_json = cJSON_PrintUnformatted(signed_event);
cJSON_Delete(signed_event);
if (!event_json) {
fprintf(stderr, "[WebSocket] Failed to serialize event\n");
fflush(stderr);
return -1;
}
// Store response in session for sending
size_t json_len = strlen(event_json);
if (json_len + LWS_PRE < sizeof(pss->pending_response)) {
memcpy(&pss->pending_response[LWS_PRE], event_json, json_len);
pss->pending_response_len = LWS_PRE + json_len;
fprintf(stderr, "[WebSocket] Response prepared (%zu bytes)\n", json_len);
fflush(stderr);
} else {
fprintf(stderr, "[WebSocket] Response too large\n");
fflush(stderr);
}
free(event_json);
return 0;
} else {
cJSON_Delete(response_data);
return -1;
}
}
/**
* Get server private key from database
*/
static int get_server_privkey(unsigned char* privkey_bytes) {
sqlite3 *db;
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
return -1;
}
sqlite3_stmt *stmt;
const char *sql = "SELECT seckey FROM blossom_seckey LIMIT 1";
int result = -1;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char *privkey_hex = (const char*)sqlite3_column_text(stmt, 0);
if (privkey_hex && nostr_hex_to_bytes(privkey_hex, privkey_bytes, 32) == 0) {
result = 0;
}
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
return result;
}
/**
* Get server public key from database
*/
static int get_server_pubkey(char* pubkey_hex, size_t size) {
sqlite3 *db;
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
return -1;
}
sqlite3_stmt *stmt;
const char *sql = "SELECT value FROM config WHERE key = 'blossom_pubkey'";
int result = -1;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char *pubkey = (const char*)sqlite3_column_text(stmt, 0);
if (pubkey) {
strncpy(pubkey_hex, pubkey, size - 1);
pubkey_hex[size - 1] = '\0';
result = 0;
}
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
return result;
}
/**
* Handle config_query command
*/
static int handle_config_query_command(cJSON* response_data) {
sqlite3 *db;
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
cJSON_AddStringToObject(response_data, "status", "error");
cJSON_AddStringToObject(response_data, "error", "Database error");
return -1;
}
cJSON_AddStringToObject(response_data, "status", "success");
cJSON *data = cJSON_CreateObject();
// Query all config settings
sqlite3_stmt *stmt;
const char *sql = "SELECT key, value FROM config ORDER BY key";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char *key = (const char*)sqlite3_column_text(stmt, 0);
const char *value = (const char*)sqlite3_column_text(stmt, 1);
if (key && value) {
cJSON_AddStringToObject(data, key, value);
}
}
sqlite3_finalize(stmt);
}
cJSON_AddItemToObject(response_data, "data", data);
sqlite3_close(db);
return 0;
}
/**
* WebSocket server thread
*/
void* admin_websocket_thread(void* arg) {
int port = *(int*)arg;
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.port = port;
info.iface = "127.0.0.1"; // Force IPv4 binding for localhost compatibility
info.protocols = protocols;
info.gid = -1;
info.uid = -1;
info.options = LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_DISABLE_IPV6;
fprintf(stderr, "[WebSocket] Starting admin WebSocket server on 127.0.0.1:%d (IPv4 only)\n", port);
fflush(stderr);
ws_context = lws_create_context(&info);
if (!ws_context) {
fprintf(stderr, "[WebSocket] Failed to create context\n");
fflush(stderr);
return NULL;
}
fprintf(stderr, "[WebSocket] Server started successfully\n");
fflush(stderr);
// Service loop
while (!force_exit) {
lws_service(ws_context, 50);
}
lws_context_destroy(ws_context);
fprintf(stderr, "[WebSocket] Server stopped\n");
fflush(stderr);
return NULL;
}
/**
* Start admin WebSocket server
*/
int start_admin_websocket_server(int port) {
static int server_port;
server_port = port;
pthread_t thread;
int result = pthread_create(&thread, NULL, admin_websocket_thread, &server_port);
if (result != 0) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"invalid_admin_command\",\n");
printf(" \"message\": \"Failed to process admin command\"\n");
printf("}\n");
return;
fprintf(stderr, "[WebSocket] Failed to create thread: %d\n", result);
fflush(stderr);
return -1;
}
pthread_detach(thread);
fprintf(stderr, "[WebSocket] Thread started\n");
fflush(stderr);
return 0;
}
// Execute the command
char *response_json = NULL;
int exec_result = execute_admin_command(command_array, command_count, admin_pubkey, &response_json);
free_command_array(command_array, command_count);
free(admin_pubkey);
if (exec_result != 0) {
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"command_execution_failed\",\n");
printf(" \"message\": \"Failed to execute admin command\"\n");
printf("}\n");
return;
}
// Create the response event (Kind 23457)
cJSON *response_event = NULL;
int create_result = create_admin_response(response_json, admin_pubkey, NULL, &response_event);
free(response_json);
if (create_result != 0) {
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\n");
printf(" \"error\": \"response_creation_failed\",\n");
printf(" \"message\": \"Failed to create admin response\"\n");
printf("}\n");
return;
}
// Return the response event as JSON
char *response_json_str = cJSON_Print(response_event);
cJSON_Delete(response_event);
printf("Status: 200 OK\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("%s\n", response_json_str);
free(response_json_str);
/**
* Stop admin WebSocket server
*/
void stop_admin_websocket_server(void) {
force_exit = 1;
}

View File

@@ -10,8 +10,8 @@
// Version information (auto-updated by build system)
#define VERSION_MAJOR 0
#define VERSION_MINOR 1
#define VERSION_PATCH 10
#define VERSION "v0.1.10"
#define VERSION_PATCH 11
#define VERSION "v0.1.11"
#include <stddef.h>
#include <stdint.h>
@@ -283,6 +283,10 @@ void send_json_response(int status, const char* json_content);
void send_json_error(int status, const char* error, const char* message);
int parse_query_params(const char* query_string, char params[][256], int max_params);
// Admin WebSocket server functions
int start_admin_websocket_server(int port);
void stop_admin_websocket_server(void);
#ifdef __cplusplus
}
#endif

View File

@@ -1830,8 +1830,18 @@ void handle_auth_challenge_request(void) {
/////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) {
// Redirect stderr to log file BEFORE any other operations
// This is necessary because spawn-fcgi doesn't preserve stderr redirections
FILE *stderr_log = freopen("logs/app/stderr.log", "a", stderr);
if (!stderr_log) {
// If redirection fails, continue anyway but log to original stderr
perror("Warning: Failed to redirect stderr to log file");
}
// Set stderr to unbuffered mode so all fprintf(stderr, ...) calls flush immediately
setvbuf(stderr, NULL, _IONBF, 0);
fprintf(stderr, "DEBUG: main() started\n");
fflush(stderr);
// Parse command line arguments
int use_test_keys = 0;
@@ -1934,6 +1944,9 @@ int main(int argc, char *argv[]) {
if (end && (end - start) == 64) {
strncpy(test_server_privkey, start, 64);
test_server_privkey[64] = '\0';
fprintf(stderr, "TEST MODE: Parsed SERVER_PRIVKEY: %s\n", test_server_privkey);
} else {
fprintf(stderr, "TEST MODE: Failed to parse SERVER_PRIVKEY (length: %ld)\n", end ? (long)(end - start) : -1L);
}
}
}
@@ -2022,6 +2035,7 @@ int main(int argc, char *argv[]) {
if (db_path_specified) {
fprintf(stderr, "\n=== SCENARIO 5: DATABASE + KEYS (VALIDATION) ===\n");
strncpy(g_db_path, specified_db_path, sizeof(g_db_path) - 1);
g_db_path[sizeof(g_db_path) - 1] = '\0';
// Check if database exists
struct stat st;
@@ -2117,6 +2131,7 @@ int main(int argc, char *argv[]) {
else if (db_path_specified) {
fprintf(stderr, "\n=== SCENARIO 2: DATABASE SPECIFIED ===\n");
strncpy(g_db_path, specified_db_path, sizeof(g_db_path) - 1);
g_db_path[sizeof(g_db_path) - 1] = '\0';
// Check if database exists
struct stat st;
@@ -2205,6 +2220,32 @@ if (!config_loaded /* && !initialize_server_config() */) {
"STARTUP: Request validator system initialized successfully\r\n");
fflush(stderr);
// Start WebSocket admin server if enabled
sqlite3 *db;
sqlite3_stmt *stmt;
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
if (rc == SQLITE_OK) {
const char *sql = "SELECT value FROM config WHERE key = 'admin_enabled'";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char *admin_enabled = (const char *)sqlite3_column_text(stmt, 0);
if (admin_enabled && (strcmp(admin_enabled, "true") == 0 || strcmp(admin_enabled, "1") == 0)) {
fprintf(stderr, "STARTUP: Starting WebSocket admin server on port 9442...\n");
if (start_admin_websocket_server(9442) == 0) {
fprintf(stderr, "STARTUP: WebSocket admin server started successfully\n");
} else {
fprintf(stderr, "WARNING: Failed to start WebSocket admin server\n");
}
} else {
fprintf(stderr, "STARTUP: Admin interface disabled in config\n");
}
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
}
/////////////////////////////////////////////////////////////////////
// THIS IS WHERE THE REQUESTS ENTER THE FastCGI