# NIP Static Build Guide ## Overview This guide explains how to build NIP as a fully static binary for minimal deployment scenarios. Static builds are essential for the "minimal install philosophy" - enabling users to boot a tiny netinst image and build their perfect system from a single ~5MB binary. ## Why Static Linking? ### Benefits 1. **Zero Dependencies**: Binary runs on any Linux system with kernel 4.19+ 2. **Minimal Deployment**: Single file, no package manager needed 3. **Predictable Behavior**: No library version conflicts 4. **Portable**: Works across different distributions 5. **Netinst Ready**: Perfect for minimal installation images ### Trade-offs - **Larger Binary Size**: ~5-10MB vs ~2-3MB dynamic - **No Shared Libraries**: Can't benefit from system library updates - **Build Complexity**: Requires careful configuration ## Build Methods ### Method 1: Using build_static.sh (Recommended) The easiest way to build a static binary: ```bash cd nip ./build_static.sh ``` This script: - Detects musl-gcc for optimal builds - Configures all static linking flags - Verifies the binary is truly static - Creates deployment package - Provides size comparison **Output:** - `nip-static` - Fully static binary - `nip-v0.2.0-weihnachtsmann-static-*.tar.gz` - Deployment package ### Method 2: Manual Build with Musl For the smallest possible binary (recommended): ```bash # Install musl-tools (Debian/Ubuntu) sudo apt install musl-tools # Or on Arch sudo pacman -S musl # Build with musl nim c \ --define:static \ --define:release \ --define:danger \ --opt:speed \ --mm:orc \ --threads:on \ --passC:-flto \ --passL:-flto \ --passL:-static \ --passL:-s \ --gcc.exe:musl-gcc \ --gcc.linkerexe:musl-gcc \ --out:nip-static \ nip.nim ``` **Expected Size:** ~5-7MB ### Method 3: Manual Build with Glibc For maximum compatibility (larger binary): ```bash nim c \ --define:static \ --define:release \ --define:danger \ --opt:speed \ --mm:orc \ --threads:on \ --passC:-flto \ --passL:-flto \ --passL:-static \ --passL:-static-libgcc \ --passL:-s \ --out:nip-static \ nip.nim ``` **Expected Size:** ~8-12MB ## Configuration Details ### Static Linking Flags The `config.nims` file includes static linking configuration when `-d:static` is defined: ```nim when defined(static): # Core static linking switch("passL", "-static") switch("passL", "-static-libgcc") # Use musl if available when defined(linux): if fileExists("/usr/lib/x86_64-linux-musl/libc.a"): switch("gcc.exe", "musl-gcc") switch("gcc.linkerexe", "musl-gcc") # Optimization flags switch("passL", "-s") # Strip symbols switch("passC", "-flto") # Link-time optimization switch("passL", "-flto") switch("passC", "-ffunction-sections") # Section garbage collection switch("passC", "-fdata-sections") switch("passL", "-Wl,--gc-sections") ``` ### Compiler Flags Explained | Flag | Purpose | Impact | |------|---------|--------| | `--define:static` | Enable static build mode | Activates static config | | `--define:release` | Release optimizations | Faster code | | `--define:danger` | Disable runtime checks | Smaller, faster | | `--opt:speed` | Optimize for speed | Better performance | | `--mm:orc` | Use ORC memory manager | Deterministic GC | | `--threads:on` | Enable threading | Parallel operations | | `--passC:-flto` | Link-time optimization (C) | Better code generation | | `--passL:-flto` | Link-time optimization (linker) | Smaller binary | | `--passL:-static` | Static linking | No dynamic deps | | `--passL:-s` | Strip symbols | Smaller binary | | `--gcc.exe:musl-gcc` | Use musl compiler | Smaller libc | ## Verification ### Check Static Linking ```bash # Should output: "not a dynamic executable" ldd nip-static # Or on some systems: "statically linked" file nip-static ``` ### Check Binary Size ```bash ls -lh nip-static # Target: 5-10MB ``` ### Test Functionality ```bash # Basic test (may require root) ./nip-static --version # Full test sudo ./nip-static setup sudo ./nip-static status ``` ## Platform-Specific Notes ### Linux (x86_64) **Recommended:** Use musl-gcc for smallest binaries ```bash # Debian/Ubuntu sudo apt install musl-tools # Arch sudo pacman -S musl # Fedora sudo dnf install musl-gcc ``` ### Linux (ARM64) ```bash # Install cross-compilation tools sudo apt install gcc-aarch64-linux-gnu musl-tools # Build for ARM64 nim c \ --cpu:arm64 \ --os:linux \ --define:static \ --gcc.exe:aarch64-linux-gnu-gcc \ --gcc.linkerexe:aarch64-linux-gnu-gcc \ --passL:-static \ nip.nim ``` ### Linux (RISC-V) ```bash # Install RISC-V toolchain sudo apt install gcc-riscv64-linux-gnu # Build for RISC-V nim c \ --cpu:riscv64 \ --os:linux \ --define:static \ --gcc.exe:riscv64-linux-gnu-gcc \ nip.nim ``` ## Troubleshooting ### Problem: Binary has dynamic dependencies **Symptom:** ```bash ldd nip-static # Shows: linux-vdso.so.1, libc.so.6, etc. ``` **Solution:** 1. Ensure `--passL:-static` is used 2. Check for dynamic library overrides 3. Use musl-gcc instead of gcc 4. Verify no `-l` flags without static variants ### Problem: Binary is too large (>15MB) **Causes:** - Not using musl (glibc is larger) - Debug symbols not stripped - LTO not enabled **Solutions:** ```bash # Strip manually if needed strip nip-static # Verify LTO is enabled nim c --define:static --passC:-flto --passL:-flto ... # Use musl nim c --define:static --gcc.exe:musl-gcc ... ``` ### Problem: "undefined reference" errors **Symptom:** ``` /usr/bin/ld: undefined reference to `pthread_create' ``` **Solution:** Add threading library explicitly: ```bash nim c --define:static --passL:-lpthread ... ``` ### Problem: SSL/TLS not working **Symptom:** ``` Error: SSL library not found ``` **Solution:** Ensure static SSL libraries are available: ```bash # Debian/Ubuntu sudo apt install libssl-dev # Build with SSL nim c --define:static --define:ssl --passL:-lssl --passL:-lcrypto ... ``` ## Size Optimization Tips ### 1. Use Musl Instead of Glibc **Savings:** ~3-5MB ```bash nim c --define:static --gcc.exe:musl-gcc nip.nim ``` ### 2. Enable Link-Time Optimization **Savings:** ~1-2MB ```bash nim c --define:static --passC:-flto --passL:-flto nip.nim ``` ### 3. Strip Symbols **Savings:** ~500KB-1MB ```bash nim c --define:static --passL:-s nip.nim # Or manually: strip nip-static ``` ### 4. Enable Section Garbage Collection **Savings:** ~500KB ```bash nim c \ --define:static \ --passC:-ffunction-sections \ --passC:-fda-sections \ --passL:-Wl,--gc-sections \ nip.nim ``` ### 5. Use UPX Compression (Optional) **Savings:** ~50-70% (but slower startup) ```bash # Install UPX sudo apt install upx-ucl # Compress binary upx --best --lzma nip-static # Result: ~2-3MB compressed ``` **Trade-off:** Slower startup time (~100-200ms decompression) ## Deployment ### Minimal Netinst Image Create a minimal bootable image with just NIP: ```bash # 1. Create minimal rootfs mkdir -p netinst/{bin,lib,etc,usr/local/bin} # 2. Copy static binary cp nip-static netinst/usr/local/bin/nip # 3. Add minimal init cat > netinst/init << 'EOF' #!/bin/sh exec /usr/local/bin/nip setup EOF chmod +x netinst/init # 4. Create initramfs cd netinst find . | cpio -o -H newc | gzip > ../netinst.img ``` **Result:** ~50-100MB bootable image with NIP ### Installation Script The static build includes a minimal install script: ```bash #!/bin/bash # Ultra-minimal NIP installation sudo cp nip /usr/local/bin/nip sudo chmod +x /usr/local/bin/nip sudo nip setup ``` ## CI/CD Integration ### GitHub Actions Example ```yaml name: Build Static Binary on: [push, pull_request] jobs: build-static: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Nim uses: jiro4989/setup-nim-action@v1 with: nim-version: '2.0.0' - name: Install musl-tools run: sudo apt-get install -y musl-tools - name: Build static binary run: | cd nip ./build_static.sh - name: Verify static linking run: | ldd nip/nip-static || echo "Fully static" - name: Upload artifact uses: actions/upload-artifact@v3 with: name: nip-static path: nip/nip-static ``` ## Performance Considerations ### Startup Time - **Dynamic binary:** ~10-20ms - **Static binary (uncompressed):** ~15-30ms - **Static binary (UPX compressed):** ~100-200ms ### Runtime Performance Static binaries have **identical runtime performance** to dynamic binaries. The only difference is startup time. ### Memory Usage Static binaries may use slightly more memory (~1-2MB) because: - Entire libc is included - No shared library benefits For NIP's use case (system package manager), this is negligible. ## Best Practices ### 1. Always Verify Static Linking ```bash ldd nip-static | grep -q "not a dynamic" || echo "WARNING: Not fully static" ``` ### 2. Test on Minimal Systems ```bash # Test in Docker with minimal base docker run --rm -v $(pwd):/app alpine:latest /app/nip-static --version ``` ### 3. Document Dependencies Even static binaries have kernel requirements: - Linux kernel 4.19+ (for modern syscalls) - x86_64, ARM64, or RISC-V architecture ### 4. Provide Multiple Variants Offer both static and dynamic builds: - Static: For minimal installs, netboot, embedded - Dynamic: For regular systems, faster startup ## References - [Nim Static Linking Guide](https://nim-lang.org/docs/nimc.html#compiler-usage-static-linking) - [Musl Libc](https://musl.libc.org/) - [UPX Compression](https://upx.github.io/) - [NIP Minimal Install Philosophy](.kiro/steering/nip/minimal-install-philosophy.md) --- **Document Version:** 1.0 **Last Updated:** November 18, 2025 **Applies To:** NIP v0.2.0 "Weihnachtsmann"