#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" TARGET_ARCH="${TARGET_ARCH:-}" IMAGE_TAG="${IMAGE_TAG:-}" BUILDER_IMAGE="${BUILDER_IMAGE:-rust:1-bookworm}" RUNTIME_IMAGE="${RUNTIME_IMAGE:-debian:bookworm-slim}" OUT_DIR="${OUT_DIR:-}" DOCKERFILE="${DOCKERFILE:-$REPO_ROOT/docker/ours-rp-runtime.Dockerfile}" BUILDER_NAME="${BUILDER_NAME:-default}" INSTALL_BINFMT="${INSTALL_BINFMT:-1}" SAVE_IMAGE="${SAVE_IMAGE:-1}" LOAD_IMAGE="${LOAD_IMAGE:-1}" usage() { cat <<'USAGE' Usage: scripts/docker/build_docker_runtime_image.sh --arch amd64|arm64 [options] Options: --arch Target architecture: amd64|arm64 --image Docker image tag (default: ours-rp-runtime-:) --out-dir Directory for docker save tar.gz (default: target/-docker) --dockerfile Dockerfile path --builder buildx builder name --builder-image Builder base image (default: rust:1-bookworm) --runtime-image Runtime base image (default: debian:bookworm-slim) --no-binfmt Do not install binfmt/qemu for cross-architecture builds --no-save Build image but do not docker save it --no-load Use buildx output tar instead of --load -h, --help Show this help USAGE } normalize_arch() { case "$1" in amd64|x86_64) printf 'amd64\n' ;; arm64|aarch64) printf 'arm64\n' ;; *) return 1 ;; esac } safe_tag_name() { printf '%s' "$1" | tr '/:' '--' } platform_for_arch() { printf 'linux/%s\n' "$1" } default_image_tag() { local arch="$1" local commit_short="$2" printf 'ours-rp-runtime-%s:%s\n' "$arch" "$commit_short" } require_command() { command -v "$1" >/dev/null 2>&1 || { echo "missing required command: $1" >&2 exit 2 } } host_arch() { local raw_arch raw_arch="$(uname -m)" normalize_arch "$raw_arch" || { echo "unsupported host architecture: $raw_arch" >&2 exit 2 } } while [[ $# -gt 0 ]]; do case "$1" in --arch) TARGET_ARCH="$(normalize_arch "$2")" || { echo "unsupported target architecture: $2" >&2 exit 2 } shift 2 ;; --image) IMAGE_TAG="$2" shift 2 ;; --out-dir) OUT_DIR="$2" shift 2 ;; --dockerfile) DOCKERFILE="$2" shift 2 ;; --builder) BUILDER_NAME="$2" shift 2 ;; --builder-image) BUILDER_IMAGE="$2" shift 2 ;; --runtime-image) RUNTIME_IMAGE="$2" shift 2 ;; --no-binfmt) INSTALL_BINFMT=0 shift ;; --no-save) SAVE_IMAGE=0 shift ;; --no-load) LOAD_IMAGE=0 shift ;; -h|--help) usage exit 0 ;; *) echo "unknown option: $1" >&2 usage >&2 exit 2 ;; esac done [[ -n "$TARGET_ARCH" ]] || { echo "--arch is required" >&2 usage >&2 exit 2 } SOURCE_COMMIT_FULL="$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || echo unknown)" SOURCE_COMMIT_SHORT="$(git -C "$REPO_ROOT" rev-parse --short=8 HEAD 2>/dev/null || echo unknown)" SOURCE_DIRTY="false" if [[ -n "$(git -C "$REPO_ROOT" status --short 2>/dev/null)" ]]; then SOURCE_DIRTY="true" fi BUILD_TIMESTAMP_UTC="$(date -u +%Y-%m-%dT%H:%M:%SZ)" if [[ -z "$IMAGE_TAG" ]]; then default_revision="$SOURCE_COMMIT_SHORT" if [[ "$SOURCE_DIRTY" == "true" ]]; then default_revision="${default_revision}-dirty" fi IMAGE_TAG="$(default_image_tag "$TARGET_ARCH" "$default_revision")" fi if [[ -z "$OUT_DIR" ]]; then OUT_DIR="$REPO_ROOT/target/${TARGET_ARCH}-docker" fi require_command docker mkdir -p "$OUT_DIR" target_platform="$(platform_for_arch "$TARGET_ARCH")" local_host_arch="$(host_arch)" if [[ "$INSTALL_BINFMT" == "1" && "$local_host_arch" != "$TARGET_ARCH" ]]; then echo "installing binfmt/qemu for $TARGET_ARCH" docker run --rm --privileged tonistiigi/binfmt --install "$TARGET_ARCH" fi if [[ "$BUILDER_NAME" != "default" ]] && ! docker buildx inspect "$BUILDER_NAME" >/dev/null 2>&1; then docker buildx create --name "$BUILDER_NAME" --driver docker-container --use >/dev/null else docker buildx use "$BUILDER_NAME" >/dev/null fi docker buildx inspect --bootstrap >/dev/null metadata_path="$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").build-metadata.json" tar_path="$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").tar.gz" build_log="$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").build.log" echo "building $target_platform image: $IMAGE_TAG" echo "repo: $REPO_ROOT" echo "dockerfile: $DOCKERFILE" echo "builder_image: $BUILDER_IMAGE" echo "runtime_image: $RUNTIME_IMAGE" echo "source_commit: $SOURCE_COMMIT_FULL" echo "source_dirty: $SOURCE_DIRTY" echo "build_timestamp_utc: $BUILD_TIMESTAMP_UTC" start_epoch="$(date +%s)" if [[ "$LOAD_IMAGE" == "1" ]]; then docker buildx build \ --platform "$target_platform" \ --builder "$BUILDER_NAME" \ --pull=false \ --load \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg "BUILDER_IMAGE=$BUILDER_IMAGE" \ --build-arg "RUNTIME_IMAGE=$RUNTIME_IMAGE" \ --build-arg "SOURCE_COMMIT=$SOURCE_COMMIT_FULL" \ --build-arg "SOURCE_DIRTY=$SOURCE_DIRTY" \ --build-arg "BUILD_TIMESTAMP_UTC=$BUILD_TIMESTAMP_UTC" \ --metadata-file "$metadata_path" \ -t "$IMAGE_TAG" \ -f "$DOCKERFILE" \ "$REPO_ROOT" 2>&1 | tee "$build_log" else raw_tar_path="$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").tar" docker buildx build \ --platform "$target_platform" \ --builder "$BUILDER_NAME" \ --pull=false \ --output "type=docker,dest=$raw_tar_path" \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg "BUILDER_IMAGE=$BUILDER_IMAGE" \ --build-arg "RUNTIME_IMAGE=$RUNTIME_IMAGE" \ --build-arg "SOURCE_COMMIT=$SOURCE_COMMIT_FULL" \ --build-arg "SOURCE_DIRTY=$SOURCE_DIRTY" \ --build-arg "BUILD_TIMESTAMP_UTC=$BUILD_TIMESTAMP_UTC" \ --metadata-file "$metadata_path" \ -t "$IMAGE_TAG" \ -f "$DOCKERFILE" \ "$REPO_ROOT" 2>&1 | tee "$build_log" gzip -f "$raw_tar_path" tar_path="${raw_tar_path}.gz" fi elapsed_secs=$(( $(date +%s) - start_epoch )) if [[ "$SAVE_IMAGE" == "1" && "$LOAD_IMAGE" == "1" ]]; then echo "saving image to $tar_path" docker save "$IMAGE_TAG" | gzip -c > "$tar_path" fi tar_sha256="" if [[ -f "$tar_path" ]]; then tar_sha256="$(sha256sum "$tar_path" | awk '{print $1}')" fi { echo "image=$IMAGE_TAG" echo "platform=$target_platform" echo "arch=$TARGET_ARCH" echo "builder_image=$BUILDER_IMAGE" echo "runtime_image=$RUNTIME_IMAGE" echo "elapsed_secs=$elapsed_secs" echo "metadata=$metadata_path" echo "tar=$tar_path" echo "tar_size_bytes=$(wc -c < "$tar_path" 2>/dev/null || echo 0)" echo "tar_sha256=$tar_sha256" echo "source_commit=$SOURCE_COMMIT_FULL" echo "source_commit_short=$SOURCE_COMMIT_SHORT" echo "source_dirty=$SOURCE_DIRTY" echo "build_timestamp_utc=$BUILD_TIMESTAMP_UTC" echo "git_commit=$SOURCE_COMMIT_FULL" echo "git_status_count=$(git -C "$REPO_ROOT" status --short 2>/dev/null | wc -l | tr -d ' ')" echo "built_at_utc=$BUILD_TIMESTAMP_UTC" } > "$OUT_DIR/$(safe_tag_name "$IMAGE_TAG").build-summary.env" echo "build complete: elapsed=${elapsed_secs}s tar=$tar_path"