Files
didactyl/build_static.sh

288 lines
8.6 KiB
Bash
Executable File

#!/bin/bash
# Build fully static MUSL binaries for Didactyl using Alpine Docker
# Produces truly portable binaries with zero runtime dependencies
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUTPUT_DIR="$SCRIPT_DIR"
DOCKERFILE="$SCRIPT_DIR/Dockerfile.alpine-musl"
# Parse command line arguments
DEBUG_BUILD=false
TARGET_PLATFORM=""
ALL_PLATFORMS=false
for arg in "$@"; do
case "$arg" in
--debug)
DEBUG_BUILD=true
;;
--platform=*)
TARGET_PLATFORM="${arg#*=}"
;;
--all-platforms|--all_platforms|-a)
ALL_PLATFORMS=true
;;
esac
done
if [ "$ALL_PLATFORMS" = true ] && [ -n "$TARGET_PLATFORM" ]; then
echo "ERROR: --all-platforms cannot be used together with --platform"
exit 1
fi
if [ "$ALL_PLATFORMS" = true ]; then
echo "Building all supported platforms (linux/amd64 + linux/arm64)..."
echo ""
DEBUG_FLAG=""
if [ "$DEBUG_BUILD" = true ]; then
DEBUG_FLAG="--debug"
fi
"$SCRIPT_DIR/build_static.sh" $DEBUG_FLAG --platform=linux/amd64
"$SCRIPT_DIR/build_static.sh" $DEBUG_FLAG --platform=linux/arm64
echo ""
echo "✓ Multi-platform build complete"
exit 0
fi
if [ "$DEBUG_BUILD" = true ]; then
echo "=========================================="
echo "Didactyl MUSL Static Binary Builder (DEBUG MODE)"
echo "=========================================="
else
echo "=========================================="
echo "Didactyl MUSL Static Binary Builder (PRODUCTION MODE)"
echo "=========================================="
fi
echo "Project directory: $SCRIPT_DIR"
echo "Output directory: $OUTPUT_DIR"
echo "Debug build: $DEBUG_BUILD"
echo ""
# Remove legacy build directory if present
if [ -d "$SCRIPT_DIR/build" ]; then
rm -rf "$SCRIPT_DIR/build"
echo "Removed legacy build directory: $SCRIPT_DIR/build"
fi
# 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 architecture (or use explicit target platform override)
if [ -n "$TARGET_PLATFORM" ]; then
PLATFORM="$TARGET_PLATFORM"
case "$PLATFORM" in
linux/amd64)
OUTPUT_NAME="didactyl_static_x86_64"
;;
linux/arm64)
OUTPUT_NAME="didactyl_static_arm64"
;;
linux/arm/v7)
OUTPUT_NAME="didactyl_static_armv7"
;;
linux/arm/v6)
OUTPUT_NAME="didactyl_static_armv6"
;;
*)
OUTPUT_NAME="didactyl_static_custom"
;;
esac
else
ARCH=$(uname -m)
case "$ARCH" in
x86_64)
PLATFORM="linux/amd64"
OUTPUT_NAME="didactyl_static_x86_64"
;;
aarch64|arm64)
PLATFORM="linux/arm64"
OUTPUT_NAME="didactyl_static_arm64"
;;
*)
echo "WARNING: Unknown architecture: $ARCH"
echo "Defaulting to linux/amd64"
PLATFORM="linux/amd64"
OUTPUT_NAME="didactyl_static_${ARCH}"
;;
esac
fi
# Append _debug suffix to output name for debug builds
if [ "$DEBUG_BUILD" = true ]; then
OUTPUT_NAME="${OUTPUT_NAME}_debug"
fi
echo "Building for platform: $PLATFORM"
echo "Output binary: $OUTPUT_NAME"
echo ""
# Verify Docker can execute the requested target platform (QEMU/binfmt for cross-arch)
HOST_ARCH=$(uname -m)
case "$HOST_ARCH" in
x86_64) HOST_PLATFORM="linux/amd64" ;;
aarch64|arm64) HOST_PLATFORM="linux/arm64" ;;
armv7l) HOST_PLATFORM="linux/arm/v7" ;;
armv6l) HOST_PLATFORM="linux/arm/v6" ;;
*) HOST_PLATFORM="unknown" ;;
esac
if [ "$HOST_PLATFORM" != "$PLATFORM" ]; then
echo "Cross-architecture build detected: host=$HOST_PLATFORM target=$PLATFORM"
echo "Checking Docker emulation support for $PLATFORM..."
if ! $DOCKER_CMD run --rm --platform "$PLATFORM" alpine:3.19 uname -m > /dev/null 2>&1; then
echo ""
echo "ERROR: Docker cannot execute $PLATFORM containers on this host"
echo "This usually means QEMU/binfmt is not configured."
echo "Run this once, then retry:"
echo " docker run --rm --privileged multiarch/qemu-user-static --reset -p yes"
echo "Optional verify command:"
echo " docker run --rm --platform $PLATFORM alpine:3.19 uname -m"
echo ""
exit 1
fi
echo "✓ Docker emulation for $PLATFORM is available"
echo ""
fi
# 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 ""
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 didactyl with full static linking"
echo ""
$DOCKER_CMD build \
--network host \
--platform "$PLATFORM" \
--build-arg DEBUG_BUILD=$DEBUG_BUILD \
-f "$DOCKERFILE" \
-t didactyl-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 "=========================================="
# Reuse the already-built final image and extract binary directly
# (avoids a second docker build pass)
CONTAINER_ID=$($DOCKER_CMD create didactyl-musl-builder:latest /didactyl_static)
# Copy binary from final image
$DOCKER_CMD cp "$CONTAINER_ID:/didactyl_static" "$OUTPUT_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: $OUTPUT_DIR/$OUTPUT_NAME"
echo ""
# Make binary executable
chmod +x "$OUTPUT_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 "$OUTPUT_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 "$OUTPUT_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 "=========================================="
echo "Build Summary"
echo "=========================================="
echo "Binary: $OUTPUT_DIR/$OUTPUT_NAME"
echo "Size: $(du -h "$OUTPUT_DIR/$OUTPUT_NAME" | cut -f1)"
echo "Static: $TRULY_STATIC"
echo "Debug: $DEBUG_BUILD"
echo "Platform: $PLATFORM"
echo "=========================================="