fix(desktop): notarize the .app zip

This commit is contained in:
zhayujie
2026-07-06 10:19:54 +08:00
parent a871c0437d
commit a951494489
4 changed files with 18 additions and 151 deletions

View File

@@ -1,94 +0,0 @@
#!/usr/bin/env bash
#
# Notarize + staple a macOS dmg.
#
# Submits ONCE and then polls that same submission id until it reaches a
# terminal state. Never resubmits: a previous version resubmitted on every
# wait timeout, which piled up duplicate "In Progress" submissions in Apple's
# queue and made everything slower.
#
# Notarization is fully automated on Apple's side (malware scan, no humans);
# most submissions finish in minutes, but large bundles or Apple-side backlog
# can take much longer, so we poll patiently instead of failing fast.
#
# Usage: notarize-dmg.sh <path-to-dmg>
# Requires env: APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID
# Tunables: NOTARIZE_MAX_WAIT_MINUTES (default 180), NOTARIZE_POLL_SECONDS (default 60)
set -euo pipefail
DMG="${1:?usage: notarize-dmg.sh <dmg>}"
: "${APPLE_ID:?APPLE_ID not set}"
: "${APPLE_APP_SPECIFIC_PASSWORD:?APPLE_APP_SPECIFIC_PASSWORD not set}"
: "${APPLE_TEAM_ID:?APPLE_TEAM_ID not set}"
MAX_WAIT_MINUTES="${NOTARIZE_MAX_WAIT_MINUTES:-180}"
POLL_SECONDS="${NOTARIZE_POLL_SECONDS:-60}"
auth=(
--apple-id "$APPLE_ID"
--password "$APPLE_APP_SPECIFIC_PASSWORD"
--team-id "$APPLE_TEAM_ID"
)
json_field() {
# json_field <key> — read a top-level string field from JSON on stdin.
python3 -c "import json,sys; print(json.load(sys.stdin).get('$1',''))" 2>/dev/null
}
echo "==> Submitting for notarization: $DMG"
submit_out="$(xcrun notarytool submit "$DMG" "${auth[@]}" --no-wait --output-format json)"
submission_id="$(echo "$submit_out" | json_field id)"
if [ -z "$submission_id" ]; then
echo "::error::could not parse submission id from notarytool output"
echo "$submit_out"
exit 1
fi
echo "==> Submission id: $submission_id (uploaded OK, polling status...)"
deadline=$(( $(date +%s) + MAX_WAIT_MINUTES * 60 ))
while :; do
# info can fail transiently (network); treat as unknown and keep polling.
status="$(xcrun notarytool info "$submission_id" "${auth[@]}" --output-format json 2>/dev/null | json_field status || true)"
echo "==> [$(date '+%H:%M:%S')] status: ${status:-<query failed, will retry>}"
case "$status" in
Accepted)
echo "==> Notarization Accepted"
break
;;
Invalid|Rejected)
echo "::error::Notarization ${status}; fetching log for diagnostics"
xcrun notarytool log "$submission_id" "${auth[@]}" || true
exit 1
;;
esac
if [ "$(date +%s)" -ge "$deadline" ]; then
echo "::error::Notarization still not finished after ${MAX_WAIT_MINUTES} minutes (id: $submission_id)."
echo "::error::NOT resubmitting. Check later with: xcrun notarytool info $submission_id"
exit 1
fi
sleep "$POLL_SECONDS"
done
echo "==> Stapling ticket into dmg"
staple_attempt=1
while :; do
if xcrun stapler staple "$DMG"; then
echo "==> Staple OK"
break
fi
if [ "$staple_attempt" -ge 3 ]; then
echo "::error::Stapling failed after 3 attempts"
exit 1
fi
echo "==> Staple failed, retrying in 15s..."
sleep 15
staple_attempt=$(( staple_attempt + 1 ))
done
echo "==> Verifying staple"
xcrun stapler validate "$DMG"
echo "==> Notarization + staple complete: $DMG"

View File

@@ -71,13 +71,13 @@ function collectBackendBinaries() {
if (process.platform === 'darwin') {
const binaries = collectBackendBinaries()
console.log(`[electron-builder.js] injecting ${binaries.length} backend binaries into mac.binaries`)
// Sign the app (+ backend binaries) here, but do NOT notarize inline.
// electron-builder runs `notarytool submit --wait`, which aborts the whole
// build on a single status-polling timeout (NSURLErrorDomain -1001) — a
// frequent transient failure on the runner -> Apple link. Notarization still
// happens: a dedicated CI step submits + staples the dmg with a retry loop,
// so a flaky poll retries instead of throwing away the signed build.
config.mac = { ...config.mac, binaries, notarize: false }
// Notarize via electron-builder's built-in flow, which zips the .app and
// submits THAT (then staples the .app and packs the dmg around it).
// Submitting the .app-zip is the path that actually gets Accepted by Apple's
// notary service; submitting the dmg directly got stuck "In Progress"
// indefinitely for this large PyInstaller bundle. notarize needs APPLE_ID /
// APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID in the environment.
config.mac = { ...config.mac, binaries, notarize: true }
}
module.exports = config