From efacfab213c0f298e3be7e2305c372487ee18af3 Mon Sep 17 00:00:00 2001 From: Hammed Abass Date: Thu, 30 Jul 2026 21:03:30 +0200 Subject: [PATCH] fix(release): expose Apple notarization failures --- .github/workflows/apple-release.yml | 9 +++ Makefile | 3 +- apple/scripts/build-dmg.sh | 3 +- apple/scripts/notarize.sh | 67 +++++++++++++++++++++ apple/scripts/tests/test-notarize.sh | 87 ++++++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 2 deletions(-) create mode 100755 apple/scripts/notarize.sh create mode 100755 apple/scripts/tests/test-notarize.sh diff --git a/.github/workflows/apple-release.yml b/.github/workflows/apple-release.yml index d590893..95e1a20 100644 --- a/.github/workflows/apple-release.yml +++ b/.github/workflows/apple-release.yml @@ -134,6 +134,15 @@ jobs: - name: Build, sign & notarize DMG run: make build-apple-dmg + - name: Upload notarization diagnostics + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: vnidrop-${{ steps.version.outputs.app }}-notarization-diagnostics + path: apple/dist/*.notary-log.json + if-no-files-found: ignore + retention-days: 14 + - name: Generate appcast env: RELEASE_REPO: ${{ github.repository }} diff --git a/Makefile b/Makefile index 1ed54cc..f0464b5 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,8 @@ check-version: ## Validate the canonical version and its platform mappings. cd $(ROOT) && $(GRADLE) verifyVersion $(GRADLE_FLAGS) check-release: ## Validate coordinated release scripts and workflow YAML. - cd $(ROOT) && bash -n packaging/android/build-release.sh packaging/android/verify-apk-signature.sh packaging/android/tests/test_verify_apk_signature.sh packaging/release/assemble-release.sh packaging/release/test-assemble-release.sh packaging/release/test-release-config.sh + cd $(ROOT) && bash -n apple/scripts/notarize.sh apple/scripts/tests/test-notarize.sh packaging/android/build-release.sh packaging/android/verify-apk-signature.sh packaging/android/tests/test_verify_apk_signature.sh packaging/release/assemble-release.sh packaging/release/test-assemble-release.sh packaging/release/test-release-config.sh + cd $(ROOT) && apple/scripts/tests/test-notarize.sh cd $(ROOT) && packaging/android/tests/test_verify_apk_signature.sh cd $(ROOT) && packaging/release/test-assemble-release.sh cd $(ROOT) && packaging/release/test-release-config.sh diff --git a/apple/scripts/build-dmg.sh b/apple/scripts/build-dmg.sh index bb0f6a5..0071a0f 100755 --- a/apple/scripts/build-dmg.sh +++ b/apple/scripts/build-dmg.sh @@ -137,7 +137,8 @@ codesign --force --sign "$DEVELOPER_ID_APP" --timestamp "$DMG" # --- Notarize + staple ------------------------------------------------------- if [ -n "${NOTARY_PROFILE:-}" ]; then echo "==> Notarizing (profile: $NOTARY_PROFILE)" - xcrun notarytool submit "$DMG" --keychain-profile "$NOTARY_PROFILE" --wait + NOTARY_LOG="$DIST_DIR/$APP_NAME-$VERSION.notary-log.json" + "$SCRIPT_DIR/notarize.sh" "$DMG" "$NOTARY_PROFILE" "$NOTARY_LOG" echo "==> Stapling" xcrun stapler staple "$DMG" xcrun stapler validate "$DMG" diff --git a/apple/scripts/notarize.sh b/apple/scripts/notarize.sh new file mode 100755 index 0000000..d48d305 --- /dev/null +++ b/apple/scripts/notarize.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -ne 3 ]]; then + printf 'Usage: %s \n' "$0" >&2 + exit 2 +fi + +artifact=$1 +keychain_profile=$2 +log_output=$3 + +[[ -s $artifact ]] || { + printf 'error: notarization artifact is missing or empty: %s\n' "$artifact" >&2 + exit 1 +} +[[ -n $keychain_profile ]] || { + printf 'error: notarization keychain profile is empty\n' >&2 + exit 1 +} +[[ -n $log_output ]] || { + printf 'error: notarization log output path is empty\n' >&2 + exit 1 +} + +rm -f "$log_output" +set +e +response="$( + xcrun notarytool submit "$artifact" \ + --keychain-profile "$keychain_profile" \ + --wait \ + --output-format json +)" +submit_exit=$? +set -e +printf '%s\n' "$response" + +submission_id="$( + printf '%s\n' "$response" | + jq -r '.id // empty' 2>/dev/null || + true +)" +status="$( + printf '%s\n' "$response" | + jq -r '.status // empty' 2>/dev/null || + true +)" + +if [[ $submit_exit -eq 0 && $status == Accepted && -n $submission_id ]]; then + printf 'Notarization accepted (submission %s)\n' "$submission_id" + exit 0 +fi + +printf 'error: notarization was not accepted (status: %s, submission: %s)\n' \ + "${status:-unknown}" "${submission_id:-unknown}" >&2 +if [[ -n $submission_id ]]; then + mkdir -p "$(dirname "$log_output")" + if xcrun notarytool log "$submission_id" "$log_output" \ + --keychain-profile "$keychain_profile"; then + printf '%s\n' 'Apple notarization log:' >&2 + cat "$log_output" >&2 + else + printf 'error: could not retrieve the Apple notarization log\n' >&2 + fi +fi +exit 1 diff --git a/apple/scripts/tests/test-notarize.sh b/apple/scripts/tests/test-notarize.sh new file mode 100755 index 0000000..1616efc --- /dev/null +++ b/apple/scripts/tests/test-notarize.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +notarize="$script_dir/../notarize.sh" +scratch="$(mktemp -d "${TMPDIR:-/tmp}/vnidrop-notarize-test.XXXXXX")" +trap 'rm -rf "$scratch"' EXIT + +mkdir -p "$scratch/bin" +artifact="$scratch/VniDrop.dmg" +calls="$scratch/calls.txt" +log_output="$scratch/notary/notary-log.json" +printf 'dmg\n' > "$artifact" + +cat > "$scratch/bin/xcrun" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +printf '%s\n' "$*" >> "$FAKE_NOTARY_CALLS" +if [[ $1 == notarytool && $2 == submit ]]; then + case "${FAKE_NOTARY_MODE:-accepted}" in + accepted) + printf '%s\n' \ + '{"id":"11111111-1111-1111-1111-111111111111","status":"Accepted"}' + ;; + invalid) + printf '%s\n' \ + '{"id":"22222222-2222-2222-2222-222222222222","status":"Invalid"}' + ;; + transport-error) + printf '%s\n' 'notary service unavailable' >&2 + exit 1 + ;; + esac +elif [[ $1 == notarytool && $2 == log ]]; then + mkdir -p "$(dirname "$4")" + printf '%s\n' \ + '{"status":"Invalid","issues":[{"message":"The signature is invalid."}]}' \ + > "$4" +else + printf 'unexpected xcrun invocation: %s\n' "$*" >&2 + exit 1 +fi +SCRIPT +chmod +x "$scratch/bin/xcrun" + +PATH="$scratch/bin:$PATH" \ + FAKE_NOTARY_CALLS="$calls" \ + FAKE_NOTARY_MODE=accepted \ + "$notarize" "$artifact" test-profile "$log_output" >/dev/null +[[ ! -e $log_output ]] +[[ $(grep -c '^notarytool submit ' "$calls") -eq 1 ]] +if grep -q '^notarytool log ' "$calls"; then + printf 'Accepted submissions must not request a rejection log\n' >&2 + exit 1 +fi + +: > "$calls" +if PATH="$scratch/bin:$PATH" \ + FAKE_NOTARY_CALLS="$calls" \ + FAKE_NOTARY_MODE=invalid \ + "$notarize" "$artifact" test-profile "$log_output" >/dev/null 2>&1; then + printf 'Invalid notarization must fail\n' >&2 + exit 1 +fi +grep -F '"The signature is invalid."' "$log_output" >/dev/null +grep -F \ + 'notarytool log 22222222-2222-2222-2222-222222222222' \ + "$calls" >/dev/null + +: > "$calls" +rm -f "$log_output" +if PATH="$scratch/bin:$PATH" \ + FAKE_NOTARY_CALLS="$calls" \ + FAKE_NOTARY_MODE=transport-error \ + "$notarize" "$artifact" test-profile "$log_output" >/dev/null 2>&1; then + printf 'Notary transport errors must fail\n' >&2 + exit 1 +fi +[[ ! -e $log_output ]] +if grep -q '^notarytool log ' "$calls"; then + printf 'A submission without an ID cannot request a rejection log\n' >&2 + exit 1 +fi + +printf 'Notarization helper tests passed.\n'