#!/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 # Detect available OCI container runtime (prefer Docker, fallback to Podman) if command -v docker &> /dev/null; then DOCKER_CMD="docker" RUNTIME_NAME="Docker" elif command -v podman &> /dev/null; then DOCKER_CMD="podman" RUNTIME_NAME="Podman" else echo "ERROR: No supported container runtime found in PATH" echo "" echo "This build requires either Docker or Podman." echo "Install one of the following:" echo " - Docker (Ubuntu/Debian): sudo apt install docker.io" echo " - Podman (Ubuntu/Debian): sudo apt install podman" echo "" exit 1 fi # Check if runtime daemon/service is usable if ! $DOCKER_CMD info &> /dev/null; then # Docker is installed but current user may not yet have docker-group membership in this shell. # If passwordless sudo is available, transparently fall back to sudo docker. if [ "$DOCKER_CMD" = "docker" ] && command -v sudo &> /dev/null && sudo -n docker info &> /dev/null; then DOCKER_CMD="sudo docker" echo "⚠ Docker requires elevated privileges in this shell; using sudo docker" echo "" else echo "ERROR: $RUNTIME_NAME is installed but not usable" echo "" if [ "$DOCKER_CMD" = "docker" ]; then echo "Please start Docker and ensure your user can access it:" echo " - sudo systemctl start docker" echo " - sudo usermod -aG docker $USER && newgrp docker" echo " - Or log out and back in after adding to docker group" echo " - Or run this script with sudo" else echo "Please verify Podman is configured for your user:" echo " - podman info" echo " - For rootless setup, ensure subuid/subgid are configured" fi echo "" exit 1 fi fi echo "✓ $RUNTIME_NAME 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_CMD 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_CMD 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 # If runtime command used sudo, copied file may be root-owned; fix ownership for current user if [ ! -w "$OUTPUT_DIR/$OUTPUT_NAME" ]; then if command -v sudo &> /dev/null && sudo -n true &> /dev/null; then sudo chown "$(id -u):$(id -g)" "$OUTPUT_DIR/$OUTPUT_NAME" || true fi fi echo "✓ Binary extracted to: $OUTPUT_DIR/$OUTPUT_NAME" echo "" # Make binary executable (fallback to sudo if ownership/permissions still block) chmod +x "$OUTPUT_DIR/$OUTPUT_NAME" 2>/dev/null || { if command -v sudo &> /dev/null && sudo -n true &> /dev/null; then sudo chmod +x "$OUTPUT_DIR/$OUTPUT_NAME" else echo "ERROR: Could not set executable bit on $OUTPUT_DIR/$OUTPUT_NAME" exit 1 fi } # 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 "=========================================="