nip/docs/static-build-guide.md

479 lines
9.8 KiB
Markdown

# 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"