v2.1.7 - Refactor SQLite connection ownership into db_ops, remove direct sqlite usage from runtime modules, and document agent-browser admin testing flow
This commit is contained in:
@@ -0,0 +1,190 @@
|
|||||||
|
# Agent Browser Testing Guide for C-Relay
|
||||||
|
|
||||||
|
This document explains how to use the `agent-browser` CLI tool to test the c-relay admin web UI from an AI agent context (no physical display required).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **agent-browser** installed globally: `npm install -g agent-browser`
|
||||||
|
- Binary location: `/home/user/.nvm/versions/node/v24.14.1/bin/agent-browser`
|
||||||
|
- The relay must be running locally (default port 8888)
|
||||||
|
- Test keys configured in `.test_keys`
|
||||||
|
|
||||||
|
## Starting the Relay for Local Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./make_and_restart_relay.sh -t
|
||||||
|
```
|
||||||
|
|
||||||
|
This reads `.test_keys` and starts the relay with:
|
||||||
|
- `ADMIN_PUBKEY` — the hex public key of the admin account
|
||||||
|
- `ADMIN_PRIVKEY` — the hex secret key (used for browser login, not by the relay itself)
|
||||||
|
- `SERVER_PRIVKEY` — the relay's own private key
|
||||||
|
|
||||||
|
## Key URLs
|
||||||
|
|
||||||
|
| URL | Purpose |
|
||||||
|
|-----|---------|
|
||||||
|
| `http://127.0.0.1:8888/api/index.html` | Admin web UI (the correct entry point) |
|
||||||
|
| `http://127.0.0.1:8888/` | Returns 406 without NIP-11 Accept header — **do not use for browser testing** |
|
||||||
|
| `http://127.0.0.1:8888/` with `Accept: application/nostr+json` | NIP-11 relay info JSON |
|
||||||
|
|
||||||
|
**Important:** The root URL `/` is a WebSocket/NIP-11 endpoint, not an HTML page. Always use `/api/index.html` for browser testing.
|
||||||
|
|
||||||
|
## Admin Login Credentials
|
||||||
|
|
||||||
|
The admin nsec for the current `.test_keys` configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
nsec: nsec1zkn0hlt4jvcvn9yt5p7ea4m7f82pf3ph0gj3d4rlcz4s64x5z86satv9qm
|
||||||
|
hex: 15a6fbfd759330c9948ba07d9ed77e49d414c4377a2516d47fc0ab0d54d411f5
|
||||||
|
npub: npub1tlyzk6slzt8d989f4pn3synk98fea64ea3jmrdc7dtd0kw8uxlas9a9fwr
|
||||||
|
hex pubkey: 5fc82b6a1f12ced29ca9a86718127629d39eeab9ec65b1b71e6adafb38fc37fb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Login Flow with agent-browser
|
||||||
|
|
||||||
|
### Step 1: Open the admin UI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser open http://127.0.0.1:8888/api/index.html
|
||||||
|
agent-browser wait --load networkidle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Take an interactive snapshot to see what is on screen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser snapshot -i
|
||||||
|
```
|
||||||
|
|
||||||
|
You will see a login modal with buttons like:
|
||||||
|
- `"Browser Extension"` — skip this
|
||||||
|
- `"Local Key"` — **use this one**
|
||||||
|
- `"Seed Phrase"` — skip
|
||||||
|
- `"Nostr Connect"` — skip
|
||||||
|
- `"Read Only"` — skip
|
||||||
|
|
||||||
|
### Step 3: Click "Local Key"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser click @e15
|
||||||
|
```
|
||||||
|
|
||||||
|
(The ref number may vary — use the ref from the snapshot output for the "Local Key" button.)
|
||||||
|
|
||||||
|
After clicking, a text input appears asking for the secret key.
|
||||||
|
|
||||||
|
### Step 4: Enter the admin nsec
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser fill @e14 "nsec1zkn0hlt4jvcvn9yt5p7ea4m7f82pf3ph0gj3d4rlcz4s64x5z86satv9qm"
|
||||||
|
```
|
||||||
|
|
||||||
|
(Use the ref from the snapshot for the textbox element.)
|
||||||
|
|
||||||
|
### Step 5: Click "Import Key"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser snapshot -i
|
||||||
|
```
|
||||||
|
|
||||||
|
Check the snapshot — the "Import Key" button should now be enabled. Click it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser click @e15
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Click "Continue" on the success screen
|
||||||
|
|
||||||
|
After import, a success screen appears with "Continue" button:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser wait 800
|
||||||
|
agent-browser snapshot -i
|
||||||
|
agent-browser click @e19
|
||||||
|
```
|
||||||
|
|
||||||
|
(Use the ref from the snapshot for the "Continue" button.)
|
||||||
|
|
||||||
|
### Step 7: Wait for admin UI to load
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser wait 5000
|
||||||
|
agent-browser snapshot -i
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now see the admin dashboard with:
|
||||||
|
- Statistics table (Database Size, Total Events, PID, etc.)
|
||||||
|
- Navigation buttons (Statistics, Subscriptions, Configuration, Authorization, etc.)
|
||||||
|
- Admin profile area showing "admin" label
|
||||||
|
|
||||||
|
## Navigating Admin Sections
|
||||||
|
|
||||||
|
After login, use the sidebar navigation buttons:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View configuration
|
||||||
|
agent-browser click @e8 # Configuration button ref
|
||||||
|
|
||||||
|
# View authorization rules
|
||||||
|
agent-browser click @e9 # Authorization button ref
|
||||||
|
|
||||||
|
# View statistics
|
||||||
|
agent-browser click @e6 # Statistics button ref
|
||||||
|
|
||||||
|
# Always snapshot after navigation to see results
|
||||||
|
agent-browser wait 2500
|
||||||
|
agent-browser snapshot -i
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checking for Errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View browser console logs
|
||||||
|
agent-browser console
|
||||||
|
|
||||||
|
# View JavaScript errors
|
||||||
|
agent-browser errors
|
||||||
|
|
||||||
|
# View relay server logs
|
||||||
|
tail -n 100 relay.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chained Command Example (Full Login in One Shot)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser open http://127.0.0.1:8888/api/index.html && \
|
||||||
|
agent-browser wait --load networkidle && \
|
||||||
|
agent-browser snapshot -i
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use refs from the snapshot to complete login steps.
|
||||||
|
|
||||||
|
## Tips for AI Agents
|
||||||
|
|
||||||
|
1. **Always use `/api/index.html`** — never the root URL
|
||||||
|
2. **Use `snapshot -i`** after every action to see the current interactive elements and their refs
|
||||||
|
3. **Refs change** between snapshots — always re-snapshot before clicking
|
||||||
|
4. **Wait after clicks** — use `agent-browser wait 2000` (milliseconds) between actions that trigger async operations
|
||||||
|
5. **The login flow has 3 screens**: method selection → key input → success confirmation
|
||||||
|
6. **Console logs are cumulative** — they show all logs since page load, which is useful for debugging admin API responses
|
||||||
|
7. **The relay log** at `relay.log` shows server-side processing of admin commands
|
||||||
|
8. **Command chaining** with `&&` works — the browser daemon persists between commands
|
||||||
|
|
||||||
|
## Verifying Admin API is Working
|
||||||
|
|
||||||
|
After login, the statistics page should show populated data:
|
||||||
|
- Database Size (e.g., "4 KB")
|
||||||
|
- Process ID (the relay PID)
|
||||||
|
- WebSocket Connections count
|
||||||
|
- Memory Usage
|
||||||
|
|
||||||
|
If these show "-" or "Loading...", check:
|
||||||
|
1. `relay.log` for errors
|
||||||
|
2. `agent-browser console` for JavaScript errors
|
||||||
|
3. `agent-browser errors` for page-level errors
|
||||||
|
|
||||||
|
## Closing the Browser
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser close --all
|
||||||
|
```
|
||||||
@@ -19,7 +19,6 @@ int get_active_connection_count(void);
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <sqlite3.h>
|
|
||||||
#include "api.h"
|
#include "api.h"
|
||||||
#include "embedded_web_content.h"
|
#include "embedded_web_content.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@@ -56,38 +55,12 @@ int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_f
|
|||||||
// Forward declaration for CPU metrics query function
|
// Forward declaration for CPU metrics query function
|
||||||
cJSON* query_cpu_metrics(void);
|
cJSON* query_cpu_metrics(void);
|
||||||
|
|
||||||
static int api_open_temp_db_connection(sqlite3** out_db) {
|
static int api_open_temp_db_connection(void** out_db) {
|
||||||
if (!out_db) {
|
return db_open_worker_connection(db_get_database_path(), out_db);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_db = NULL;
|
|
||||||
const char* db_path = db_get_database_path();
|
|
||||||
if (!db_path || db_path[0] == '\0') {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sqlite3_open_v2(db_path, out_db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK) {
|
|
||||||
if (*out_db) {
|
|
||||||
sqlite3_close(*out_db);
|
|
||||||
*out_db = NULL;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_exec(*out_db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
|
|
||||||
sqlite3_busy_timeout(*out_db, 5000);
|
|
||||||
db_set_thread_connection(*out_db);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void api_close_temp_db_connection(sqlite3* db) {
|
static void api_close_temp_db_connection(void* db) {
|
||||||
if (!db) {
|
db_close_worker_connection(db);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
db_clear_thread_connection();
|
|
||||||
sqlite3_close(db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monitoring system helper functions
|
// Monitoring system helper functions
|
||||||
@@ -97,7 +70,7 @@ int get_monitoring_throttle_seconds(void) {
|
|||||||
|
|
||||||
// Query event kind distribution from database
|
// Query event kind distribution from database
|
||||||
cJSON* query_event_kind_distribution(void) {
|
cJSON* query_event_kind_distribution(void) {
|
||||||
sqlite3* temp_db = NULL;
|
void* temp_db = NULL;
|
||||||
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
||||||
DEBUG_ERROR("Database not available for monitoring query");
|
DEBUG_ERROR("Database not available for monitoring query");
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -137,7 +110,7 @@ cJSON* query_event_kind_distribution(void) {
|
|||||||
|
|
||||||
// Query time-based statistics from database
|
// Query time-based statistics from database
|
||||||
cJSON* query_time_based_statistics(void) {
|
cJSON* query_time_based_statistics(void) {
|
||||||
sqlite3* temp_db = NULL;
|
void* temp_db = NULL;
|
||||||
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
||||||
DEBUG_ERROR("Database not available for time stats query");
|
DEBUG_ERROR("Database not available for time stats query");
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -190,7 +163,7 @@ cJSON* query_time_based_statistics(void) {
|
|||||||
|
|
||||||
// Query top pubkeys by event count from database
|
// Query top pubkeys by event count from database
|
||||||
cJSON* query_top_pubkeys(void) {
|
cJSON* query_top_pubkeys(void) {
|
||||||
sqlite3* temp_db = NULL;
|
void* temp_db = NULL;
|
||||||
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
||||||
DEBUG_ERROR("Database not available for top pubkeys query");
|
DEBUG_ERROR("Database not available for top pubkeys query");
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -237,7 +210,7 @@ cJSON* query_top_pubkeys(void) {
|
|||||||
// Query detailed subscription information from database log (ADMIN ONLY)
|
// Query detailed subscription information from database log (ADMIN ONLY)
|
||||||
// Uses subscriptions table instead of in-memory iteration to avoid mutex contention
|
// Uses subscriptions table instead of in-memory iteration to avoid mutex contention
|
||||||
cJSON* query_subscription_details(void) {
|
cJSON* query_subscription_details(void) {
|
||||||
sqlite3* temp_db = NULL;
|
void* temp_db = NULL;
|
||||||
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
||||||
DEBUG_ERROR("Database not available for subscription details query");
|
DEBUG_ERROR("Database not available for subscription details query");
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1210,7 +1183,7 @@ cJSON* query_cpu_metrics(void) {
|
|||||||
|
|
||||||
// Generate stats JSON from database queries
|
// Generate stats JSON from database queries
|
||||||
char* generate_stats_json(void) {
|
char* generate_stats_json(void) {
|
||||||
sqlite3* temp_db = NULL;
|
void* temp_db = NULL;
|
||||||
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
if (!db_is_available() && api_open_temp_db_connection(&temp_db) != 0) {
|
||||||
DEBUG_ERROR("Database not available for stats generation");
|
DEBUG_ERROR("Database not available for stats generation");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
+41
-4
@@ -31,7 +31,6 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <libwebsockets.h>
|
#include <libwebsockets.h>
|
||||||
|
|
||||||
// External database connection (from main.c)
|
// External database connection (from main.c)
|
||||||
|
|
||||||
// External shutdown flag (from main.c)
|
// External shutdown flag (from main.c)
|
||||||
@@ -111,6 +110,14 @@ static char g_temp_relay_privkey[65] = {0};
|
|||||||
// Runtime cache for relay private key to support strict mode (g_db = NULL)
|
// Runtime cache for relay private key to support strict mode (g_db = NULL)
|
||||||
static char g_cached_relay_privkey[65] = {0};
|
static char g_cached_relay_privkey[65] = {0};
|
||||||
|
|
||||||
|
static int config_open_temp_db_connection(void** out_db) {
|
||||||
|
return db_open_worker_connection(db_get_database_path(), out_db);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void config_close_temp_db_connection(void* db) {
|
||||||
|
db_close_worker_connection(db);
|
||||||
|
}
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// UTILITY FUNCTIONS
|
// UTILITY FUNCTIONS
|
||||||
// ================================
|
// ================================
|
||||||
@@ -2990,7 +2997,8 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
|||||||
int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi) {
|
int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi) {
|
||||||
// Suppress unused parameter warning
|
// Suppress unused parameter warning
|
||||||
(void)wsi;
|
(void)wsi;
|
||||||
if (!db_is_available()) {
|
void* temp_db = NULL;
|
||||||
|
if (!db_is_available() && config_open_temp_db_connection(&temp_db) != 0) {
|
||||||
snprintf(error_message, error_size, "database not available");
|
snprintf(error_message, error_size, "database not available");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3082,6 +3090,9 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
|||||||
if (!admin_pubkey) {
|
if (!admin_pubkey) {
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
cJSON_Delete(results_array);
|
cJSON_Delete(results_array);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3090,12 +3101,18 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
|||||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
cJSON_Delete(results_array);
|
cJSON_Delete(results_array);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_Delete(results_array);
|
cJSON_Delete(results_array);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
snprintf(error_message, error_size, "failed to send auth query response");
|
snprintf(error_message, error_size, "failed to send auth query response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3104,7 +3121,8 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
|||||||
int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi) {
|
int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi) {
|
||||||
// Suppress unused parameter warning
|
// Suppress unused parameter warning
|
||||||
(void)wsi;
|
(void)wsi;
|
||||||
if (!db_is_available()) {
|
void* temp_db = NULL;
|
||||||
|
if (!db_is_available() && config_open_temp_db_connection(&temp_db) != 0) {
|
||||||
snprintf(error_message, error_size, "database not available");
|
snprintf(error_message, error_size, "database not available");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3197,6 +3215,9 @@ int handle_config_query_unified(cJSON* event, const char* query_type, char* erro
|
|||||||
if (!admin_pubkey) {
|
if (!admin_pubkey) {
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
cJSON_Delete(results_array);
|
cJSON_Delete(results_array);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3205,12 +3226,18 @@ int handle_config_query_unified(cJSON* event, const char* query_type, char* erro
|
|||||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
cJSON_Delete(results_array);
|
cJSON_Delete(results_array);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_Delete(results_array);
|
cJSON_Delete(results_array);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
snprintf(error_message, error_size, "failed to send config query response");
|
snprintf(error_message, error_size, "failed to send config query response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3713,7 +3740,8 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
|
|||||||
int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||||
// Suppress unused parameter warning
|
// Suppress unused parameter warning
|
||||||
(void)wsi;
|
(void)wsi;
|
||||||
if (!db_is_available()) {
|
void* temp_db = NULL;
|
||||||
|
if (!db_is_available() && config_open_temp_db_connection(&temp_db) != 0) {
|
||||||
snprintf(error_message, error_size, "database not available");
|
snprintf(error_message, error_size, "database not available");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3819,6 +3847,9 @@ int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_s
|
|||||||
|
|
||||||
if (!admin_pubkey) {
|
if (!admin_pubkey) {
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3826,10 +3857,16 @@ int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_s
|
|||||||
// 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) {
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_Delete(response);
|
cJSON_Delete(response);
|
||||||
|
if (temp_db) {
|
||||||
|
config_close_temp_db_connection(temp_db);
|
||||||
|
}
|
||||||
snprintf(error_message, error_size, "failed to send stats query response");
|
snprintf(error_message, error_size, "failed to send stats query response");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,33 @@ void db_clear_thread_connection(void) {
|
|||||||
g_thread_db = NULL;
|
g_thread_db = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int db_open_worker_connection(const char* db_path, void** out_connection) {
|
||||||
|
if (!out_connection) return -1;
|
||||||
|
*out_connection = NULL;
|
||||||
|
|
||||||
|
const char* effective_path = (db_path && db_path[0] != '\0') ? db_path : g_database_path;
|
||||||
|
if (!effective_path || effective_path[0] == '\0') return -1;
|
||||||
|
|
||||||
|
sqlite3* db = NULL;
|
||||||
|
int rc = sqlite3_open_v2(effective_path, &db, SQLITE_OPEN_READWRITE, NULL);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
if (db) sqlite3_close(db);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
|
||||||
|
sqlite3_busy_timeout(db, 5000);
|
||||||
|
db_set_thread_connection(db);
|
||||||
|
*out_connection = (void*)db;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void db_close_worker_connection(void* connection) {
|
||||||
|
if (!connection) return;
|
||||||
|
db_clear_thread_connection();
|
||||||
|
sqlite3_close((sqlite3*)connection);
|
||||||
|
}
|
||||||
|
|
||||||
int db_is_available(void) {
|
int db_is_available(void) {
|
||||||
return db_active_connection() != NULL;
|
return db_active_connection() != NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ const char* db_get_database_path(void);
|
|||||||
int db_set_thread_connection(void* connection);
|
int db_set_thread_connection(void* connection);
|
||||||
void db_clear_thread_connection(void);
|
void db_clear_thread_connection(void);
|
||||||
|
|
||||||
|
// Worker/runtime SQLite connection lifecycle (kept internal to db_ops.c)
|
||||||
|
int db_open_worker_connection(const char* db_path, void** out_connection);
|
||||||
|
void db_close_worker_connection(void* connection);
|
||||||
|
|
||||||
// DB result codes (backend-agnostic)
|
// DB result codes (backend-agnostic)
|
||||||
#define DB_OK 0
|
#define DB_OK 0
|
||||||
#define DB_ERROR 1
|
#define DB_ERROR 1
|
||||||
|
|||||||
+2
-2
@@ -13,8 +13,8 @@
|
|||||||
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
||||||
#define CRELAY_VERSION_MAJOR 2
|
#define CRELAY_VERSION_MAJOR 2
|
||||||
#define CRELAY_VERSION_MINOR 1
|
#define CRELAY_VERSION_MINOR 1
|
||||||
#define CRELAY_VERSION_PATCH 6
|
#define CRELAY_VERSION_PATCH 7
|
||||||
#define CRELAY_VERSION "v2.1.6"
|
#define CRELAY_VERSION "v2.1.7"
|
||||||
|
|
||||||
// Relay metadata (authoritative source for NIP-11 information)
|
// Relay metadata (authoritative source for NIP-11 information)
|
||||||
#define RELAY_NAME "C-Relay"
|
#define RELAY_NAME "C-Relay"
|
||||||
|
|||||||
+8
-27
@@ -5,7 +5,6 @@
|
|||||||
#include "db_ops.h"
|
#include "db_ops.h"
|
||||||
#include "ip_ban.h"
|
#include "ip_ban.h"
|
||||||
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -199,24 +198,12 @@ static void wake_event_loop(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static sqlite3* open_worker_connection(void) {
|
static void* open_worker_connection(void) {
|
||||||
if (g_pool.db_path[0] == '\0') {
|
void* db_conn = NULL;
|
||||||
|
if (db_open_worker_connection(g_pool.db_path, &db_conn) != 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
return db_conn;
|
||||||
sqlite3* db = NULL;
|
|
||||||
int rc = sqlite3_open_v2(g_pool.db_path, &db, SQLITE_OPEN_READWRITE, NULL);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
if (db) {
|
|
||||||
sqlite3_close(db);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
|
|
||||||
sqlite3_busy_timeout(db, 5000);
|
|
||||||
|
|
||||||
return db;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void complete_job_with_result(thread_pool_job_node_t* node,
|
static void complete_job_with_result(thread_pool_job_node_t* node,
|
||||||
@@ -502,14 +489,12 @@ static void* reader_worker_main(void* arg) {
|
|||||||
snprintf(thread_name, sizeof(thread_name), "db-read-%d", reader_index);
|
snprintf(thread_name, sizeof(thread_name), "db-read-%d", reader_index);
|
||||||
pthread_setname_np(pthread_self(), thread_name);
|
pthread_setname_np(pthread_self(), thread_name);
|
||||||
|
|
||||||
sqlite3* worker_db = open_worker_connection();
|
void* worker_db = open_worker_connection();
|
||||||
if (!worker_db) {
|
if (!worker_db) {
|
||||||
DEBUG_ERROR("Reader worker failed to open SQLite connection");
|
DEBUG_ERROR("Reader worker failed to open SQLite connection");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
db_set_thread_connection(worker_db);
|
|
||||||
|
|
||||||
while (g_pool.running) {
|
while (g_pool.running) {
|
||||||
thread_pool_job_node_t* node = queue_pop(&g_pool.read_q);
|
thread_pool_job_node_t* node = queue_pop(&g_pool.read_q);
|
||||||
if (!node) break;
|
if (!node) break;
|
||||||
@@ -517,8 +502,7 @@ static void* reader_worker_main(void* arg) {
|
|||||||
execute_read_job(node);
|
execute_read_job(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
db_clear_thread_connection();
|
db_close_worker_connection(worker_db);
|
||||||
sqlite3_close(worker_db);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,14 +511,12 @@ static void* writer_worker_main(void* arg) {
|
|||||||
|
|
||||||
pthread_setname_np(pthread_self(), "db-write");
|
pthread_setname_np(pthread_self(), "db-write");
|
||||||
|
|
||||||
sqlite3* worker_db = open_worker_connection();
|
void* worker_db = open_worker_connection();
|
||||||
if (!worker_db) {
|
if (!worker_db) {
|
||||||
DEBUG_ERROR("Writer worker failed to open SQLite connection");
|
DEBUG_ERROR("Writer worker failed to open SQLite connection");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
db_set_thread_connection(worker_db);
|
|
||||||
|
|
||||||
while (g_pool.running) {
|
while (g_pool.running) {
|
||||||
thread_pool_job_node_t* node = queue_pop(&g_pool.write_q);
|
thread_pool_job_node_t* node = queue_pop(&g_pool.write_q);
|
||||||
if (!node) break;
|
if (!node) break;
|
||||||
@@ -542,8 +524,7 @@ static void* writer_worker_main(void* arg) {
|
|||||||
execute_write_job(node);
|
execute_write_job(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
db_clear_thread_connection();
|
db_close_worker_connection(worker_db);
|
||||||
sqlite3_close(worker_db);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-19
@@ -18,8 +18,6 @@
|
|||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#include <sqlite3.h>
|
|
||||||
|
|
||||||
// Include nostr_core_lib for Nostr functionality
|
// Include nostr_core_lib for Nostr functionality
|
||||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||||
@@ -531,22 +529,10 @@ static void* async_event_worker_main(void* arg) {
|
|||||||
(void)arg;
|
(void)arg;
|
||||||
pthread_setname_np(pthread_self(), "event-worker");
|
pthread_setname_np(pthread_self(), "event-worker");
|
||||||
|
|
||||||
sqlite3* worker_db = NULL;
|
void* worker_db = NULL;
|
||||||
const char* db_path = db_get_database_path();
|
const char* db_path = db_get_database_path();
|
||||||
if (db_path && db_path[0] != '\0') {
|
if (db_open_worker_connection(db_path, &worker_db) != 0) {
|
||||||
if (sqlite3_open_v2(db_path, &worker_db, SQLITE_OPEN_READWRITE, NULL) == SQLITE_OK) {
|
DEBUG_WARN("event-worker: failed to open dedicated DB connection; using default connection context");
|
||||||
sqlite3_exec(worker_db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
|
|
||||||
sqlite3_busy_timeout(worker_db, 5000);
|
|
||||||
db_set_thread_connection(worker_db);
|
|
||||||
} else {
|
|
||||||
if (worker_db) {
|
|
||||||
sqlite3_close(worker_db);
|
|
||||||
worker_db = NULL;
|
|
||||||
}
|
|
||||||
DEBUG_WARN("event-worker: failed to open dedicated DB connection; using default connection context");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DEBUG_WARN("event-worker: database path unavailable; using default connection context");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (g_async_event_worker_running) {
|
while (g_async_event_worker_running) {
|
||||||
@@ -620,8 +606,7 @@ static void* async_event_worker_main(void* arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (worker_db) {
|
if (worker_db) {
|
||||||
db_clear_thread_connection();
|
db_close_worker_connection(worker_db);
|
||||||
sqlite3_close(worker_db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
Reference in New Issue
Block a user