kepr-build-apk.sh

5.5 KB · raw

1#!/bin/sh2# Build and publish the signed release APK for Kepr Builds.3#4# Release authority is injected through protected process environment variables.5# The script checks that configuration before starting Gradle, then validates the produced6# APK's package, version, APK Signature Scheme, and pinned release certificate7# before copying anything to the Kepr staging directory.8set -eu910JAVA_HOME="/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home"11if [ ! -d "$JAVA_HOME" ]; then12    echo "refusing to publish: required Java 17 runtime not found at $JAVA_HOME" >&213    exit 114fi15export JAVA_HOME1617ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)18cd "$ROOT_DIR"1920# Keep the tool-selection and public metadata gates shared with the hermetic21# negative-path test; this script owns signing/build orchestration only.22# shellcheck source=scripts/kepr-build-helpers.sh23. "$ROOT_DIR/scripts/kepr-build-helpers.sh"2425: "${PEACH_RELEASE_STORE_FILE:=}"26: "${PEACH_RELEASE_STORE_PASSWORD:=}"27: "${PEACH_RELEASE_KEY_ALIAS:=}"28: "${PEACH_RELEASE_KEY_PASSWORD:=}"2930if [ -z "$PEACH_RELEASE_STORE_FILE" ] || [ -z "$PEACH_RELEASE_STORE_PASSWORD" ] ||31    [ -z "$PEACH_RELEASE_KEY_ALIAS" ] || [ -z "$PEACH_RELEASE_KEY_PASSWORD" ]; then32    echo "refusing to publish: protected release signing environment is incomplete" >&233    exit 134fi3536case "$PEACH_RELEASE_STORE_FILE" in37    /*) STORE_FILE="$PEACH_RELEASE_STORE_FILE" ;;38    *) STORE_FILE="$ROOT_DIR/$PEACH_RELEASE_STORE_FILE" ;;39esac40if [ ! -f "$STORE_FILE" ] || [ ! -r "$STORE_FILE" ]; then41    echo "refusing to publish: configured release keystore is missing or unreadable" >&242    exit 143fi4445# Resolve the SDK from the environment first, then the non-secret sdk.dir in46# local.properties. Pick the newest *versioned* build-tools directory containing both47# tools; build-tools/latest is intentionally never trusted or consulted.48SDK_ROOT="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"49if [ -z "$SDK_ROOT" ]; then50    SDK_ROOT=$(sed -n 's/^sdk\.dir=//p' "$ROOT_DIR/local.properties" 2>/dev/null | sed -n '1p')51fi52if [ -z "$SDK_ROOT" ] || [ ! -d "$SDK_ROOT/build-tools" ]; then53    echo "refusing to publish: Android SDK build-tools directory not found" >&254    exit 155fi5657BUILD_TOOLS_DIR=$(find_stable_build_tools_dir "$SDK_ROOT" || true)58if [ -z "$BUILD_TOOLS_DIR" ]; then59    echo "refusing to publish: no versioned build-tools directory has apksigner and aapt2" >&260    exit 161fi62APKSIGNER="$BUILD_TOOLS_DIR/apksigner"63AAPT2="$BUILD_TOOLS_DIR/aapt2"6465ROOT_VERSION=$(read_root_semver "$ROOT_DIR/VERSION" || true)66if [ -z "$ROOT_VERSION" ]; then67    echo "refusing to publish: VERSION is missing or not strict semantic version" >&268    exit 169fi7071"$ROOT_DIR/gradlew" --no-configuration-cache :app:assembleRelease7273case "${KEPR_TARGET:-}" in74    android-arm64)75        APK_NAME="app-arm64-v8a-release.apk"76        EXPECTED_ABI="arm64-v8a"77        ;;78    android-x86)79        APK_NAME="app-x86_64-release.apk"80        EXPECTED_ABI="x86_64"81        ;;82    *)83        echo "refusing to publish: unsupported KEPR_TARGET '${KEPR_TARGET:-unset}'" >&284        exit 185        ;;86esac8788APK="$ROOT_DIR/app/build/outputs/apk/release/$APK_NAME"89METADATA="$ROOT_DIR/app/build/outputs/apk/release/output-metadata.json"90if [ ! -f "$APK" ] || [ ! -r "$APK" ]; then91    echo "refusing to publish: release APK was not produced" >&292    exit 193fi94if [ ! -f "$METADATA" ] || [ ! -r "$METADATA" ]; then95    echo "refusing to publish: Gradle output metadata was not produced" >&296    exit 197fi9899BADGING_FILE=$(mktemp "${TMPDIR:-/tmp}/peach-apk-badging.XXXXXX")100cleanup_badging() {101    [ -e "$BADGING_FILE" ] && trash "$BADGING_FILE"102}103trap cleanup_badging EXIT HUP INT TERM104"$AAPT2" dump badging "$APK" > "$BADGING_FILE"105if ! validate_release_metadata \106    "$METADATA" \107    "$BADGING_FILE" \108    "$ROOT_VERSION" \109    "com.peachpasswords" \110    "$APK_NAME" \111    "$EXPECTED_ABI"; then112    exit 1113fi114115# apksigner's plain verify accepts v1-only APKs, so require an explicit v2/v3/v4116# success line before publication. Capture output in memory; it contains only117# public certificate metadata and is never written with signing secrets.118SIGNING_OUTPUT=$("$APKSIGNER" verify --verbose --print-certs "$APK" 2>/dev/null) || {119    echo "refusing to publish: apksigner verification failed" >&2120    exit 1121}122if ! printf '%s\n' "$SIGNING_OUTPUT" | grep -Eq 'Verified using v[234] scheme.*: true'; then123    echo "refusing to publish: APK is not signed with APK Signature Scheme v2 or newer" >&2124    exit 1125fi126127PINNED_RELEASE_CERT_SHA256="e10b975178f0bee7fb5a4802519da70ec2988b6ef7d8c9401fbbcff5a1137668"128CERT_DIGEST_COUNT=$(printf '%s\n' "$SIGNING_OUTPUT" | grep -c 'certificate SHA-256 digest:' || true)129if [ "$CERT_DIGEST_COUNT" -ne 1 ]; then130    echo "refusing to publish: release APK must have exactly one signer certificate" >&2131    exit 1132fi133CERT_DIGEST=$(printf '%s\n' "$SIGNING_OUTPUT" |134    sed -n 's/.*certificate SHA-256 digest:[[:space:]]*//p' |135    sed -n '1p' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')136if [ "$CERT_DIGEST" != "$PINNED_RELEASE_CERT_SHA256" ]; then137    echo "refusing to publish: APK signer certificate does not match the pinned Peach release digest" >&2138    exit 1139fi140141# Only a fully verified artifact reaches the Kepr staging directory. The142# basename includes SemVer + ABI so Kepr's Content-Disposition carries both143# to the browser, matching the {version}/{platform} tokens in kepr.build.144mkdir -p "$ROOT_DIR/build/kepr"145STAGED_NAME="peach-android-${ROOT_VERSION}-${KEPR_TARGET}.apk"146cp "$APK" "$ROOT_DIR/build/kepr/$STAGED_NAME"147echo "release APK verified and staged: build/kepr/$STAGED_NAME"