Skip to content

Commit 9321686

Browse files
committed
fix(build): staple notarization ticket to .app and DMG
Without a stapled ticket, Gatekeeper must contact Apple online on first launch. When that check fails (no network, captive portal, slow notary servers), users see "could not verify" — even though the app is correctly notarized. Cask installs are especially affected: the cask discards the DMG context after copying, so any ticket on the DMG alone is lost. Notarize and staple the .app explicitly (via ditto+zip submit), and also wait+staple the DMG so direct DMG installs work offline too.
1 parent dfae782 commit 9321686

File tree

2 files changed

+45
-7
lines changed

2 files changed

+45
-7
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,13 @@ jobs:
7676
TEAM_ID: ${{ secrets.TEAM_ID }}
7777
APP_PASSWORD: ${{ secrets.APP_PASSWORD }}
7878
run: |
79-
# Use tag name as version (v0.3.0-rc1 → 0.3.0-rc1), fall back to VERSION file
79+
# Tag builds: use tag as version and staple notarization for offline Gatekeeper
8080
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
8181
BUILD_VERSION="--version=${GITHUB_REF_NAME#v}"
82+
STAPLE_FLAG="--staple"
8283
fi
8384
if [ "${{ matrix.variant }}" = "homebrew" ] && [ -n "${DEVELOPER_ID:-}" ]; then
84-
./scripts/build_release.sh ${BUILD_VERSION:-}
85+
./scripts/build_release.sh $STAPLE_FLAG ${BUILD_VERSION:-}
8586
else
8687
./scripts/build_release.sh ${{ matrix.build-flags }} --no-notarize ${BUILD_VERSION:-}
8788
fi

scripts/build_release.sh

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Build a self-contained MeetingTranscriber.app bundle.
33
#
44
# Usage:
5-
# ./scripts/build_release.sh [--no-notarize] [--appstore]
5+
# ./scripts/build_release.sh [--no-notarize] [--staple] [--appstore]
66
#
77
# Output:
88
# .build/release/MeetingTranscriber.dmg
@@ -30,11 +30,13 @@ MACOS_DIR="$CONTENTS/MacOS"
3030
RESOURCES="$CONTENTS/Resources"
3131

3232
NOTARIZE=true
33+
STAPLE=false
3334
APPSTORE=false
3435
OVERRIDE_VERSION=""
3536
for arg in "$@"; do
3637
case "$arg" in
3738
--no-notarize) NOTARIZE=false ;;
39+
--staple) STAPLE=true ;;
3840
--appstore) APPSTORE=true ;;
3941
--version=*) OVERRIDE_VERSION="${arg#--version=}" ;;
4042
esac
@@ -48,6 +50,7 @@ else
4850
fi
4951
echo "Building MeetingTranscriber v${VERSION}"
5052
echo " Notarize: $NOTARIZE"
53+
echo " Staple: $STAPLE"
5154
echo " App Store: $APPSTORE"
5255
echo "======================================="
5356

@@ -135,6 +138,31 @@ if [ "$NOTARIZE" = true ]; then
135138
--options runtime --timestamp --entitlements "$ENTITLEMENTS" \
136139
"$APP_BUNDLE"
137140
echo " Signed with Developer ID for notarization"
141+
142+
# When --staple is set, notarize and staple the .app itself so Gatekeeper
143+
# does not need to contact Apple online when the app is launched (cask
144+
# installs strip the DMG context, so the DMG ticket alone is not sufficient).
145+
if [ "$STAPLE" = true ]; then
146+
if [ -z "${APPLE_ID:-}" ] || [ -z "${TEAM_ID:-}" ] || [ -z "${APP_PASSWORD:-}" ]; then
147+
echo " ERROR: APPLE_ID, TEAM_ID, and APP_PASSWORD must be set for notarization."
148+
exit 1
149+
fi
150+
151+
echo " Notarizing .app bundle..."
152+
APP_ZIP="$BUILD_DIR/MeetingTranscriber-app.zip"
153+
rm -f "$APP_ZIP"
154+
ditto -c -k --keepParent "$APP_BUNDLE" "$APP_ZIP"
155+
xcrun notarytool submit "$APP_ZIP" \
156+
--apple-id "$APPLE_ID" \
157+
--team-id "$TEAM_ID" \
158+
--password "$APP_PASSWORD" \
159+
--wait
160+
rm -f "$APP_ZIP"
161+
162+
echo " Stapling notarization ticket to .app..."
163+
xcrun stapler staple "$APP_BUNDLE"
164+
xcrun stapler validate "$APP_BUNDLE"
165+
fi
138166
else
139167
# Use local development certificate if available (extract 40-char hex SHA-1 hash)
140168
SIGN_HASH=$(security find-identity -v -p codesigning 2>/dev/null | grep -oE '[0-9A-F]{40}' | head -1)
@@ -187,13 +215,22 @@ if [ -z "${HOMEBREW_TEMP:-}" ]; then
187215
exit 1
188216
fi
189217

218+
WAIT_FLAG=""
219+
[ "$STAPLE" = true ] && WAIT_FLAG="--wait"
220+
190221
xcrun notarytool submit "$DMG_PATH" \
191222
--apple-id "$APPLE_ID" \
192223
--team-id "$TEAM_ID" \
193-
--password "$APP_PASSWORD"
194-
195-
echo " DMG submitted for notarization (no --wait, no staple)"
196-
echo " Gatekeeper will verify online when users open the DMG"
224+
--password "$APP_PASSWORD" \
225+
$WAIT_FLAG
226+
227+
if [ "$STAPLE" = true ]; then
228+
echo " Stapling notarization ticket to DMG..."
229+
xcrun stapler staple "$DMG_PATH"
230+
xcrun stapler validate "$DMG_PATH"
231+
else
232+
echo " DMG submitted for notarization (Gatekeeper will verify online)"
233+
fi
197234
fi
198235
else
199236
echo ""

0 commit comments

Comments
 (0)