# 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-pg/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-pg Files

You can directly copy these files from c-relay-pg:

### 1. Dockerfile.alpine-musl
```bash
cp /path/to/c-relay-pg/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-pg/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-pg/.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-pg 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 <stdio.h>

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!