Skip to content

feat(macos): sign and notarize the DMG, app, and server binary#10510

Open
localai-bot wants to merge 1 commit into
masterfrom
feat/macos-signing-notarization
Open

feat(macos): sign and notarize the DMG, app, and server binary#10510
localai-bot wants to merge 1 commit into
masterfrom
feat/macos-signing-notarization

Conversation

@localai-bot

@localai-bot localai-bot commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Background / the issue

Until now LocalAI's macOS artifacts shipped unsigned and un-notarized:

  • LocalAI.dmg and the LocalAI.app inside it had no Developer ID signature.
  • The bare local-ai-<tag>-darwin-arm64 server binary (the one the launcher downloads at runtime) was also unsigned.

Because of that, macOS Gatekeeper quarantined the downloads and users had to run manual workarounds (xattr -d com.apple.quarantine, right-click > Open, etc.) just to launch LocalAI. This was tracked in #6244 (signing) and #6268 (quarantine workaround), and called out in the install docs.

This PR fixes that: both artifacts are now Developer ID code-signed and Apple-notarized, so they launch with no prompt and no workaround.

What

  1. LocalAI.dmg + the LocalAI.app inside it - built with fyne package, codesigned under the hardened runtime, wrapped into a drag-to-Applications DMG via hdiutil, the DMG is signed, then notarytool submits and stapler staples it. Replaces macos-dmg-creator (which built the app+DMG atomically with no point to sign in between).
  2. The bare local-ai-<tag>-darwin-arm64 server binary - signed + notarized via GoReleaser's native notarize block (quill backend, so it still runs on the existing Linux GoReleaser job, no extra macOS runner). Signing both is what makes it truly workaround-free.

How it stays green for forks/PRs

Every signing step is gated on its secret being present. contrib/macos/sign-and-notarize.sh no-ops when the MACOS_* secrets are unset, and the GoReleaser notarize block is enabled: '{{ isEnvSet \"MACOS_SIGN_P12\" }}'. So forks, PRs, and local make build-launcher-darwin keep producing unsigned artifacts and never fail.

Required repo secrets (configured on mudler/LocalAI)

MACOS_CERTIFICATE, MACOS_CERTIFICATE_PWD, MACOS_CI_KEYCHAIN_PWD, MACOS_SIGN_IDENTITY, MACOS_NOTARY_KEY, MACOS_NOTARY_KEY_ID, MACOS_NOTARY_ISSUER_ID.

Files

  • contrib/macos/sign-and-notarize.sh (new) - import-cert / sign / notarize+staple helper, all no-op without secrets.
  • contrib/macos/Launcher.entitlements (new) - hardened-runtime entitlements (network + JIT/unsigned-exec-memory for Fyne).
  • cmd/launcher/FyneApp.toml (new) - deterministic fyne package metadata.
  • Makefile - new build-launcher-darwin (fyne + sign), dmg-launcher-darwin (hdiutil + sign), notarize-launcher-darwin, release-launcher-darwin; Linux launcher mv updated to LocalAI.tar.xz (FyneApp.toml renames the output).
  • .goreleaser.yaml - build id: local-ai + notarize: block.
  • .github/workflows/release.yaml - cert import + signed DMG build on the darwin job; MACOS_* env on the GoReleaser job.
  • docs/content/installation/macos.md - drop the quarantine workaround, document verification.

Known limitations / things to watch

  • Not validatable in this PR's CI. The actual fyne/hdiutil/codesign/notarytool invocations only run on the macOS runner during a real v* release, so the signing path is exercised for the first time on the next release tag, not on this PR. Local validation was limited to bash -n, make -n release-launcher-darwin, and plist/toml lint (all pass).
  • First notarization may bounce on a hardened-runtime entitlement and need 1-2 tweaks to contrib/macos/Launcher.entitlements; debug via xcrun notarytool log <submission-id>.
  • Linux launcher output renamed by FyneApp.toml (launcher.tar.xz -> LocalAI.tar.xz); the build-launcher-linux mv was updated to match - watch that job stays green.
  • Darwin remains arm64-only (unchanged).

Verification plan

Push a throwaway tag (e.g. v0.0.0-sigtest), confirm the goreleaser + launcher-build-darwin jobs succeed, download the DMG/binary and run spctl --assess --type open -v on a Mac, then delete the test tag/release.

Closes #6244
Closes #6268

Assisted-by: Claude:claude-opus-4-8 [Claude Code]

Produce a Gatekeeper-clean macOS distribution with no user workaround:

- Launcher DMG + the LocalAI.app inside it are built via fyne, codesigned
  with the Developer ID under the hardened runtime, then the DMG is signed,
  notarized (notarytool) and stapled. Replaces macos-dmg-creator (which had
  no signing hook) with fyne package + hdiutil so we control the .app before
  packaging.
- The bare local-ai darwin server binary is signed + notarized via
  GoReleaser's native notarize block (quill backend, runs on Linux).
- All signing is gated on secrets being present, so forks/PRs/local builds
  stay unsigned and green (contrib/macos/sign-and-notarize.sh no-ops).
- Add hardened-runtime entitlements and FyneApp.toml for deterministic
  packaging; update macOS install docs to drop the quarantine workaround.

Assisted-by: Claude:claude-opus-4-8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
@localai-bot

This comment was marked as outdated.

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.

v3.5.0 MacOS installer is broken on Apple M series Fix Signing Process for macOS Builds

2 participants