479 lines
9.8 KiB
Markdown
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"
|