Skip to content

ci: add macOS code signing and notarization - #116

Merged
developer0hye merged 12 commits into
masterfrom
ci/macos-codesign-notarize
Feb 17, 2026
Merged

developer0hye merged 12 commits into
masterfrom
ci/macos-codesign-notarize

Conversation

@developer0hye

@developer0hye developer0hye commented Feb 17, 2026

Copy link
Copy Markdown
Owner

Summary

Add Developer ID code signing and notarization for macOS release builds. This eliminates the Gatekeeper warning dialog that currently appears when users download and open the DMG.

Key Changes

  • Certificate import: Decode MACOS_CERTIFICATE secret (base64-encoded .p12), create a temporary keychain, and import the certificate
  • Inside-out code signing: Per Apple's current requirements (macOS 13+), sign each component individually instead of using deprecated codesign --deep:
    1. Sign all dylibs in Contents/Frameworks/
    2. Sign .framework bundles (binary inside first, then the bundle)
    3. Sign Qt plugins in Contents/PlugIns/
    4. Sign the main app bundle last (with entitlements)
  • Entitlements (scripts/entitlements.plist): Grants allow-unsigned-executable-memory and disable-library-validation — applied only to the main executable, not nested code
  • Improved macOS packaging:
    • Run macdeployqt before copying ONNX Runtime dylibs to prevent interference
    • Use cp -a with broader glob to preserve symlinks
    • Suppress only "would duplicate" rpath warnings, not all errors
  • Notarization: Submit signed DMG to Apple's notary service via xcrun notarytool submit --wait --timeout 20m, then staple the ticket. Restricted to tag pushes (releases only) to avoid unnecessary Apple API calls
  • Graceful fallback: When secrets are unavailable (PR builds, forks), falls back to ad-hoc signing (codesign --sign -) — no build breakage
  • Cleanup: Temporary keychain is deleted in an always() step
  • Documentation: Added docs/macos-codesigning.md with setup instructions and troubleshooting guide

Required Secrets

Secret Description
MACOS_CERTIFICATE Base64-encoded .p12 (Developer ID Application cert + private key)
MACOS_CERTIFICATE_PASSWORD Password for the .p12 file
APPLE_ID Apple ID email for notarization
APPLE_ID_PASSWORD App-specific password (not regular Apple ID password)
APPLE_TEAM_ID 10-character Apple Developer Team ID

Verification

  • Tag push: Full signing + notarization pipeline runs
  • PR/master push: Signing only (no notarization), or ad-hoc fallback if no secrets
  • After release: spctl --assess --type open --context context:primary-signature YoloLabel-macOS.dmg

🤖 Generated with Claude Code

Replace ad-hoc signing with Developer ID Application certificate signing
and Apple notarization for macOS release builds. This eliminates
Gatekeeper warnings when users download the DMG.

- Import Developer ID certificate from secrets into a temporary keychain
- Sign .app bundle with hardened runtime and Apple timestamp
- Sign DMG separately
- Submit DMG to Apple notary service and staple the ticket
- Gracefully fall back to ad-hoc signing when secrets are unavailable
  (PR/fork builds)
- Clean up temporary keychain after the job completes
- Add setup documentation in docs/macos-codesigning.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: developer0hye <developer.0hye@gmail.com>
@developer0hye
developer0hye force-pushed the ci/macos-codesign-notarize branch from a867438 to 5e62354 Compare February 17, 2026 01:24
developer0hye and others added 11 commits February 17, 2026 10:43
The Qt platform plugin (libqcocoa.dylib) in PlugIns/ was still signed
by Qt Company's Team ID, causing library validation failure under
hardened runtime. Widen the find scope from Contents/Frameworks to
Contents so all nested dylibs are re-signed with our Developer ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: developer0hye <developer.0hye@gmail.com>
notarytool submit --wait was hanging indefinitely when notarization
failed. Add --timeout 10m to the command and timeout-minutes: 15 to
the step. On failure, fetch the notarization log to diagnose the issue.

Signed-off-by: developer0hye <developer.0hye@gmail.com>
Apple notarization timed out at 10m with "In Progress" status, likely
due to the deeper code signing (hardened runtime on all nested dylibs).
Increase notarytool timeout to 30m and step timeout to 35m to allow
Apple's service enough time to complete. Also add codesign --verify
before DMG creation to catch signing issues early.

Signed-off-by: developer0hye <developer.0hye@gmail.com>
notarytool submit --wait hangs indefinitely with no diagnostic output.
Split into explicit submit + poll loop with notarytool info so each
poll iteration logs the full status. Also add debug steps to:
- list all Mach-O binaries in the app bundle
- show notarytool history (previous submission statuses)

Signed-off-by: developer0hye <developer.0hye@gmail.com>
The inside-out signing approach (individually signing each dylib with
--options runtime) causes Apple's notary service to hang indefinitely
-- all 5 such submissions remain stuck at "In Progress" after 2+ hours.

Switch back to --deep signing (which completed notarization in 59s)
but add an entitlements.plist with:
- allow-unsigned-executable-memory: needed by ONNX Runtime
- disable-library-validation: allow loading bundled dylibs

This should fix both the notarization hang and the app launch issue.

Signed-off-by: developer0hye <developer.0hye@gmail.com>
hdiutil create fails with "Resource busy" immediately after codesign
because macOS hasn't fully released locks on the app bundle yet. Add
a brief sleep to let the system release resource locks.

Signed-off-by: developer0hye <developer.0hye@gmail.com>
Apple's notary hangs indefinitely when nested dylibs/frameworks are
signed with --options runtime (hardened runtime). All 5+ submissions
with hardened-runtime nested code remain "In Progress" after hours,
while the original submission without it was accepted in 59 seconds.

New approach: sign nested code (dylibs, frameworks, plugins) with
Developer ID + timestamp only, then sign the main .app bundle with
--options runtime + entitlements. The entitlements grant:
- allow-unsigned-executable-memory (ONNX Runtime needs this)
- disable-library-validation (allow loading bundled dylibs)

Signed-off-by: developer0hye <developer.0hye@gmail.com>
All approaches using individual codesign invocations (find+exec on each
dylib/framework) cause Apple's notary to hang permanently. The only
build that passed notarization (a674dc2d, 59 seconds) used a single
codesign --deep --sign pass.

Revert to this exact approach: one codesign --force --deep call with
--options runtime and --timestamp on the .app bundle. Remove entitlements
for now to exactly match what worked before. Entitlements can be added
back once notarization is confirmed working.

Signed-off-by: developer0hye <developer.0hye@gmail.com>
Remove debugging artifacts (Mach-O listing, submission history, manual
polling loop) that were added during Apple notarization service outage
investigation. Replace with clean notarytool submit --wait --timeout.
Add entitlements.plist to codesign command for hardened runtime
compatibility. Update troubleshooting docs with findings from debugging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: developer0hye <developer.0hye@gmail.com>
- Replace deprecated `codesign --deep` with inside-out signing per
  Apple's current requirements (macOS 13+): sign dylibs, then
  framework bundles, then plugins, then the main app bundle last.
  Entitlements are only applied to the main executable.
- Run macdeployqt before copying ONNX Runtime dylibs to prevent
  macdeployqt from interfering with them.
- Use `cp -a` with broader glob to copy both versioned dylib and
  symlink (libonnxruntime.dylib -> libonnxruntime.1.24.1.dylib).
- Suppress only "would duplicate" rpath warnings instead of hiding
  all errors with `|| true`.
- Restrict notarization to tag pushes (releases) to avoid unnecessary
  Apple API calls on every master push.

Signed-off-by: developer0hye <developer.0hye@gmail.com>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: developer0hye <developer.0hye@gmail.com>
Document the issues found and fixed during macOS CI packaging:
inside-out signing, macdeployqt framework bug, rpath ordering,
dylib symlink handling, and notarization scope. Also records
non-critical runtime warnings and macOS 15+ Gatekeeper changes.

Signed-off-by: developer0hye <developer.0hye@gmail.com>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: developer0hye <developer.0hye@gmail.com>
@developer0hye
developer0hye merged commit 4884729 into master Feb 17, 2026
5 checks passed
@developer0hye

Copy link
Copy Markdown
Owner Author

#95

@developer0hye
developer0hye deleted the ci/macos-codesign-notarize branch February 17, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant