Files
ginxsom/build_static.sh

326 lines
9.4 KiB
Bash
Executable File

#!/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 <arm64|armv7|x86_64>]"
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 <arm64|armv7|x86_64>]"
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 ""