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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,3 +4,5 @@ nostr_core_lib/
|
||||
blobs/
|
||||
c-relay/
|
||||
text_graph/
|
||||
.test_keys/
|
||||
ginxsom-local.service
|
||||
5
.roo/commands/push.md
Normal file
5
.roo/commands/push.md
Normal 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.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
Binary file not shown.
80
config/ginxsom-local.conf
Normal file
80
config/ginxsom-local.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
199
deploy_local.sh
Executable file
199
deploy_local.sh
Executable 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/"
|
||||
@@ -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>
|
||||
|
||||
88
src/main.c
88
src/main.c
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -246,7 +246,7 @@ main() {
|
||||
generate_nostr_event
|
||||
create_auth_header
|
||||
perform_upload
|
||||
# test_retrieval
|
||||
test_retrieval
|
||||
|
||||
echo
|
||||
log_info "Test completed!"
|
||||
|
||||
Reference in New Issue
Block a user