From 827653aa291e37ae63c0cfa1eee4fbaa20cea122 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 1 Aug 2026 00:00:00 +0000 Subject: build: pin and verify release inputs --- scripts/build-apk | 87 ++++++++++++++++++++++++++++++++++++--------- scripts/build-server | 34 ++++++++++++++++++ scripts/check | 2 ++ scripts/check-server | 12 +++++-- scripts/check-server-source | 27 ++++++++++++++ scripts/check-wrapper | 24 +++++++++++-- scripts/update-server | 22 +++++++++--- 7 files changed, 183 insertions(+), 25 deletions(-) create mode 100755 scripts/build-server create mode 100755 scripts/check-server-source (limited to 'scripts') diff --git a/scripts/build-apk b/scripts/build-apk index 476731b..3040ee0 100755 --- a/scripts/build-apk +++ b/scripts/build-apk @@ -8,7 +8,7 @@ # KEY_PASS password for that key (often the same as KEYSTORE_PASS) # # Required tooling: -# Android build-tools 35.0.0 apksigner under ANDROID_SDK_ROOT or on PATH. +# Android build-tools 35.0.0 under ANDROID_SDK_ROOT or on PATH. # # Usage: # scripts/build-apk @@ -25,11 +25,10 @@ set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd)" APK="$ROOT/app/build/outputs/apk/release/app-release.apk" -JAR="$ROOT/app/src/main/assets/scrcpy-server.jar" require() { name="$1" - eval "val=\${$name:-}" + val="$2" if [ -z "$val" ]; then echo "build-apk: \$$name is not set" >&2 echo " see the header of $0 for the required environment" >&2 @@ -37,10 +36,10 @@ require() { fi } -require KEYSTORE_PATH -require KEYSTORE_PASS -require KEY_ALIAS -require KEY_PASS +require KEYSTORE_PATH "${KEYSTORE_PATH:-}" +require KEYSTORE_PASS "${KEYSTORE_PASS:-}" +require KEY_ALIAS "${KEY_ALIAS:-}" +require KEY_PASS "${KEY_PASS:-}" if [ ! -f "$KEYSTORE_PATH" ]; then echo "build-apk: keystore not found at $KEYSTORE_PATH" >&2 @@ -62,23 +61,79 @@ fi SUM=$(sha256sum "$APK" | awk '{print $1}') APKSIGNER="" -if [ -n "${ANDROID_SDK_ROOT:-}" ]; then - APKSIGNER="$ANDROID_SDK_ROOT/build-tools/35.0.0/apksigner" - if [ ! -x "$APKSIGNER" ]; then - APKSIGNER="" - fi +AAPT2="" +ZIPALIGN="" +SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}" +if [ -n "$SDK_ROOT" ]; then + APKSIGNER="$SDK_ROOT/build-tools/35.0.0/apksigner" + AAPT2="$SDK_ROOT/build-tools/35.0.0/aapt2" + ZIPALIGN="$SDK_ROOT/build-tools/35.0.0/zipalign" + [ -x "$APKSIGNER" ] || APKSIGNER="" + [ -x "$AAPT2" ] || AAPT2="" + [ -x "$ZIPALIGN" ] || ZIPALIGN="" fi if [ -z "$APKSIGNER" ] && command -v apksigner >/dev/null 2>&1; then APKSIGNER=$(command -v apksigner) fi -if [ -z "$APKSIGNER" ]; then - echo "build-apk: apksigner 35.0.0 is required" >&2 - echo " set ANDROID_SDK_ROOT or put apksigner on PATH" >&2 +if [ -z "$AAPT2" ] && command -v aapt2 >/dev/null 2>&1; then + AAPT2=$(command -v aapt2) +fi +if [ -z "$ZIPALIGN" ] && command -v zipalign >/dev/null 2>&1; then + ZIPALIGN=$(command -v zipalign) +fi +if [ -z "$APKSIGNER" ] || [ -z "$AAPT2" ] || [ -z "$ZIPALIGN" ]; then + echo "build-apk: build-tools 35.0.0 are required" >&2 + echo " set ANDROID_SDK_ROOT or put apksigner, aapt2, and zipalign on PATH" >&2 exit 1 fi +# Capture rather than pipe: POSIX sh has no pipefail, so +# `apksigner verify | sed` reports sed's status and a rejected APK would +# sail past set -e and be announced below as if it were signed. echo "build-apk: apksigner verify" -"$APKSIGNER" verify --verbose --print-certs "$APK" | sed 's/^/ /' +if ! verify_out=$("$APKSIGNER" verify --verbose --print-certs "$APK" 2>&1); then + printf '%s\n' "$verify_out" >&2 + echo "build-apk: apksigner rejected $APK" >&2 + exit 1 +fi +printf '%s\n' "$verify_out" | sed 's/^/ /' + +"$ZIPALIGN" -c -P 16 4 "$APK" + +expected_permissions=$(cat <&2 + printf '%s\n' "$actual_permissions" >&2 + exit 1 +fi + +expected_sdk=$(cat <&2 + printf '%s\n' "$actual_sdk" >&2 + exit 1 +fi + +expected_abis="native-code: 'arm64-v8a' 'x86_64'" +actual_abis=$("$AAPT2" dump badging "$APK" | sed -n '/^native-code:/p') +if [ "$actual_abis" != "$expected_abis" ]; then + echo "build-apk: unexpected release ABIs: $actual_abis" >&2 + exit 1 +fi echo "build-apk: $APK" echo "build-apk: sha256 $SUM" diff --git a/scripts/build-server b/scripts/build-server new file mode 100755 index 0000000..ddd814f --- /dev/null +++ b/scripts/build-server @@ -0,0 +1,34 @@ +#!/bin/sh +# Build the pinned scrcpy server source. + +set -eu + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SCRCPY="$ROOT/vendor/scrcpy" +VERSION_FILE="$ROOT/app/src/main/assets/scrcpy-server.version" +BUILD_FILE="$SCRCPY/server/build.gradle" +OUTPUT="$SCRCPY/server/build/outputs/apk/release/server-release-unsigned.apk" + +if [ ! -s "$BUILD_FILE" ]; then + echo "build-server: vendor/scrcpy is not initialized" >&2 + exit 1 +fi +if [ ! -s "$VERSION_FILE" ]; then + echo "build-server: missing $VERSION_FILE" >&2 + exit 1 +fi + +expected=$(cat "$VERSION_FILE") +declared=$(sed -n 's/.*versionName[ =]*"\([^"]*\)".*/\1/p' "$BUILD_FILE") +if [ -z "$declared" ] || [ "$declared" != "$expected" ]; then + echo "build-server: source version '$declared', asset version '$expected'" >&2 + exit 1 +fi + +echo "build-server: scrcpy $expected" >&2 +"$SCRCPY/gradlew" -p "$SCRCPY" --no-daemon :server:assembleRelease >&2 +if [ ! -s "$OUTPUT" ]; then + echo "build-server: Gradle did not produce $OUTPUT" >&2 + exit 1 +fi +printf '%s\n' "$OUTPUT" diff --git a/scripts/check b/scripts/check index 6886411..8196859 100755 --- a/scripts/check +++ b/scripts/check @@ -7,4 +7,6 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)" "$ROOT/scripts/check-wrapper" "$ROOT/scripts/check-server" +cd "$ROOT" "$ROOT/gradlew" --no-daemon :app:test :adb:test :app:lint +"$ROOT/scripts/check-server-source" diff --git a/scripts/check-server b/scripts/check-server index 9a895a1..6b773a5 100755 --- a/scripts/check-server +++ b/scripts/check-server @@ -16,7 +16,7 @@ for file in "$JAR" "$SUM" "$VERSION"; do fi done -expected=$(awk 'NR == 1 { print $1 }' "$SUM") +expected=$(cat "$SUM") case "$expected" in *[!0-9a-f]*|'') echo "check-server: invalid SHA-256 in $SUM" >&2 @@ -36,4 +36,12 @@ if [ "$actual" != "$expected" ]; then exit 1 fi -echo "check-server: ok ($(cat "$VERSION"))" +if ! awk 'BEGIN { ok = 0 } + /^[0-9]+([.][0-9]+)*$/ { if (NR == 1) ok = 1; next } + { ok = 0 } + END { exit !(ok && NR == 1) }' "$VERSION"; then + echo "check-server: invalid version in $VERSION" >&2 + exit 1 +fi +version=$(cat "$VERSION") +echo "check-server: ok ($version)" diff --git a/scripts/check-server-source b/scripts/check-server-source new file mode 100755 index 0000000..47d161a --- /dev/null +++ b/scripts/check-server-source @@ -0,0 +1,27 @@ +#!/bin/sh +# Build and test the pinned scrcpy server, then compare its executable dex +# with the bundled upstream release. + +set -eu + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SCRCPY="$ROOT/vendor/scrcpy" +OFFICIAL="$ROOT/app/src/main/assets/scrcpy-server.jar" + +"$ROOT/scripts/check-server" +built=$("$ROOT/scripts/build-server") + +echo "check-server-source: unit, lint, checkstyle" >&2 +"$SCRCPY/gradlew" -p "$SCRCPY" --no-daemon \ + :server:testDebugUnitTest :server:lint :server:checkstyle >&2 + +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT INT TERM +unzip -p "$OFFICIAL" classes.dex >"$tmp/official.dex" +unzip -p "$built" classes.dex >"$tmp/source.dex" +if ! cmp -s "$tmp/official.dex" "$tmp/source.dex"; then + echo "check-server-source: source dex differs from upstream release" >&2 + sha256sum "$tmp/official.dex" "$tmp/source.dex" >&2 + exit 1 +fi +echo "check-server-source: ok" diff --git a/scripts/check-wrapper b/scripts/check-wrapper index 3997bed..c56b935 100755 --- a/scripts/check-wrapper +++ b/scripts/check-wrapper @@ -18,7 +18,25 @@ if [ ! -f "$EXPECTED_FILE" ]; then exit 1 fi -# sha256sum reads its first column from the .sha256 sidecar; the second -# column ('gradle-wrapper.jar') is informational. -( cd "$(dirname "$WRAPPER")" && sha256sum -c "$(basename "$EXPECTED_FILE")" >/dev/null ) +line=$(cat "$EXPECTED_FILE") +expected=${line% gradle-wrapper.jar} +if [ "$line" != "$expected gradle-wrapper.jar" ]; then + echo "check-wrapper: malformed $EXPECTED_FILE" >&2 + exit 1 +fi +case "$expected" in + *[!0-9a-f]*|'') + echo "check-wrapper: invalid SHA-256 in $EXPECTED_FILE" >&2 + exit 1 + ;; +esac +if [ "${#expected}" -ne 64 ]; then + echo "check-wrapper: invalid SHA-256 length in $EXPECTED_FILE" >&2 + exit 1 +fi +actual=$(sha256sum "$WRAPPER" | awk '{ print $1 }') +if [ "$actual" != "$expected" ]; then + echo "check-wrapper: gradle-wrapper.jar checksum mismatch" >&2 + exit 1 +fi echo "check-wrapper: ok" diff --git a/scripts/update-server b/scripts/update-server index df12e3a..337ef67 100755 --- a/scripts/update-server +++ b/scripts/update-server @@ -4,7 +4,7 @@ # # Usage: # scripts/update-server # use DEFAULT_VERSION below -# scripts/update-server 4.0 # use explicit version +# scripts/update-server 4.1 # use explicit version # # Bumping to a new release: # 1. Run with the new version. The script will print the observed SHA-256 @@ -15,16 +15,22 @@ set -eu -DEFAULT_VERSION='4.0' +DEFAULT_VERSION='4.1' VERSION="${1:-$DEFAULT_VERSION}" -ROOT="$(git rev-parse --show-toplevel)" +if ! printf '%s\n' "$VERSION" \ + | awk '/^[0-9]+([.][0-9]+)*$/ { ok = 1 } END { exit !ok }'; then + echo "update-server: invalid version: $VERSION" >&2 + exit 2 +fi + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" ASSETS="$ROOT/app/src/main/assets" URL="https://github.com/Genymobile/scrcpy/releases/download/v$VERSION/scrcpy-server-v$VERSION" # Known-good SHA-256 sums. Keep one line per blessed version. EXPECTED=$(cat <" >&2 fi +if [ -f "$ASSETS/scrcpy-server.jar" ] \ + && [ "$(cat "$ASSETS/scrcpy-server.version" 2>/dev/null || true)" = "$VERSION" ] \ + && [ "$(sha256sum "$ASSETS/scrcpy-server.jar" | awk '{print $1}')" = "$want" ]; then + printf '%s\n' "$want" >"$ASSETS/scrcpy-server.sha256" + echo "update-server: already installed $ASSETS/scrcpy-server.jar v$VERSION" + exit 0 +fi + mkdir -p "$ASSETS" tmp=$(mktemp) trap 'rm -f "$tmp"' EXIT -- cgit v1.2.3