#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$BASH_SOURCE")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PATCH_SOURCE="$REPO_ROOT/deploy/docker-installer/custom-tal-patch" BASE_COMPONENT_PACKAGE="" OUT_DIR="$(printenv OUT_DIR 2>/dev/null || true)" PREFIX="$(printenv PREFIX 2>/dev/null || true)" ALLOW_DIRTY=0 [[ -n "$OUT_DIR" ]] || OUT_DIR="$REPO_ROOT/target/custom-tal-patch" [[ -n "$PREFIX" ]] || PREFIX="ours-rp-custom-tal-patch" usage() { cat <<'USAGE' Usage: scripts/docker/build_custom_tal_patch.sh \ --base-component-package \ [--out-dir ] [--prefix ] [--allow-dirty] The patch targets the Arm64 component package from the customer stack. It overlays the runner, Compose, fixture mount and test-service tools; it does not rebuild or replace the runtime image. USAGE } die() { echo "error: $*" >&2 exit 2 } while [[ $# -gt 0 ]]; do case "$1" in --base-component-package) BASE_COMPONENT_PACKAGE="$2"; shift 2 ;; --out-dir) OUT_DIR="$2"; shift 2 ;; --prefix) PREFIX="$2"; shift 2 ;; --allow-dirty) ALLOW_DIRTY=1; shift ;; -h|--help) usage; exit 0 ;; *) die "unknown option: $1" ;; esac done [[ -f "$BASE_COMPONENT_PACKAGE" ]] || die "missing base component package: $BASE_COMPONENT_PACKAGE" [[ -d "$PATCH_SOURCE" ]] || die "missing patch source: $PATCH_SOURCE" SOURCE_COMMIT="$(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 die "source worktree is dirty; use --allow-dirty for a development patch" fi package_root="$(tar -tzf "$BASE_COMPONENT_PACKAGE" | awk -F/ '!seen { print $1; seen = 1 }')" [[ -n "$package_root" ]] || die "cannot determine component package root" manifest_text="$(tar -xOf "$BASE_COMPONENT_PACKAGE" "$package_root/PACKAGE-MANIFEST.env")" \ || die "base component package has no PACKAGE-MANIFEST.env" read_manifest_value() { printf '%s\n' "$manifest_text" | awk -F= -v key="$1" '$1 == key { print substr($0, index($0, "=") + 1); exit }' } base_source_commit="$(read_manifest_value source_commit)" base_package_arch="$(read_manifest_value package_arch)" base_runtime_image="$(read_manifest_value runtime_image)" [[ "$base_source_commit" == 9f4f4cf069e9fa8f6f97ed0f1065adac38abe82f ]] \ || die "base source commit mismatch: $base_source_commit" [[ "$base_package_arch" == arm64 ]] || die "base package must be arm64: $base_package_arch" timestamp="$(date -u +%Y%m%dT%H%M%SZ)" patch_name="$PREFIX-$SOURCE_COMMIT_SHORT" if [[ "$SOURCE_DIRTY" == true ]]; then patch_name="$patch_name-dirty" fi patch_name="$patch_name-$timestamp" stage="$OUT_DIR/$patch_name" tar_path="$OUT_DIR/$patch_name.tar.gz" base_sha256="$(sha256sum "$BASE_COMPONENT_PACKAGE" | awk '{ print $1 }')" rm -rf "$stage" mkdir -p "$stage/payload/compose" "$stage/payload/scripts/soak" \ "$stage/payload/custom-fixtures" "$stage/payload/tools" cp "$PATCH_SOURCE/apply_custom_tal_patch.sh" "$stage/" cp "$PATCH_SOURCE/rollback_custom_tal_patch.sh" "$stage/" cp "$PATCH_SOURCE/custom-tal.env.example" "$stage/" cp "$REPO_ROOT/deploy/docker-installer/compose/docker-compose.yml" "$stage/payload/compose/" cp "$REPO_ROOT/scripts/soak/run_soak.sh" "$stage/payload/scripts/soak/" cp "$REPO_ROOT/deploy/docker-installer/custom-fixtures/README.md" "$stage/payload/custom-fixtures/" cp "$PATCH_SOURCE/tools/generate_custom_fixture.py" "$stage/payload/tools/" cp "$PATCH_SOURCE/tools/serve_https.py" "$stage/payload/tools/" cp "$PATCH_SOURCE/tools/start_fixed_services.sh" "$stage/payload/tools/" cp "$PATCH_SOURCE/tools/stop_fixed_services.sh" "$stage/payload/tools/" chmod +x "$stage/apply_custom_tal_patch.sh" "$stage/rollback_custom_tal_patch.sh" \ "$stage/payload/scripts/soak/run_soak.sh" "$stage/payload/tools/"*.sh \ "$stage/payload/tools/"*.py { echo "patch_schema_version=1" echo "patch_name=$patch_name" echo "created_at_utc=$timestamp" echo "source_commit=$SOURCE_COMMIT" echo "source_commit_short=$SOURCE_COMMIT_SHORT" echo "source_dirty=$SOURCE_DIRTY" echo "base_component_archive=$(basename "$BASE_COMPONENT_PACKAGE")" echo "base_component_archive_sha256=$base_sha256" echo "base_component_root=$package_root" echo "base_source_commit=$base_source_commit" echo "base_package_arch=$base_package_arch" echo "base_runtime_image=$base_runtime_image" echo "overlay_runtime_image_unchanged=true" echo "custom_mode=RIRS=custom,TAL_INPUT_MODE=custom-file-with-ta" echo "rrdp_port=18443" echo "rsync_port=1873" echo "payload_root=payload" while IFS= read -r relative_path; do safe_name="$(printf '%s' "$relative_path" | tr '/.' '__')" file_sha256="$(sha256sum "$stage/payload/$relative_path" | awk '{ print $1 }')" echo "payload_"$safe_name"_sha256=$file_sha256" done < <(cd "$stage/payload" && find . -type f -printf '%P\n' | sort) } > "$stage/PATCH-MANIFEST.env" cat > "$stage/PATCH-SUMMARY.txt" < "$OUT_DIR/$patch_name.summary.env" echo "patch built: $tar_path"