321 lines
10 KiB
Bash
Executable File
321 lines
10 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
|
|
|
|
# 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 "=========================================="
|