#!/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_TAR="" METRICS_IMAGE="" METRICS_IMAGE_TAR="" PROMETHEUS_IMAGE="${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}" PROMETHEUS_IMAGE_TAR="${PROMETHEUS_IMAGE_TAR:-}" GRAFANA_IMAGE="${GRAFANA_IMAGE:-grafana/grafana:11.3.1}" GRAFANA_IMAGE_TAR="${GRAFANA_IMAGE_TAR:-}" OUT_DIR="${OUT_DIR:-}" PACKAGE_PREFIX="${PACKAGE_PREFIX:-}" TEMPLATE_DIR="${TEMPLATE_DIR:-$REPO_ROOT/deploy/docker-installer}" ALLOW_DIRTY=0 usage() { cat <<'USAGE' Usage: scripts/docker/build_docker_installer_package.sh --arch amd64|arm64 [options] Options: --arch Target architecture: amd64|arm64 --prometheus-image Prometheus image tag to record and package. --prometheus-image-tar Existing Prometheus docker save tar/tar.gz to include. --grafana-image Grafana image tag to record and package. --grafana-image-tar Existing Grafana docker save tar/tar.gz to include. --out-dir Output directory. --prefix Package directory/tar prefix. --template-dir Package template directory. --allow-dirty Allow a development build from a dirty worktree and mark image tags, package name and manifest as dirty. -h, --help Show help. Every package rebuilds runtime and metrics images from the current HEAD. Their tags are ours-rp-runtime-: and ours-rp-metrics-:. External runtime/metrics image tags and tar archives are intentionally rejected. USAGE } normalize_arch() { case "$1" in amd64|x86_64) printf 'amd64\n' ;; arm64|aarch64) printf 'arm64\n' ;; *) return 1 ;; esac } platform_for_arch() { printf 'linux/%s\n' "$1" } safe_tag_name() { printf '%s' "$1" | tr '/:' '--' } runtime_image_tag() { local arch="$1" local revision="$2" printf 'ours-rp-runtime-%s:%s\n' "$arch" "$revision" } metrics_image_tag() { local arch="$1" local revision="$2" printf 'ours-rp-metrics-%s:%s\n' "$arch" "$revision" } default_package_prefix() { printf 'ours-rp-installer-%s\n' "$1" } default_host_data_dir() { printf '/var/lib/ours-rp-%s-installer\n' "$1" } default_compose_project() { printf 'ours-rp-%s-installer\n' "$1" } default_metrics_instance() { printf '%s-installer\n' "$1" } replace_or_append_env() { local env_path="$1" local key="$2" local value="$3" local tmp_env tmp_env="${env_path}.tmp" awk -v key="$key" -v value="$value" ' BEGIN { done=0 } $0 ~ "^" key "=" { print key "=" value; done=1; next } { print } END { if (!done) print key "=" value } ' "$env_path" > "$tmp_env" mv "$tmp_env" "$env_path" } replace_text_in_file() { local file_path="$1" local old_text="$2" local new_text="$3" OLD_TEXT="$old_text" NEW_TEXT="$new_text" perl -0pi -e 's/\Q$ENV{OLD_TEXT}\E/$ENV{NEW_TEXT}/g' "$file_path" } ensure_image_for_platform() { local image="$1" local expected_platform="$2" local role="$3" local actual_platform if ! docker image inspect "$image" >/dev/null 2>&1; then echo "pulling $role image for $expected_platform: $image" >&2 docker pull --platform "$expected_platform" "$image" >&2 fi actual_platform="$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$image" 2>/dev/null || echo unknown)" if [[ "$actual_platform" != "$expected_platform" ]]; then echo "re-pulling $role image for $expected_platform: $image (current=$actual_platform)" >&2 docker pull --platform "$expected_platform" "$image" >&2 actual_platform="$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$image" 2>/dev/null || echo unknown)" fi [[ "$actual_platform" == "$expected_platform" ]] || { cat >&2 <&2 exit 2 } printf '%s\n' "$existing_tar" return 0 fi ensure_image_for_platform "$image" "$expected_platform" "$role" local tar_path="$out_dir/$(safe_tag_name "$image")-${TARGET_ARCH}.tar.gz" echo "saving $role image to $tar_path" >&2 docker save "$image" | gzip -c > "$tar_path" printf '%s\n' "$tar_path" } image_label() { local image="$1" local key="$2" docker image inspect --format "{{ index .Config.Labels \"$key\" }}" "$image" } 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-tar|--metrics-image|--metrics-image-tar) echo "$1 is no longer supported: installer packages always rebuild runtime and metrics images from current HEAD" >&2 exit 2 ;; --prometheus-image) PROMETHEUS_IMAGE="$2" shift 2 ;; --prometheus-image-tar) PROMETHEUS_IMAGE_TAR="$2" shift 2 ;; --grafana-image) GRAFANA_IMAGE="$2" shift 2 ;; --grafana-image-tar) GRAFANA_IMAGE_TAR="$2" shift 2 ;; --out-dir) OUT_DIR="$2" shift 2 ;; --prefix) PACKAGE_PREFIX="$2" shift 2 ;; --template-dir) TEMPLATE_DIR="$2" shift 2 ;; --allow-dirty) ALLOW_DIRTY=1 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 } [[ -d "$TEMPLATE_DIR" ]] || { echo "missing template dir: $TEMPLATE_DIR" >&2 exit 2 } SOURCE_COMMIT_FULL="$(git -C "$REPO_ROOT" rev-parse HEAD)" SOURCE_COMMIT_SHORT="$(git -C "$REPO_ROOT" rev-parse --short=8 HEAD)" SOURCE_DIRTY="false" if [[ -n "$(git -C "$REPO_ROOT" status --short)" ]]; then SOURCE_DIRTY="true" fi if [[ "$SOURCE_DIRTY" == "true" && "$ALLOW_DIRTY" != "1" ]]; then cat >&2 <&2; exit 2; } [[ -f "$METRICS_IMAGE_TAR" ]] || { echo "metrics image build did not produce $METRICS_IMAGE_TAR" >&2; exit 2; } runtime_image_revision="$(image_label "$IMAGE_TAG" "org.opencontainers.image.revision")" metrics_image_revision="$(image_label "$METRICS_IMAGE" "org.opencontainers.image.revision")" runtime_image_dirty="$(image_label "$IMAGE_TAG" "org.opencontainers.image.source-dirty")" metrics_image_dirty="$(image_label "$METRICS_IMAGE" "org.opencontainers.image.source-dirty")" [[ "$runtime_image_revision" == "$SOURCE_COMMIT_FULL" ]] || { echo "runtime image revision mismatch: $runtime_image_revision != $SOURCE_COMMIT_FULL" >&2; exit 2; } [[ "$metrics_image_revision" == "$SOURCE_COMMIT_FULL" ]] || { echo "metrics image revision mismatch: $metrics_image_revision != $SOURCE_COMMIT_FULL" >&2; exit 2; } [[ "$runtime_image_dirty" == "$SOURCE_DIRTY" ]] || { echo "runtime image dirty flag mismatch" >&2; exit 2; } [[ "$metrics_image_dirty" == "$SOURCE_DIRTY" ]] || { echo "metrics image dirty flag mismatch" >&2; exit 2; } package_name="${PACKAGE_PREFIX}-${REVISION_TOKEN}-${TIMESTAMP}" stage="$OUT_DIR/$package_name" tar_path="$OUT_DIR/$package_name.tar.gz" rm -rf "$stage" rsync -a --delete "$TEMPLATE_DIR"/ "$stage"/ mkdir -p "$stage/images" cp "$IMAGE_TAR" "$stage/images/" cp "$METRICS_IMAGE_TAR" "$stage/images/" monitor_image_stage="$OUT_DIR/.monitor-images-$TARGET_ARCH-$TIMESTAMP" rm -rf "$monitor_image_stage" mkdir -p "$monitor_image_stage" prometheus_tar="$(save_image_if_needed "$PROMETHEUS_IMAGE" "$PROMETHEUS_IMAGE_TAR" "$monitor_image_stage" "prometheus" "$TARGET_PLATFORM")" grafana_tar="$(save_image_if_needed "$GRAFANA_IMAGE" "$GRAFANA_IMAGE_TAR" "$monitor_image_stage" "grafana" "$TARGET_PLATFORM")" cp "$prometheus_tar" "$stage/images/" cp "$grafana_tar" "$stage/images/" if [[ -f "$stage/.env.example" ]]; then replace_or_append_env "$stage/.env.example" "PACKAGE_ARCH" "$TARGET_ARCH" replace_or_append_env "$stage/.env.example" "PACKAGE_PLATFORM" "$TARGET_PLATFORM" replace_or_append_env "$stage/.env.example" "COMPOSE_PROJECT_NAME" "$COMPOSE_PROJECT_DEFAULT" replace_or_append_env "$stage/.env.example" "RPKI_IMAGE" "$IMAGE_TAG" replace_or_append_env "$stage/.env.example" "RPKI_PLATFORM" "$TARGET_PLATFORM" replace_or_append_env "$stage/.env.example" "METRICS_IMAGE" "$METRICS_IMAGE" replace_or_append_env "$stage/.env.example" "METRICS_PLATFORM" "$TARGET_PLATFORM" replace_or_append_env "$stage/.env.example" "HOST_DATA_DIR" "$HOST_DATA_DIR_DEFAULT" replace_or_append_env "$stage/.env.example" "RTR_REPORT_DIR" "$HOST_DATA_DIR_DEFAULT/empty-rtr-report" replace_or_append_env "$stage/.env.example" "RTR_REPORT_CONTAINER_DIR" "/var/lib/ours-rp/rtr-report" replace_or_append_env "$stage/.env.example" "ALLOW_CROSS_ARCH" "0" replace_or_append_env "$stage/.env.example" "METRICS_INSTANCE" "$METRICS_INSTANCE_DEFAULT" replace_or_append_env "$stage/.env.example" "MONITOR_PLATFORM" "$TARGET_PLATFORM" replace_or_append_env "$stage/.env.example" "PROMETHEUS_IMAGE" "$PROMETHEUS_IMAGE" replace_or_append_env "$stage/.env.example" "GRAFANA_IMAGE" "$GRAFANA_IMAGE" fi replace_text_in_file "$stage/docs/README.zh-CN.md" "__PACKAGE_ARCH__" "$TARGET_ARCH" replace_text_in_file "$stage/docs/README.en.md" "__PACKAGE_ARCH__" "$TARGET_ARCH" replace_text_in_file "$stage/docs/operations.zh-CN.md" "__PACKAGE_ARCH__" "$TARGET_ARCH" replace_text_in_file "$stage/docs/operations.en.md" "__PACKAGE_ARCH__" "$TARGET_ARCH" replace_text_in_file "$stage/docs/troubleshooting.zh-CN.md" "__PACKAGE_ARCH__" "$TARGET_ARCH" replace_text_in_file "$stage/docs/troubleshooting.en.md" "__PACKAGE_ARCH__" "$TARGET_ARCH" replace_text_in_file "$stage/docs/README.zh-CN.md" "__PACKAGE_PLATFORM__" "$TARGET_PLATFORM" replace_text_in_file "$stage/docs/README.en.md" "__PACKAGE_PLATFORM__" "$TARGET_PLATFORM" replace_text_in_file "$stage/docs/troubleshooting.zh-CN.md" "__PACKAGE_PLATFORM__" "$TARGET_PLATFORM" replace_text_in_file "$stage/docs/troubleshooting.en.md" "__PACKAGE_PLATFORM__" "$TARGET_PLATFORM" replace_text_in_file "$stage/docs/README.zh-CN.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT" replace_text_in_file "$stage/docs/README.en.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT" replace_text_in_file "$stage/docs/operations.zh-CN.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT" replace_text_in_file "$stage/docs/operations.en.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT" replace_text_in_file "$stage/docs/troubleshooting.zh-CN.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT" replace_text_in_file "$stage/docs/troubleshooting.en.md" "__HOST_DATA_DIR__" "$HOST_DATA_DIR_DEFAULT" replace_text_in_file "$stage/docs/README.zh-CN.md" "__RUNTIME_IMAGE__" "$IMAGE_TAG" replace_text_in_file "$stage/docs/README.en.md" "__RUNTIME_IMAGE__" "$IMAGE_TAG" replace_text_in_file "$stage/docs/README.zh-CN.md" "__METRICS_IMAGE__" "$METRICS_IMAGE" replace_text_in_file "$stage/docs/README.en.md" "__METRICS_IMAGE__" "$METRICS_IMAGE" runtime_tar_sha256="$(sha256sum "$IMAGE_TAR" | awk '{print $1}')" metrics_tar_sha256="$(sha256sum "$METRICS_IMAGE_TAR" | awk '{print $1}')" prometheus_tar_sha256="$(sha256sum "$prometheus_tar" | awk '{print $1}')" grafana_tar_sha256="$(sha256sum "$grafana_tar" | awk '{print $1}')" cat > "$stage/PACKAGE-MANIFEST.env" </dev/null | wc -l | tr -d ' ') PACKAGE_ARCH=$TARGET_ARCH PACKAGE_PLATFORM=$TARGET_PLATFORM package_arch=$TARGET_ARCH package_platform=$TARGET_PLATFORM cross_arch_default=deny cross_arch_enable_var=ALLOW_CROSS_ARCH RPKI_IMAGE=$IMAGE_TAG RPKI_PLATFORM=$TARGET_PLATFORM image_tag=$IMAGE_TAG image_tar=$(basename "$IMAGE_TAR") image_tar_size_bytes=$(wc -c < "$IMAGE_TAR") image_tar_sha256=$runtime_tar_sha256 runtime_image_revision=$runtime_image_revision runtime_image_dirty=$runtime_image_dirty rpki_image=$IMAGE_TAG rpki_platform=$TARGET_PLATFORM METRICS_IMAGE=$METRICS_IMAGE METRICS_PLATFORM=$TARGET_PLATFORM metrics_image=$METRICS_IMAGE metrics_image_tar=$(basename "$METRICS_IMAGE_TAR") metrics_image_tar_size_bytes=$(wc -c < "$METRICS_IMAGE_TAR") metrics_image_tar_sha256=$metrics_tar_sha256 metrics_image_revision=$metrics_image_revision metrics_image_dirty=$metrics_image_dirty PROMETHEUS_IMAGE=$PROMETHEUS_IMAGE prometheus_image=$PROMETHEUS_IMAGE prometheus_image_tar=$(basename "$prometheus_tar") prometheus_image_tar_size_bytes=$(wc -c < "$prometheus_tar") prometheus_image_tar_sha256=$prometheus_tar_sha256 GRAFANA_IMAGE=$GRAFANA_IMAGE grafana_image=$GRAFANA_IMAGE grafana_image_tar=$(basename "$grafana_tar") grafana_image_tar_size_bytes=$(wc -c < "$grafana_tar") grafana_image_tar_sha256=$grafana_tar_sha256 target_platform=$TARGET_PLATFORM MONITOR_PLATFORM=$TARGET_PLATFORM rpki_image_tar=$(basename "$IMAGE_TAR") monitor_platform=$TARGET_PLATFORM metrics_platform=$TARGET_PLATFORM default_host_data_dir=$HOST_DATA_DIR_DEFAULT default_compose_project_name=$COMPOSE_PROJECT_DEFAULT default_metrics_instance=$METRICS_INSTANCE_DEFAULT EOF cat > "$stage/PACKAGE-SUMMARY.txt" < "$OUT_DIR/$package_name.summary.env" echo "package built: $tar_path"