forked from tfgrid/zosbuilder
316 lines
9.9 KiB
Bash
316 lines
9.9 KiB
Bash
#!/bin/bash
|
|
# Container management for rootless Docker/Podman builds
|
|
|
|
# Source common functions
|
|
LIB_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${LIB_SCRIPT_DIR}/common.sh"
|
|
|
|
# Container configuration
|
|
CONTAINER_RUNTIME=""
|
|
BUILDER_IMAGE="zero-os-builder:latest"
|
|
ALPINE_VERSION="${ALPINE_VERSION:-3.22}"
|
|
|
|
# Detect available container runtime
|
|
function docker_detect_runtime() {
|
|
section_header "Detecting Container Runtime"
|
|
|
|
if command_exists "podman"; then
|
|
CONTAINER_RUNTIME="podman"
|
|
log_info "Using Podman as container runtime"
|
|
elif command_exists "docker"; then
|
|
CONTAINER_RUNTIME="docker"
|
|
log_info "Using Docker as container runtime"
|
|
else
|
|
log_error "No container runtime found (podman or docker required)"
|
|
return 1
|
|
fi
|
|
|
|
# Check if rootless setup is working
|
|
docker_verify_rootless
|
|
}
|
|
|
|
# Verify rootless container setup
|
|
function docker_verify_rootless() {
|
|
section_header "Verifying Rootless Container Setup"
|
|
|
|
log_info "Checking ${CONTAINER_RUNTIME} rootless configuration"
|
|
safe_execute ${CONTAINER_RUNTIME} system info
|
|
|
|
# Test basic rootless functionality
|
|
log_info "Testing rootless container execution"
|
|
safe_execute ${CONTAINER_RUNTIME} run --rm alpine:${ALPINE_VERSION} echo "Rootless container test successful"
|
|
|
|
log_info "Rootless container setup verified"
|
|
}
|
|
|
|
# Build container image with build tools
|
|
function docker_build_container() {
|
|
local dockerfile_path="${1:-${PROJECT_ROOT}/Dockerfile}"
|
|
local tag="${2:-${BUILDER_IMAGE}}"
|
|
|
|
section_header "Building Container Image"
|
|
|
|
# Create Dockerfile if it doesn't exist
|
|
if [[ ! -f "$dockerfile_path" ]]; then
|
|
docker_create_dockerfile "$dockerfile_path"
|
|
fi
|
|
|
|
log_info "Building container image: ${tag}"
|
|
safe_execute ${CONTAINER_RUNTIME} build -t "${tag}" -f "${dockerfile_path}" "${PROJECT_ROOT}"
|
|
|
|
log_info "Container image built successfully: ${tag}"
|
|
}
|
|
|
|
# Create optimized Dockerfile for build environment
|
|
function docker_create_dockerfile() {
|
|
local dockerfile_path="$1"
|
|
|
|
section_header "Creating Dockerfile"
|
|
|
|
cat > "$dockerfile_path" << 'EOF'
|
|
FROM alpine:3.22
|
|
|
|
# Install build dependencies
|
|
RUN apk add --no-cache \
|
|
build-base \
|
|
rust \
|
|
cargo \
|
|
upx \
|
|
git \
|
|
wget \
|
|
tar \
|
|
gzip \
|
|
xz \
|
|
cpio \
|
|
binutils \
|
|
linux-headers \
|
|
musl-dev \
|
|
pkgconfig \
|
|
openssl-dev
|
|
|
|
# Add Rust musl target
|
|
RUN rustup target add x86_64-unknown-linux-musl
|
|
|
|
# Create non-root user for builds
|
|
RUN adduser -D -s /bin/sh builder && \
|
|
chown -R builder:builder /home/builder
|
|
|
|
# Set working directory
|
|
WORKDIR /workspace
|
|
|
|
# Switch to non-root user
|
|
USER builder
|
|
|
|
# Set environment variables for static linking
|
|
ENV RUSTFLAGS="-C target-feature=+crt-static"
|
|
ENV CC_x86_64_unknown_linux_musl="musl-gcc"
|
|
ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER="musl-gcc"
|
|
|
|
CMD ["/bin/sh"]
|
|
EOF
|
|
|
|
log_info "Created Dockerfile: ${dockerfile_path}"
|
|
}
|
|
|
|
# Start rootless container for building
|
|
function docker_start_rootless() {
|
|
local image="${1:-${BUILDER_IMAGE}}"
|
|
local workdir="${2:-/workspace}"
|
|
local command="${3:-/bin/sh}"
|
|
|
|
section_header "Starting Rootless Container"
|
|
|
|
# Setup volume mounts
|
|
local user_args="--user $(id -u):$(id -g)"
|
|
local volume_args="-v ${PROJECT_ROOT}:${workdir}"
|
|
local env_args=""
|
|
|
|
# Pass through environment variables
|
|
local env_vars=(
|
|
"DEBUG"
|
|
"ALPINE_VERSION"
|
|
"KERNEL_VERSION"
|
|
"RUST_TARGET"
|
|
"OPTIMIZATION_LEVEL"
|
|
)
|
|
|
|
for var in "${env_vars[@]}"; do
|
|
if [[ -n "${!var:-}" ]]; then
|
|
env_args="${env_args} -e ${var}=${!var}"
|
|
fi
|
|
done
|
|
|
|
log_info "Starting container with rootless privileges"
|
|
safe_execute ${CONTAINER_RUNTIME} run --rm -it \
|
|
${user_args} \
|
|
${volume_args} \
|
|
${env_args} \
|
|
-w "${workdir}" \
|
|
"${image}" \
|
|
${command}
|
|
}
|
|
|
|
# Run build command in container
|
|
function docker_run_build() {
|
|
local build_command="${1:-./scripts/build.sh}"
|
|
local image="${2:-${BUILDER_IMAGE}}"
|
|
|
|
section_header "Running Build in Container"
|
|
|
|
# Extract script path from command (first part before any arguments)
|
|
local script_path=$(echo "$build_command" | cut -d' ' -f1)
|
|
|
|
# Ensure build script is executable
|
|
safe_execute chmod +x "${PROJECT_ROOT}/${script_path}"
|
|
|
|
log_info "Executing build command in container: ${build_command}"
|
|
|
|
# Pass through environment variables for proper logging
|
|
local env_args=""
|
|
local env_vars=(
|
|
"DEBUG"
|
|
"ALPINE_VERSION"
|
|
"KERNEL_VERSION"
|
|
"RUST_TARGET"
|
|
"OPTIMIZATION_LEVEL"
|
|
)
|
|
|
|
for var in "${env_vars[@]}"; do
|
|
if [[ -n "${!var:-}" ]]; then
|
|
env_args="${env_args} -e ${var}=${!var}"
|
|
fi
|
|
done
|
|
|
|
# Run with privileged access for chroot mounts and system operations
|
|
log_info "Container environment: ${env_args}"
|
|
${CONTAINER_RUNTIME} run --rm -it \
|
|
--privileged \
|
|
${env_args} \
|
|
-v "${PROJECT_ROOT}:/workspace" \
|
|
-w /workspace \
|
|
"${image}" \
|
|
${build_command}
|
|
}
|
|
|
|
# Commit container state for reuse
|
|
function docker_commit_builder() {
|
|
local container_id="$1"
|
|
local new_tag="${2:-${BUILDER_IMAGE}-cached}"
|
|
|
|
section_header "Committing Builder Container"
|
|
|
|
log_info "Committing container ${container_id} as ${new_tag}"
|
|
safe_execute ${CONTAINER_RUNTIME} commit "${container_id}" "${new_tag}"
|
|
|
|
log_info "Container committed successfully: ${new_tag}"
|
|
}
|
|
|
|
# Clean up container images and running containers
|
|
function docker_cleanup() {
|
|
local keep_builder="${1:-false}"
|
|
|
|
section_header "Cleaning Up Containers and Images"
|
|
|
|
if [[ "$keep_builder" != "true" ]]; then
|
|
log_info "Cleaning up builder containers and images"
|
|
|
|
# Stop and remove any containers using the builder image
|
|
local containers_using_image=$(${CONTAINER_RUNTIME} ps -a --filter "ancestor=${BUILDER_IMAGE}" --format "{{.ID}}" 2>/dev/null || true)
|
|
if [[ -n "$containers_using_image" ]]; then
|
|
log_info "Stopping containers using builder image"
|
|
for container_id in $containers_using_image; do
|
|
log_info "Stopping container: $container_id"
|
|
${CONTAINER_RUNTIME} stop "$container_id" 2>/dev/null || true
|
|
${CONTAINER_RUNTIME} rm "$container_id" 2>/dev/null || true
|
|
done
|
|
fi
|
|
|
|
# Stop and remove development container if it exists
|
|
local dev_container="zero-os-dev"
|
|
if ${CONTAINER_RUNTIME} container exists "$dev_container" 2>/dev/null; then
|
|
log_info "Removing development container: $dev_container"
|
|
${CONTAINER_RUNTIME} rm -f "$dev_container" 2>/dev/null || true
|
|
fi
|
|
|
|
# Now remove the images
|
|
log_info "Removing builder images"
|
|
${CONTAINER_RUNTIME} rmi "${BUILDER_IMAGE}" 2>/dev/null || log_warn "Could not remove ${BUILDER_IMAGE} (may not exist)"
|
|
${CONTAINER_RUNTIME} rmi "${BUILDER_IMAGE}-cached" 2>/dev/null || log_warn "Could not remove ${BUILDER_IMAGE}-cached (may not exist)"
|
|
fi
|
|
|
|
log_info "Pruning unused containers and images"
|
|
${CONTAINER_RUNTIME} system prune -f 2>/dev/null || log_warn "Container prune failed"
|
|
|
|
log_info "Container cleanup complete"
|
|
}
|
|
|
|
# Check container runtime capabilities
|
|
function docker_check_capabilities() {
|
|
section_header "Checking Container Capabilities"
|
|
|
|
# Check user namespace support
|
|
if [[ -f /proc/sys/user/max_user_namespaces ]]; then
|
|
local max_namespaces=$(cat /proc/sys/user/max_user_namespaces)
|
|
log_info "User namespaces available: ${max_namespaces}"
|
|
|
|
if [[ "$max_namespaces" -eq 0 ]]; then
|
|
log_warn "User namespaces are disabled, rootless containers may not work"
|
|
fi
|
|
fi
|
|
|
|
# Check subuid/subgid configuration
|
|
local current_user=$(whoami)
|
|
if [[ -f /etc/subuid ]] && grep -q "^${current_user}:" /etc/subuid; then
|
|
log_info "subuid configured for user: ${current_user}"
|
|
else
|
|
log_warn "subuid not configured for user: ${current_user}"
|
|
log_warn "Run: echo '${current_user}:100000:65536' | sudo tee -a /etc/subuid"
|
|
fi
|
|
|
|
if [[ -f /etc/subgid ]] && grep -q "^${current_user}:" /etc/subgid; then
|
|
log_info "subgid configured for user: ${current_user}"
|
|
else
|
|
log_warn "subgid not configured for user: ${current_user}"
|
|
log_warn "Run: echo '${current_user}:100000:65536' | sudo tee -a /etc/subgid"
|
|
fi
|
|
}
|
|
|
|
# Setup rootless environment
|
|
function docker_setup_rootless() {
|
|
section_header "Setting Up Rootless Environment"
|
|
|
|
local current_user=$(whoami)
|
|
|
|
# Check if running as root
|
|
if [[ "$EUID" -eq 0 ]]; then
|
|
log_error "Do not run as root. Rootless containers require non-root user."
|
|
return 1
|
|
fi
|
|
|
|
# Check and setup subuid/subgid if needed
|
|
if ! grep -q "^${current_user}:" /etc/subuid 2>/dev/null; then
|
|
log_info "Setting up subuid for ${current_user}"
|
|
echo "${current_user}:100000:65536" | sudo tee -a /etc/subuid
|
|
fi
|
|
|
|
if ! grep -q "^${current_user}:" /etc/subgid 2>/dev/null; then
|
|
log_info "Setting up subgid for ${current_user}"
|
|
echo "${current_user}:100000:65536" | sudo tee -a /etc/subgid
|
|
fi
|
|
|
|
# Initialize container runtime if needed
|
|
if [[ "$CONTAINER_RUNTIME" == "podman" ]]; then
|
|
log_info "Initializing Podman for rootless use"
|
|
safe_execute podman system migrate || true
|
|
fi
|
|
|
|
log_info "Rootless environment setup complete"
|
|
}
|
|
|
|
# Export functions
|
|
export -f docker_detect_runtime docker_verify_rootless
|
|
export -f docker_build_container docker_create_dockerfile
|
|
export -f docker_start_rootless docker_run_build
|
|
export -f docker_commit_builder docker_cleanup
|
|
export -f docker_check_capabilities docker_setup_rootless
|