#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ARTIFACT_DIR="" VERSION="" BUILD="" COMMIT="" REQUIRE_NATIVE_ARCHITECTURE="" usage() { cat <<'EOF' usage: validate_github_release_artifacts.sh \ --directory PATH \ --version X.Y.Z \ --build POSITIVE_INTEGER \ --commit FULL_LOWERCASE_GIT_COMMIT \ [--require-native-architecture arm64|x86_64] Validates the exact three-file GitHub Release set: two ad-hoc-signed thin App ZIPs and one checksum inventory covering exactly those ZIPs. Every ZIP receives a bounded safety preflight before extraction. The required version, build, and full commit are bound to Info.plist, runtime-manifest.json, and the shared App/PetCore/CLI build identity. EOF } while (($# > 0)); do case "$1" in --directory) (($# >= 2)) || { usage >&2; exit 2; } ARTIFACT_DIR="$2" shift 2 ;; --version) (($# >= 2)) || { usage >&2; exit 2; } VERSION="$2" shift 2 ;; --build) (($# >= 2)) || { usage >&2; exit 2; } BUILD="$2" shift 2 ;; --commit) (($# >= 2)) || { usage >&2; exit 2; } COMMIT="$2" shift 2 ;; --require-native-architecture) (($# >= 2)) || { usage >&2; exit 2; } REQUIRE_NATIVE_ARCHITECTURE="$2" shift 2 ;; -h|--help) usage exit 0 ;; *) printf 'unknown argument: %s\n' "$1" >&2 usage >&2 exit 2 ;; esac done [[ -d "$ARTIFACT_DIR" && ! -L "$ARTIFACT_DIR" ]] || { echo 'GitHub Release validation requires a regular artifact directory' >&2 exit 2 } [[ "$VERSION" =~ ^[0-9]+([.][0-9]+){2}$ ]] || { echo '--version must be a three-component semantic version' >&2 exit 2 } [[ "$BUILD" =~ ^[1-9][0-9]*$ ]] || { echo '--build must be a positive integer' >&2 exit 2 } [[ "$COMMIT" =~ ^[0-9a-f]{40}$ ]] || { echo '--commit must be a full lowercase Git commit' >&2 exit 2 } case "$REQUIRE_NATIVE_ARCHITECTURE" in "") ;; arm64|x86_64) ;; *) echo '--require-native-architecture must be arm64 or x86_64' >&2; exit 2 ;; esac for dependency in ditto python3 shasum; do command -v "$dependency" >/dev/null 2>&1 || { printf 'GitHub Release artifact validation requires %s\n' "$dependency" >&2 exit 1 } done if [[ -n "$REQUIRE_NATIVE_ARCHITECTURE" ]]; then HOST_ARCH="$(uname -m)" case "$HOST_ARCH" in aarch64) HOST_ARCH="arm64" ;; amd64) HOST_ARCH="x86_64" ;; esac [[ "$HOST_ARCH" == "$REQUIRE_NATIVE_ARCHITECTURE" ]] || { printf 'native packaged validation requires %s hardware; runner reports %s\n' \ "$REQUIRE_NATIVE_ARCHITECTURE" "$HOST_ARCH" >&2 exit 1 } fi "$ROOT_DIR/script/validate_release_artifact_metadata.py" \ --directory "$ARTIFACT_DIR" \ --version "$VERSION" TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/apc-github-release-validation.XXXXXX")" cleanup() { rm -rf "$TMP_DIR" } trap cleanup EXIT if [[ -n "$REQUIRE_NATIVE_ARCHITECTURE" ]]; then ARCHITECTURES=("$REQUIRE_NATIVE_ARCHITECTURE") else ARCHITECTURES=(arm64 x86_64) fi for architecture in "${ARCHITECTURES[@]}"; do archive_name="AgentPetCompanion-$VERSION-macos-$architecture.zip" archive="$ARTIFACT_DIR/$archive_name" extract_dir="$TMP_DIR/$architecture" # This must remain before every extraction of downloaded release content. "$ROOT_DIR/script/validate_release_zip.py" --archive "$archive" mkdir -p "$extract_dir" ditto -x -k "$archive" "$extract_dir" extracted_app="$extract_dir/AgentPetCompanion.app" [[ -d "$extracted_app" && ! -L "$extracted_app" ]] || { echo 'GitHub Release ZIP did not extract the expected regular App bundle' >&2 exit 1 } "$ROOT_DIR/script/validate_release_identity.py" \ --app "$extracted_app" \ --architecture "$architecture" \ --version "$VERSION" \ --build "$BUILD" \ --commit "$COMMIT" "$ROOT_DIR/script/validate_app_bundle.sh" \ --github-release \ --architecture "$architecture" \ "$extracted_app" done if [[ -n "$REQUIRE_NATIVE_ARCHITECTURE" ]]; then printf 'GitHub Release exact inventory and native %s artifact validated for one exact identity\n' \ "$REQUIRE_NATIVE_ARCHITECTURE" else echo 'GitHub Release artifacts validated for both architectures and one exact identity' fi