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/deploy_local.sh b/deploy_local.sh index e434dd3..c211875 100755 --- a/deploy_local.sh +++ b/deploy_local.sh @@ -3,17 +3,17 @@ # Copy the binary to the deployment location cp build/c_relay_x86 ~/Storage/c_relay/crelay -# Copy the local service file to systemd -# sudo cp systemd/c-relay-local.service /etc/systemd/system/ +# Copy the service file to systemd (use the main service file) +sudo cp systemd/c-relay.service /etc/systemd/system/c-relay-local.service # Reload systemd daemon to pick up the new service -# sudo systemctl daemon-reload +sudo systemctl daemon-reload # Enable the service (if not already enabled) -# sudo systemctl enable c-relay-local.service +sudo systemctl enable c-relay-local.service # Restart the service -# sudo systemctl restart c-relay-local.service +sudo systemctl restart c-relay-local.service # Show service status -# sudo systemctl status c-relay-local.service --no-pager -l +sudo systemctl status c-relay-local.service --no-pager -l diff --git a/relay.pid b/relay.pid index d5cf0d0..cebf67b 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -84610 +198023 diff --git a/screenshots/2025-11-11_11-57.png b/screenshots/2025-11-11_11-57.png new file mode 100644 index 0000000..ad8019c Binary files /dev/null and b/screenshots/2025-11-11_11-57.png differ diff --git a/screenshots/raffles.png b/screenshots/raffles.png new file mode 100644 index 0000000..0df88d0 Binary files /dev/null and b/screenshots/raffles.png differ diff --git a/src/main.h b/src/main.h index 2fd15d8..eb7247e 100644 --- a/src/main.h +++ b/src/main.h @@ -12,8 +12,8 @@ // Version information (auto-updated by build system) #define VERSION_MAJOR 1 #define VERSION_MINOR 0 -#define VERSION_PATCH 4 -#define VERSION "v1.0.4" +#define VERSION_PATCH 5 +#define VERSION "v1.0.5" // Avoid VERSION_MAJOR redefinition warning from nostr_core_lib #undef VERSION_MAJOR diff --git a/src/websockets.c b/src/websockets.c index 4f555e4..fb24feb 100644 --- a/src/websockets.c +++ b/src/websockets.c @@ -497,8 +497,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso if (pss->reassembly_buffer) { DEBUG_WARN("Starting new reassembly but buffer already exists - cleaning up"); free(pss->reassembly_buffer); + pss->reassembly_buffer = NULL; // Ensure buffer is NULL after cleanup } - pss->reassembly_buffer = NULL; pss->reassembly_size = 0; pss->reassembly_capacity = 0; pss->reassembly_active = 1; @@ -546,8 +546,9 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso char* complete_message = pss->reassembly_buffer; size_t message_len = pss->reassembly_size; - // Reset reassembly state (but keep buffer for reuse if needed) + // Reset reassembly state (keep buffer pointer for processing) pss->reassembly_size = 0; + pss->reassembly_active = 0; // Parse JSON message DEBUG_TRACE("Parsing reassembled JSON message of length %zu", message_len); @@ -1163,8 +1164,10 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Clean up the reassembled message if (json) cJSON_Delete(json); - // Note: complete_message points to reassembly_buffer, which is managed separately - // and should not be freed here - it will be cleaned up in LWS_CALLBACK_CLOSED + // Free the reassembly buffer now that processing is complete + free(complete_message); + pss->reassembly_buffer = NULL; + pss->reassembly_capacity = 0; return 0; // Fragmented message processed } else {