From 77648e01e081677e176007cafd67fc584ff8cd1c Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 30 Jun 2026 08:41:58 +0000 Subject: [PATCH] fix(macos): staple the notarization ticket to the .app, not just the dmg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stapling only the dmg leaves the LocalAI.app bundle with no embedded notarization ticket. Gatekeeper then falls back to an online notarization check on first launch, so the app fails to open on a Mac that is offline or behind a firewall, or once it has been copied out of the dmg — while it keeps working on the (online) build host, which masks the problem. Notarize and staple the .app before packaging it into the dmg so the bundle verifies offline. Adds a `notarize-app` subcommand to contrib/macos/sign-and-notarize.sh (zips the bundle for notarytool, then staples + validates) and invokes it from dmg-launcher-darwin. Stays a no-op when notary secrets are unset, so unsigned local/fork builds are unaffected. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: mudler --- Makefile | 9 ++++++-- contrib/macos/sign-and-notarize.sh | 37 ++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 3e640a3b7..32046d6a3 100644 --- a/Makefile +++ b/Makefile @@ -1482,8 +1482,13 @@ build-launcher-darwin: mv cmd/launcher/LocalAI.app dist/LocalAI.app bash contrib/macos/sign-and-notarize.sh sign dist/LocalAI.app -# Wrap the (signed) app into a drag-to-Applications DMG via hdiutil, then sign the DMG. +# Notarize + staple the .app itself, then wrap it into a drag-to-Applications +# DMG via hdiutil and sign the DMG. The app is stapled BEFORE packaging so the +# bundle carries its own ticket and verifies offline (a dmg-only staple leaves +# the app relying on an online Gatekeeper check, which fails offline / once the +# app is copied out of the dmg). No-op without notary secrets. dmg-launcher-darwin: build-launcher-darwin + bash contrib/macos/sign-and-notarize.sh notarize-app dist/LocalAI.app rm -rf dist/dmg dist/LocalAI.dmg mkdir -p dist/dmg cp -R dist/LocalAI.app dist/dmg/LocalAI.app @@ -1495,7 +1500,7 @@ dmg-launcher-darwin: build-launcher-darwin notarize-launcher-darwin: dmg-launcher-darwin bash contrib/macos/sign-and-notarize.sh notarize dist/LocalAI.dmg -# Single entrypoint for CI: build -> sign app -> dmg -> sign dmg -> notarize -> staple. +# Single entrypoint for CI: build -> sign app -> notarize+staple app -> dmg -> sign dmg -> notarize+staple dmg. release-launcher-darwin: notarize-launcher-darwin @echo "dist/LocalAI.dmg is ready" diff --git a/contrib/macos/sign-and-notarize.sh b/contrib/macos/sign-and-notarize.sh index 73497c769..e7cd3b7be 100755 --- a/contrib/macos/sign-and-notarize.sh +++ b/contrib/macos/sign-and-notarize.sh @@ -71,13 +71,42 @@ cmd_notarize() { echo "[notarize] notarized and stapled $dmg" } +# Notarize and staple the .app bundle itself. Stapling the dmg alone is not +# enough: an app with no embedded ticket has no local proof of notarization, so +# Gatekeeper falls back to an online check — and the app then fails to launch on +# a machine that is offline / behind a firewall, or once it has been copied out +# of the dmg. Stapling the bundle makes it verify offline. notarytool needs an +# archive for a bundle, so we zip it first. +cmd_notarize_app() { + local app="$1" + if [ -z "${MACOS_NOTARY_KEY:-}" ]; then + echo "[notarize] MACOS_NOTARY_KEY unset: skipping notarization of $app" + return 0 + fi + local keyfile zip + keyfile="$(mktemp).p8" + zip="$(mktemp).zip" + echo "$MACOS_NOTARY_KEY" | base64 --decode > "$keyfile" + ditto -c -k --keepParent "$app" "$zip" + xcrun notarytool submit "$zip" \ + --key "$keyfile" \ + --key-id "${MACOS_NOTARY_KEY_ID:?}" \ + --issuer "${MACOS_NOTARY_ISSUER_ID:?}" \ + --wait + rm -f "$keyfile" "$zip" + xcrun stapler staple "$app" + xcrun stapler validate "$app" + echo "[notarize] notarized and stapled $app" +} + main() { local sub="${1:-}"; shift || true case "$sub" in - import-cert) cmd_import_cert ;; - sign) cmd_sign "$@" ;; - notarize) cmd_notarize "$@" ;; - *) echo "usage: $0 {import-cert|sign |notarize }" >&2; exit 2 ;; + import-cert) cmd_import_cert ;; + sign) cmd_sign "$@" ;; + notarize) cmd_notarize "$@" ;; + notarize-app) cmd_notarize_app "$@" ;; + *) echo "usage: $0 {import-cert|sign |notarize |notarize-app }" >&2; exit 2 ;; esac }