#!/bin/bash # Build fully static MUSL binaries for Ginxsom using Alpine Docker # Produces truly portable binaries with zero runtime dependencies set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BUILD_DIR="$SCRIPT_DIR/build" DOCKERFILE="$SCRIPT_DIR/Dockerfile.alpine-musl" # Parse command line arguments DEBUG_BUILD=false TARGET_ARCH="" while [[ $# -gt 0 ]]; do case "$1" in --debug) DEBUG_BUILD=true shift ;; --arch) if [[ -z "$2" ]]; then echo "ERROR: --arch requires a value" echo "Usage: $0 [--debug] [--arch ]" exit 1 fi case "$2" in arm64|armv7|x86_64) TARGET_ARCH="$2" ;; *) echo "ERROR: Unsupported architecture '$2'" echo "Supported values: arm64, armv7, x86_64" exit 1 esac shift 2 ;; *) echo "ERROR: Unknown argument '$1'" echo "Usage: $0 [--debug] [--arch ]" exit 1 ;; esac done if [ "$DEBUG_BUILD" = true ]; then echo "==========================================" echo "Ginxsom MUSL Static Binary Builder (DEBUG MODE)" echo "==========================================" else echo "==========================================" echo "Ginxsom MUSL Static Binary Builder (PRODUCTION MODE)" echo "==========================================" fi echo "Project directory: $SCRIPT_DIR" echo "Build directory: $BUILD_DIR" echo "Debug build: $DEBUG_BUILD" if [[ -n "$TARGET_ARCH" ]]; then echo "Requested target architecture: $TARGET_ARCH" fi echo "" # Create build directory mkdir -p "$BUILD_DIR" # Check if Docker is available if ! command -v docker &> /dev/null; then echo "ERROR: Docker is not installed or not in PATH" echo "" echo "Docker is required to build MUSL static binaries." echo "Please install Docker:" echo " - Ubuntu/Debian: sudo apt install docker.io" echo " - Or visit: https://docs.docker.com/engine/install/" echo "" exit 1 fi # Check if Docker daemon is running if ! docker info &> /dev/null; then echo "ERROR: Docker daemon is not running or user not in docker group" echo "" echo "Please start Docker and ensure you're in the docker group:" echo " - sudo systemctl start docker" echo " - sudo usermod -aG docker $USER && newgrp docker" echo " - Or start Docker Desktop" echo "" exit 1 fi DOCKER_CMD="docker" echo "✓ Docker is available and running" echo "" # Detect host architecture ARCH=$(uname -m) case "$ARCH" in x86_64) HOST_ARCH="x86_64" ;; aarch64|arm64) HOST_ARCH="arm64" ;; armv7l|armv7) HOST_ARCH="armv7" ;; *) HOST_ARCH="unknown" ;; esac # Resolve target architecture if [[ -n "$TARGET_ARCH" ]]; then ARCH="$TARGET_ARCH" fi case "$ARCH" in x86_64) PLATFORM="linux/amd64" OUTPUT_NAME="ginxsom-fcgi_static_x86_64" ;; aarch64|arm64) ARCH="arm64" PLATFORM="linux/arm64" OUTPUT_NAME="ginxsom-fcgi_static_arm64" ;; armv7l|armv7) ARCH="armv7" PLATFORM="linux/arm/v7" OUTPUT_NAME="ginxsom-fcgi_static_armv7" ;; *) echo "WARNING: Unknown architecture: $ARCH" echo "Defaulting to linux/amd64" PLATFORM="linux/amd64" OUTPUT_NAME="ginxsom-fcgi_static_${ARCH}" ;; esac # When cross-building, ensure buildx exists and warn about QEMU/binfmt setup if [[ "$HOST_ARCH" != "unknown" && "$ARCH" != "$HOST_ARCH" ]]; then echo "NOTE: Cross-building from host '$HOST_ARCH' to target '$ARCH'." echo " Docker QEMU/binfmt support is required for this to work." if ! docker buildx version >/dev/null 2>&1; then echo "ERROR: docker buildx is required for cross-architecture builds but is not available." echo "Install/enable buildx and binfmt support, then retry." exit 1 fi echo "✓ docker buildx is available for cross-architecture build" echo "" fi # Append _debug suffix to output name for debug builds so production binary is never overwritten if [ "$DEBUG_BUILD" = true ]; then OUTPUT_NAME="${OUTPUT_NAME}_debug" fi echo "Building for platform: $PLATFORM" echo "Output binary: $OUTPUT_NAME" echo "" # Check if Alpine base image is cached echo "Checking for cached Alpine Docker image..." if ! docker images alpine:3.19 --format "{{.Repository}}:{{.Tag}}" | grep -q "alpine:3.19"; then echo "⚠ Alpine 3.19 image not found in cache" echo "Attempting to pull Alpine 3.19 image..." if ! docker pull alpine:3.19; then echo "" echo "ERROR: Failed to pull Alpine 3.19 image" echo "This is required for the static build." echo "" echo "Possible solutions:" echo " 1. Check your internet connection" echo " 2. Try again later (Docker Hub may be temporarily unavailable)" echo " 3. If you have IPv6 issues, disable IPv6 for Docker" echo "" exit 1 fi echo "✓ Alpine 3.19 image pulled successfully" else echo "✓ Alpine 3.19 image found in cache" fi echo "" # Build the Docker image echo "==========================================" echo "Step 1: Building Alpine Docker image" echo "==========================================" echo "This will:" echo " - Use Alpine Linux (native MUSL)" echo " - Build all dependencies statically" echo " - Compile Ginxsom with full static linking" echo "" $DOCKER_CMD build \ --platform "$PLATFORM" \ --build-arg DEBUG_BUILD=$DEBUG_BUILD \ -f "$DOCKERFILE" \ -t ginxsom-musl-builder:latest \ --progress=plain \ . || { echo "" echo "ERROR: Docker build failed" echo "Check the output above for details" exit 1 } echo "" echo "✓ Docker image built successfully" echo "" # Extract the binary from the container echo "==========================================" echo "Step 2: Extracting static binary" echo "==========================================" # Build the builder stage to extract the binary $DOCKER_CMD build \ --platform "$PLATFORM" \ --build-arg DEBUG_BUILD=$DEBUG_BUILD \ --target builder \ -f "$DOCKERFILE" \ -t ginxsom-static-builder-stage:latest \ . > /dev/null 2>&1 # Create a temporary container to copy the binary CONTAINER_ID=$($DOCKER_CMD create ginxsom-static-builder-stage:latest) # Copy binary from container $DOCKER_CMD cp "$CONTAINER_ID:/build/ginxsom-fcgi_static" "$BUILD_DIR/$OUTPUT_NAME" || { echo "ERROR: Failed to extract binary from container" $DOCKER_CMD rm "$CONTAINER_ID" 2>/dev/null exit 1 } # Clean up container $DOCKER_CMD rm "$CONTAINER_ID" > /dev/null echo "✓ Binary extracted to: $BUILD_DIR/$OUTPUT_NAME" echo "" # Make binary executable chmod +x "$BUILD_DIR/$OUTPUT_NAME" # Verify the binary echo "==========================================" echo "Step 3: Verifying static binary" echo "==========================================" echo "" echo "Checking for dynamic dependencies:" if LDD_OUTPUT=$(timeout 5 ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1); then if echo "$LDD_OUTPUT" | grep -q "not a dynamic executable"; then echo "✓ Binary is fully static (no dynamic dependencies)" TRULY_STATIC=true elif echo "$LDD_OUTPUT" | grep -q "statically linked"; then echo "✓ Binary is statically linked" TRULY_STATIC=true else echo "⚠ WARNING: Binary may have dynamic dependencies:" echo "$LDD_OUTPUT" TRULY_STATIC=false fi else # ldd failed or timed out - check with file command instead if file "$BUILD_DIR/$OUTPUT_NAME" | grep -q "statically linked"; then echo "✓ Binary is statically linked (verified with file command)" TRULY_STATIC=true else echo "⚠ Could not verify static linking (ldd check failed)" TRULY_STATIC=false fi fi echo "" echo "File size: $(ls -lh "$BUILD_DIR/$OUTPUT_NAME" | awk '{print $5}')" echo "" # Summary echo "==========================================" echo "Build Summary" echo "==========================================" echo "Binary: $BUILD_DIR/$OUTPUT_NAME" echo "Size: $(du -h "$BUILD_DIR/$OUTPUT_NAME" | cut -f1)" echo "Platform: $PLATFORM" if [ "$DEBUG_BUILD" = true ]; then echo "Build Type: DEBUG (with symbols, no optimization)" else echo "Build Type: PRODUCTION (optimized, stripped)" fi if [ "$TRULY_STATIC" = true ]; then echo "Linkage: Fully static binary (Alpine MUSL-based)" echo "Portability: Works on ANY Linux distribution" else echo "Linkage: Static binary (may have minimal dependencies)" fi echo "" echo "✓ Build complete!" echo "" # Clean up old dynamic build artifacts echo "==========================================" echo "Cleaning up old build artifacts" echo "==========================================" echo "" if ls build/*.o 2>/dev/null | grep -q .; then echo "Removing old .o files from dynamic builds..." rm -f build/*.o echo "✓ Cleanup complete" else echo "No .o files to clean" fi # Also remove old dynamic binary if it exists if [ -f "build/ginxsom-fcgi" ]; then echo "Removing old dynamic binary..." rm -f build/ginxsom-fcgi echo "✓ Old binary removed" fi echo "" echo "Deployment:" echo " scp $BUILD_DIR/$OUTPUT_NAME user@server:/path/to/ginxsom/" echo ""