mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 11:07:11 +08:00
fix(desktop): notarize the .app zip
This commit is contained in:
60
.github/workflows/release.yml
vendored
60
.github/workflows/release.yml
vendored
@@ -108,6 +108,10 @@ jobs:
|
|||||||
# is the correct state for unsigned builds.
|
# is the correct state for unsigned builds.
|
||||||
MAC_CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
|
MAC_CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
|
||||||
MAC_CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
|
MAC_CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
|
||||||
|
# electron-builder's built-in notarization reads these.
|
||||||
|
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_LINK: ${{ secrets.WIN_CSC_LINK }}
|
||||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
|
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
@@ -128,57 +132,15 @@ jobs:
|
|||||||
# Never let electron-builder publish: our publish target is a generic
|
# Never let electron-builder publish: our publish target is a generic
|
||||||
# (read-only) feed served from R2/D1, which it can't upload to. We mirror
|
# (read-only) feed served from R2/D1, which it can't upload to. We mirror
|
||||||
# installers to R2 and register them in D1 ourselves (publish-r2 job).
|
# installers to R2 and register them in D1 ourselves (publish-r2 job).
|
||||||
# `--publish never` still emits the latest*.yml files we parse for sha512.
|
# `--publish never` still emits the latest*.yml files.
|
||||||
# Use the dynamic config (electron-builder.js) so mac.binaries is
|
# Use the dynamic config (electron-builder.js): it populates
|
||||||
# populated with the embedded backend's Mach-O files for signing.
|
# mac.binaries (backend Mach-O files to sign) and enables built-in
|
||||||
# electron-builder signs but does NOT notarize (notarize:false); the
|
# notarization, which zips the .app and submits that (the path Apple
|
||||||
# dedicated step below notarizes + staples with retry.
|
# actually accepts for this large bundle) then staples + packs the dmg.
|
||||||
npx electron-builder ${{ matrix.eb_flags }} --config electron-builder.js --publish never
|
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
|
# Upload artifacts regardless of outcome, so a failed run still surfaces
|
||||||
# step `if:` directly, and step-level `env` isn't visible to that step's
|
# the built installers (and, on success, the notarized+stapled dmg).
|
||||||
# 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: submit ONCE, then poll that submission id
|
|
||||||
# until Accepted/Invalid (see notarize-dmg.sh — never resubmits).
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Always upload artifacts, even when notarization timed out. The
|
|
||||||
# notarization ticket is keyed to the dmg's hash on Apple's servers: if
|
|
||||||
# the submission is eventually Accepted, THIS exact dmg becomes valid
|
|
||||||
# as-is (download it, `xcrun stapler staple`, publish manually) — no
|
|
||||||
# rebuild needed. Rebuilding would change the hash and waste the ticket.
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -55,7 +55,6 @@ desktop/build/*
|
|||||||
!desktop/build/requirements-desktop.txt
|
!desktop/build/requirements-desktop.txt
|
||||||
!desktop/build/build-backend.sh
|
!desktop/build/build-backend.sh
|
||||||
!desktop/build/entitlements.mac.plist
|
!desktop/build/entitlements.mac.plist
|
||||||
!desktop/build/notarize-dmg.sh
|
|
||||||
|
|
||||||
# Icon authoring scratch dir: intermediate assets used to produce the final
|
# Icon authoring scratch dir: intermediate assets used to produce the final
|
||||||
# icons. Only the finished icons under desktop/resources/ should be committed.
|
# icons. Only the finished icons under desktop/resources/ should be committed.
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -71,13 +71,13 @@ function collectBackendBinaries() {
|
|||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
const binaries = collectBackendBinaries()
|
const binaries = collectBackendBinaries()
|
||||||
console.log(`[electron-builder.js] injecting ${binaries.length} backend binaries into mac.binaries`)
|
console.log(`[electron-builder.js] injecting ${binaries.length} backend binaries into mac.binaries`)
|
||||||
// Sign the app (+ backend binaries) here, but do NOT notarize inline.
|
// Notarize via electron-builder's built-in flow, which zips the .app and
|
||||||
// electron-builder runs `notarytool submit --wait`, which aborts the whole
|
// submits THAT (then staples the .app and packs the dmg around it).
|
||||||
// build on a single status-polling timeout (NSURLErrorDomain -1001) — a
|
// Submitting the .app-zip is the path that actually gets Accepted by Apple's
|
||||||
// frequent transient failure on the runner -> Apple link. Notarization still
|
// notary service; submitting the dmg directly got stuck "In Progress"
|
||||||
// happens: a dedicated CI step submits + staples the dmg with a retry loop,
|
// indefinitely for this large PyInstaller bundle. notarize needs APPLE_ID /
|
||||||
// so a flaky poll retries instead of throwing away the signed build.
|
// APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID in the environment.
|
||||||
config.mac = { ...config.mac, binaries, notarize: false }
|
config.mac = { ...config.mac, binaries, notarize: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config
|
module.exports = config
|
||||||
|
|||||||
Reference in New Issue
Block a user