mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 11:07:11 +08:00
fix(desktop): notarize dmg in a retryable step to survive poll timeouts
This commit is contained in:
45
.github/workflows/release.yml
vendored
45
.github/workflows/release.yml
vendored
@@ -108,9 +108,6 @@ jobs:
|
||||
# is the correct state for unsigned builds.
|
||||
MAC_CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
|
||||
MAC_CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
|
||||
run: |
|
||||
@@ -134,8 +131,50 @@ jobs:
|
||||
# `--publish never` still emits the latest*.yml files we parse for sha512.
|
||||
# Use the dynamic config (electron-builder.js) so mac.binaries is
|
||||
# populated with the embedded backend's Mach-O files for signing.
|
||||
# electron-builder signs but does NOT notarize (notarize:false); the
|
||||
# dedicated step below notarizes + staples with retry.
|
||||
npx electron-builder ${{ matrix.eb_flags }} --config electron-builder.js --publish never
|
||||
|
||||
# Whether Mac signing secrets are present. `secrets` can't be used in a
|
||||
# step `if:` directly, and step-level `env` isn't visible to that step's
|
||||
# own `if:`, so surface it as an output for the notarize step to gate on.
|
||||
- name: Check mac signing secrets
|
||||
id: macsign
|
||||
if: matrix.platform == 'mac'
|
||||
shell: bash
|
||||
env:
|
||||
MAC_CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
|
||||
run: |
|
||||
if [ -n "$MAC_CSC_LINK" ]; then
|
||||
echo "enabled=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "enabled=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::MAC_CSC_LINK not set — skipping notarization (unsigned build)."
|
||||
fi
|
||||
|
||||
# Notarize + staple the dmg in a separate, retryable step. electron-builder's
|
||||
# inline notarize aborts the whole build on a single notarytool poll timeout
|
||||
# (NSURLErrorDomain -1001); this step retries so a flaky poll doesn't discard
|
||||
# the signed build. Skipped for Windows and for unsigned builds (no cert).
|
||||
- name: Notarize macOS dmg
|
||||
if: matrix.platform == 'mac' && steps.macsign.outputs.enabled == 'true'
|
||||
working-directory: desktop
|
||||
shell: bash
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
dmgs=(release/*.dmg)
|
||||
if [ ${#dmgs[@]} -eq 0 ]; then
|
||||
echo "::error::no dmg found to notarize"
|
||||
exit 1
|
||||
fi
|
||||
for dmg in "${dmgs[@]}"; do
|
||||
bash build/notarize-dmg.sh "$dmg"
|
||||
done
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -55,6 +55,7 @@ desktop/build/*
|
||||
!desktop/build/requirements-desktop.txt
|
||||
!desktop/build/build-backend.sh
|
||||
!desktop/build/entitlements.mac.plist
|
||||
!desktop/build/notarize-dmg.sh
|
||||
|
||||
# Icon authoring scratch dir: intermediate assets used to produce the final
|
||||
# icons. Only the finished icons under desktop/resources/ should be committed.
|
||||
|
||||
98
desktop/build/notarize-dmg.sh
Executable file
98
desktop/build/notarize-dmg.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Notarize + staple a macOS dmg with retry, so a transient notarytool
|
||||
# status-polling timeout (NSURLErrorDomain -1001) doesn't throw away an
|
||||
# otherwise-good signed build.
|
||||
#
|
||||
# electron-builder's inline notarize aborts the whole build on the first poll
|
||||
# timeout. Here we retry the submit-and-wait, and before each retry we check
|
||||
# whether a previous submission already reached a terminal state (Accepted),
|
||||
# so a flaky poll on an already-successful submission still passes.
|
||||
#
|
||||
# Usage: notarize-dmg.sh <path-to-dmg>
|
||||
# Requires env: APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID
|
||||
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_ATTEMPTS="${NOTARIZE_MAX_ATTEMPTS:-5}"
|
||||
|
||||
auth=(
|
||||
--apple-id "$APPLE_ID"
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD"
|
||||
--team-id "$APPLE_TEAM_ID"
|
||||
)
|
||||
|
||||
# Check whether the most recent submission for this app is already Accepted.
|
||||
# Used to short-circuit retries when the failure was only a status-poll timeout
|
||||
# on a submission that Apple actually accepted.
|
||||
latest_status_is_accepted() {
|
||||
local out
|
||||
out="$(xcrun notarytool history "${auth[@]}" --output-format json 2>/dev/null || true)"
|
||||
# Grab the status of the first (most recent) history entry.
|
||||
echo "$out" | python3 -c '
|
||||
import json, sys
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
hist = data.get("history", [])
|
||||
if hist and hist[0].get("status") == "Accepted":
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
pass
|
||||
sys.exit(1)
|
||||
' 2>/dev/null
|
||||
}
|
||||
|
||||
echo "==> Notarizing: $DMG"
|
||||
|
||||
attempt=1
|
||||
while :; do
|
||||
echo "==> notarytool submit (attempt ${attempt}/${MAX_ATTEMPTS})"
|
||||
if xcrun notarytool submit "$DMG" "${auth[@]}" --wait --timeout 30m; then
|
||||
echo "==> Notarization Accepted"
|
||||
break
|
||||
fi
|
||||
|
||||
echo "::warning::notarytool submit failed on attempt ${attempt}"
|
||||
|
||||
# The submit may have been accepted but the status poll timed out; verify.
|
||||
if latest_status_is_accepted; then
|
||||
echo "==> Latest submission already Accepted (poll timed out); continuing"
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then
|
||||
echo "::error::Notarization failed after ${MAX_ATTEMPTS} attempts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep_s=$(( attempt * 30 ))
|
||||
echo "==> Retrying in ${sleep_s}s..."
|
||||
sleep "$sleep_s"
|
||||
attempt=$(( attempt + 1 ))
|
||||
done
|
||||
|
||||
echo "==> Stapling ticket into dmg"
|
||||
# Staple has its own transient failures; retry a few times.
|
||||
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"
|
||||
@@ -71,7 +71,13 @@ function collectBackendBinaries() {
|
||||
if (process.platform === 'darwin') {
|
||||
const binaries = collectBackendBinaries()
|
||||
console.log(`[electron-builder.js] injecting ${binaries.length} backend binaries into mac.binaries`)
|
||||
config.mac = { ...config.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 }
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
|
||||
Reference in New Issue
Block a user