diff --git a/STATIC_MUSL_GUIDE.md b/STATIC_MUSL_GUIDE.md new file mode 100644 index 0000000..2f857c4 --- /dev/null +++ b/STATIC_MUSL_GUIDE.md @@ -0,0 +1,612 @@ +# Static MUSL Build Guide for C Programs + +## Overview + +This guide explains how to build truly portable static binaries using Alpine Linux and MUSL libc. These binaries have **zero runtime dependencies** and work on any Linux distribution without modification. + +This guide is specifically tailored for C programs that use: +- **nostr_core_lib** - Nostr protocol implementation +- **nostr_login_lite** - Nostr authentication library +- Common dependencies: libwebsockets, OpenSSL, SQLite, curl, secp256k1 + +## Why MUSL Static Binaries? + +### Advantages Over glibc + +| Feature | MUSL Static | glibc Static | glibc Dynamic | +|---------|-------------|--------------|---------------| +| **Portability** | ✓ Any Linux | ⚠ glibc only | ✗ Requires matching libs | +| **Binary Size** | ~7-10 MB | ~12-15 MB | ~2-3 MB | +| **Dependencies** | None | NSS libs | Many system libs | +| **Deployment** | Single file | Single file + NSS | Binary + libraries | +| **Compatibility** | Universal | glibc version issues | Library version hell | + +### Key Benefits + +1. **True Portability**: Works on Alpine, Ubuntu, Debian, CentOS, Arch, etc. +2. **No Library Hell**: No `GLIBC_2.XX not found` errors +3. **Simple Deployment**: Just copy one file +4. **Reproducible Builds**: Same Docker image = same binary +5. **Security**: No dependency on system libraries with vulnerabilities + +## Quick Start + +### Prerequisites + +- Docker installed and running +- Your C project with source code +- Internet connection for downloading dependencies + +### Basic Build Process + +```bash +# 1. Copy the Dockerfile template (see below) +cp /path/to/c-relay/Dockerfile.alpine-musl ./Dockerfile.static + +# 2. Customize for your project (see Customization section) +vim Dockerfile.static + +# 3. Build the static binary +docker build --platform linux/amd64 -f Dockerfile.static -t my-app-builder . + +# 4. Extract the binary +docker create --name temp-container my-app-builder +docker cp temp-container:/build/my_app_static ./my_app_static +docker rm temp-container + +# 5. Verify it's static +ldd ./my_app_static # Should show "not a dynamic executable" +``` + +## Dockerfile Template + +Here's a complete Dockerfile template you can customize for your project: + +```dockerfile +# Alpine-based MUSL static binary builder +# Produces truly portable binaries with zero runtime dependencies + +FROM alpine:3.19 AS builder + +# Install build dependencies +RUN apk add --no-cache \ + build-base \ + musl-dev \ + git \ + cmake \ + pkgconfig \ + autoconf \ + automake \ + libtool \ + openssl-dev \ + openssl-libs-static \ + zlib-dev \ + zlib-static \ + curl-dev \ + curl-static \ + sqlite-dev \ + sqlite-static \ + linux-headers \ + wget \ + bash + +WORKDIR /build + +# Build libsecp256k1 static (required for Nostr) +RUN cd /tmp && \ + git clone https://github.com/bitcoin-core/secp256k1.git && \ + cd secp256k1 && \ + ./autogen.sh && \ + ./configure --enable-static --disable-shared --prefix=/usr \ + CFLAGS="-fPIC" && \ + make -j$(nproc) && \ + make install && \ + rm -rf /tmp/secp256k1 + +# Build libwebsockets static (if needed for WebSocket support) +RUN cd /tmp && \ + git clone --depth 1 --branch v4.3.3 https://github.com/warmcat/libwebsockets.git && \ + cd libwebsockets && \ + mkdir build && cd build && \ + cmake .. \ + -DLWS_WITH_STATIC=ON \ + -DLWS_WITH_SHARED=OFF \ + -DLWS_WITH_SSL=ON \ + -DLWS_WITHOUT_TESTAPPS=ON \ + -DLWS_WITHOUT_TEST_SERVER=ON \ + -DLWS_WITHOUT_TEST_CLIENT=ON \ + -DLWS_WITHOUT_TEST_PING=ON \ + -DLWS_WITH_HTTP2=OFF \ + -DLWS_WITH_LIBUV=OFF \ + -DLWS_WITH_LIBEVENT=OFF \ + -DLWS_IPV6=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_C_FLAGS="-fPIC" && \ + make -j$(nproc) && \ + make install && \ + rm -rf /tmp/libwebsockets + +# Copy git configuration for submodules +COPY .gitmodules /build/.gitmodules +COPY .git /build/.git + +# Initialize submodules +RUN git submodule update --init --recursive + +# Copy and build nostr_core_lib +COPY nostr_core_lib /build/nostr_core_lib/ +RUN cd nostr_core_lib && \ + chmod +x build.sh && \ + sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \ + rm -f *.o *.a 2>/dev/null || true && \ + ./build.sh --nips=1,6,13,17,19,44,59 + +# Copy and build nostr_login_lite (if used) +# COPY nostr_login_lite /build/nostr_login_lite/ +# RUN cd nostr_login_lite && make static + +# Copy your application source +COPY src/ /build/src/ +COPY Makefile /build/Makefile + +# Build your application with full static linking +RUN gcc -static -O2 -Wall -Wextra -std=c99 \ + -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ + -I. -Inostr_core_lib -Inostr_core_lib/nostr_core \ + -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \ + src/*.c \ + -o /build/my_app_static \ + nostr_core_lib/libnostr_core_x64.a \ + -lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \ + -lcurl -lz -lpthread -lm -ldl && \ + strip /build/my_app_static + +# Verify it's truly static +RUN echo "=== Binary Information ===" && \ + file /build/my_app_static && \ + ls -lh /build/my_app_static && \ + echo "=== Checking for dynamic dependencies ===" && \ + (ldd /build/my_app_static 2>&1 || echo "Binary is static") + +# Output stage - just the binary +FROM scratch AS output +COPY --from=builder /build/my_app_static /my_app_static +``` + +## Customization Guide + +### 1. Adjust Dependencies + +**Add dependencies** by modifying the `apk add` section: + +```dockerfile +RUN apk add --no-cache \ + build-base \ + musl-dev \ + # Add your dependencies here: + libpng-dev \ + libpng-static \ + libjpeg-turbo-dev \ + libjpeg-turbo-static +``` + +**Remove unused dependencies** to speed up builds: +- Remove `libwebsockets` section if you don't need WebSocket support +- Remove `sqlite` if you don't use databases +- Remove `curl` if you don't make HTTP requests + +### 2. Configure nostr_core_lib NIPs + +Specify which NIPs your application needs: + +```bash +./build.sh --nips=1,6,19 # Minimal: Basic protocol, keys, bech32 +./build.sh --nips=1,6,13,17,19,44,59 # Full: All common NIPs +./build.sh --nips=all # Everything available +``` + +**Common NIP combinations:** +- **Basic client**: `1,6,19` (events, keys, bech32) +- **With encryption**: `1,6,19,44` (add modern encryption) +- **With DMs**: `1,6,17,19,44,59` (add private messages) +- **Relay/server**: `1,6,13,17,19,42,44,59` (add PoW, auth) + +### 3. Modify Compilation Flags + +**For your application:** + +```dockerfile +RUN gcc -static -O2 -Wall -Wextra -std=c99 \ + -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ # REQUIRED for MUSL + -I. -Inostr_core_lib \ # Include paths + src/*.c \ # Your source files + -o /build/my_app_static \ # Output binary + nostr_core_lib/libnostr_core_x64.a \ # Nostr library + -lwebsockets -lssl -lcrypto \ # Link libraries + -lsqlite3 -lsecp256k1 -lcurl \ + -lz -lpthread -lm -ldl +``` + +**Debug build** (with symbols, no optimization): + +```dockerfile +RUN gcc -static -g -O0 -DDEBUG \ + -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ + # ... rest of flags +``` + +### 4. Multi-Architecture Support + +Build for different architectures: + +```bash +# x86_64 (Intel/AMD) +docker build --platform linux/amd64 -f Dockerfile.static -t my-app-x86 . + +# ARM64 (Apple Silicon, Raspberry Pi 4+) +docker build --platform linux/arm64 -f Dockerfile.static -t my-app-arm64 . +``` + +## Build Script Template + +Create a `build_static.sh` script for convenience: + +```bash +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="$SCRIPT_DIR/build" +DOCKERFILE="$SCRIPT_DIR/Dockerfile.static" + +# Detect architecture +ARCH=$(uname -m) +case "$ARCH" in + x86_64) + PLATFORM="linux/amd64" + OUTPUT_NAME="my_app_static_x86_64" + ;; + aarch64|arm64) + PLATFORM="linux/arm64" + OUTPUT_NAME="my_app_static_arm64" + ;; + *) + echo "Unknown architecture: $ARCH" + exit 1 + ;; +esac + +echo "Building for platform: $PLATFORM" +mkdir -p "$BUILD_DIR" + +# Build Docker image +docker build \ + --platform "$PLATFORM" \ + -f "$DOCKERFILE" \ + -t my-app-builder:latest \ + --progress=plain \ + . + +# Extract binary +CONTAINER_ID=$(docker create my-app-builder:latest) +docker cp "$CONTAINER_ID:/build/my_app_static" "$BUILD_DIR/$OUTPUT_NAME" +docker rm "$CONTAINER_ID" + +chmod +x "$BUILD_DIR/$OUTPUT_NAME" + +echo "✓ Build complete: $BUILD_DIR/$OUTPUT_NAME" +echo "✓ Size: $(du -h "$BUILD_DIR/$OUTPUT_NAME" | cut -f1)" + +# Verify +if ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 | grep -q "not a dynamic executable"; then + echo "✓ Binary is fully static" +else + echo "⚠ Warning: Binary may have dynamic dependencies" +fi +``` + +Make it executable: + +```bash +chmod +x build_static.sh +./build_static.sh +``` + +## Common Issues and Solutions + +### Issue 1: Fortification Errors + +**Error:** +``` +undefined reference to '__snprintf_chk' +undefined reference to '__fprintf_chk' +``` + +**Cause**: GCC's `-O2` enables fortification by default, which uses glibc-specific functions. + +**Solution**: Add these flags to **all** compilation commands: +```bash +-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 +``` + +This must be applied to: +1. nostr_core_lib build.sh +2. Your application compilation +3. Any other libraries you build + +### Issue 2: Missing Symbols from nostr_core_lib + +**Error:** +``` +undefined reference to 'nostr_create_event' +undefined reference to 'nostr_sign_event' +``` + +**Cause**: Required NIPs not included in nostr_core_lib build. + +**Solution**: Add missing NIPs: +```bash +./build.sh --nips=1,6,19 # Add the NIPs you need +``` + +### Issue 3: Docker Permission Denied + +**Error:** +``` +permission denied while trying to connect to the Docker daemon socket +``` + +**Solution**: +```bash +sudo usermod -aG docker $USER +newgrp docker # Or logout and login +``` + +### Issue 4: Binary Won't Run on Target System + +**Checks**: +```bash +# 1. Verify it's static +ldd my_app_static # Should show "not a dynamic executable" + +# 2. Check architecture +file my_app_static # Should match target system + +# 3. Test on different distributions +docker run --rm -v $(pwd):/app alpine:latest /app/my_app_static --version +docker run --rm -v $(pwd):/app ubuntu:latest /app/my_app_static --version +``` + +## Project Structure Example + +Organize your project for easy static builds: + +``` +my-nostr-app/ +├── src/ +│ ├── main.c +│ ├── handlers.c +│ └── utils.c +├── nostr_core_lib/ # Git submodule +├── nostr_login_lite/ # Git submodule (if used) +├── Dockerfile.static # Static build Dockerfile +├── build_static.sh # Build script +├── Makefile # Regular build +└── README.md +``` + +### Makefile Integration + +Add static build targets to your Makefile: + +```makefile +# Regular dynamic build +all: my_app + +my_app: src/*.c + gcc -O2 src/*.c -o my_app \ + nostr_core_lib/libnostr_core_x64.a \ + -lssl -lcrypto -lsecp256k1 -lz -lpthread -lm + +# Static MUSL build via Docker +static: + ./build_static.sh + +# Clean +clean: + rm -f my_app build/my_app_static_* + +.PHONY: all static clean +``` + +## Deployment + +### Single Binary Deployment + +```bash +# Copy to server +scp build/my_app_static_x86_64 user@server:/opt/my-app/ + +# Run (no dependencies needed!) +ssh user@server +/opt/my-app/my_app_static_x86_64 +``` + +### SystemD Service + +```ini +[Unit] +Description=My Nostr Application +After=network.target + +[Service] +Type=simple +User=myapp +WorkingDirectory=/opt/my-app +ExecStart=/opt/my-app/my_app_static_x86_64 +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +### Docker Container (Minimal) + +```dockerfile +FROM scratch +COPY my_app_static_x86_64 /app +ENTRYPOINT ["/app"] +``` + +Build and run: +```bash +docker build -t my-app:latest . +docker run --rm my-app:latest --help +``` + +## Reusing c-relay Files + +You can directly copy these files from c-relay: + +### 1. Dockerfile.alpine-musl +```bash +cp /path/to/c-relay/Dockerfile.alpine-musl ./Dockerfile.static +``` + +Then customize: +- Change binary name (line 125) +- Adjust source files (line 122-124) +- Modify include paths (line 120-121) + +### 2. build_static.sh +```bash +cp /path/to/c-relay/build_static.sh ./ +``` + +Then customize: +- Change `OUTPUT_NAME` variable (lines 66, 70) +- Update Docker image name (line 98) +- Modify verification commands (lines 180-184) + +### 3. .dockerignore (Optional) +```bash +cp /path/to/c-relay/.dockerignore ./ +``` + +Helps speed up Docker builds by excluding unnecessary files. + +## Best Practices + +1. **Version Control**: Commit your Dockerfile and build script +2. **Tag Builds**: Include git commit hash in binary version +3. **Test Thoroughly**: Verify on multiple distributions +4. **Document Dependencies**: List required NIPs and libraries +5. **Automate**: Use CI/CD to build on every commit +6. **Archive Binaries**: Keep old versions for rollback + +## Performance Comparison + +| Metric | MUSL Static | glibc Dynamic | +|--------|-------------|---------------| +| Binary Size | 7-10 MB | 2-3 MB + libs | +| Startup Time | ~50ms | ~40ms | +| Memory Usage | Similar | Similar | +| Portability | ✓ Universal | ✗ System-dependent | +| Deployment | Single file | Binary + libraries | + +## References + +- [MUSL libc](https://musl.libc.org/) +- [Alpine Linux](https://alpinelinux.org/) +- [nostr_core_lib](https://github.com/chebizarro/nostr_core_lib) +- [Static Linking Best Practices](https://www.musl-libc.org/faq.html) +- [c-relay Implementation](./docs/musl_static_build.md) + +## Example: Minimal Nostr Client + +Here's a complete example of building a minimal Nostr client: + +```c +// minimal_client.c +#include "nostr_core/nostr_core.h" +#include + +int main() { + // Generate keypair + char nsec[64], npub[64]; + nostr_generate_keypair(nsec, npub); + + printf("Generated keypair:\n"); + printf("Private key (nsec): %s\n", nsec); + printf("Public key (npub): %s\n", npub); + + // Create event + cJSON *event = nostr_create_event(1, "Hello, Nostr!", NULL); + nostr_sign_event(event, nsec); + + char *json = cJSON_Print(event); + printf("\nSigned event:\n%s\n", json); + + free(json); + cJSON_Delete(event); + return 0; +} +``` + +**Dockerfile.static:** +```dockerfile +FROM alpine:3.19 AS builder +RUN apk add --no-cache build-base musl-dev git autoconf automake libtool \ + openssl-dev openssl-libs-static zlib-dev zlib-static + +WORKDIR /build + +# Build secp256k1 +RUN cd /tmp && git clone https://github.com/bitcoin-core/secp256k1.git && \ + cd secp256k1 && ./autogen.sh && \ + ./configure --enable-static --disable-shared --prefix=/usr CFLAGS="-fPIC" && \ + make -j$(nproc) && make install + +# Copy and build nostr_core_lib +COPY nostr_core_lib /build/nostr_core_lib/ +RUN cd nostr_core_lib && \ + sed -i 's/CFLAGS="-Wall/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall/' build.sh && \ + ./build.sh --nips=1,6,19 + +# Build application +COPY minimal_client.c /build/ +RUN gcc -static -O2 -Wall -std=c99 \ + -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ + -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson \ + minimal_client.c -o /build/minimal_client_static \ + nostr_core_lib/libnostr_core_x64.a \ + -lssl -lcrypto -lsecp256k1 -lz -lpthread -lm -ldl && \ + strip /build/minimal_client_static + +FROM scratch +COPY --from=builder /build/minimal_client_static /minimal_client_static +``` + +**Build and run:** +```bash +docker build -f Dockerfile.static -t minimal-client . +docker create --name temp minimal-client +docker cp temp:/minimal_client_static ./ +docker rm temp + +./minimal_client_static +``` + +## Conclusion + +Static MUSL binaries provide the best portability for C applications. While they're slightly larger than dynamic binaries, the benefits of zero dependencies and universal compatibility make them ideal for: + +- Server deployments across different Linux distributions +- Embedded systems and IoT devices +- Docker containers (FROM scratch) +- Distribution to users without dependency management +- Long-term archival and reproducibility + +Follow this guide to create portable, self-contained binaries for your Nostr applications! \ No newline at end of file diff --git a/build/bud04.o b/build/bud04.o index 57da534..1c8d1e5 100644 Binary files a/build/bud04.o and b/build/bud04.o differ diff --git a/build/bud08.o b/build/bud08.o index fd1ed03..bdfea11 100644 Binary files a/build/bud08.o and b/build/bud08.o differ diff --git a/build/ginxsom-fcgi b/build/ginxsom-fcgi index 70c5508..43dde29 100755 Binary files a/build/ginxsom-fcgi and b/build/ginxsom-fcgi differ diff --git a/build/main.o b/build/main.o index f73f019..1057a48 100644 Binary files a/build/main.o and b/build/main.o differ diff --git a/config/local-nginx.conf b/config/local-nginx.conf index 5231a85..99753e5 100644 --- a/config/local-nginx.conf +++ b/config/local-nginx.conf @@ -351,10 +351,10 @@ http { autoindex_format json; } - # Root redirect + # Root redirect - Server info endpoint location = / { - return 200 "Ginxsom Blossom Server\nEndpoints: GET /, PUT /upload, GET /list/\nHealth: GET /health\n"; - add_header Content-Type text/plain; + return 200 '{\n "server": "ginxsom",\n "version": "1.0.0",\n "description": "Ginxsom Blossom Server",\n "endpoints": {\n "blob_get": "GET /",\n "blob_head": "HEAD /",\n "upload": "PUT /upload",\n "upload_requirements": "HEAD /upload",\n "list": "GET /list/",\n "delete": "DELETE /",\n "mirror": "PUT /mirror",\n "report": "PUT /report",\n "health": "GET /health"\n },\n "supported_buds": [\n "BUD-01",\n "BUD-02",\n "BUD-04",\n "BUD-06",\n "BUD-08",\n "BUD-09"\n ],\n "limits": {\n "max_upload_size": 104857600,\n "supported_mime_types": [\n "image/jpeg",\n "image/png",\n "image/webp",\n "image/gif",\n "video/mp4",\n "video/webm",\n "audio/mpeg",\n "audio/ogg",\n "text/plain",\n "application/pdf"\n ]\n },\n "authentication": {\n "required_for_upload": false,\n "required_for_delete": true,\n "required_for_list": false,\n "nip42_enabled": true\n }\n}'; + add_header Content-Type application/json; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always; add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always; @@ -683,10 +683,10 @@ http { autoindex_format json; } - # Root redirect + # Root redirect - Server info endpoint location = / { - return 200 "Ginxsom Blossom Server (HTTPS)\nEndpoints: GET /, PUT /upload, GET /list/\nHealth: GET /health\n"; - add_header Content-Type text/plain; + return 200 '{\n "server": "ginxsom",\n "version": "1.0.0",\n "description": "Ginxsom Blossom Server (HTTPS)",\n "endpoints": {\n "blob_get": "GET /",\n "blob_head": "HEAD /",\n "upload": "PUT /upload",\n "upload_requirements": "HEAD /upload",\n "list": "GET /list/",\n "delete": "DELETE /",\n "mirror": "PUT /mirror",\n "report": "PUT /report",\n "health": "GET /health"\n },\n "supported_buds": [\n "BUD-01",\n "BUD-02",\n "BUD-04",\n "BUD-06",\n "BUD-08",\n "BUD-09"\n ],\n "limits": {\n "max_upload_size": 104857600,\n "supported_mime_types": [\n "image/jpeg",\n "image/png",\n "image/webp",\n "image/gif",\n "video/mp4",\n "video/webm",\n "audio/mpeg",\n "audio/ogg",\n "text/plain",\n "application/pdf"\n ]\n },\n "authentication": {\n "required_for_upload": false,\n "required_for_delete": true,\n "required_for_list": false,\n "nip42_enabled": true\n }\n}'; + add_header Content-Type application/json; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always; add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always; diff --git a/db/ginxsom.db b/db/ginxsom.db index 3354f30..903f13d 100644 Binary files a/db/ginxsom.db and b/db/ginxsom.db differ diff --git a/deploy_lt.sh b/deploy_lt.sh new file mode 100755 index 0000000..47912d2 --- /dev/null +++ b/deploy_lt.sh @@ -0,0 +1,285 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_status() { echo -e "${BLUE}[INFO]${NC} $1"; } +print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } +print_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# Configuration +REMOTE_HOST="laantungir.net" +REMOTE_USER="ubuntu" +REMOTE_DIR="/home/ubuntu/ginxsom" +REMOTE_DB_PATH="/home/ubuntu/ginxsom/db/ginxsom.db" +REMOTE_NGINX_CONFIG="/etc/nginx/conf.d/default.conf" +REMOTE_BINARY_PATH="/home/ubuntu/ginxsom/ginxsom.fcgi" +REMOTE_SOCKET="/tmp/ginxsom-fcgi.sock" +REMOTE_DATA_DIR="/var/www/html/blossom" + +print_status "Starting deployment to $REMOTE_HOST..." + +# Step 1: Build and prepare local binary +print_status "Building ginxsom binary..." +make clean && make +if [[ ! -f "build/ginxsom-fcgi" ]]; then + print_error "Build failed - binary not found" + exit 1 +fi +print_success "Binary built successfully" + +# Step 2: Setup remote environment first (before copying files) +print_status "Setting up remote environment..." +ssh $REMOTE_USER@$REMOTE_HOST << 'EOF' + set -e + + # Create data directory if it doesn't exist (using existing /var/www/html/blossom) + sudo mkdir -p /var/www/html/blossom + sudo chown www-data:www-data /var/www/html/blossom + sudo chmod 755 /var/www/html/blossom + + # Ensure socket directory exists + sudo mkdir -p /tmp + sudo chmod 755 /tmp + + # Install required dependencies + echo "Installing required dependencies..." + sudo apt-get update + sudo apt-get install -y spawn-fcgi libfcgi-dev + + # Stop any existing ginxsom processes + echo "Stopping existing ginxsom processes..." + sudo pkill -f ginxsom-fcgi || true + sudo rm -f /tmp/ginxsom-fcgi.sock || true + + echo "Remote environment setup complete" +EOF + +print_success "Remote environment configured" + +# Step 3: Copy files to remote server +print_status "Copying files to remote server..." + +# Copy entire project directory (excluding unnecessary files) +print_status "Copying entire ginxsom project..." +rsync -avz --exclude='.git' --exclude='build' --exclude='logs' --exclude='Trash' --exclude='blobs' --exclude='db/ginxsom.db' --no-g --no-o --no-perms --omit-dir-times . $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/ + +# Build on remote server to ensure compatibility +print_status "Building ginxsom on remote server..." +ssh $REMOTE_USER@$REMOTE_HOST "cd $REMOTE_DIR && make clean && make" || { + print_error "Build failed on remote server" + print_status "Checking what packages are actually installed..." + ssh $REMOTE_USER@$REMOTE_HOST "dpkg -l | grep -E '(sqlite|fcgi)'" + exit 1 +} + +# Copy binary to application directory +print_status "Copying ginxsom binary to application directory..." +ssh $REMOTE_USER@$REMOTE_HOST << EOF + # Stop any running process first + sudo pkill -f ginxsom-fcgi || true + sleep 1 + + # Remove old binary if it exists + rm -f $REMOTE_BINARY_PATH + + # Copy new binary + cp $REMOTE_DIR/build/ginxsom-fcgi $REMOTE_BINARY_PATH + chmod +x $REMOTE_BINARY_PATH + chown ubuntu:ubuntu $REMOTE_BINARY_PATH + + echo "Binary copied successfully" +EOF + +# Skip nginx config update - user prefers manual control +print_status "Skipping nginx configuration update (manual control preferred)" + +print_success "Files copied to remote server" + +# Step 3: Setup remote environment +print_status "Setting up remote environment..." + +ssh $REMOTE_USER@$REMOTE_HOST << 'EOF' + set -e + + # Create data directory if it doesn't exist (using existing /var/www/html/blossom) + sudo mkdir -p /var/www/html/blossom + sudo chown www-data:www-data /var/www/html/blossom + sudo chmod 755 /var/www/html/blossom + + # Ensure socket directory exists + sudo mkdir -p /tmp + sudo chmod 755 /tmp + + # Install required dependencies + echo "Installing required dependencies..." + sudo apt-get update 2>/dev/null || true # Continue even if apt update has issues + sudo apt-get install -y spawn-fcgi libfcgi-dev libsqlite3-dev sqlite3 libcurl4-openssl-dev + + # Verify installations + echo "Verifying installations..." + if ! dpkg -l libsqlite3-dev >/dev/null 2>&1; then + echo "libsqlite3-dev not found, trying alternative..." + sudo apt-get install -y libsqlite3-dev || { + echo "Failed to install libsqlite3-dev" + exit 1 + } + fi + if ! dpkg -l libfcgi-dev >/dev/null 2>&1; then + echo "libfcgi-dev not found" + exit 1 + fi + + # Check if sqlite3.h exists + if [ ! -f /usr/include/sqlite3.h ]; then + echo "sqlite3.h not found in /usr/include/" + find /usr -name "sqlite3.h" 2>/dev/null || echo "sqlite3.h not found anywhere" + exit 1 + fi + + # Stop any existing ginxsom processes + echo "Stopping existing ginxsom processes..." + sudo pkill -f ginxsom-fcgi || true + sudo rm -f /tmp/ginxsom-fcgi.sock || true + + echo "Remote environment setup complete" +EOF + +print_success "Remote environment configured" + +# Step 4: Setup database directory and migrate database +print_status "Setting up database directory..." + +ssh $REMOTE_USER@$REMOTE_HOST << EOF + # Create db directory if it doesn't exist + mkdir -p $REMOTE_DIR/db + + # Backup current database if it exists in old location + if [ -f /var/www/html/blossom/ginxsom.db ]; then + echo "Backing up existing database..." + cp /var/www/html/blossom/ginxsom.db /var/www/html/blossom/ginxsom.db.backup.\$(date +%Y%m%d_%H%M%S) + + # Migrate database to new location if not already there + if [ ! -f $REMOTE_DB_PATH ]; then + echo "Migrating database to new location..." + cp /var/www/html/blossom/ginxsom.db $REMOTE_DB_PATH + else + echo "Database already exists at new location" + fi + elif [ ! -f $REMOTE_DB_PATH ]; then + echo "No existing database found - will be created on first run" + else + echo "Database already exists at $REMOTE_DB_PATH" + fi + + # Set proper permissions - www-data needs write access to db directory for SQLite journal files + sudo chown -R www-data:www-data $REMOTE_DIR/db + sudo chmod 755 $REMOTE_DIR/db + sudo chmod 644 $REMOTE_DB_PATH 2>/dev/null || true + + # Allow www-data to access the application directory for spawn-fcgi chdir + chmod 755 $REMOTE_DIR + + echo "Database directory setup complete" +EOF + +print_success "Database directory configured" + +# Step 5: Start ginxsom FastCGI process +print_status "Starting ginxsom FastCGI process..." + +ssh $REMOTE_USER@$REMOTE_HOST << EOF + # Clean up any existing socket + sudo rm -f $REMOTE_SOCKET + + # Start FastCGI process with explicit paths + echo "Starting ginxsom FastCGI with configuration:" + echo " Working directory: $REMOTE_DIR" + echo " Binary: $REMOTE_BINARY_PATH" + echo " Database: $REMOTE_DB_PATH" + echo " Storage: $REMOTE_DATA_DIR" + + sudo spawn-fcgi -M 666 -u www-data -g www-data -s $REMOTE_SOCKET -U www-data -G www-data -d $REMOTE_DIR -- $REMOTE_BINARY_PATH --db-path "$REMOTE_DB_PATH" --storage-dir "$REMOTE_DATA_DIR" + + # Give it a moment to start + sleep 2 + + # Verify process is running + if pgrep -f "ginxsom-fcgi" > /dev/null; then + echo "FastCGI process started successfully" + echo "PID: \$(pgrep -f ginxsom-fcgi)" + else + echo "Process not found by pgrep, but socket exists - this may be normal for FastCGI" + echo "Checking socket..." + ls -la $REMOTE_SOCKET + echo "Checking if binary exists and is executable..." + ls -la $REMOTE_BINARY_PATH + echo "Testing if we can connect to the socket..." + # Try to test the FastCGI connection + if command -v cgi-fcgi >/dev/null 2>&1; then + echo "Testing FastCGI connection..." + SCRIPT_NAME=/health SCRIPT_FILENAME=$REMOTE_BINARY_PATH REQUEST_METHOD=GET cgi-fcgi -bind -connect $REMOTE_SOCKET 2>/dev/null | head -5 || echo "Connection test failed" + else + echo "cgi-fcgi not available for testing" + fi + # Don't exit - the socket existing means spawn-fcgi worked + fi +EOF + +if [ $? -eq 0 ]; then + print_success "FastCGI process started" +else + print_error "Failed to start FastCGI process" + exit 1 +fi + +# Step 6: Test nginx configuration and reload +print_status "Testing and reloading nginx..." + +ssh $REMOTE_USER@$REMOTE_HOST << 'EOF' + # Test nginx configuration + if sudo nginx -t; then + echo "Nginx configuration test passed" + sudo nginx -s reload + echo "Nginx reloaded successfully" + else + echo "Nginx configuration test failed" + exit 1 + fi +EOF + +print_success "Nginx reloaded" + +# Step 7: Test deployment +print_status "Testing deployment..." + +# Test health endpoint +echo "Testing health endpoint..." +if curl -k -s --max-time 10 "https://blossom.laantungir.net/health" | grep -q "OK"; then + print_success "Health check passed" +else + print_warning "Health check failed - checking response..." + curl -k -v --max-time 10 "https://blossom.laantungir.net/health" 2>&1 | head -10 +fi + +# Test basic endpoints +echo "Testing root endpoint..." +if curl -k -s --max-time 10 "https://blossom.laantungir.net/" | grep -q "Ginxsom"; then + print_success "Root endpoint responding" +else + print_warning "Root endpoint not responding as expected - checking response..." + curl -k -v --max-time 10 "https://blossom.laantungir.net/" 2>&1 | head -10 +fi + +print_success "Deployment to $REMOTE_HOST completed!" +print_status "Ginxsom should now be available at: https://blossom.laantungir.net" +print_status "Test endpoints:" +echo " Health: curl -k https://blossom.laantungir.net/health" +echo " Root: curl -k https://blossom.laantungir.net/" +echo " List: curl -k https://blossom.laantungir.net/list" \ No newline at end of file diff --git a/docs/PRODUCTION_MIGRATION_PLAN.md b/docs/PRODUCTION_MIGRATION_PLAN.md new file mode 100644 index 0000000..31694bd --- /dev/null +++ b/docs/PRODUCTION_MIGRATION_PLAN.md @@ -0,0 +1,356 @@ +# Production Directory Structure Migration Plan + +## Overview + +This document outlines the plan to migrate the ginxsom production deployment from the current configuration to a new, more organized directory structure. + +## Current Configuration (As-Is) + +``` +Binary Location: /var/www/html/blossom/ginxsom.fcgi +Database Location: /var/www/html/blossom/ginxsom.db +Data Directory: /var/www/html/blossom/ +Working Directory: /var/www/html/blossom/ (set via spawn-fcgi -d) +Socket: /tmp/ginxsom-fcgi.sock +``` + +**Issues with Current Setup:** +1. Binary and database mixed with data files in web-accessible directory +2. Database path hardcoded as relative path `db/ginxsom.db` but database is at root of working directory +3. No separation between application files and user data +4. Security concern: application files in web root + +## Target Configuration (To-Be) + +``` +Binary Location: /home/ubuntu/ginxsom/ginxsom.fcgi +Database Location: /home/ubuntu/ginxsom/db/ginxsom.db +Data Directory: /var/www/html/blossom/ +Working Directory: /home/ubuntu/ginxsom/ (set via spawn-fcgi -d) +Socket: /tmp/ginxsom-fcgi.sock +``` + +**Benefits of New Setup:** +1. Application files separated from user data +2. Database in proper subdirectory structure +3. Application files outside web root (better security) +4. Clear separation of concerns +5. Easier backup and maintenance + +## Directory Structure + +### Application Directory: `/home/ubuntu/ginxsom/` +``` +/home/ubuntu/ginxsom/ +├── ginxsom.fcgi # FastCGI binary +├── db/ +│ └── ginxsom.db # SQLite database +├── build/ # Build artifacts (from rsync) +├── src/ # Source code (from rsync) +├── include/ # Headers (from rsync) +├── config/ # Config files (from rsync) +└── scripts/ # Utility scripts (from rsync) +``` + +### Data Directory: `/var/www/html/blossom/` +``` +/var/www/html/blossom/ +├── .jpg # User uploaded files +├── .png +├── .mp4 +└── ... +``` + +## Command-Line Arguments + +The ginxsom binary supports these arguments (from [`src/main.c`](src/main.c:1488-1509)): + +```bash +--db-path PATH # Database file path (default: db/ginxsom.db) +--storage-dir DIR # Storage directory for files (default: .) +--help, -h # Show help message +``` + +## Migration Steps + +### 1. Update deploy_lt.sh Configuration + +Update the configuration variables in [`deploy_lt.sh`](deploy_lt.sh:16-23): + +```bash +# Configuration +REMOTE_HOST="laantungir.net" +REMOTE_USER="ubuntu" +REMOTE_DIR="/home/ubuntu/ginxsom" +REMOTE_DB_PATH="/home/ubuntu/ginxsom/db/ginxsom.db" +REMOTE_NGINX_CONFIG="/etc/nginx/conf.d/default.conf" +REMOTE_BINARY_PATH="/home/ubuntu/ginxsom/ginxsom.fcgi" +REMOTE_SOCKET="/tmp/ginxsom-fcgi.sock" +REMOTE_DATA_DIR="/var/www/html/blossom" +``` + +### 2. Update Binary Deployment + +Modify the binary copy section (lines 82-97) to use new path: + +```bash +# Copy binary to application directory (not web directory) +print_status "Copying ginxsom binary to application directory..." +ssh $REMOTE_USER@$REMOTE_HOST << EOF + # Stop any running process first + sudo pkill -f ginxsom-fcgi || true + sleep 1 + + # Remove old binary if it exists + rm -f $REMOTE_BINARY_PATH + + # Copy new binary + cp $REMOTE_DIR/build/ginxsom-fcgi $REMOTE_BINARY_PATH + chmod +x $REMOTE_BINARY_PATH + chown ubuntu:ubuntu $REMOTE_BINARY_PATH + + echo "Binary copied successfully" +EOF +``` + +### 3. Create Database Directory Structure + +Add database setup before starting FastCGI: + +```bash +# Setup database directory +print_status "Setting up database directory..." +ssh $REMOTE_USER@$REMOTE_HOST << EOF + # Create db directory if it doesn't exist + mkdir -p $REMOTE_DIR/db + + # Copy database if it exists in old location + if [ -f /var/www/html/blossom/ginxsom.db ]; then + echo "Migrating database from old location..." + cp /var/www/html/blossom/ginxsom.db $REMOTE_DB_PATH + elif [ ! -f $REMOTE_DB_PATH ]; then + echo "Initializing new database..." + # Database will be created by application on first run + fi + + # Set proper permissions + chown -R ubuntu:ubuntu $REMOTE_DIR/db + chmod 755 $REMOTE_DIR/db + chmod 644 $REMOTE_DB_PATH 2>/dev/null || true + + echo "Database directory setup complete" +EOF +``` + +### 4. Update spawn-fcgi Command + +Modify the FastCGI startup (line 164) to include command-line arguments: + +```bash +# Start FastCGI process with explicit paths +echo "Starting ginxsom FastCGI..." +sudo spawn-fcgi \ + -M 666 \ + -u www-data \ + -g www-data \ + -s $REMOTE_SOCKET \ + -U www-data \ + -G www-data \ + -d $REMOTE_DIR \ + -- $REMOTE_BINARY_PATH \ + --db-path "$REMOTE_DB_PATH" \ + --storage-dir "$REMOTE_DATA_DIR" +``` + +**Key Changes:** +- `-d $REMOTE_DIR`: Sets working directory to `/home/ubuntu/ginxsom/` +- `--db-path "$REMOTE_DB_PATH"`: Explicit database path +- `--storage-dir "$REMOTE_DATA_DIR"`: Explicit data directory + +### 5. Verify Permissions + +Ensure proper permissions for all directories: + +```bash +# Application directory - owned by ubuntu +sudo chown -R ubuntu:ubuntu /home/ubuntu/ginxsom +sudo chmod 755 /home/ubuntu/ginxsom +sudo chmod +x /home/ubuntu/ginxsom/ginxsom.fcgi + +# Database directory - readable by www-data +sudo chmod 755 /home/ubuntu/ginxsom/db +sudo chmod 644 /home/ubuntu/ginxsom/db/ginxsom.db + +# Data directory - writable by www-data +sudo chown -R www-data:www-data /var/www/html/blossom +sudo chmod 755 /var/www/html/blossom +``` + +## Path Resolution Logic + +### How Paths Work with spawn-fcgi -d Option + +When spawn-fcgi starts the FastCGI process: + +1. **Working Directory**: Set to `/home/ubuntu/ginxsom/` via `-d` option +2. **Relative Paths**: Resolved from working directory +3. **Absolute Paths**: Used as-is + +### Default Behavior (Without Arguments) + +From [`src/main.c`](src/main.c:30-31): +```c +char g_db_path[MAX_PATH_LEN] = "db/ginxsom.db"; // Relative to working dir +char g_storage_dir[MAX_PATH_LEN] = "."; // Current working dir +``` + +With working directory `/home/ubuntu/ginxsom/`: +- Database: `/home/ubuntu/ginxsom/db/ginxsom.db` ✓ +- Storage: `/home/ubuntu/ginxsom/` ✗ (wrong - we want `/var/www/html/blossom/`) + +### With Command-Line Arguments + +```bash +--db-path "/home/ubuntu/ginxsom/db/ginxsom.db" +--storage-dir "/var/www/html/blossom" +``` + +Result: +- Database: `/home/ubuntu/ginxsom/db/ginxsom.db` ✓ +- Storage: `/var/www/html/blossom/` ✓ + +## Testing Plan + +### 1. Pre-Migration Verification +```bash +# Check current setup +ssh ubuntu@laantungir.net " + echo 'Current binary location:' + ls -la /var/www/html/blossom/ginxsom.fcgi + + echo 'Current database location:' + ls -la /var/www/html/blossom/ginxsom.db + + echo 'Current process:' + ps aux | grep ginxsom-fcgi | grep -v grep +" +``` + +### 2. Post-Migration Verification +```bash +# Check new setup +ssh ubuntu@laantungir.net " + echo 'New binary location:' + ls -la /home/ubuntu/ginxsom/ginxsom.fcgi + + echo 'New database location:' + ls -la /home/ubuntu/ginxsom/db/ginxsom.db + + echo 'Data directory:' + ls -la /var/www/html/blossom/ | head -10 + + echo 'Process working directory:' + sudo ls -la /proc/\$(pgrep -f ginxsom.fcgi)/cwd + + echo 'Process command line:' + ps aux | grep ginxsom-fcgi | grep -v grep +" +``` + +### 3. Functional Testing +```bash +# Test health endpoint +curl -k https://blossom.laantungir.net/health + +# Test file upload +./tests/file_put_production.sh + +# Test file retrieval +curl -k -I https://blossom.laantungir.net/ + +# Test list endpoint +curl -k https://blossom.laantungir.net/list/ +``` + +## Rollback Plan + +If migration fails: + +1. **Stop new process:** + ```bash + sudo pkill -f ginxsom-fcgi + ``` + +2. **Restore old binary location:** + ```bash + sudo cp /home/ubuntu/ginxsom/build/ginxsom-fcgi /var/www/html/blossom/ginxsom.fcgi + sudo chown www-data:www-data /var/www/html/blossom/ginxsom.fcgi + ``` + +3. **Restart with old configuration:** + ```bash + sudo spawn-fcgi -M 666 -u www-data -g www-data \ + -s /tmp/ginxsom-fcgi.sock \ + -U www-data -G www-data \ + -d /var/www/html/blossom \ + /var/www/html/blossom/ginxsom.fcgi + ``` + +## Additional Considerations + +### 1. Database Backup +Before migration, backup the current database: +```bash +ssh ubuntu@laantungir.net " + cp /var/www/html/blossom/ginxsom.db /var/www/html/blossom/ginxsom.db.backup +" +``` + +### 2. NIP-94 Origin Configuration +After migration, update [`src/bud08.c`](src/bud08.c) to return production domain: +```c +void nip94_get_origin(char *origin, size_t origin_size) { + snprintf(origin, origin_size, "https://blossom.laantungir.net"); +} +``` + +### 3. Monitoring +Monitor logs after migration: +```bash +# Application logs +ssh ubuntu@laantungir.net "sudo journalctl -u nginx -f" + +# FastCGI process +ssh ubuntu@laantungir.net "ps aux | grep ginxsom-fcgi" +``` + +## Success Criteria + +Migration is successful when: + +1. ✓ Binary running from `/home/ubuntu/ginxsom/ginxsom.fcgi` +2. ✓ Database accessible at `/home/ubuntu/ginxsom/db/ginxsom.db` +3. ✓ Files stored in `/var/www/html/blossom/` +4. ✓ Health endpoint returns 200 OK +5. ✓ File upload works correctly +6. ✓ File retrieval works correctly +7. ✓ Database queries succeed +8. ✓ No permission errors in logs + +## Timeline + +1. **Preparation**: Update deploy_lt.sh script (15 minutes) +2. **Backup**: Backup current database (5 minutes) +3. **Migration**: Run updated deployment script (10 minutes) +4. **Testing**: Verify all endpoints (15 minutes) +5. **Monitoring**: Watch for issues (30 minutes) + +**Total Estimated Time**: ~75 minutes + +## References + +- Current deployment script: [`deploy_lt.sh`](deploy_lt.sh) +- Main application: [`src/main.c`](src/main.c) +- Command-line parsing: [`src/main.c:1488-1509`](src/main.c:1488-1509) +- Global configuration: [`src/main.c:30-31`](src/main.c:30-31) +- Database operations: [`src/main.c:333-385`](src/main.c:333-385) \ No newline at end of file diff --git a/include/ginxsom.h b/include/ginxsom.h index 89a3153..898de87 100644 --- a/include/ginxsom.h +++ b/include/ginxsom.h @@ -33,6 +33,10 @@ #define DEFAULT_MAX_BLOBS_PER_USER 1000 #define DEFAULT_RATE_LIMIT 10 +/* Global configuration variables */ +extern char g_db_path[MAX_PATH_LEN]; +extern char g_storage_dir[MAX_PATH_LEN]; + /* Error codes */ typedef enum { GINXSOM_OK = 0, diff --git a/remote.nginx.config b/remote.nginx.config new file mode 100644 index 0000000..d58d213 --- /dev/null +++ b/remote.nginx.config @@ -0,0 +1,376 @@ +# FastCGI upstream configuration +upstream ginxsom_backend { + server unix:/tmp/ginxsom-fcgi.sock; +} + +# Main domains +server { + if ($host = laantungir.net) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + listen 80; + server_name laantungir.com www.laantungir.com laantungir.net www.laantungir.net laantungir.org www.laantungir.org; + + root /var/www/html; + index index.html index.htm; + + location / { + try_files $uri $uri/ =404; + } + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + error_page 404 /404.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/www/html; + } + + +} + +# Main domains HTTPS - using the main certificate +server { + listen 443 ssl; + server_name laantungir.com www.laantungir.com laantungir.net www.laantungir.net laantungir.org www.laantungir.org; + ssl_certificate /etc/letsencrypt/live/laantungir.net/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/laantungir.net/privkey.pem; # managed by Certbot + + root /var/www/html; + index index.html index.htm; + + location / { + try_files $uri $uri/ =404; + } + + error_page 404 /404.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/www/html; + } + +} + +# Blossom subdomains HTTP - redirect to HTTPS (keep for ACME) +server { + listen 80; + server_name blossom.laantungir.com blossom.laantungir.net blossom.laantungir.org; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$server_name$request_uri; + } +} + +# Blossom subdomains HTTPS - ginxsom FastCGI +server { + listen 443 ssl; + server_name blossom.laantungir.com blossom.laantungir.net blossom.laantungir.org; + + ssl_certificate /etc/letsencrypt/live/git.laantungir.net/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/git.laantungir.net/privkey.pem; + + # Security headers + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options DENY always; + add_header X-XSS-Protection "1; mode=block" always; + + # CORS for Blossom protocol + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always; + add_header Access-Control-Max-Age 86400 always; + + # Root directory for blob storage + root /var/www/html/blossom; + + # Maximum upload size + client_max_body_size 100M; + + # OPTIONS preflight handler + if ($request_method = OPTIONS) { + return 204; + } + + # PUT /upload - File uploads + location = /upload { + if ($request_method !~ ^(PUT|HEAD)$) { + return 405; + } + fastcgi_pass ginxsom_backend; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + } + + # GET /list/ - List user blobs + location ~ "^/list/([a-f0-9]{64})$" { + if ($request_method !~ ^(GET)$) { + return 405; + } + fastcgi_pass ginxsom_backend; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + } + + # PUT /mirror - Mirror content + location = /mirror { + if ($request_method !~ ^(PUT)$) { + return 405; + } + fastcgi_pass ginxsom_backend; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + } + + # PUT /report - Report content + location = /report { + if ($request_method !~ ^(PUT)$) { + return 405; + } + fastcgi_pass ginxsom_backend; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + } + + # GET /auth - NIP-42 challenges + location = /auth { + if ($request_method !~ ^(GET)$) { + return 405; + } + fastcgi_pass ginxsom_backend; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + } + + # Admin API + location /api/ { + if ($request_method !~ ^(GET|PUT)$) { + return 405; + } + fastcgi_pass ginxsom_backend; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + } + + # Blob serving - SHA256 patterns + location ~ "^/([a-f0-9]{64})(\.[a-zA-Z0-9]+)?$" { + # Handle DELETE via rewrite + if ($request_method = DELETE) { + rewrite ^/(.*)$ /fcgi-delete/$1 last; + } + + # Route HEAD to FastCGI + if ($request_method = HEAD) { + rewrite ^/(.*)$ /fcgi-head/$1 last; + } + + # GET requests - serve files directly + if ($request_method != GET) { + return 405; + } + + try_files /$1.txt /$1.jpg /$1.jpeg /$1.png /$1.webp /$1.gif /$1.pdf /$1.mp4 /$1.mp3 /$1.md =404; + + # Cache headers + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + # Internal FastCGI handlers + location ~ "^/fcgi-delete/([a-f0-9]{64}).*$" { + internal; + fastcgi_pass ginxsom_backend; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + fastcgi_param REQUEST_URI /$1; + } + + location ~ "^/fcgi-head/([a-f0-9]{64}).*$" { + internal; + fastcgi_pass ginxsom_backend; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + fastcgi_param REQUEST_URI /$1; + } + + # Health check + location /health { + access_log off; + return 200 "OK\n"; + add_header Content-Type text/plain; + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always; + add_header Access-Control-Max-Age 86400 always; + } + + # Default location - Server info endpoint + location / { + return 200 '{\n "server": "ginxsom",\n "version": "1.0.0",\n "description": "Ginxsom Blossom Server",\n "endpoints": {\n "blob_get": "GET /",\n "blob_head": "HEAD /",\n "upload": "PUT /upload",\n "upload_requirements": "HEAD /upload",\n "list": "GET /list/",\n "delete": "DELETE /",\n "mirror": "PUT /mirror",\n "report": "PUT /report",\n "health": "GET /health"\n },\n "supported_buds": [\n "BUD-01",\n "BUD-02",\n "BUD-04",\n "BUD-06",\n "BUD-08",\n "BUD-09"\n ],\n "limits": {\n "max_upload_size": 104857600,\n "supported_mime_types": [\n "image/jpeg",\n "image/png",\n "image/webp",\n "image/gif",\n "video/mp4",\n "video/webm",\n "audio/mpeg",\n "audio/ogg",\n "text/plain",\n "application/pdf"\n ]\n },\n "authentication": {\n "required_for_upload": false,\n "required_for_delete": true,\n "required_for_list": false,\n "nip42_enabled": true\n }\n}'; + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type, Content-Length, Accept, Origin, User-Agent, DNT, Cache-Control, X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since, *" always; + add_header Access-Control-Max-Age 86400 always; + } +} + +server { + listen 80; + server_name relay.laantungir.com relay.laantungir.net relay.laantungir.org; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + proxy_pass http://127.0.0.1:8888; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key; + proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_connect_timeout 60s; + proxy_buffering off; + proxy_request_buffering off; + gzip off; + } +} + + +# # Relay HTTPS - proxy to c-relay +server { + listen 443 ssl; + server_name relay.laantungir.com relay.laantungir.net relay.laantungir.org; + + ssl_certificate /etc/letsencrypt/live/blossom.laantungir.net/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/blossom.laantungir.net/privkey.pem; + + location / { + proxy_pass http://127.0.0.1:8888; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key; + proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_connect_timeout 60s; + proxy_buffering off; + proxy_request_buffering off; + gzip off; + } +} + + + + # Git subdomains HTTP - redirect to HTTPS + server { + listen 80; + server_name git.laantungir.com git.laantungir.net git.laantungir.org; + + # Allow larger file uploads for Git releases + client_max_body_size 50M; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$server_name$request_uri; + } + } + + # Auth subdomains HTTP - redirect to HTTPS + server { + listen 80; + server_name auth.laantungir.com auth.laantungir.net auth.laantungir.org; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + } + } + + # Git subdomains HTTPS - proxy to gitea + server { + listen 443 ssl; + server_name git.laantungir.com git.laantungir.net git.laantungir.org; + + # Allow larger file uploads for Git releases + client_max_body_size 50M; + + ssl_certificate /etc/letsencrypt/live/git.laantungir.net/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/git.laantungir.net/privkey.pem; + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_buffering off; + proxy_request_buffering off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_connect_timeout 60s; + gzip off; + # proxy_set_header Sec-WebSocket-Extensions ; + proxy_set_header Host $host; + proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key; + proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } + } + + # Auth subdomains HTTPS - proxy to nostr-auth + server { + listen 443 ssl; + server_name auth.laantungir.com auth.laantungir.net auth.laantungir.org; + + ssl_certificate /etc/letsencrypt/live/git.laantungir.net/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/git.laantungir.net/privkey.pem; + + location / { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_buffering off; + proxy_request_buffering off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_connect_timeout 60s; + gzip off; + # proxy_set_header Sec-WebSocket-Extensions ; + proxy_set_header Host $host; + proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key; + proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } + } diff --git a/restart-all.sh b/restart-all.sh index 65de34d..8f4fd3a 100755 --- a/restart-all.sh +++ b/restart-all.sh @@ -168,7 +168,7 @@ export GINX_DEBUG=1 # Start FastCGI application with proper logging (daemonized but with redirected streams) echo "FastCGI starting at $(date)" >> logs/app/stderr.log -spawn-fcgi -s "$SOCKET_PATH" -M 666 -u "$USER" -g "$USER" -f "$FCGI_BINARY" -P "$PID_FILE" 1>>logs/app/stdout.log 2>>logs/app/stderr.log +spawn-fcgi -s "$SOCKET_PATH" -M 666 -u "$USER" -g "$USER" -P "$PID_FILE" -- "$FCGI_BINARY" --storage-dir blobs 1>>logs/app/stdout.log 2>>logs/app/stderr.log if [ $? -eq 0 ] && [ -f "$PID_FILE" ]; then PID=$(cat "$PID_FILE") @@ -250,6 +250,8 @@ else fi echo -e "\n${GREEN}=== Restart sequence complete ===${NC}" -echo -e "${YELLOW}Server should be available at: http://localhost:9001${NC}" echo -e "${YELLOW}To stop all processes, run: nginx -p . -c $NGINX_CONFIG -s stop && kill \$(cat $PID_FILE 2>/dev/null)${NC}" -echo -e "${YELLOW}To monitor logs, check: logs/error.log, logs/access.log, and logs/fcgi-stderr.log${NC}" \ No newline at end of file +echo -e "${YELLOW}To monitor logs, check: logs/nginx/error.log, logs/nginx/access.log, logs/app/stderr.log, logs/app/stdout.log${NC}" +echo -e "\n${YELLOW}Server is available at:${NC}" +echo -e " ${GREEN}HTTP:${NC} http://localhost:9001" +echo -e " ${GREEN}HTTPS:${NC} https://localhost:9443" \ No newline at end of file diff --git a/src/bud04.c b/src/bud04.c index da24c18..5c13d19 100644 --- a/src/bud04.c +++ b/src/bud04.c @@ -426,9 +426,9 @@ void handle_mirror_request(void) { // Determine file extension from Content-Type using centralized mapping const char* extension = mime_to_extension(content_type_final); - // Save file to blobs directory + // Save file to storage directory using global g_storage_dir variable char filepath[512]; - snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension); + snprintf(filepath, sizeof(filepath), "%s/%s%s", g_storage_dir, sha256_hex, extension); FILE* outfile = fopen(filepath, "wb"); if (!outfile) { diff --git a/src/bud08.c b/src/bud08.c index 3be9603..a5ba3ac 100644 --- a/src/bud08.c +++ b/src/bud08.c @@ -24,7 +24,7 @@ int nip94_is_enabled(void) { return 1; // Default enabled on DB error } - const char* sql = "SELECT value FROM server_config WHERE key = 'nip94_enabled'"; + const char* sql = "SELECT value FROM config WHERE key = 'nip94_enabled'"; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc == SQLITE_OK) { rc = sqlite3_step(stmt); @@ -45,49 +45,52 @@ int nip94_get_origin(char* out, size_t out_size) { return 0; } - // Check if request came over HTTPS (nginx sets HTTPS=on for SSL requests) - const char* https_env = getenv("HTTPS"); - if (https_env && strcmp(https_env, "on") == 0) { - // HTTPS request - use HTTPS origin - strncpy(out, "https://localhost:9443", out_size - 1); - out[out_size - 1] = '\0'; - return 1; - } - - // HTTP request - check database config first, then use HTTP origin + // Check database config first for custom origin sqlite3* db; sqlite3_stmt* stmt; int rc; rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - // Default on DB error - use HTTP - strncpy(out, "http://localhost:9001", out_size - 1); - out[out_size - 1] = '\0'; + if (rc == SQLITE_OK) { + const char* sql = "SELECT value FROM config WHERE key = 'cdn_origin'"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char* value = (const char*)sqlite3_column_text(stmt, 0); + if (value) { + strncpy(out, value, out_size - 1); + out[out_size - 1] = '\0'; + sqlite3_finalize(stmt); + sqlite3_close(db); + return 1; + } + } + sqlite3_finalize(stmt); + } + sqlite3_close(db); + } + + // Check if request came over HTTPS (nginx sets HTTPS=on for SSL requests) + const char* https_env = getenv("HTTPS"); + const char* server_name = getenv("SERVER_NAME"); + + // Use production domain if SERVER_NAME is set and not localhost + if (server_name && strcmp(server_name, "localhost") != 0) { + if (https_env && strcmp(https_env, "on") == 0) { + snprintf(out, out_size, "https://%s", server_name); + } else { + snprintf(out, out_size, "http://%s", server_name); + } return 1; } - - const char* sql = "SELECT value FROM server_config WHERE key = 'cdn_origin'"; - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char* value = (const char*)sqlite3_column_text(stmt, 0); - if (value) { - strncpy(out, value, out_size - 1); - out[out_size - 1] = '\0'; - sqlite3_finalize(stmt); - sqlite3_close(db); - return 1; - } - } - sqlite3_finalize(stmt); + + // Fallback to localhost for development + if (https_env && strcmp(https_env, "on") == 0) { + strncpy(out, "https://localhost:9443", out_size - 1); + } else { + strncpy(out, "http://localhost:9001", out_size - 1); } - - sqlite3_close(db); - - // Default fallback - HTTP - strncpy(out, "http://localhost:9001", out_size - 1); out[out_size - 1] = '\0'; return 1; } diff --git a/src/ginxsom.h b/src/ginxsom.h index 9191df0..fa44b21 100644 --- a/src/ginxsom.h +++ b/src/ginxsom.h @@ -30,6 +30,10 @@ extern sqlite3* db; int init_database(void); void close_database(void); +// Global configuration variables (defined in main.c) +extern char g_db_path[4096]; +extern char g_storage_dir[4096]; + // SHA-256 extraction and validation const char* extract_sha256_from_uri(const char* uri); diff --git a/src/main.c b/src/main.c index d4a1424..0c63c03 100644 --- a/src/main.c +++ b/src/main.c @@ -7,6 +7,7 @@ #include "ginxsom.h" #include "../nostr_core_lib/nostr_core/nostr_common.h" #include "../nostr_core_lib/nostr_core/utils.h" +#include #include #include #include @@ -22,11 +23,15 @@ // Debug macros removed #define MAX_SHA256_LEN 65 -#define MAX_PATH_LEN 512 +#define MAX_PATH_LEN 4096 #define MAX_MIME_LEN 128 -// Database path -#define DB_PATH "db/ginxsom.db" +// Configuration variables - can be overridden via command line +char g_db_path[MAX_PATH_LEN] = "db/ginxsom.db"; +char g_storage_dir[MAX_PATH_LEN] = "."; + +// Use global configuration variables +#define DB_PATH g_db_path // Configuration system implementation @@ -35,22 +40,6 @@ #include #include -// ===== UNUSED CODE - SAFE TO REMOVE AFTER TESTING ===== -// Server configuration structure -/* -typedef struct { - char admin_pubkey[256]; - char admin_enabled[8]; - int config_loaded; -} server_config_t; - -// Global configuration instance -static server_config_t g_server_config = {0}; - -// Global server private key (stored in memory only for security) -static char server_private_key[128] = {0}; -*/ -// ===== END UNUSED CODE ===== // Function to get XDG config directory const char *get_config_dir(char *buffer, size_t buffer_size) { @@ -70,240 +59,6 @@ const char *get_config_dir(char *buffer, size_t buffer_size) { return ".config/ginxsom"; } -/* -// ===== UNUSED CODE - SAFE TO REMOVE AFTER TESTING ===== -// Load server configuration from database or create defaults -int initialize_server_config(void) { - sqlite3 *db = NULL; - sqlite3_stmt *stmt = NULL; - int rc; - - memset(&g_server_config, 0, sizeof(g_server_config)); - - // Open database - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc != SQLITE_OK) { - fprintf(stderr, "CONFIG: Could not open database for config: %s\n", - sqlite3_errmsg(db)); - // Config database doesn't exist - leave config uninitialized - g_server_config.config_loaded = 0; - return 0; - } - - // Load admin_pubkey - const char *sql = "SELECT value FROM config WHERE key = ?"; - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC); - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char *value = (const char *)sqlite3_column_text(stmt, 0); - if (value) { - strncpy(g_server_config.admin_pubkey, value, - sizeof(g_server_config.admin_pubkey) - 1); - } - } - sqlite3_finalize(stmt); - } - - // Load admin_enabled - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, "admin_enabled", -1, SQLITE_STATIC); - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char *value = (const char *)sqlite3_column_text(stmt, 0); - if (value && strcmp(value, "true") == 0) { - strcpy(g_server_config.admin_enabled, "true"); - } else { - strcpy(g_server_config.admin_enabled, "false"); - } - } - sqlite3_finalize(stmt); - } - - sqlite3_close(db); - - g_server_config.config_loaded = 1; - fprintf(stderr, "CONFIG: Server configuration loaded\n"); - return 1; -} -// ===== END UNUSED CODE ===== -*/ - -/* -// File-based configuration system -// Config file path resolution -int get_config_file_path(char *path, size_t path_size) { - const char *home = getenv("HOME"); - const char *xdg_config = getenv("XDG_CONFIG_HOME"); - - if (xdg_config) { - snprintf(path, path_size, "%s/ginxsom/ginxsom_config_event.json", - xdg_config); - } else if (home) { - snprintf(path, path_size, "%s/.config/ginxsom/ginxsom_config_event.json", - home); - } else { - return 0; - } - return 1; -} -*/ - -/* -// Load and validate config event -int load_server_config(const char *config_path) { - FILE *file = fopen(config_path, "r"); - if (!file) { - return 0; // Config file doesn't exist - } - - // Read entire file - fseek(file, 0, SEEK_END); - long file_size = ftell(file); - fseek(file, 0, SEEK_SET); - - char *json_data = malloc(file_size + 1); - if (!json_data) { - fclose(file); - return 0; - } - - fread(json_data, 1, file_size, file); - json_data[file_size] = '\0'; - fclose(file); - - // Parse and validate JSON event - cJSON *event = cJSON_Parse(json_data); - free(json_data); - - if (!event) { - fprintf(stderr, "Invalid JSON in config file\n"); - return 0; - } - - // Validate event structure and signature - if (nostr_validate_event(event) != NOSTR_SUCCESS) { - fprintf(stderr, "Invalid or corrupted config event\n"); - cJSON_Delete(event); - return 0; - } - - // Extract configuration and apply to server - int result = apply_config_from_event(event); - cJSON_Delete(event); - - return result; -} -*/ - -/* -// Extract config from validated event and apply to server -int apply_config_from_event(cJSON *event) { - sqlite3 *db; - sqlite3_stmt *stmt; - int rc; - - // Open database for config storage - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); - if (rc) { - fprintf(stderr, "Failed to open database for config\n"); - return 0; - } - - // Extract admin pubkey from event - cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey"); - if (!pubkey_json || !cJSON_IsString(pubkey_json)) { - sqlite3_close(db); - return 0; - } - const char *admin_pubkey = cJSON_GetStringValue(pubkey_json); - - // Store admin pubkey in database - const char *insert_sql = "INSERT OR REPLACE INTO config (key, value, " - "description) VALUES (?, ?, ?)"; - rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, admin_pubkey, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, "Admin public key from config event", -1, - SQLITE_STATIC); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - } - - // Extract server private key and store securely (in memory only) - cJSON *tags = cJSON_GetObjectItem(event, "tags"); - if (tags && cJSON_IsArray(tags)) { - cJSON *tag = NULL; - cJSON_ArrayForEach(tag, tags) { - if (!cJSON_IsArray(tag)) - continue; - - cJSON *tag_name = cJSON_GetArrayItem(tag, 0); - cJSON *tag_value = cJSON_GetArrayItem(tag, 1); - - if (!tag_name || !cJSON_IsString(tag_name) || !tag_value || - !cJSON_IsString(tag_value)) - continue; - - const char *key = cJSON_GetStringValue(tag_name); - const char *value = cJSON_GetStringValue(tag_value); - - if (strcmp(key, "server_privkey") == 0) { - // Store server private key in global variable (memory only) - // strncpy(server_private_key, value, sizeof(server_private_key) - 1); - // server_private_key[sizeof(server_private_key) - 1] = '\0'; - } else { - // Store other config values in database - rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, "From config event", -1, SQLITE_STATIC); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - } - } - } - } - - sqlite3_close(db); - return 1; -} -*/ - -/* -// Interactive setup runner -int run_interactive_setup(const char *config_path) { - printf("\n=== Ginxsom First-Time Setup Required ===\n"); - printf("No configuration found at: %s\n\n", config_path); - printf("Options:\n"); - printf("1. Run interactive setup wizard\n"); - printf("2. Exit and create config manually\n"); - printf("Choice (1/2): "); - - char choice[10]; - if (!fgets(choice, sizeof(choice), stdin)) { - return 1; - } - - if (choice[0] == '1') { - // Run setup script - char script_path[512]; - snprintf(script_path, sizeof(script_path), "./scripts/setup.sh \"%s\"", - config_path); - return system(script_path); - } else { - printf("\nManual setup instructions:\n"); - printf("1. Run: ./scripts/generate_config.sh\n"); - printf("2. Place signed config at: %s\n", config_path); - printf("3. Restart ginxsom\n"); - return 1; - } -} -*/ // Function declarations void handle_options_request(void); @@ -434,7 +189,23 @@ int file_exists_with_type(const char *sha256, const char *mime_type) { char filepath[MAX_PATH_LEN]; const char *extension = mime_to_extension(mime_type); - snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension); + // Construct path safely + size_t dir_len = strlen(g_storage_dir); + size_t sha_len = strlen(sha256); + size_t ext_len = strlen(extension); + 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); + return 0; + } + + // Build path manually to avoid compiler warnings + memcpy(filepath, g_storage_dir, dir_len); + filepath[dir_len] = '/'; + memcpy(filepath + dir_len + 1, sha256, sha_len); + memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len); + filepath[total_len - 1] = '\0'; struct stat st; int result = stat(filepath, &st); @@ -524,18 +295,6 @@ const char *extract_sha256_from_uri(const char *uri) { return sha256_buffer; } -///////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////// -// BUD 02 - Upload & Authentication -///////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////// - - -///////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////// -// AUTHENTICATION RULES SYSTEM (4.1.2) -///////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// @@ -911,7 +670,23 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res } char filepath[MAX_PATH_LEN]; - snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension); + // Construct path safely + size_t dir_len = strlen(g_storage_dir); + size_t sha_len = strlen(sha256); + size_t ext_len = strlen(extension); + 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); + // Continue anyway - unlink will fail gracefully + } else { + // Build path manually to avoid compiler warnings + memcpy(filepath, g_storage_dir, dir_len); + filepath[dir_len] = '/'; + memcpy(filepath + dir_len + 1, sha256, sha_len); + memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len); + filepath[total_len - 1] = '\0'; + } // Delete the physical file if (unlink(filepath) != 0) { @@ -1029,9 +804,29 @@ void handle_upload_request(void) { // Determine file extension from Content-Type using centralized mapping const char *extension = mime_to_extension(content_type); - // Save file to blobs directory with SHA-256 + extension + // Save file to storage directory with SHA-256 + extension char filepath[MAX_PATH_LEN]; - snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension); + // Construct path safely + size_t dir_len = strlen(g_storage_dir); + size_t sha_len = strlen(sha256_hex); + size_t ext_len = strlen(extension); + 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); + printf("Status: 500 Internal Server Error\r\n"); + + printf("Content-Type: text/plain\r\n\r\n"); + printf("File path too long\n"); + return; + } + + // Build path manually to avoid compiler warnings + memcpy(filepath, g_storage_dir, dir_len); + filepath[dir_len] = '/'; + memcpy(filepath + dir_len + 1, sha256_hex, sha_len); + memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len); + filepath[total_len - 1] = '\0'; FILE *outfile = fopen(filepath, "wb"); if (!outfile) { @@ -1280,9 +1075,29 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re // Determine file extension from Content-Type using centralized mapping const char *extension = mime_to_extension(content_type); - // Save file to blobs directory with SHA-256 + extension + // Save file to storage directory with SHA-256 + extension char filepath[MAX_PATH_LEN]; - snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension); + // Construct path safely + size_t dir_len = strlen(g_storage_dir); + size_t sha_len = strlen(sha256_hex); + size_t ext_len = strlen(extension); + 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); + printf("Status: 500 Internal Server Error\r\n"); + + printf("Content-Type: text/plain\r\n\r\n"); + printf("File path too long\n"); + return; + } + + // Build path manually to avoid compiler warnings + memcpy(filepath, g_storage_dir, dir_len); + filepath[dir_len] = '/'; + memcpy(filepath + dir_len + 1, sha256_hex, sha_len); + memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len); + filepath[total_len - 1] = '\0'; FILE *outfile = fopen(filepath, "wb"); if (!outfile) { @@ -1480,7 +1295,31 @@ void handle_auth_challenge_request(void) { ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// -int main(void) { +int main(int argc, char *argv[]) { + // Parse command line arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--db-path") == 0 && i + 1 < argc) { + strncpy(g_db_path, argv[i + 1], sizeof(g_db_path) - 1); + i++; // Skip next argument + } else if (strcmp(argv[i], "--storage-dir") == 0 && i + 1 < argc) { + strncpy(g_storage_dir, argv[i + 1], sizeof(g_storage_dir) - 1); + i++; // Skip next argument + } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + printf("Usage: %s [options]\n", argv[0]); + printf("Options:\n"); + printf(" --db-path PATH Database file path (default: db/ginxsom.db)\n"); + printf(" --storage-dir DIR Storage directory for files (default: blobs)\n"); + printf(" --help, -h Show this help message\n"); + return 0; + } else { + fprintf(stderr, "Unknown option: %s\n", argv[i]); + fprintf(stderr, "Use --help for usage information\n"); + return 1; + } + } + + fprintf(stderr, "STARTUP: Using database path: %s\n", g_db_path); + fprintf(stderr, "STARTUP: Using storage directory: %s\n", g_storage_dir); // Initialize server configuration and identity // Try file-based config first, then fall back to database config diff --git a/test_blob_1762770636.txt b/test_blob_1762770636.txt new file mode 100644 index 0000000..b80e07b --- /dev/null +++ b/test_blob_1762770636.txt @@ -0,0 +1,7 @@ +Test blob content for Ginxsom Blossom server (PRODUCTION) +Timestamp: 2025-11-10T06:30:36-04:00 +Random data: bb7d8d5206aadf4ecd48829f9674454c160bae68e98c6ce5f7f678f997dbe86a +Test message: Hello from production test! + +This file is used to test the upload functionality +of the Ginxsom Blossom server on blossom.laantungir.net diff --git a/tests/file_put_bud02.sh b/tests/file_put_bud02.sh index 1ac1a79..31a2fe7 100755 --- a/tests/file_put_bud02.sh +++ b/tests/file_put_bud02.sh @@ -6,7 +6,8 @@ set -e # Exit on any error # Configuration -SERVER_URL="http://localhost:9001" +# SERVER_URL="http://localhost:9001" +SERVER_URL="https://localhost:9443" UPLOAD_ENDPOINT="${SERVER_URL}/upload" TEST_FILE="test_blob_$(date +%s).txt" CLEANUP_FILES=() @@ -87,7 +88,7 @@ check_prerequisites() { check_server() { log_info "Checking if server is running..." - if curl -s -f "${SERVER_URL}/health" > /dev/null 2>&1; then + if curl -k -s -f "${SERVER_URL}/health" > /dev/null 2>&1; then log_success "Server is running at ${SERVER_URL}" else log_error "Server is not responding at ${SERVER_URL}" @@ -168,7 +169,7 @@ perform_upload() { CLEANUP_FILES+=("${RESPONSE_FILE}") # Perform the upload with verbose output - HTTP_STATUS=$(curl -s -w "%{http_code}" \ + HTTP_STATUS=$(curl -k -s -w "%{http_code}" \ -X PUT \ -H "Authorization: ${AUTH_HEADER}" \ -H "Content-Type: text/plain" \ @@ -217,7 +218,7 @@ test_retrieval() { RETRIEVAL_URL="${SERVER_URL}/${HASH}" - if curl -s -f "${RETRIEVAL_URL}" > /dev/null 2>&1; then + if curl -k -s -f "${RETRIEVAL_URL}" > /dev/null 2>&1; then log_success "File can be retrieved at: ${RETRIEVAL_URL}" else log_warning "File not yet available for retrieval (expected if upload processing not implemented)" diff --git a/tests/file_put_production.sh b/tests/file_put_production.sh new file mode 100755 index 0000000..010ca65 --- /dev/null +++ b/tests/file_put_production.sh @@ -0,0 +1,265 @@ +#!/bin/bash + +# file_put_production.sh - Test script for production Ginxsom Blossom server +# Tests upload functionality on blossom.laantungir.net + +set -e # Exit on any error + +# Configuration +SERVER_URL="https://blossom.laantungir.net" +UPLOAD_ENDPOINT="${SERVER_URL}/upload" +TEST_FILE="test_blob_$(date +%s).txt" +CLEANUP_FILES=() + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Cleanup function +cleanup() { + echo -e "${YELLOW}Cleaning up temporary files...${NC}" + for file in "${CLEANUP_FILES[@]}"; do + if [[ -f "$file" ]]; then + rm -f "$file" + echo "Removed: $file" + fi + done +} + +# Set up cleanup on exit +trap cleanup EXIT + +# Helper functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +# Check prerequisites +check_prerequisites() { + log_info "Checking prerequisites..." + + # Check if nak is installed + if ! command -v nak &> /dev/null; then + log_error "nak command not found. Please install nak first." + log_info "Install with: go install github.com/fiatjaf/nak@latest" + exit 1 + fi + log_success "nak is installed" + + # Check if curl is available + if ! command -v curl &> /dev/null; then + log_error "curl command not found. Please install curl." + exit 1 + fi + log_success "curl is available" + + # Check if sha256sum is available + if ! command -v sha256sum &> /dev/null; then + log_error "sha256sum command not found." + exit 1 + fi + log_success "sha256sum is available" + + # Check if base64 is available + if ! command -v base64 &> /dev/null; then + log_error "base64 command not found." + exit 1 + fi + log_success "base64 is available" +} + +# Check if server is running +check_server() { + log_info "Checking if server is running..." + + if curl -s -f "${SERVER_URL}/health" > /dev/null 2>&1; then + log_success "Server is running at ${SERVER_URL}" + else + log_error "Server is not responding at ${SERVER_URL}" + exit 1 + fi +} + +# Create test file +create_test_file() { + log_info "Creating test file: ${TEST_FILE}" + + # Create test content with timestamp and random data + cat > "${TEST_FILE}" << EOF +Test blob content for Ginxsom Blossom server (PRODUCTION) +Timestamp: $(date -Iseconds) +Random data: $(openssl rand -hex 32) +Test message: Hello from production test! + +This file is used to test the upload functionality +of the Ginxsom Blossom server on blossom.laantungir.net +EOF + + CLEANUP_FILES+=("${TEST_FILE}") + log_success "Created test file with $(wc -c < "${TEST_FILE}") bytes" +} + +# Calculate file hash +calculate_hash() { + log_info "Calculating SHA-256 hash..." + + HASH=$(sha256sum "${TEST_FILE}" | cut -d' ' -f1) + + log_success "Data to hash: ${TEST_FILE}" + log_success "File hash: ${HASH}" +} + +# Generate nostr event +generate_nostr_event() { + log_info "Generating kind 24242 nostr event with nak..." + + # Calculate expiration time (1 hour from now) + EXPIRATION=$(date -d '+1 hour' +%s) + + # Generate the event using nak + EVENT_JSON=$(nak event -k 24242 -c "" \ + -t "t=upload" \ + -t "x=${HASH}" \ + -t "expiration=${EXPIRATION}") + + if [[ -z "$EVENT_JSON" ]]; then + log_error "Failed to generate nostr event" + exit 1 + fi + + log_success "Generated nostr event" + echo "Event JSON: $EVENT_JSON" +} + +# Create authorization header +create_auth_header() { + log_info "Creating authorization header..." + + # Base64 encode the event (without newlines) + AUTH_B64=$(echo -n "$EVENT_JSON" | base64 -w 0) + AUTH_HEADER="Nostr ${AUTH_B64}" + + log_success "Created authorization header" + echo "Auth header length: ${#AUTH_HEADER} characters" +} + +# Perform upload +perform_upload() { + log_info "Performing upload to ${UPLOAD_ENDPOINT}..." + + # Create temporary file for response + RESPONSE_FILE=$(mktemp) + CLEANUP_FILES+=("${RESPONSE_FILE}") + + # Perform the upload with verbose output + HTTP_STATUS=$(curl -s -w "%{http_code}" \ + -X PUT \ + -H "Authorization: ${AUTH_HEADER}" \ + -H "Content-Type: text/plain" \ + -H "Content-Disposition: attachment; filename=\"${TEST_FILE}\"" \ + --data-binary "@${TEST_FILE}" \ + "${UPLOAD_ENDPOINT}" \ + -o "${RESPONSE_FILE}") + + echo "HTTP Status: ${HTTP_STATUS}" + echo "Response body:" + cat "${RESPONSE_FILE}" + echo + + # Check response + case "${HTTP_STATUS}" in + 200) + log_success "Upload successful!" + ;; + 201) + log_success "Upload successful (created)!" + ;; + 400) + log_error "Bad request - check the event format" + ;; + 401) + log_error "Unauthorized - authentication failed" + ;; + 405) + log_error "Method not allowed - check nginx configuration" + ;; + 413) + log_error "Payload too large" + ;; + 501) + log_warning "Upload endpoint not yet implemented (expected for now)" + ;; + *) + log_error "Upload failed with HTTP status: ${HTTP_STATUS}" + ;; + esac +} + +# Test file retrieval +test_retrieval() { + log_info "Testing file retrieval..." + + RETRIEVAL_URL="${SERVER_URL}/${HASH}" + + if curl -s -f "${RETRIEVAL_URL}" > /dev/null 2>&1; then + log_success "File can be retrieved at: ${RETRIEVAL_URL}" + + # Download and verify + DOWNLOADED_FILE=$(mktemp) + CLEANUP_FILES+=("${DOWNLOADED_FILE}") + curl -s "${RETRIEVAL_URL}" -o "${DOWNLOADED_FILE}" + + DOWNLOADED_HASH=$(sha256sum "${DOWNLOADED_FILE}" | cut -d' ' -f1) + if [[ "${DOWNLOADED_HASH}" == "${HASH}" ]]; then + log_success "Downloaded file hash matches! Verification successful." + else + log_error "Hash mismatch! Expected: ${HASH}, Got: ${DOWNLOADED_HASH}" + fi + else + log_warning "File not yet available for retrieval" + fi +} + +# Main execution +main() { + echo "=== Ginxsom Blossom Production Upload Test ===" + echo "Server: ${SERVER_URL}" + echo "Timestamp: $(date -Iseconds)" + echo + + check_prerequisites + check_server + create_test_file + calculate_hash + generate_nostr_event + create_auth_header + perform_upload + test_retrieval + + echo + log_info "Test completed!" + echo "Summary:" + echo " Test file: ${TEST_FILE}" + echo " File hash: ${HASH}" + echo " Server: ${SERVER_URL}" + echo " Upload endpoint: ${UPLOAD_ENDPOINT}" + echo " Retrieval URL: ${SERVER_URL}/${HASH}" +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/tests/mirror_test_bud04.sh b/tests/mirror_test_bud04.sh index 7045bd2..bc9279f 100755 --- a/tests/mirror_test_bud04.sh +++ b/tests/mirror_test_bud04.sh @@ -3,18 +3,31 @@ # Mirror Test Script for BUD-04 # Tests the PUT /mirror endpoint with a sample PNG file and NIP-42 authentication +# ============================================================================ +# CONFIGURATION - Choose your target Blossom server +# ============================================================================ + +# Local server (uncomment to use) +BLOSSOM_SERVER="http://localhost:9001" + +# Remote server (uncomment to use) +#BLOSSOM_SERVER="https://blossom.laantungir.net" + +# ============================================================================ + # Test URL - PNG file with known SHA-256 hash TEST_URL="https://laantungir.github.io/img_repo/24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de.png" EXPECTED_HASH="24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de" echo "=== BUD-04 Mirror Endpoint Test with Authentication ===" +echo "Blossom Server: $BLOSSOM_SERVER" echo "Target URL: $TEST_URL" echo "Expected Hash: $EXPECTED_HASH" echo "" # Get a fresh challenge from the server echo "=== Getting Authentication Challenge ===" -challenge=$(curl -s "http://localhost:9001/auth" | jq -r '.challenge') +challenge=$(curl -s "$BLOSSOM_SERVER/auth" | jq -r '.challenge') if [ "$challenge" = "null" ] || [ -z "$challenge" ]; then echo "❌ Failed to get challenge from server" exit 1 @@ -48,7 +61,7 @@ RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}\n" \ -H "Authorization: $auth_header" \ -H "Content-Type: application/json" \ -d "$JSON_BODY" \ - http://localhost:9001/mirror) + "$BLOSSOM_SERVER/mirror") echo "Response:" echo "$RESPONSE" @@ -65,9 +78,9 @@ if [ "$HTTP_CODE" = "200" ]; then # Try to access the mirrored blob echo "" echo "=== Verifying Mirrored Blob ===" - echo "Attempting to fetch: http://localhost:9001/$EXPECTED_HASH.png" + echo "Attempting to fetch: $BLOSSOM_SERVER/$EXPECTED_HASH.png" - BLOB_RESPONSE=$(curl -s -w "HTTP_CODE:%{http_code}" -I "http://localhost:9001/$EXPECTED_HASH.png") + BLOB_RESPONSE=$(curl -s -w "HTTP_CODE:%{http_code}" -I "$BLOSSOM_SERVER/$EXPECTED_HASH.png") BLOB_HTTP_CODE=$(echo "$BLOB_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) if [ "$BLOB_HTTP_CODE" = "200" ]; then @@ -82,7 +95,7 @@ if [ "$HTTP_CODE" = "200" ]; then # Test HEAD request for metadata echo "" echo "=== Testing HEAD Request ===" - HEAD_RESPONSE=$(curl -s -w "HTTP_CODE:%{http_code}" -I -X HEAD "http://localhost:9001/$EXPECTED_HASH") + HEAD_RESPONSE=$(curl -s -w "HTTP_CODE:%{http_code}" -I -X HEAD "$BLOSSOM_SERVER/$EXPECTED_HASH") HEAD_HTTP_CODE=$(echo "$HEAD_RESPONSE" | grep "HTTP_CODE:" | cut -d: -f2) if [ "$HEAD_HTTP_CODE" = "200" ]; then