v0.1.32 - Migrate to nostr_core_lib v0.4.13 and unify app/nostr logging via callback API

This commit is contained in:
Your Name
2026-03-15 08:37:03 -04:00
parent ba8b51d1c6
commit 66ac92b4a0
25 changed files with 561 additions and 70473 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -113,7 +113,7 @@ server {
return 405;
}
try_files /blobs/$1.txt /blobs/$1.jpg /blobs/$1.jpeg /blobs/$1.png /blobs/$1.webp /blobs/$1.gif /blobs/$1.pdf /blobs/$1.mp4 /blobs/$1.mp3 /blobs/$1.md =404;
try_files /blobs/$1.html /blobs/$1.js /blobs/$1.mjs /blobs/$1.css /blobs/$1.txt /blobs/$1.jpg /blobs/$1.jpeg /blobs/$1.png /blobs/$1.webp /blobs/$1.gif /blobs/$1.pdf /blobs/$1.mp4 /blobs/$1.mp3 /blobs/$1.md =404;
# Cache control for blobs
add_header Cache-Control "public, max-age=31536000, immutable";

70145
debug.log

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,142 @@
# Unified Logging Migration Plan
## Overview
Migrate ginxsom to use nostr_core_lib v0.4.13's callback-based logging API and unify all application logging into a single sink (`logs/app/app.log`).
## Architecture Context
```mermaid
graph TD
subgraph Current State - v0.4.8
A[ginxsom app] -->|fprintf stderr ~122 calls| B[nginx error_log]
A -->|app_log ~80+ calls| C[logs/app/app.log]
A -->|validator_debug_log ~10 calls| D[logs/app/debug.log]
E[nostr_core_lib] -->|direct file write| F[nostr_core_lib/debug.log]
G[systemd journald] -->|spawn-fcgi parent only| H[journal]
end
```
```mermaid
graph TD
subgraph Target State - v0.4.13
A2[ginxsom app] -->|stderr: pre-init fatal only ~10 calls| B2[nginx error_log]
A2 -->|app_log: all operational logs| C2[logs/app/app.log]
E2[nostr_core_lib] -->|callback| A2
G2[systemd journald] -->|spawn-fcgi parent only| H2[journal]
end
```
## Logging Policy
| Sink | Purpose | When Used |
|------|---------|-----------|
| `stderr` -> nginx error_log | Pre-init fatal errors only | Before `app_log` is available, or truly unrecoverable errors during early `main` |
| `logs/app/app.log` via `app_log` | All application operational logging | Everything after logger init: app events, nostr_core_lib callback, validator, relay, admin |
| `logs/app/debug.log` | **ELIMINATED** | Replaced by `app_log(LOG_DEBUG, ...)` |
| `nostr_core_lib/debug.log` | **ELIMINATED** | v0.4.13 removes direct file logging; callback replaces it |
## Log Levels
```c
typedef enum {
LOG_TRACE = 0, // NEW: maps to NOSTR_LOG_LEVEL_TRACE
LOG_DEBUG = 1, // maps to NOSTR_LOG_LEVEL_DEBUG
LOG_INFO = 2, // maps to NOSTR_LOG_LEVEL_INFO
LOG_WARN = 3, // maps to NOSTR_LOG_LEVEL_WARN
LOG_ERROR = 4 // maps to NOSTR_LOG_LEVEL_ERROR
} log_level_t;
```
**Note:** Existing code uses `LOG_DEBUG=0, LOG_INFO=1, LOG_WARN=2, LOG_ERROR=3`. Adding `LOG_TRACE` at 0 shifts all values up by 1. All existing call sites use the enum names (not raw integers), so this is safe.
## Implementation Steps
### Phase 1: Update nostr_core_lib submodule
1. Update the `nostr_core_lib` git submodule to v0.4.13
2. Rebuild `libnostr_core_x64.a` from the updated source
3. Verify the new headers exist: `nostr_core/nostr_log.h` (or equivalent in `nostr_core.h`)
4. Verify build still compiles with `make clean && make`
### Phase 2: Enhance app_log in ginxsom.h and main.c
5. Add `LOG_TRACE` level to `log_level_t` enum in `ginxsom.h`
6. Add a global `g_log_level` variable to control minimum log level
7. Update `app_log()` in `main.c` to:
- Check message level against `g_log_level` before writing
- Handle the new `LOG_TRACE` level string
- Keep the existing file-based sink (`logs/app/app.log`)
### Phase 3: Register nostr_core_lib log callback
8. Add `#include "nostr_core/nostr_core.h"` (or `nostr_log.h`) to `main.c` if not already present
9. Create `nostr_log_cb()` callback function that maps nostr log levels to `app_log()` levels and prefixes messages with `[nostr:component]`
10. In `main()`, after `nostr_init()` / `nostr_crypto_init()`, call:
- `nostr_set_log_callback(nostr_log_cb, NULL)`
- `nostr_set_log_level(NOSTR_LOG_LEVEL_INFO)` (or TRACE for debug builds)
11. In shutdown path, call `nostr_set_log_callback(NULL, NULL)` before `nostr_cleanup()`
### Phase 4: Eliminate debug.log
12. Remove `validator_debug_log()` function from `request_validator.c`
13. Replace all `validator_debug_log()` calls with `app_log(LOG_DEBUG, "VALIDATOR: %s", ...)` calls
14. Delete stale `logs/app/debug.log` and `nostr_core_lib/debug.log` references from `.gitignore` or docs if present
15. Remove `debug.log` from project root if it was generated by nostr_core_lib
### Phase 5: Migrate fprintf(stderr) calls to app_log
Migrate post-startup `fprintf(stderr, ...)` calls file-by-file. Keep only pre-init and truly fatal stderr calls in early `main()`.
**Files to migrate (by priority):**
16. `src/admin_auth.c` (~28 fprintf calls) - Replace with `app_log(LOG_ERROR, "AUTH: ...")` etc.
17. `src/request_validator.c` (~2 fprintf calls) - Replace with `app_log(LOG_DEBUG, "VALIDATOR: ...")`
18. `src/main.c` - Categorize ~90 fprintf calls:
- **Keep as stderr** (~10): Pre-logger-init errors in early `main()`, help text output, keypair display banner
- **Migrate to app_log** (~80): All post-init startup messages, database operations, request logging, upload handling, shutdown messages
### Phase 6: Remove duplicate log_level_t declarations
19. Remove the duplicate `typedef enum { LOG_DEBUG... } log_level_t` and `void app_log(...)` forward declarations from `src/relay_client.c` and `src/admin_commands.c` — these files should just `#include "ginxsom.h"` instead
### Phase 7: Update nostr_crypto_init to nostr_init
20. Replace `nostr_crypto_init()` calls with `nostr_init()` in `main.c` and `request_validator.c` if v0.4.13 consolidates initialization under `nostr_init()`
21. Add `nostr_cleanup()` call in shutdown path if not already present
### Phase 8: Update Makefile and build
22. Verify `Makefile` CFLAGS and LIBS are compatible with v0.4.13 (check if new link flags needed)
23. Remove any `LOGGING_FLAGS` build-time defines that are no longer needed
24. Run `make clean && make` and verify clean build
### Phase 9: Verify and test
25. Run the application and verify:
- `logs/app/app.log` contains both app logs and `[nostr:websocket]` / `[nostr:nip013]` tagged lines
- `logs/app/debug.log` is no longer created
- `nostr_core_lib/debug.log` is no longer created
- `logs/nginx/error.log` only contains pre-init errors and nginx's own messages
26. Run existing test suite to verify no regressions
## Files Modified
| File | Changes |
|------|---------|
| `nostr_core_lib/` | Submodule updated to v0.4.13 |
| `src/ginxsom.h` | Add LOG_TRACE, add g_log_level extern |
| `src/main.c` | Enhanced app_log, nostr_log_cb, callback registration, fprintf migration |
| `src/request_validator.c` | Remove validator_debug_log, replace with app_log |
| `src/admin_auth.c` | Replace fprintf(stderr) with app_log |
| `src/relay_client.c` | Remove duplicate log_level_t typedef |
| `src/admin_commands.c` | Remove duplicate log_level_t typedef |
| `Makefile` | Verify/update build flags |
| `.gitignore` | Add/update debug.log exclusions |
## Risk Mitigation
- **Backward compatibility**: Enum names are used everywhere, not raw integers, so shifting values is safe
- **Build breakage**: Phase 1 verifies build before any code changes
- **Runtime breakage**: Callback registration is additive — if it fails, app still works, just without nostr_core_lib log routing
- **stderr removal safety**: Only post-init calls are migrated; pre-init fatal errors stay on stderr for nginx capture

View File

@@ -88,43 +88,9 @@ static void admin_nip94_build_blob_url(const char* origin, const char* sha256,
snprintf(out, out_size, "%s/%s%s", origin, sha256, extension);
}
// Centralized MIME type to file extension mapping (from main.c)
// Centralized MIME type to file extension mapping (delegates to shared BUD-08 mapping)
static const char* admin_mime_to_extension(const char* mime_type) {
if (!mime_type) {
return ".bin";
}
if (strstr(mime_type, "image/jpeg")) {
return ".jpg";
} else if (strstr(mime_type, "image/webp")) {
return ".webp";
} else if (strstr(mime_type, "image/png")) {
return ".png";
} else if (strstr(mime_type, "image/gif")) {
return ".gif";
} else if (strstr(mime_type, "video/mp4")) {
return ".mp4";
} else if (strstr(mime_type, "video/webm")) {
return ".webm";
} else if (strstr(mime_type, "audio/mpeg")) {
return ".mp3";
} else if (strstr(mime_type, "audio/ogg")) {
return ".ogg";
} else if (strstr(mime_type, "text/plain")) {
return ".txt";
} else if (strstr(mime_type, "text/html")) {
return ".html";
} else if (strstr(mime_type, "text/css")) {
return ".css";
} else if (strstr(mime_type, "application/javascript")) {
return ".js";
} else if (strstr(mime_type, "text/javascript")) {
return ".mjs";
} else if (strstr(mime_type, "application/pdf")) {
return ".pdf";
} else {
return ".bin";
}
return mime_to_extension(mime_type);
}
// Main API request handler

View File

@@ -28,7 +28,7 @@ static int g_keys_loaded = 0; // Whether keys have been loaded
static int ensure_keys_loaded(void) {
if (!g_keys_loaded) {
if (get_blossom_private_key(g_blossom_seckey, sizeof(g_blossom_seckey)) != 0) {
fprintf(stderr, "ERROR: Cannot load blossom private key for admin auth\n");
app_log(LOG_ERROR, "ERROR: Cannot load blossom private key for admin auth\n");
return -1;
}
g_keys_loaded = 1;
@@ -83,13 +83,13 @@ int validate_admin_event(cJSON *event) {
cJSON *sig = cJSON_GetObjectItem(event, "sig");
if (!cJSON_IsString(pubkey) || !cJSON_IsString(sig)) {
fprintf(stderr, "AUTH: Invalid event format - missing pubkey or sig\n");
app_log(LOG_ERROR, "AUTH: Invalid event format - missing pubkey or sig\n");
return 0;
}
// Check if pubkey matches configured admin pubkey
if (!validate_admin_pubkey(pubkey->valuestring)) {
fprintf(stderr, "AUTH: Pubkey %s is not authorized admin\n", pubkey->valuestring);
app_log(LOG_ERROR, "AUTH: Pubkey %s is not authorized admin\n", pubkey->valuestring);
return 0;
}
@@ -114,14 +114,14 @@ int decrypt_admin_command(cJSON *event, char **decrypted_command_out) {
// Get admin pubkey from event
cJSON *admin_pubkey_json = cJSON_GetObjectItem(event, "pubkey");
if (!cJSON_IsString(admin_pubkey_json)) {
fprintf(stderr, "AUTH: Missing or invalid pubkey in event\n");
app_log(LOG_ERROR, "AUTH: Missing or invalid pubkey in event\n");
return -1;
}
// Get encrypted content
cJSON *content = cJSON_GetObjectItem(event, "content");
if (!cJSON_IsString(content)) {
fprintf(stderr, "AUTH: Missing or invalid content in event\n");
app_log(LOG_ERROR, "AUTH: Missing or invalid content in event\n");
return -1;
}
@@ -130,12 +130,12 @@ int decrypt_admin_command(cJSON *event, char **decrypted_command_out) {
unsigned char admin_public_key[32];
if (nostr_hex_to_bytes(g_blossom_seckey, blossom_private_key, 32) != 0) {
fprintf(stderr, "AUTH: Failed to parse blossom private key\n");
app_log(LOG_ERROR, "AUTH: Failed to parse blossom private key\n");
return -1;
}
if (nostr_hex_to_bytes(admin_pubkey_json->valuestring, admin_public_key, 32) != 0) {
fprintf(stderr, "AUTH: Failed to parse admin public key\n");
app_log(LOG_ERROR, "AUTH: Failed to parse admin public key\n");
return -1;
}
@@ -152,14 +152,14 @@ int decrypt_admin_command(cJSON *event, char **decrypted_command_out) {
);
if (result != NOSTR_SUCCESS) {
fprintf(stderr, "AUTH: NIP-44 decryption failed with error code %d\n", result);
app_log(LOG_ERROR, "AUTH: NIP-44 decryption failed with error code %d\n", result);
return -1;
}
// Allocate and copy decrypted content
*decrypted_command_out = malloc(strlen(decrypted_buffer) + 1);
if (!*decrypted_command_out) {
fprintf(stderr, "AUTH: Failed to allocate memory for decrypted content\n");
app_log(LOG_ERROR, "AUTH: Failed to allocate memory for decrypted content\n");
return -1;
}
strcpy(*decrypted_command_out, decrypted_buffer);
@@ -176,19 +176,19 @@ int parse_admin_command(const char *decrypted_content, char ***command_array_out
// Parse the decrypted content as JSON array
cJSON *content_json = cJSON_Parse(decrypted_content);
if (!content_json) {
fprintf(stderr, "AUTH: Failed to parse decrypted content as JSON\n");
app_log(LOG_ERROR, "AUTH: Failed to parse decrypted content as JSON\n");
return -1;
}
if (!cJSON_IsArray(content_json)) {
fprintf(stderr, "AUTH: Decrypted content is not a JSON array\n");
app_log(LOG_ERROR, "AUTH: Decrypted content is not a JSON array\n");
cJSON_Delete(content_json);
return -1;
}
int array_size = cJSON_GetArraySize(content_json);
if (array_size < 1) {
fprintf(stderr, "AUTH: Command array is empty\n");
app_log(LOG_ERROR, "AUTH: Command array is empty\n");
cJSON_Delete(content_json);
return -1;
}
@@ -196,7 +196,7 @@ int parse_admin_command(const char *decrypted_content, char ***command_array_out
// Allocate command array
char **command_array = malloc(array_size * sizeof(char *));
if (!command_array) {
fprintf(stderr, "AUTH: Failed to allocate command array\n");
app_log(LOG_ERROR, "AUTH: Failed to allocate command array\n");
cJSON_Delete(content_json);
return -1;
}
@@ -205,7 +205,7 @@ int parse_admin_command(const char *decrypted_content, char ***command_array_out
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(content_json, i);
if (!cJSON_IsString(item)) {
fprintf(stderr, "AUTH: Command array element %d is not a string\n", i);
app_log(LOG_ERROR, "AUTH: Command array element %d is not a string\n", i);
// Clean up allocated strings
for (int j = 0; j < i; j++) {
free(command_array[j]);
@@ -217,7 +217,7 @@ int parse_admin_command(const char *decrypted_content, char ***command_array_out
command_array[i] = malloc(strlen(item->valuestring) + 1);
if (!command_array[i]) {
fprintf(stderr, "AUTH: Failed to allocate command string\n");
app_log(LOG_ERROR, "AUTH: Failed to allocate command string\n");
// Clean up allocated strings
for (int j = 0; j < i; j++) {
free(command_array[j]);
@@ -228,7 +228,7 @@ int parse_admin_command(const char *decrypted_content, char ***command_array_out
}
strcpy(command_array[i], item->valuestring);
if (!command_array[i]) {
fprintf(stderr, "AUTH: Failed to duplicate command string\n");
app_log(LOG_ERROR, "AUTH: Failed to duplicate command string\n");
// Clean up allocated strings
for (int j = 0; j < i; j++) {
free(command_array[j]);
@@ -274,7 +274,7 @@ int process_admin_command(cJSON *event, char ***command_array_out, int *command_
sqlite3_close(db);
if (strlen(blossom_pubkey) != 64) {
fprintf(stderr, "ERROR: Cannot determine blossom pubkey for admin auth\n");
app_log(LOG_ERROR, "ERROR: Cannot determine blossom pubkey for admin auth\n");
return -1;
}
@@ -296,7 +296,7 @@ int process_admin_command(cJSON *event, char ***command_array_out, int *command_
*admin_pubkey_out = malloc(strlen(admin_pubkey_json->valuestring) + 1);
if (!*admin_pubkey_out) {
fprintf(stderr, "AUTH: Failed to allocate admin pubkey string\n");
app_log(LOG_ERROR, "AUTH: Failed to allocate admin pubkey string\n");
return -1;
}
strcpy(*admin_pubkey_out, admin_pubkey_json->valuestring);
@@ -386,7 +386,7 @@ int create_admin_response(const char *response_json, const char *admin_pubkey, c
sqlite3_close(db);
if (strlen(blossom_pubkey) != 64) {
fprintf(stderr, "ERROR: Cannot determine blossom pubkey for response\n");
app_log(LOG_ERROR, "ERROR: Cannot determine blossom pubkey for response\n");
return -1;
}
@@ -395,12 +395,12 @@ int create_admin_response(const char *response_json, const char *admin_pubkey, c
unsigned char admin_public_key[32];
if (nostr_hex_to_bytes(g_blossom_seckey, blossom_private_key, 32) != 0) {
fprintf(stderr, "AUTH: Failed to parse blossom private key\n");
app_log(LOG_ERROR, "AUTH: Failed to parse blossom private key\n");
return -1;
}
if (nostr_hex_to_bytes(admin_pubkey, admin_public_key, 32) != 0) {
fprintf(stderr, "AUTH: Failed to parse admin public key\n");
app_log(LOG_ERROR, "AUTH: Failed to parse admin public key\n");
return -1;
}
@@ -415,14 +415,14 @@ int create_admin_response(const char *response_json, const char *admin_pubkey, c
);
if (result != NOSTR_SUCCESS) {
fprintf(stderr, "AUTH: NIP-44 encryption failed with error code %d\n", result);
app_log(LOG_ERROR, "AUTH: NIP-44 encryption failed with error code %d\n", result);
return -1;
}
// Create Kind 23457 response event
cJSON *response_event = cJSON_CreateObject();
if (!response_event) {
fprintf(stderr, "AUTH: Failed to create response event JSON\n");
app_log(LOG_ERROR, "AUTH: Failed to create response event JSON\n");
return -1;
}
@@ -444,7 +444,7 @@ int create_admin_response(const char *response_json, const char *admin_pubkey, c
// Convert private key hex to bytes
unsigned char blossom_private_key_bytes[32];
if (nostr_hex_to_bytes(g_blossom_seckey, blossom_private_key_bytes, 32) != 0) {
fprintf(stderr, "AUTH: Failed to parse blossom private key for signing\n");
app_log(LOG_ERROR, "AUTH: Failed to parse blossom private key for signing\n");
cJSON_Delete(response_event);
return -1;
}
@@ -452,7 +452,7 @@ int create_admin_response(const char *response_json, const char *admin_pubkey, c
// Create a temporary event structure for signing
cJSON* temp_event = cJSON_Duplicate(response_event, 1);
if (!temp_event) {
fprintf(stderr, "AUTH: Failed to create temp event for signing\n");
app_log(LOG_ERROR, "AUTH: Failed to create temp event for signing\n");
cJSON_Delete(response_event);
return -1;
}
@@ -467,7 +467,7 @@ int create_admin_response(const char *response_json, const char *admin_pubkey, c
);
if (!signed_event) {
fprintf(stderr, "AUTH: Failed to sign admin response event\n");
app_log(LOG_ERROR, "AUTH: Failed to sign admin response event\n");
cJSON_Delete(response_event);
cJSON_Delete(temp_event);
return -1;
@@ -481,7 +481,7 @@ int create_admin_response(const char *response_json, const char *admin_pubkey, c
cJSON_AddStringToObject(response_event, "id", cJSON_GetStringValue(signed_id));
cJSON_AddStringToObject(response_event, "sig", cJSON_GetStringValue(signed_sig));
} else {
fprintf(stderr, "AUTH: Signed event missing id or sig\n");
app_log(LOG_ERROR, "AUTH: Signed event missing id or sig\n");
cJSON_Delete(response_event);
cJSON_Delete(signed_event);
cJSON_Delete(temp_event);

View File

@@ -3,7 +3,8 @@
*/
#include "admin_commands.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "app_log.h"
#include "../nostr_core_lib/nostr_core/nip044.h"
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
@@ -11,16 +12,6 @@
#include <ctype.h>
#include <time.h>
// Forward declare app_log
typedef enum {
LOG_DEBUG = 0,
LOG_INFO = 1,
LOG_WARN = 2,
LOG_ERROR = 3
} log_level_t;
void app_log(log_level_t level, const char* format, ...);
// Global state
static struct {
int initialized;

24
src/app_log.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef APP_LOG_H
#define APP_LOG_H
#ifdef __cplusplus
extern "C" {
#endif
// Centralized application logging (writes to logs/app/app.log)
typedef enum {
LOG_TRACE = 0,
LOG_DEBUG = 1,
LOG_INFO = 2,
LOG_WARN = 3,
LOG_ERROR = 4
} log_level_t;
extern log_level_t g_log_level;
void app_log(log_level_t level, const char* format, ...);
#ifdef __cplusplus
}
#endif
#endif // APP_LOG_H

View File

@@ -95,43 +95,60 @@ int nip94_get_origin(char* out, size_t out_size) {
return 1;
}
#define GINXSOM_MIME_EXTENSION_ENTRIES(X) \
X("image/jpeg", ".jpg") \
X("image/webp", ".webp") \
X("image/png", ".png") \
X("image/gif", ".gif") \
X("video/mp4", ".mp4") \
X("video/webm", ".webm") \
X("audio/mpeg", ".mp3") \
X("audio/ogg", ".ogg") \
X("text/plain", ".txt") \
X("text/html", ".html") \
X("text/css", ".css") \
X("application/javascript", ".js") \
X("text/javascript", ".mjs") \
X("application/pdf", ".pdf")
typedef struct {
const char* mime;
const char* extension;
} mime_extension_entry_t;
static const mime_extension_entry_t g_mime_extension_map[] = {
#define MAKE_MIME_ENTRY(mime, ext) {mime, ext},
GINXSOM_MIME_EXTENSION_ENTRIES(MAKE_MIME_ENTRY)
#undef MAKE_MIME_ENTRY
};
static const char* g_supported_mime_types[] = {
#define MAKE_MIME_ONLY(mime, ext) mime,
GINXSOM_MIME_EXTENSION_ENTRIES(MAKE_MIME_ONLY)
#undef MAKE_MIME_ONLY
};
const char* const* ginxsom_supported_mime_types(size_t* count) {
if (count) {
*count = sizeof(g_supported_mime_types) / sizeof(g_supported_mime_types[0]);
}
return g_supported_mime_types;
}
// Centralized MIME type to file extension mapping
const char* mime_to_extension(const char* mime_type) {
if (!mime_type) {
return ".bin";
}
if (strstr(mime_type, "image/jpeg")) {
return ".jpg";
} else if (strstr(mime_type, "image/webp")) {
return ".webp";
} else if (strstr(mime_type, "image/png")) {
return ".png";
} else if (strstr(mime_type, "image/gif")) {
return ".gif";
} else if (strstr(mime_type, "video/mp4")) {
return ".mp4";
} else if (strstr(mime_type, "video/webm")) {
return ".webm";
} else if (strstr(mime_type, "audio/mpeg")) {
return ".mp3";
} else if (strstr(mime_type, "audio/ogg")) {
return ".ogg";
} else if (strstr(mime_type, "text/plain")) {
return ".txt";
} else if (strstr(mime_type, "text/html")) {
return ".html";
} else if (strstr(mime_type, "text/css")) {
return ".css";
} else if (strstr(mime_type, "application/javascript")) {
return ".js";
} else if (strstr(mime_type, "text/javascript")) {
return ".mjs";
} else if (strstr(mime_type, "application/pdf")) {
return ".pdf";
} else {
return ".bin";
size_t count = sizeof(g_mime_extension_map) / sizeof(g_mime_extension_map[0]);
for (size_t i = 0; i < count; i++) {
if (strstr(mime_type, g_mime_extension_map[i].mime)) {
return g_mime_extension_map[i].extension;
}
}
return ".bin";
}
// Build canonical blob URL from origin + sha256 + extension

View File

@@ -10,8 +10,8 @@
// Version information (auto-updated by build system)
#define VERSION_MAJOR 0
#define VERSION_MINOR 1
#define VERSION_PATCH 31
#define VERSION "v0.1.31"
#define VERSION_PATCH 32
#define VERSION "v0.1.32"
#include <stddef.h>
#include <stdint.h>
@@ -20,6 +20,8 @@
#include <sqlite3.h>
#include "../nostr_core_lib/cjson/cJSON.h"
#include "app_log.h"
#ifdef __cplusplus
extern "C" {
#endif
@@ -226,6 +228,7 @@ int nip94_get_origin(char* out, size_t out_size);
// MIME type and file extension handling
const char* mime_to_extension(const char* mime_type);
const char* const* ginxsom_supported_mime_types(size_t* count);
void nip94_build_blob_url(const char* origin, const char* sha256, const char* mime_type, char* out, size_t out_size);
// Image dimension parsing
@@ -250,15 +253,6 @@ void send_json_response(int status_code, const char* json_content);
// Logging utilities
void log_request(const char* method, const char* uri, const char* auth_status, int status_code);
// Centralized application logging (writes to logs/app/app.log)
typedef enum {
LOG_DEBUG = 0,
LOG_INFO = 1,
LOG_WARN = 2,
LOG_ERROR = 3
} log_level_t;
void app_log(log_level_t level, const char* format, ...);
// SHA-256 validation helper (used by multiple BUDs)
int validate_sha256_format(const char* sha256);

View File

@@ -8,6 +8,7 @@
#include "relay_client.h"
#include "admin_commands.h"
#include "../nostr_core_lib/nostr_core/nostr_common.h"
#include "../nostr_core_lib/nostr_core/nostr_log.h"
#include "../nostr_core_lib/nostr_core/utils.h"
#include <getopt.h>
#include <curl/curl.h>
@@ -34,41 +35,108 @@ static void shutdown_handler(int signum) {
g_shutdown_requested = 1;
}
// Centralized logging level (minimum severity to emit)
log_level_t g_log_level = LOG_DEBUG;
static nostr_log_level_t app_log_level_to_nostr(log_level_t level) {
switch (level) {
case LOG_TRACE:
return NOSTR_LOG_LEVEL_TRACE;
case LOG_DEBUG:
return NOSTR_LOG_LEVEL_DEBUG;
case LOG_INFO:
return NOSTR_LOG_LEVEL_INFO;
case LOG_WARN:
return NOSTR_LOG_LEVEL_WARN;
case LOG_ERROR:
default:
return NOSTR_LOG_LEVEL_ERROR;
}
}
static void nostr_log_cb(int level, const char *component, const char *message,
void *user_data) {
(void)user_data;
const char *comp = (component && component[0]) ? component : "core";
const char *msg = message ? message : "";
log_level_t app_level = LOG_INFO;
switch (level) {
case NOSTR_LOG_LEVEL_ERROR:
app_level = LOG_ERROR;
break;
case NOSTR_LOG_LEVEL_WARN:
app_level = LOG_WARN;
break;
case NOSTR_LOG_LEVEL_INFO:
app_level = LOG_INFO;
break;
case NOSTR_LOG_LEVEL_DEBUG:
app_level = LOG_DEBUG;
break;
case NOSTR_LOG_LEVEL_TRACE:
app_level = LOG_TRACE;
break;
default:
app_level = LOG_INFO;
break;
}
app_log(app_level, "[nostr:%s] %s", comp, msg);
}
// Centralized logging system (declaration in ginxsom.h)
void app_log(log_level_t level, const char *format, ...) {
if (level < g_log_level) {
return;
}
FILE *log_file = fopen("logs/app/app.log", "a");
if (!log_file) {
return; // Silently fail if we can't open log file
}
// Get timestamp
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
// Get log level string
const char *level_str;
switch (level) {
case LOG_DEBUG: level_str = "DEBUG"; break;
case LOG_INFO: level_str = "INFO"; break;
case LOG_WARN: level_str = "WARN"; break;
case LOG_ERROR: level_str = "ERROR"; break;
default: level_str = "UNKNOWN"; break;
case LOG_TRACE:
level_str = "TRACE";
break;
case LOG_DEBUG:
level_str = "DEBUG";
break;
case LOG_INFO:
level_str = "INFO";
break;
case LOG_WARN:
level_str = "WARN";
break;
case LOG_ERROR:
level_str = "ERROR";
break;
default:
level_str = "UNKNOWN";
break;
}
// Write log prefix with timestamp, PID, and level
fprintf(log_file, "[%s] [PID:%d] [%s] ", timestamp, getpid(), level_str);
// Write formatted message
va_list args;
va_start(args, format);
vfprintf(log_file, format, args);
va_end(args);
// Ensure newline
fprintf(log_file, "\n");
fclose(log_file);
}
@@ -130,19 +198,19 @@ int initialize_database(const char *db_path) {
rc = sqlite3_open_v2(db_path, &db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
// If database was just created, initialize schema
if (!db_exists) {
fprintf(stderr, "Database not found, initializing schema...\n");
app_log(LOG_INFO, "Database not found, initializing schema...\n");
// Enable foreign key constraints
rc = sqlite3_exec(db, "PRAGMA foreign_keys = ON;", NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to enable foreign keys: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to enable foreign keys: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -164,7 +232,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_blobs, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create blobs table: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create blobs table: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -182,7 +250,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_config, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create config table: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create config table: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -198,7 +266,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_system, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create system table: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create system table: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -218,7 +286,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_auth_rules, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create auth_rules table: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create auth_rules table: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -236,7 +304,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_indexes, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create indexes: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create indexes: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -259,7 +327,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, insert_config, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to insert default config: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to insert default config: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -279,7 +347,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_view, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create storage_stats view: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create storage_stats view: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -303,7 +371,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_overview_view, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create blob_overview view: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create blob_overview view: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -323,7 +391,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_type_view, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create blob_type_distribution view: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create blob_type_distribution view: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -340,7 +408,7 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_time_view, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create blob_time_stats view: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create blob_time_stats view: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
@@ -364,13 +432,13 @@ int initialize_database(const char *db_path) {
rc = sqlite3_exec(db, create_uploaders_view, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to create top_uploaders view: %s\n", err_msg);
app_log(LOG_ERROR, "Failed to create top_uploaders view: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
}
fprintf(stderr, "Database schema initialized successfully\n");
app_log(LOG_INFO, "Database schema initialized successfully\n");
}
sqlite3_close(db);
@@ -409,19 +477,19 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
// Derive public key from private key hex string
int derive_pubkey_from_privkey(const char *privkey_hex, char *pubkey_hex_out) {
if (!privkey_hex || strlen(privkey_hex) != 64) {
fprintf(stderr, "ERROR: Invalid private key format\n");
app_log(LOG_ERROR, "ERROR: Invalid private key format\n");
return -1;
}
unsigned char seckey_bytes[32];
if (nostr_hex_to_bytes(privkey_hex, seckey_bytes, 32) != NOSTR_SUCCESS) {
fprintf(stderr, "ERROR: Failed to parse private key hex\n");
app_log(LOG_ERROR, "ERROR: Failed to parse private key hex\n");
return -1;
}
unsigned char pubkey_bytes[32];
if (nostr_ec_public_key_from_private_key(seckey_bytes, pubkey_bytes) != NOSTR_SUCCESS) {
fprintf(stderr, "ERROR: Failed to derive public key\n");
app_log(LOG_ERROR, "ERROR: Failed to derive public key\n");
return -1;
}
@@ -432,12 +500,12 @@ int derive_pubkey_from_privkey(const char *privkey_hex, char *pubkey_hex_out) {
// Set database path based on pubkey (db/<pubkey>.db)
int set_db_path_from_pubkey(const char *pubkey) {
if (!pubkey || strlen(pubkey) != 64) {
fprintf(stderr, "ERROR: Invalid pubkey for database naming\n");
app_log(LOG_ERROR, "ERROR: Invalid pubkey for database naming\n");
return -1;
}
snprintf(g_db_path, sizeof(g_db_path), "db/%s.db", pubkey);
fprintf(stderr, "DATABASE: Set database path to %s\n", g_db_path);
app_log(LOG_INFO, "DATABASE: Set database path to %s\n", g_db_path);
return 0;
}
@@ -454,7 +522,7 @@ int validate_database_pubkey_match(const char *db_path, const char *expected_pub
// Check if filename matches pattern: <pubkey>.db
size_t filename_len = strlen(filename);
if (filename_len != 67) { // 64 chars + ".db" = 67
fprintf(stderr, "WARNING: Database filename doesn't match pubkey pattern: %s\n", filename);
app_log(LOG_WARN, "WARNING: Database filename doesn't match pubkey pattern: %s\n", filename);
return 0; // Don't enforce for non-standard names (backward compatibility)
}
@@ -465,14 +533,14 @@ int validate_database_pubkey_match(const char *db_path, const char *expected_pub
// Compare with expected pubkey
if (strcasecmp(filename_pubkey, expected_pubkey) != 0) {
fprintf(stderr, "ERROR: Database pubkey mismatch!\n");
fprintf(stderr, " Database filename: %s\n", filename_pubkey);
fprintf(stderr, " Expected pubkey: %s\n", expected_pubkey);
fprintf(stderr, " → Database filename doesn't match the keys stored inside\n");
app_log(LOG_ERROR, "ERROR: Database pubkey mismatch!\n");
app_log(LOG_ERROR, " Database filename: %s\n", filename_pubkey);
app_log(LOG_ERROR, " Expected pubkey: %s\n", expected_pubkey);
app_log(LOG_ERROR, " → Database filename doesn't match the keys stored inside\n");
return -1;
}
fprintf(stderr, "DATABASE: Pubkey validation passed: %s\n", filename_pubkey);
app_log(LOG_INFO, "DATABASE: Pubkey validation passed: %s\n", filename_pubkey);
return 0;
}
@@ -480,7 +548,7 @@ int validate_database_pubkey_match(const char *db_path, const char *expected_pub
int generate_random_private_key_bytes(unsigned char *key_bytes, size_t len) {
FILE *fp = fopen("/dev/urandom", "rb");
if (!fp) {
fprintf(stderr, "ERROR: Cannot open /dev/urandom for key generation\n");
app_log(LOG_ERROR, "ERROR: Cannot open /dev/urandom for key generation\n");
return -1;
}
@@ -488,7 +556,7 @@ int generate_random_private_key_bytes(unsigned char *key_bytes, size_t len) {
fclose(fp);
if (bytes_read != len) {
fprintf(stderr, "ERROR: Failed to read %zu bytes from /dev/urandom\n", len);
app_log(LOG_ERROR, "ERROR: Failed to read %zu bytes from /dev/urandom\n", len);
return -1;
}
@@ -497,21 +565,21 @@ int generate_random_private_key_bytes(unsigned char *key_bytes, size_t len) {
// Generate server keypair and store in database
int generate_server_keypair(void) {
fprintf(stderr, "DEBUG: generate_server_keypair() called\n");
app_log(LOG_DEBUG, "DEBUG: generate_server_keypair() called\n");
unsigned char seckey_bytes[32];
char seckey_hex[65];
char pubkey_hex[65];
// Generate random private key
fprintf(stderr, "DEBUG: Generating random private key...\n");
app_log(LOG_DEBUG, "DEBUG: Generating random private key...\n");
if (generate_random_private_key_bytes(seckey_bytes, 32) != 0) {
fprintf(stderr, "DEBUG: Failed to generate random bytes\n");
app_log(LOG_DEBUG, "DEBUG: Failed to generate random bytes\n");
return -1;
}
// Validate the private key
if (nostr_ec_private_key_verify(seckey_bytes) != NOSTR_SUCCESS) {
fprintf(stderr, "ERROR: Generated invalid private key\n");
app_log(LOG_ERROR, "ERROR: Generated invalid private key\n");
return -1;
}
@@ -521,7 +589,7 @@ int generate_server_keypair(void) {
// Derive public key
unsigned char pubkey_bytes[32];
if (nostr_ec_public_key_from_private_key(seckey_bytes, pubkey_bytes) != NOSTR_SUCCESS) {
fprintf(stderr, "ERROR: Failed to derive public key\n");
app_log(LOG_ERROR, "ERROR: Failed to derive public key\n");
return -1;
}
@@ -538,19 +606,19 @@ int generate_server_keypair(void) {
// Set database path based on pubkey (MUST be done before storing keys)
if (set_db_path_from_pubkey(pubkey_hex) != 0) {
fprintf(stderr, "ERROR: Failed to set database path\n");
app_log(LOG_ERROR, "ERROR: Failed to set database path\n");
return -1;
}
// Initialize database with new path
if (initialize_database(g_db_path) != 0) {
fprintf(stderr, "ERROR: Failed to initialize database at %s\n", g_db_path);
app_log(LOG_ERROR, "ERROR: Failed to initialize database at %s\n", g_db_path);
return -1;
}
// Store private key securely
if (store_blossom_private_key(seckey_hex) != 0) {
fprintf(stderr, "ERROR: Failed to store blossom private key\n");
app_log(LOG_ERROR, "ERROR: Failed to store blossom private key\n");
return -1;
}
@@ -561,14 +629,14 @@ int generate_server_keypair(void) {
rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc) {
fprintf(stderr, "ERROR: Can't open database for config: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "ERROR: Can't open database for config: %s\n", sqlite3_errmsg(db));
return -1;
}
const char *sql = "INSERT OR REPLACE INTO config (key, value, description) VALUES (?, ?, ?)";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "ERROR: SQL prepare failed: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "ERROR: SQL prepare failed: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
@@ -582,52 +650,52 @@ int generate_server_keypair(void) {
sqlite3_close(db);
if (rc != SQLITE_DONE) {
fprintf(stderr, "ERROR: Failed to store blossom public key in config\n");
app_log(LOG_ERROR, "ERROR: Failed to store blossom public key in config\n");
return -1;
}
// Display keys for admin setup
fprintf(stderr, "========================================\n");
fprintf(stderr, "SERVER KEYPAIR GENERATED SUCCESSFULLY\n");
fprintf(stderr, "========================================\n");
fprintf(stderr, "Blossom Public Key: %s\n", pubkey_hex);
fprintf(stderr, "Blossom Private Key: %s\n", seckey_hex);
fprintf(stderr, "Database Path: %s\n", g_db_path);
fprintf(stderr, "========================================\n");
fprintf(stderr, "IMPORTANT: Save the private key securely!\n");
fprintf(stderr, "This key is used for decrypting admin messages.\n");
fprintf(stderr, "========================================\n");
app_log(LOG_INFO, "========================================\n");
app_log(LOG_INFO, "SERVER KEYPAIR GENERATED SUCCESSFULLY\n");
app_log(LOG_INFO, "========================================\n");
app_log(LOG_INFO, "Blossom Public Key: %s\n", pubkey_hex);
app_log(LOG_INFO, "Blossom Private Key: %s\n", seckey_hex);
app_log(LOG_INFO, "Database Path: %s\n", g_db_path);
app_log(LOG_INFO, "========================================\n");
app_log(LOG_WARN, "IMPORTANT: Save the private key securely!\n");
app_log(LOG_INFO, "This key is used for decrypting admin messages.\n");
app_log(LOG_INFO, "========================================\n");
return 0;
}
// Load server keys from database
int load_server_keys(void) {
fprintf(stderr, "DEBUG: load_server_keys() called\n");
app_log(LOG_DEBUG, "DEBUG: load_server_keys() called\n");
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
// Try to load blossom private key
fprintf(stderr, "DEBUG: Trying to load blossom private key...\n");
app_log(LOG_DEBUG, "DEBUG: Trying to load blossom private key...\n");
if (get_blossom_private_key(g_blossom_seckey, sizeof(g_blossom_seckey)) != 0) {
fprintf(stderr, "DEBUG: No blossom private key found in database\n");
app_log(LOG_DEBUG, "DEBUG: No blossom private key found in database\n");
return -1; // Keys must exist in database
}
fprintf(stderr, "STARTUP: Blossom private key loaded successfully\n");
app_log(LOG_INFO, "STARTUP: Blossom private key loaded successfully\n");
// Derive public key from private key
if (derive_pubkey_from_privkey(g_blossom_seckey, g_blossom_pubkey) != 0) {
fprintf(stderr, "ERROR: Failed to derive public key from private key\n");
app_log(LOG_ERROR, "ERROR: Failed to derive public key from private key\n");
return -1;
}
fprintf(stderr, "STARTUP: Derived blossom pubkey: %s\n", g_blossom_pubkey);
app_log(LOG_INFO, "STARTUP: Derived blossom pubkey: %s\n", g_blossom_pubkey);
// Validate database filename matches pubkey
if (validate_database_pubkey_match(g_db_path, g_blossom_pubkey) != 0) {
fprintf(stderr, "ERROR: Database pubkey validation failed\n");
app_log(LOG_ERROR, "ERROR: Database pubkey validation failed\n");
return -1;
}
@@ -644,7 +712,7 @@ int load_server_keys(void) {
const char *pubkey = (const char *)sqlite3_column_text(stmt, 0);
if (pubkey && strlen(pubkey) == 64) {
strncpy(g_admin_pubkey, pubkey, sizeof(g_admin_pubkey) - 1);
fprintf(stderr, "STARTUP: Admin pubkey loaded from config: %s\n", g_admin_pubkey);
app_log(LOG_INFO, "STARTUP: Admin pubkey loaded from config: %s\n", g_admin_pubkey);
}
}
sqlite3_finalize(stmt);
@@ -679,14 +747,14 @@ int store_blossom_private_key(const char *seckey) {
// Validate key format
if (!seckey || strlen(seckey) != 64) {
fprintf(stderr, "ERROR: Invalid blossom private key format\n");
app_log(LOG_ERROR, "ERROR: Invalid blossom private key format\n");
return -1;
}
// Create blossom_seckey table if it doesn't exist
rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if (rc) {
fprintf(stderr, "ERROR: Can't open database: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "ERROR: Can't open database: %s\n", sqlite3_errmsg(db));
return -1;
}
@@ -694,7 +762,7 @@ int store_blossom_private_key(const char *seckey) {
const char *create_sql = "CREATE TABLE IF NOT EXISTS blossom_seckey (id INTEGER PRIMARY KEY CHECK (id = 1), seckey TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), CHECK (length(seckey) = 64))";
rc = sqlite3_exec(db, create_sql, NULL, NULL, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "ERROR: Failed to create blossom_seckey table: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "ERROR: Failed to create blossom_seckey table: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
@@ -703,7 +771,7 @@ int store_blossom_private_key(const char *seckey) {
const char *sql = "INSERT OR REPLACE INTO blossom_seckey (id, seckey) VALUES (1, ?)";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "ERROR: SQL prepare failed: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "ERROR: SQL prepare failed: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
@@ -714,7 +782,7 @@ int store_blossom_private_key(const char *seckey) {
sqlite3_close(db);
if (rc != SQLITE_DONE) {
fprintf(stderr, "ERROR: Failed to store blossom private key\n");
app_log(LOG_ERROR, "ERROR: Failed to store blossom private key\n");
return -1;
}
@@ -930,7 +998,7 @@ int insert_blob_metadata(const char *sha256, long size, const char *type,
rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if (rc) {
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "Can't open database: %s\n", sqlite3_errmsg(db));
return 0;
}
@@ -939,7 +1007,7 @@ int insert_blob_metadata(const char *sha256, long size, const char *type,
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "SQL error: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 0;
}
@@ -985,7 +1053,7 @@ int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) {
rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_CREATE, NULL);
if (rc) {
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "Can't open database: %s\n", sqlite3_errmsg(db));
return 0;
}
@@ -994,7 +1062,7 @@ int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) {
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
app_log(LOG_ERROR, "SQL error: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 0;
}
@@ -1039,7 +1107,7 @@ int file_exists_with_type(const char *sha256, const char *mime_type) {
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256, extension);
app_log(LOG_WARN, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256, extension);
return 0;
}
@@ -1192,7 +1260,7 @@ void log_request(const char *method, const char *uri, const char *auth_status,
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
// For now, log to stdout - later can be configured to log files
fprintf(stderr, "LOG: [%s] %s %s - Auth: %s - Status: %d\r\n", timestamp,
app_log(LOG_INFO, "LOG: [%s] %s %s - Auth: %s - Status: %d\r\n", timestamp,
method ? method : "NULL", uri ? uri : "NULL",
auth_status ? auth_status : "none", status_code);
}
@@ -1489,36 +1557,7 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
}
// Determine file extension from MIME type and delete physical file
const char *extension = "";
if (strstr(blob_type_copy, "image/jpeg")) {
extension = ".jpg";
} else if (strstr(blob_type_copy, "image/webp")) {
extension = ".webp";
} else if (strstr(blob_type_copy, "image/png")) {
extension = ".png";
} else if (strstr(blob_type_copy, "image/gif")) {
extension = ".gif";
} else if (strstr(blob_type_copy, "video/mp4")) {
extension = ".mp4";
} else if (strstr(blob_type_copy, "video/webm")) {
extension = ".webm";
} else if (strstr(blob_type_copy, "audio/mpeg")) {
extension = ".mp3";
} else if (strstr(blob_type_copy, "audio/ogg")) {
extension = ".ogg";
} else if (strstr(blob_type_copy, "text/plain")) {
extension = ".txt";
} else if (strstr(blob_type_copy, "text/html")) {
extension = ".html";
} else if (strstr(blob_type_copy, "text/css")) {
extension = ".css";
} else if (strstr(blob_type_copy, "application/javascript")) {
extension = ".js";
} else if (strstr(blob_type_copy, "text/javascript")) {
extension = ".mjs";
} else {
extension = ".bin";
}
const char *extension = mime_to_extension(blob_type_copy);
char filepath[MAX_PATH_LEN];
// Construct path safely
@@ -1528,7 +1567,7 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256, extension);
app_log(LOG_WARN, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256, extension);
// Continue anyway - unlink will fail gracefully
} else {
// Build path manually to avoid compiler warnings
@@ -1664,7 +1703,7 @@ void handle_upload_request(void) {
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
app_log(LOG_WARN, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
@@ -1695,7 +1734,7 @@ void handle_upload_request(void) {
// Set file permissions to 644 (owner read/write, group/others read) -
// standard for web files
if (chmod(filepath, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0) {
fprintf(stderr, "WARNING: Failed to set file permissions for %s\r\n",
app_log(LOG_WARN, "WARNING: Failed to set file permissions for %s\r\n",
filepath);
// Continue anyway - this is not a fatal error
} else {
@@ -1873,7 +1912,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
file_data = validation_result->file_data;
file_size = validation_result->file_size;
should_free_file_data = 0; // Validator owns the memory
fprintf(stderr, "UPLOAD: Using file data from validator (%zu bytes)\n", file_size);
app_log(LOG_DEBUG, "UPLOAD: Using file data from validator (%zu bytes)\n", file_size);
} else {
// Fallback: read from stdin (for non-Blossom uploads or when validation didn't provide file data)
file_data = malloc(content_length);
@@ -1897,7 +1936,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
file_size = bytes_read;
should_free_file_data = 1; // We own the memory
fprintf(stderr, "UPLOAD: Read file data from stdin (%zu bytes)\n", file_size);
app_log(LOG_DEBUG, "UPLOAD: Read file data from stdin (%zu bytes)\n", file_size);
}
// Calculate SHA-256 hash using nostr_core function
@@ -1935,7 +1974,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
size_t total_len = dir_len + 1 + sha_len + ext_len + 1; // +1 for /, +1 for null
if (total_len > sizeof(filepath)) {
fprintf(stderr, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
app_log(LOG_WARN, "WARNING: File path too long for buffer: %s/%s%s\n", g_storage_dir, sha256_hex, extension);
printf("Status: 500 Internal Server Error\r\n");
printf("Content-Type: text/plain\r\n\r\n");
@@ -1966,7 +2005,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re
// Set file permissions to 644 (owner read/write, group/others read) -
// standard for web files
if (chmod(filepath, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0) {
fprintf(stderr, "WARNING: Failed to set file permissions for %s\r\n",
app_log(LOG_WARN, "WARNING: Failed to set file permissions for %s\r\n",
filepath);
// Continue anyway - this is not a fatal error
}
@@ -2205,24 +2244,28 @@ int main(int argc, char *argv[]) {
use_test_keys = 1;
} else if (strcmp(argv[i], "--generate-keys") == 0) {
g_generate_keys = 1;
fprintf(stderr, "WARNING: --generate-keys is deprecated. Keys are generated automatically when needed.\n");
app_log(LOG_WARN, "WARNING: --generate-keys is deprecated. Keys are generated automatically when needed.\n");
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
fprintf(stderr, "Use --help for usage information\n");
app_log(LOG_ERROR, "Unknown option: %s\n", argv[i]);
app_log(LOG_ERROR, "Use --help for usage information\n");
return 1;
}
}
app_log(LOG_INFO, "Storage directory: %s", g_storage_dir);
// CRITICAL: Initialize nostr crypto system BEFORE key operations
app_log(LOG_INFO, "Initializing nostr crypto system...");
int crypto_init_result = nostr_crypto_init();
if (crypto_init_result != 0) {
app_log(LOG_ERROR, "Failed to initialize nostr crypto system (result: %d)", crypto_init_result);
// Initialize nostr core system BEFORE key operations and relay activity
app_log(LOG_INFO, "Initializing nostr core system...");
int nostr_init_result = nostr_init();
if (nostr_init_result != NOSTR_SUCCESS) {
app_log(LOG_ERROR, "Failed to initialize nostr core system (result: %d)",
nostr_init_result);
return 1;
}
app_log(LOG_INFO, "Nostr crypto system initialized successfully");
nostr_set_log_callback(nostr_log_cb, NULL);
nostr_set_log_level(app_log_level_to_nostr(g_log_level));
app_log(LOG_INFO, "Nostr core system initialized successfully");
// ========================================================================
// DATABASE AND KEY INITIALIZATION - 5 SCENARIOS
@@ -2336,15 +2379,15 @@ int main(int argc, char *argv[]) {
// Derive pubkey from provided privkey
if (derive_pubkey_from_privkey(test_server_privkey, g_blossom_pubkey) != 0) {
fprintf(stderr, "ERROR: Invalid server private key\n");
app_log(LOG_ERROR, "ERROR: Invalid server private key\n");
return 1;
}
fprintf(stderr, "KEYS: Derived pubkey: %s\n", g_blossom_pubkey);
app_log(LOG_INFO, "KEYS: Derived pubkey: %s\n", g_blossom_pubkey);
// Scenario 5: Both database path and keys specified
if (db_path_specified) {
fprintf(stderr, "\n=== SCENARIO 5: DATABASE PATH + KEYS ===\n");
app_log(LOG_INFO, "\n=== SCENARIO 5: DATABASE PATH + KEYS ===\n");
// Check if specified path is a directory or file
struct stat st;
@@ -2361,33 +2404,33 @@ int main(int argc, char *argv[]) {
if (is_directory) {
// Build database path from directory + derived pubkey
snprintf(g_db_path, sizeof(g_db_path), "%s/%s.db", specified_db_path, g_blossom_pubkey);
fprintf(stderr, "DATABASE: Using directory path, derived database: %s\n", g_db_path);
app_log(LOG_INFO, "DATABASE: Using directory path, derived database: %s\n", g_db_path);
} else {
// Use specified file path directly
strncpy(g_db_path, specified_db_path, sizeof(g_db_path) - 1);
g_db_path[sizeof(g_db_path) - 1] = '\0';
fprintf(stderr, "DATABASE: Using file path: %s\n", g_db_path);
app_log(LOG_INFO, "DATABASE: Using file path: %s\n", g_db_path);
}
// Check if database exists
if (stat(g_db_path, &st) == 0) {
// Database exists - validate keys match
fprintf(stderr, "DATABASE: Found existing database, validating keys...\n");
app_log(LOG_INFO, "DATABASE: Found existing database, validating keys...\n");
// Load keys from database
if (get_blossom_private_key(g_blossom_seckey, sizeof(g_blossom_seckey)) != 0) {
fprintf(stderr, "ERROR: Invalid database: missing server keys\n");
app_log(LOG_ERROR, "ERROR: Invalid database: missing server keys\n");
return 1;
}
// Compare with provided key
if (strcmp(g_blossom_seckey, test_server_privkey) != 0) {
fprintf(stderr, "ERROR: Server private key doesn't match database\n");
fprintf(stderr, " Provided key and database keys are different\n");
app_log(LOG_ERROR, "ERROR: Server private key doesn't match database\n");
app_log(LOG_ERROR, " Provided key and database keys are different\n");
return 1;
}
fprintf(stderr, "VALIDATION: Keys match database - continuing\n");
app_log(LOG_INFO, "VALIDATION: Keys match database - continuing\n");
// Validate pubkey matches filename
if (validate_database_pubkey_match(g_db_path, g_blossom_pubkey) != 0) {
@@ -2395,11 +2438,11 @@ int main(int argc, char *argv[]) {
}
} else {
// Database doesn't exist - create it with provided keys
fprintf(stderr, "DATABASE: No existing database, creating new one...\n");
app_log(LOG_INFO, "DATABASE: No existing database, creating new one...\n");
// Initialize new database
if (initialize_database(g_db_path) != 0) {
fprintf(stderr, "ERROR: Failed to initialize database\n");
app_log(LOG_ERROR, "ERROR: Failed to initialize database\n");
return 1;
}
@@ -2408,7 +2451,7 @@ int main(int argc, char *argv[]) {
g_blossom_seckey[64] = '\0';
if (store_blossom_private_key(test_server_privkey) != 0) {
fprintf(stderr, "ERROR: Failed to store private key\n");
app_log(LOG_ERROR, "ERROR: Failed to store private key\n");
return 1;
}
@@ -2440,28 +2483,28 @@ int main(int argc, char *argv[]) {
sqlite3_close(db);
}
fprintf(stderr, "DATABASE: New database created successfully\n");
app_log(LOG_INFO, "DATABASE: New database created successfully\n");
}
}
// Scenario 3 continued: Create new database with provided keys
else {
// Set database path based on derived pubkey
if (set_db_path_from_pubkey(g_blossom_pubkey) != 0) {
fprintf(stderr, "ERROR: Failed to set database path\n");
app_log(LOG_ERROR, "ERROR: Failed to set database path\n");
return 1;
}
// Check if database already exists
struct stat st;
if (stat(g_db_path, &st) == 0) {
fprintf(stderr, "ERROR: Database already exists for this pubkey: %s\n", g_db_path);
fprintf(stderr, " Use --db-path to open existing database or use different keys\n");
app_log(LOG_ERROR, "ERROR: Database already exists for this pubkey: %s\n", g_db_path);
app_log(LOG_ERROR, " Use --db-path to open existing database or use different keys\n");
return 1;
}
// Initialize new database
if (initialize_database(g_db_path) != 0) {
fprintf(stderr, "ERROR: Failed to initialize database\n");
app_log(LOG_ERROR, "ERROR: Failed to initialize database\n");
return 1;
}
@@ -2470,7 +2513,7 @@ int main(int argc, char *argv[]) {
g_blossom_seckey[64] = '\0';
if (store_blossom_private_key(test_server_privkey) != 0) {
fprintf(stderr, "ERROR: Failed to store private key\n");
app_log(LOG_ERROR, "ERROR: Failed to store private key\n");
return 1;
}
@@ -2502,7 +2545,7 @@ int main(int argc, char *argv[]) {
sqlite3_close(db);
}
fprintf(stderr, "KEYS: New database created successfully\n");
app_log(LOG_INFO, "KEYS: New database created successfully\n");
}
}
@@ -2510,7 +2553,7 @@ int main(int argc, char *argv[]) {
// Note: --db-path should specify a DIRECTORY, not a full file path
// The actual database filename will be derived from the server's pubkey
else if (db_path_specified) {
fprintf(stderr, "\n=== SCENARIO 2: DATABASE DIRECTORY SPECIFIED ===\n");
app_log(LOG_INFO, "\n=== SCENARIO 2: DATABASE DIRECTORY SPECIFIED ===\n");
// Check if specified path is a directory or file
struct stat st;
@@ -2526,8 +2569,8 @@ int main(int argc, char *argv[]) {
if (is_directory) {
// Treat as directory - will derive filename from pubkey after loading keys
fprintf(stderr, "DATABASE: Directory specified: %s\n", specified_db_path);
fprintf(stderr, "DATABASE: Will derive filename from server pubkey\n");
app_log(LOG_INFO, "DATABASE: Directory specified: %s\n", specified_db_path);
app_log(LOG_INFO, "DATABASE: Will derive filename from server pubkey\n");
// Look for any .db file that matches the pubkey pattern
DIR *dir = opendir(specified_db_path);
@@ -2542,7 +2585,7 @@ int main(int argc, char *argv[]) {
// Found a potential database file
snprintf(g_db_path, sizeof(g_db_path), "%s/%s", specified_db_path, entry->d_name);
found_db = 1;
fprintf(stderr, "DATABASE: Found existing database: %s\n", g_db_path);
app_log(LOG_INFO, "DATABASE: Found existing database: %s\n", g_db_path);
break;
}
}
@@ -2551,7 +2594,7 @@ int main(int argc, char *argv[]) {
if (!found_db) {
// No database found - this is OK, we'll create one if we have keys
fprintf(stderr, "DATABASE: No existing database found in directory\n");
app_log(LOG_INFO, "DATABASE: No existing database found in directory\n");
// g_db_path will be set later based on pubkey
}
} else {
@@ -2562,36 +2605,36 @@ int main(int argc, char *argv[]) {
// If we found a database file, try to load it
if (g_db_path[0] != '\0' && stat(g_db_path, &st) == 0) {
fprintf(stderr, "DATABASE: Opening existing database: %s\n", g_db_path);
app_log(LOG_INFO, "DATABASE: Opening existing database: %s\n", g_db_path);
// Load keys from database
if (load_server_keys() != 0) {
fprintf(stderr, "ERROR: Failed to load keys from database\n");
fprintf(stderr, " → Database may be corrupted or not a valid ginxsom database\n");
app_log(LOG_ERROR, "ERROR: Failed to load keys from database\n");
app_log(LOG_ERROR, " → Database may be corrupted or not a valid ginxsom database\n");
return 1;
}
fprintf(stderr, "DATABASE: Keys loaded and validated successfully\n");
app_log(LOG_INFO, "DATABASE: Keys loaded and validated successfully\n");
} else {
// No database file exists - we need keys to create one
fprintf(stderr, "ERROR: No database found and no --server-privkey provided\n");
fprintf(stderr, " → Use --server-privkey to create a new database\n");
app_log(LOG_ERROR, "ERROR: No database found and no --server-privkey provided\n");
app_log(LOG_ERROR, " → Use --server-privkey to create a new database\n");
return 1;
}
}
// Scenario 1: No Arguments (Fresh Start)
else {
fprintf(stderr, "\n=== SCENARIO 1: FRESH START (NO ARGUMENTS) ===\n");
fprintf(stderr, "FRESH START: Generating new server keypair...\n");
app_log(LOG_INFO, "\n=== SCENARIO 1: FRESH START (NO ARGUMENTS) ===\n");
app_log(LOG_INFO, "FRESH START: Generating new server keypair...\n");
// Generate new keypair (this will set g_db_path based on pubkey)
if (generate_server_keypair() != 0) {
fprintf(stderr, "ERROR: Failed to generate server keypair\n");
app_log(LOG_ERROR, "ERROR: Failed to generate server keypair\n");
return 1;
}
fprintf(stderr, "FRESH START: New instance created successfully\n");
app_log(LOG_INFO, "FRESH START: New instance created successfully\n");
}
// ========================================================================
@@ -2619,14 +2662,13 @@ int config_loaded = 0;
// Fall back to database configuration if file config failed
if (!config_loaded /* && !initialize_server_config() */) {
fprintf(
stderr,
"STARTUP: No configuration found - server starting in setup mode\n");
fprintf(stderr, "STARTUP: Run interactive setup with: ginxsom --setup\n");
// For interactive mode (when stdin is available), offer setup
app_log(LOG_WARN,
"STARTUP: No configuration found - server starting in setup mode");
app_log(LOG_WARN, "STARTUP: Run interactive setup with: ginxsom --setup");
// For interactive mode (when stdin is available), offer setup
} else if (!config_loaded) {
fprintf(stderr, "STARTUP: Database configuration loaded successfully\n");
app_log(LOG_INFO, "STARTUP: Database configuration loaded successfully");
}
// Initialize request validator system
@@ -2688,10 +2730,8 @@ if (!config_loaded /* && !initialize_server_config() */) {
int first_request = 1;
while (FCGI_Accept() >= 0 && !g_shutdown_requested) {
// Test stderr capture on first request
if (first_request) {
fprintf(stderr, "FCGI: First request received - testing nginx stderr capture\n");
fflush(stderr);
app_log(LOG_DEBUG, "FCGI: First request received");
first_request = 0;
}
const char *request_method = getenv("REQUEST_METHOD");
@@ -2752,16 +2792,12 @@ if (!config_loaded /* && !initialize_server_config() */) {
printf(" \"limits\": {\n");
printf(" \"max_upload_size\": 104857600,\n");
printf(" \"supported_mime_types\": [\n");
printf(" \"image/jpeg\",\n");
printf(" \"image/png\",\n");
printf(" \"image/webp\",\n");
printf(" \"image/gif\",\n");
printf(" \"video/mp4\",\n");
printf(" \"video/webm\",\n");
printf(" \"audio/mpeg\",\n");
printf(" \"audio/ogg\",\n");
printf(" \"text/plain\",\n");
printf(" \"application/pdf\"\n");
size_t supported_mime_count = 0;
const char* const* supported_mimes = ginxsom_supported_mime_types(&supported_mime_count);
for (size_t i = 0; i < supported_mime_count; i++) {
printf(" \"%s\"%s\n", supported_mimes[i],
(i + 1 < supported_mime_count) ? "," : "");
}
printf(" ]\n");
printf(" },\n");
printf(" \"authentication\": {\n");
@@ -3047,16 +3083,12 @@ if (!config_loaded /* && !initialize_server_config() */) {
printf(" \"limits\": {\n");
printf(" \"max_upload_size\": 104857600,\n");
printf(" \"supported_mime_types\": [\n");
printf(" \"image/jpeg\",\n");
printf(" \"image/png\",\n");
printf(" \"image/webp\",\n");
printf(" \"image/gif\",\n");
printf(" \"video/mp4\",\n");
printf(" \"video/webm\",\n");
printf(" \"audio/mpeg\",\n");
printf(" \"audio/ogg\",\n");
printf(" \"text/plain\",\n");
printf(" \"application/pdf\"\n");
size_t supported_mime_count = 0;
const char* const* supported_mimes = ginxsom_supported_mime_types(&supported_mime_count);
for (size_t i = 0; i < supported_mime_count; i++) {
printf(" \"%s\"%s\n", supported_mimes[i],
(i + 1 < supported_mime_count) ? "," : "");
}
printf(" ]\n");
printf(" },\n");
printf(" \"authentication\": {\n");
@@ -3110,8 +3142,11 @@ if (!config_loaded /* && !initialize_server_config() */) {
}
// Graceful shutdown - stop relay client thread cleanly
app_log(LOG_INFO, "Shutting down ginxsom (shutdown_requested=%d)...", (int)g_shutdown_requested);
app_log(LOG_INFO, "Shutting down ginxsom (shutdown_requested=%d)...",
(int)g_shutdown_requested);
relay_client_stop();
nostr_set_log_callback(NULL, NULL);
nostr_cleanup();
app_log(LOG_INFO, "Ginxsom shutdown complete");
return 0;

View File

@@ -6,6 +6,7 @@
#include "relay_client.h"
#include "admin_commands.h"
#include "app_log.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include <sqlite3.h>
#include <stdio.h>
@@ -15,16 +16,6 @@
#include <unistd.h>
#include <time.h>
// Forward declare app_log to avoid including ginxsom.h (which has typedef conflicts)
typedef enum {
LOG_DEBUG = 0,
LOG_INFO = 1,
LOG_WARN = 2,
LOG_ERROR = 3
} log_level_t;
void app_log(log_level_t level, const char* format, ...);
// Maximum number of relays to connect to
#define MAX_RELAYS 10

View File

@@ -92,14 +92,25 @@ struct {
} g_last_rule_violation = {0};
/**
* Helper function for consistent debug logging to our debug.log file
* Helper function for consistent validator debug logging via app logger
*/
static void validator_debug_log(const char *message) {
FILE *debug_log = fopen("logs/app/debug.log", "a");
if (debug_log) {
fprintf(debug_log, "%ld %s", (long)time(NULL), message);
fclose(debug_log);
const char *msg = message ? message : "";
size_t msg_len = strlen(msg);
if (msg_len > 0 && msg[msg_len - 1] == '\n') {
char trimmed[1024];
size_t copy_len = msg_len - 1;
if (copy_len >= sizeof(trimmed)) {
copy_len = sizeof(trimmed) - 1;
}
memcpy(trimmed, msg, copy_len);
trimmed[copy_len] = '\0';
app_log(LOG_DEBUG, "%s", trimmed);
return;
}
app_log(LOG_DEBUG, "%s", msg);
}
//=============================================================================
@@ -143,9 +154,9 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) {
}
// Initialize nostr_core_lib if not already done
if (nostr_crypto_init() != NOSTR_SUCCESS) {
if (nostr_init() != NOSTR_SUCCESS) {
validator_debug_log(
"VALIDATOR: Failed to initialize nostr crypto system\n");
"VALIDATOR: Failed to initialize nostr core system\n");
return NOSTR_ERROR_CRYPTO_INIT;
}
@@ -1142,15 +1153,15 @@ static int reload_auth_config(void) {
}
// Debug logging
fprintf(stderr,
app_log(LOG_DEBUG,
"VALIDATOR: Configuration loaded from unified config table - "
"auth_required: %d, max_file_size: %ld, nip42_mode: %d, "
"cache_timeout: %d\n",
"cache_timeout: %d",
g_auth_cache.auth_required, g_auth_cache.max_file_size,
g_auth_cache.nip42_mode, cache_timeout);
fprintf(stderr,
app_log(LOG_DEBUG,
"VALIDATOR: NIP-42 mode details - nip42_mode=%d (0=disabled, "
"1=optional/enabled, 2=required)\n",
"1=optional/enabled, 2=required)",
g_auth_cache.nip42_mode);
return NOSTR_SUCCESS;

View File

@@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<title>test_html</title>
/* payload type: text/html */
console.log("test_html");

View File

@@ -0,0 +1,57 @@
#!/bin/bash
set -eu
BASE_URL="https://blossom.laantungir.net"
PRIVKEY="22cc83aa57928a2800234c939240c9a6f0f44a33ea3838a860ed38930b195afd"
FILE="/tmp/ginxsom_browser_sample.html"
cat > "$FILE" <<'EOF'
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Ginxsom Browser Sample</title>
<style>
body { font-family: system-ui, sans-serif; margin: 2rem; background: #0b1020; color: #e6e9f2; }
.card { max-width: 720px; padding: 1.25rem 1.5rem; border: 1px solid #2a3350; border-radius: 12px; background: #121a33; }
code { background: #1b2545; padding: 0.1rem 0.35rem; border-radius: 6px; }
.ok { color: #69f0ae; }
</style>
</head>
<body>
<div class="card">
<h1>Ginxsom HTML test</h1>
<p class="ok">If you can see this page, remote HTML upload and retrieval are working.</p>
<p>Timestamp: <code id="ts"></code></p>
</div>
<script>
document.getElementById("ts").textContent = new Date().toISOString();
</script>
</body>
</html>
EOF
SHA=$(sha256sum "$FILE" | cut -d' ' -f1)
EXP=$(date -d '+1 hour' +%s)
EVENT=$(nak event -k 24242 -c "" --sec "$PRIVKEY" -t "t=upload" -t "x=$SHA" -t "expiration=$EXP")
AUTH="Nostr $(echo -n "$EVENT" | base64 -w 0)"
RESP=$(mktemp)
STATUS=$(curl -s -w "%{http_code}" -o "$RESP" \
-X PUT \
-H "Authorization: $AUTH" \
-H "Content-Type: text/html" \
--data-binary "@$FILE" \
"$BASE_URL/upload")
URL=$(jq -r '.url // empty' "$RESP")
GET_CODE=0
if [ -n "$URL" ]; then
GET_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
fi
echo "UPLOAD_STATUS=$STATUS"
echo "SHA256=$SHA"
echo "URL=$URL"
echo "GET_CODE=$GET_CODE"