# MUSL Static Binary Build Guide ## Overview This guide explains how to build truly portable MUSL-based static binaries of c-relay using Alpine Linux Docker containers. These binaries have **zero runtime dependencies** and work on any Linux distribution. ## Why MUSL? ### MUSL vs glibc Static Binaries **MUSL Advantages:** - **Truly Static**: No hidden dependencies on system libraries - **Smaller Size**: ~7.6MB vs ~12MB+ for glibc static builds - **Better Portability**: Works on ANY Linux distribution without modification - **Cleaner Linking**: No glibc-specific extensions or fortified functions - **Simpler Deployment**: Single binary, no library compatibility issues **glibc Limitations:** - Static builds still require dynamic loading for NSS (Name Service Switch) - Fortified functions (`__*_chk`) don't exist in MUSL - Larger binary size due to glibc's complexity - May have compatibility issues across different glibc versions ## Build Process ### Prerequisites - Docker installed and running - Sufficient disk space (~2GB for Docker layers) - Internet connection (for downloading dependencies) ### Quick Start ```bash # Build MUSL static binary ./build_static.sh # The binary will be created at: # build/c_relay_static_musl_x86_64 (on x86_64) # build/c_relay_static_musl_arm64 (on ARM64) ``` ### What Happens During Build 1. **Alpine Linux Base**: Uses Alpine 3.19 with native MUSL support 2. **Static Dependencies**: Builds all dependencies with static linking: - libsecp256k1 (Bitcoin cryptography) - libwebsockets (WebSocket server) - OpenSSL (TLS/crypto) - SQLite (database) - curl (HTTP client) - zlib (compression) 3. **nostr_core_lib**: Builds with MUSL-compatible flags: - Disables glibc fortification (`-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0`) - Includes required NIPs: 001, 006, 013, 017, 019, 044, 059 - Produces static library (~316KB) 4. **c-relay Compilation**: Links everything statically: - All source files compiled with `-static` flag - Fortification disabled to avoid `__*_chk` symbols - Results in ~7.6MB stripped binary 5. **Verification**: Confirms binary is truly static: - `ldd` shows "not a dynamic executable" - `file` shows "statically linked" - Binary executes successfully ## Technical Details ### Dockerfile Structure The build uses a multi-stage Dockerfile (`Dockerfile.alpine-musl`): ```dockerfile # Stage 1: Builder (Alpine Linux) FROM alpine:3.19 AS builder - Install build tools and static libraries - Build dependencies from source - Compile nostr_core_lib with MUSL flags - Compile c-relay with full static linking - Strip binary to reduce size # Stage 2: Output (scratch) FROM scratch AS output - Contains only the final binary ``` ### Key Compilation Flags **For nostr_core_lib:** ```bash CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2" ``` **For c-relay:** ```bash gcc -static -O2 -Wall -Wextra -std=c99 \ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ [source files] \ -lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \ -lcurl -lz -lpthread -lm -ldl ``` ### Fortification Issue **Problem**: GCC's `-O2` optimization enables fortification by default, replacing standard functions with `__*_chk` variants (e.g., `__snprintf_chk`, `__fprintf_chk`). These are glibc-specific and don't exist in MUSL. **Solution**: Explicitly disable fortification with: - `-U_FORTIFY_SOURCE` (undefine any existing definition) - `-D_FORTIFY_SOURCE=0` (set to 0) This must be applied to **both** nostr_core_lib and c-relay compilation. ### NIP Dependencies The build includes these NIPs in nostr_core_lib: - **NIP-001**: Basic protocol (event creation, signing) - **NIP-006**: Key derivation from mnemonic - **NIP-013**: Proof of Work validation - **NIP-017**: Private Direct Messages - **NIP-019**: Bech32 encoding (nsec/npub) - **NIP-044**: Modern encryption - **NIP-059**: Gift Wrap (required by NIP-017) ## Verification ### Check Binary Type ```bash # Should show "statically linked" file build/c_relay_static_musl_x86_64 # Should show "not a dynamic executable" ldd build/c_relay_static_musl_x86_64 # Check size (should be ~7.6MB) ls -lh build/c_relay_static_musl_x86_64 ``` ### Test Execution ```bash # Show help ./build/c_relay_static_musl_x86_64 --help # Show version ./build/c_relay_static_musl_x86_64 --version # Run relay ./build/c_relay_static_musl_x86_64 --port 8888 ``` ### Cross-Distribution Testing Test the binary on different distributions to verify portability: ```bash # Alpine Linux docker run --rm -v $(pwd)/build:/app alpine:latest /app/c_relay_static_musl_x86_64 --version # Ubuntu docker run --rm -v $(pwd)/build:/app ubuntu:latest /app/c_relay_static_musl_x86_64 --version # Debian docker run --rm -v $(pwd)/build:/app debian:latest /app/c_relay_static_musl_x86_64 --version # CentOS docker run --rm -v $(pwd)/build:/app centos:latest /app/c_relay_static_musl_x86_64 --version ``` ## Troubleshooting ### Docker Permission Denied **Problem**: `permission denied while trying to connect to the Docker daemon socket` **Solution**: Add user to docker group: ```bash sudo usermod -aG docker $USER newgrp docker # Or logout and login again ``` ### Build Fails with Fortification Errors **Problem**: `undefined reference to '__snprintf_chk'` or `'__fprintf_chk'` **Solution**: Ensure fortification is disabled in both: 1. nostr_core_lib build.sh (line 534) 2. c-relay compilation flags in Dockerfile ### Binary Won't Execute **Problem**: Binary fails to run on target system **Checks**: 1. Verify it's truly static: `ldd binary` should show "not a dynamic executable" 2. Check architecture matches: `file binary` should show correct arch 3. Ensure execute permissions: `chmod +x binary` ### Missing NIP Functions **Problem**: `undefined reference to 'nostr_nip*'` during linking **Solution**: Add missing NIPs to the build command: ```bash ./build.sh --nips=1,6,13,17,19,44,59 ``` ## Deployment ### Single Binary Deployment ```bash # Copy binary to server scp build/c_relay_static_musl_x86_64 user@server:/opt/c-relay/ # Run on server (no dependencies needed!) ssh user@server cd /opt/c-relay ./c_relay_static_musl_x86_64 --port 8888 ``` ### SystemD Service ```ini [Unit] Description=C-Relay Nostr Relay (MUSL Static) After=network.target [Service] Type=simple User=c-relay WorkingDirectory=/opt/c-relay ExecStart=/opt/c-relay/c_relay_static_musl_x86_64 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` ## Performance Comparison | Metric | MUSL Static | glibc Static | glibc Dynamic | |--------|-------------|--------------|---------------| | Binary Size | 7.6 MB | 12+ MB | 2-3 MB | | Startup Time | ~50ms | ~60ms | ~40ms | | Memory Usage | Similar | Similar | Similar | | Portability | ✓ Any Linux | ⚠ glibc only | ✗ Requires libs | | Dependencies | None | NSS libs | Many libs | ## Best Practices 1. **Always verify** the binary is truly static before deployment 2. **Test on multiple distributions** to ensure portability 3. **Keep Docker images updated** for security patches 4. **Document the build date** and commit hash for reproducibility 5. **Store binaries** with architecture in filename (e.g., `_x86_64`, `_arm64`) ## References - [MUSL libc](https://musl.libc.org/) - [Alpine Linux](https://alpinelinux.org/) - [Static Linking Best Practices](https://www.musl-libc.org/faq.html) - [GCC Fortification](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html) ## Changelog ### 2025-10-11 - Initial MUSL build system implementation - Alpine Docker-based build process - Fortification fix for nostr_core_lib - Complete NIP dependency resolution - Documentation created