v0.1.26 - Add local deployment system with deploy_local.sh, ginxsom-local.service, nginx SSL config, and fix GET blob retrieval

This commit is contained in:
Your Name
2026-02-16 09:23:23 -04:00
parent 640386e2c1
commit 1905e1586d
13 changed files with 1783 additions and 7 deletions

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ nostr_core_lib/
blobs/
c-relay/
text_graph/
.test_keys/
ginxsom-local.service

5
.roo/commands/push.md Normal file
View File

@@ -0,0 +1,5 @@
---
description: "increment and push"
---
Run increment_and_push.sh adding a good comment on the command line following the command.

Binary file not shown.

Binary file not shown.

Binary file not shown.

80
config/ginxsom-local.conf Normal file
View File

@@ -0,0 +1,80 @@
# Ginxsom Blossom Server nginx configuration
# Local deployment configuration
# Port: 9443 with SSL
server {
listen 9443 ssl http2;
server_name localhost;
# SSL configuration
ssl_certificate /home/teknari/.ssl_for_local_servers/cert.pem;
ssl_certificate_key /home/teknari/.ssl_for_local_servers/key.pem;
# SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
# CORS headers for all responses (including error responses)
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, HEAD, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-SHA-256, X-Content-Type, X-Content-Length" always;
add_header Access-Control-Max-Age 86400 always;
# Handle preflight requests
location / {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, HEAD, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-SHA-256, X-Content-Type, X-Content-Length";
add_header Access-Control-Max-Age 86400;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
# Pass all requests to FastCGI
include fastcgi_params;
fastcgi_pass unix:/tmp/ginxsom.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# All dynamic endpoints go through FastCGI
location ~ ^/(upload|list|delete|mirror|media|report|health|admin) {
include fastcgi_params;
fastcgi_pass unix:/tmp/ginxsom.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Handle large uploads
client_max_body_size 100M;
fastcgi_read_timeout 300s;
# Disable buffering for large uploads
fastcgi_request_buffering off;
fastcgi_buffering off;
}
# Blob serving - SHA256 hashes
location ~ "^/([a-fA-F0-9]{64})" {
include fastcgi_params;
fastcgi_pass unix:/tmp/ginxsom.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Enable range requests for video streaming
add_header Accept-Ranges bytes always;
# Cache static blobs
expires 1y;
add_header Cache-Control "public, immutable" always;
}
# Deny access to sensitive files
location ~ /\.(ht|git|svn) {
deny all;
}
}

1390
debug.log

File diff suppressed because it is too large Load Diff

199
deploy_local.sh Executable file
View File

@@ -0,0 +1,199 @@
#!/bin/bash
# Deploy Ginxsom to local installation directory
# Builds static binary and deploys to ~/Storage/Blossom
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOY_DIR="$HOME/Storage/Blossom"
SERVICE_NAME="ginxsom.service"
NGINX_SITE_NAME="ginxsom"
NGINX_PORT="9443"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${YELLOW}=== Ginxsom Local Deployment ===${NC}"
echo "Source: $SCRIPT_DIR"
echo "Target: $DEPLOY_DIR"
echo "Nginx Port: $NGINX_PORT"
echo ""
# Step 1: Build static binary
echo -e "${YELLOW}Step 1: Building static binary...${NC}"
cd "$SCRIPT_DIR"
./build_static.sh
if [ $? -ne 0 ]; then
echo -e "${RED}Build failed! Cannot continue.${NC}"
exit 1
fi
echo -e "${GREEN}✓ Build complete${NC}"
echo ""
# Step 2: Create deployment directory
echo -e "${YELLOW}Step 2: Preparing deployment directory...${NC}"
mkdir -p "$DEPLOY_DIR"
mkdir -p "$DEPLOY_DIR/blobs"
mkdir -p "$DEPLOY_DIR/db"
mkdir -p "$DEPLOY_DIR/logs"
echo -e "${GREEN}✓ Directories created${NC}"
echo ""
# Step 3: Stop service before copying binary
echo -e "${YELLOW}Step 3: Stopping service before binary update...${NC}"
if systemctl list-unit-files | grep -q "$SERVICE_NAME"; then
sudo systemctl stop "$SERVICE_NAME" || true
sleep 1
echo -e "${GREEN}✓ Service stopped${NC}"
fi
echo ""
# Step 4: Copy binary
echo -e "${YELLOW}Step 4: Copying binary...${NC}"
# Detect architecture
ARCH=$(uname -m)
case "$ARCH" in
x86_64) BINARY_NAME="ginxsom-fcgi_static_x86_64" ;;
aarch64|arm64) BINARY_NAME="ginxsom-fcgi_static_arm64" ;;
*) BINARY_NAME="ginxsom-fcgi_static_${ARCH}" ;;
esac
SOURCE_BINARY="$SCRIPT_DIR/build/$BINARY_NAME"
TARGET_BINARY="$DEPLOY_DIR/ginxsom"
if [ ! -f "$SOURCE_BINARY" ]; then
echo -e "${RED}Error: Binary not found at $SOURCE_BINARY${NC}"
exit 1
fi
cp "$SOURCE_BINARY" "$TARGET_BINARY"
chmod +x "$TARGET_BINARY"
echo -e "${GREEN}✓ Binary copied to $TARGET_BINARY${NC}"
echo ""
# Step 5: Copy configuration files if they exist
echo -e "${YELLOW}Step 5: Copying configuration files...${NC}"
if [ -f "$SCRIPT_DIR/mime.types" ]; then
cp "$SCRIPT_DIR/mime.types" "$DEPLOY_DIR/"
echo " - mime.types"
fi
if [ -f "$SCRIPT_DIR/.admin_keys" ]; then
cp "$SCRIPT_DIR/.admin_keys" "$DEPLOY_DIR/"
chmod 600 "$DEPLOY_DIR/.admin_keys"
echo " - .admin_keys"
fi
echo -e "${GREEN}✓ Configuration files copied${NC}"
echo ""
# Step 6: Setup nginx configuration
echo -e "${YELLOW}Step 6: Setting up nginx configuration...${NC}"
# Copy nginx config to sites-available
if [ -f "$SCRIPT_DIR/config/ginxsom-local.conf" ]; then
sudo cp "$SCRIPT_DIR/config/ginxsom-local.conf" "/etc/nginx/sites-available/$NGINX_SITE_NAME"
echo " - Copied nginx config to sites-available"
# Create symlink in sites-enabled if it doesn't exist
if [ ! -L "/etc/nginx/sites-enabled/$NGINX_SITE_NAME" ]; then
sudo ln -s "/etc/nginx/sites-available/$NGINX_SITE_NAME" "/etc/nginx/sites-enabled/$NGINX_SITE_NAME"
echo " - Created symlink in sites-enabled"
else
echo " - Symlink already exists in sites-enabled"
fi
# Test nginx configuration
echo " - Testing nginx configuration..."
if sudo nginx -t; then
echo -e "${GREEN}✓ Nginx configuration valid${NC}"
else
echo -e "${RED}✗ Nginx configuration test failed${NC}"
exit 1
fi
else
echo -e "${YELLOW}Warning: nginx config not found at $SCRIPT_DIR/config/ginxsom-local.conf${NC}"
fi
echo ""
# Step 7: Restart service
echo -e "${YELLOW}Step 7: Restarting service...${NC}"
# Check if service exists
if systemctl list-unit-files | grep -q "$SERVICE_NAME"; then
echo "Stopping service..."
sudo systemctl stop "$SERVICE_NAME" || true
echo "Reloading systemd daemon..."
sudo systemctl daemon-reload
echo "Starting service..."
sudo systemctl start "$SERVICE_NAME"
# Wait a moment for service to start
sleep 2
# Check status
if systemctl is-active --quiet "$SERVICE_NAME"; then
echo -e "${GREEN}✓ Service started successfully${NC}"
systemctl status "$SERVICE_NAME" --no-pager -l
else
echo -e "${RED}✗ Service failed to start${NC}"
echo "Check logs with: journalctl -u $SERVICE_NAME -n 50"
exit 1
fi
else
echo -e "${YELLOW}Service not found. Install with:${NC}"
echo " sudo cp ginxsom-local.service /etc/systemd/system/ginxsom.service"
echo " sudo systemctl daemon-reload"
echo " sudo systemctl enable ginxsom.service"
echo " sudo systemctl start ginxsom.service"
fi
echo ""
# Step 8: Start/restart nginx
echo -e "${YELLOW}Step 8: Starting nginx...${NC}"
if systemctl is-active --quiet nginx; then
echo "Reloading nginx..."
sudo systemctl reload nginx
else
echo "Starting nginx..."
sudo systemctl start nginx
fi
if systemctl is-active --quiet nginx; then
echo -e "${GREEN}✓ Nginx is running${NC}"
else
echo -e "${RED}✗ Nginx failed to start${NC}"
exit 1
fi
echo ""
echo -e "${GREEN}=== Deployment complete ===${NC}"
echo ""
echo "Service Information:"
echo " FastCGI Service: $SERVICE_NAME"
echo " Nginx Port: $NGINX_PORT"
echo " Socket: /tmp/ginxsom.sock"
echo " Storage: $DEPLOY_DIR/blobs"
echo " Database: $DEPLOY_DIR/db"
echo ""
echo "Service commands:"
echo " Status: systemctl status $SERVICE_NAME"
echo " Stop: sudo systemctl stop $SERVICE_NAME"
echo " Start: sudo systemctl start $SERVICE_NAME"
echo " Restart: sudo systemctl restart $SERVICE_NAME"
echo " Logs: journalctl -u $SERVICE_NAME -f"
echo ""
echo "Nginx commands:"
echo " Status: systemctl status nginx"
echo " Reload: sudo systemctl reload nginx"
echo " Logs: sudo tail -f /var/log/nginx/error.log"
echo ""
echo "Test the server:"
echo " curl http://localhost:$NGINX_PORT/health"
echo " curl http://localhost:$NGINX_PORT/"

View File

@@ -10,8 +10,8 @@
// Version information (auto-updated by build system)
#define VERSION_MAJOR 0
#define VERSION_MINOR 1
#define VERSION_PATCH 25
#define VERSION "v0.1.25"
#define VERSION_PATCH 26
#define VERSION "v0.1.26"
#include <stddef.h>
#include <stdint.h>

View File

@@ -973,9 +973,11 @@ int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) {
sqlite3_stmt *stmt;
int rc;
rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_CREATE, NULL);
fprintf(stderr, "get_blob_metadata: Looking for %s in database %s\n", sha256, g_db_path);
rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
if (rc) {
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
fprintf(stderr, "get_blob_metadata: Can't open database %s: %s\n", g_db_path, sqlite3_errmsg(db));
return 0;
}
@@ -984,7 +986,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));
fprintf(stderr, "get_blob_metadata: SQL error: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 0;
}
@@ -992,8 +994,10 @@ int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) {
sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
fprintf(stderr, "get_blob_metadata: sqlite3_step returned %d (SQLITE_ROW=%d, SQLITE_DONE=%d)\n", rc, SQLITE_ROW, SQLITE_DONE);
if (rc == SQLITE_ROW) {
fprintf(stderr, "get_blob_metadata: Found blob in database\n");
strncpy(metadata->sha256, (char *)sqlite3_column_text(stmt, 0),
MAX_SHA256_LEN - 1);
metadata->size = sqlite3_column_int64(stmt, 1);
@@ -1092,6 +1096,70 @@ void handle_head_request(const char *sha256) {
// HEAD request - no body content
}
// Handle GET request for blob - serve the actual file
void handle_get_request(const char *sha256) {
blob_metadata_t metadata = {0};
// Debug logging
fprintf(stderr, "GET: Requesting blob %s\n", sha256);
fprintf(stderr, "GET: Database path: %s\n", g_db_path);
fprintf(stderr, "GET: Storage dir: %s\n", g_storage_dir);
// Validate SHA-256 format (64 hex characters)
if (strlen(sha256) != 64) {
printf("Status: 400 Bad Request\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Invalid SHA-256 hash format\n");
return;
}
// Check if blob exists in database
if (!get_blob_metadata(sha256, &metadata)) {
fprintf(stderr, "GET: Blob not found in database\n");
printf("Status: 404 Not Found\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Blob not found\n");
return;
}
// Construct file path
char filepath[4096];
const char *extension = mime_to_extension(metadata.type);
snprintf(filepath, sizeof(filepath), "%s/%s%s", g_storage_dir, sha256, extension);
// Open and serve the file
FILE *file = fopen(filepath, "rb");
if (!file) {
printf("Status: 404 Not Found\r\n");
printf("Content-Type: text/plain\r\n\r\n");
printf("Blob file not found on disk\n");
return;
}
// Send headers
printf("Status: 200 OK\r\n");
printf("Content-Type: %s\r\n", metadata.type);
printf("Content-Length: %ld\r\n", metadata.size);
printf("Cache-Control: public, max-age=31536000, immutable\r\n");
printf("ETag: \"%s\"\r\n", metadata.sha256);
printf("X-Ginxsom-Server: FastCGI\r\n");
if (strlen(metadata.filename) > 0) {
printf("X-Original-Filename: %s\r\n", metadata.filename);
}
printf("\r\n");
// Send file content
char buffer[8192];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
fwrite(buffer, 1, bytes_read, stdout);
}
fclose(file);
}
// Extract SHA-256 from request URI (Blossom compliant - ignores any extension)
const char *extract_sha256_from_uri(const char *uri) {
static char sha256_buffer[MAX_SHA256_LEN];
@@ -2794,6 +2862,9 @@ if (!config_loaded /* && !initialize_server_config() */) {
operation = "list";
} else if (strcmp(request_method, "GET") == 0 && strcmp(request_uri, "/auth") == 0) {
operation = "challenge";
} else if (strcmp(request_method, "GET") == 0 && extract_sha256_from_uri(request_uri) != NULL) {
operation = "get_blob"; // Public blob retrieval - no auth required
resource_hash = extract_sha256_from_uri(request_uri);
} else if (strcmp(request_method, "DELETE") == 0) {
operation = "delete";
resource_hash = extract_sha256_from_uri(request_uri);
@@ -3044,6 +3115,17 @@ if (!config_loaded /* && !initialize_server_config() */) {
strcmp(request_uri, "/auth") == 0) {
// Handle GET /auth requests using the existing handler
handle_auth_challenge_request();
} else if (strcmp(request_method, "GET") == 0) {
// Handle GET /<sha256> requests for blob retrieval
const char *sha256 = extract_sha256_from_uri(request_uri);
if (sha256) {
handle_get_request(sha256);
log_request("GET", request_uri, "public", 200);
} else {
send_error_response(400, "invalid_hash", "Invalid SHA-256 hash in URI",
"URI must contain a valid 64-character hex hash");
log_request("GET", request_uri, "none", 400);
}
} else if (strcmp(request_method, "DELETE") == 0) {
// Handle DELETE /<sha256> requests with pre-validated auth
const char *sha256 = extract_sha256_from_uri(request_uri);

View File

@@ -305,7 +305,25 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
return NOSTR_SUCCESS;
}
// Check if authentication header is provided (required for non-report operations)
// Check if this is a GET blob request - allow public blob retrieval
if (request->operation && strcmp(request->operation, "get_blob") == 0) {
result->valid = 1;
result->error_code = NOSTR_SUCCESS;
strcpy(result->reason, "Public blob retrieval - no authentication required");
validator_debug_log("VALIDATOR_DEBUG: GET blob detected, bypassing authentication\n");
return NOSTR_SUCCESS;
}
// Check if this is a HEAD request - allow public metadata retrieval
if (request->operation && (strcmp(request->operation, "head") == 0 || strcmp(request->operation, "head_upload") == 0)) {
result->valid = 1;
result->error_code = NOSTR_SUCCESS;
strcpy(result->reason, "Public HEAD request - no authentication required");
validator_debug_log("VALIDATOR_DEBUG: HEAD request detected, bypassing authentication\n");
return NOSTR_SUCCESS;
}
// Check if authentication header is provided (required for non-public operations)
if (!request->auth_header) {
result->valid = 0;

View File

@@ -246,7 +246,7 @@ main() {
generate_nostr_event
create_auth_header
perform_upload
# test_retrieval
test_retrieval
echo
log_info "Test completed!"