The Notarization API changed a bit, so my old scripts broke. Revisiting the approach, it’s delightfully simple to notarize an app nowadays if you use the Apple toolchain on macOS.
I haven’t watched the WWDC 2022 video on Notarization but did take the documentation link there into account.
The result is this for TableFlip, an app I distribute on my own and via the App Store; note I use Xcode archives for all my apps. Better hackers than me have custom build scripts, of course. Adjust accordingly.
In Xcode, select Product ▶ Archive from the main menu and build your Mac app for release as usual.
When the Organizer window pops up, use the large Distribute App button. For TableFlip, I use it twice:
First, distribute to App Store Connect. Everything there’s automated in the upload wizard and, the rest depends on App Store Review. Once that’s out of the way:
Second, distribute via “Developer ID”, i.e. sign and export an
.appbundle for distribution on your own. Do upload to the notary service.
Wait for the notarization process to finish. (Xcode displays a notification and you get an email.) Then, form the Organizer window, select “Export Notarized App” from the right sidebar.
In 2022 and Xcode 13, you still need to select a different list item and then re-select the notarized app archive to make the export button re-validate and activate. I sometimes also have to restart Xcode when things go super bad and Xcode just won’t update the status of the archive. Balls.
With the notarized
.appexported to disk, I zip this and put the version into the filename. Yesterday, that was
TableFlip-v1.4.0.zip. This zip is used to distribute Sparkle updates.
I do not distribute direct app downloads this way because you get all kinds of trouble with Gatekeeper path randomization and app translocation. Distribution via
.dmgsolved most of these issues. So zip for Sparkle updates only.
To prepare the downloadable
.dmgfor the website, I use DropDMG. The disk image contains graphical backgrounds, and DropDMG automatically code-signs the disk image file. Nice. (Note: use APFS when you target macOS 10.13+ for faster mounting speeds. Got this tip from Zorg, who you might know as the current maintainer of Sparkle, via Slack in Nov 2021.)
Finally, I notarize the
.dmg, too! Otherwise, users would be warned when opening the
.dmgitself, even though the app inside is notarized. Manual notarization also means you need to staple the ticket to the file for distribution; otherwise, users would have to request the ticket from Apple’s servers, and that sounds like an unnecessary point of failure to me.
The command line call to sign
foo.dmgis of this form:
$ xcrun notarytool submit \ foo.dmg --wait \ --apple-id firstname.lastname@example.org \ --password app-spec-ific-pass \ --team-id ABC123123123
You can also use keychain instead of passwords, as Rich Siegel kindly pointed out in chat, also mentioning
man notarytoolfor reference. The password-based version works well enough for me, though. Note that you use an app-specific password, not your main Apple ID password. You can generate these from https://appleid.apple.com.
This new command waits for the notarization to finish, which makes timing of the next step simpler than it used to be:
The stapling step is:
$ xcrun stapler staple foo.dmg ... The staple and validate action worked!
Bonus: you can verify that the stapling worked anytime in the future via:
$ xcrun stapler validate foo.dmg The validate action worked!
There’s no need for this in the process, though, as the stapling call contains a verification step automatically. (At least it tells as much.)
No need to zip these for distribution – DMG files can be created with compression, and DropDMG offers this, too.
.dmg files then get uploaded to AWS for global distribution (it’s rather generous on traffic compared to my own host).
Users can download the
.dmg binary file in their browsers directly. When users open the file, there won’t be a Gatekeeper warning even if the Mac is offline. Dragging the app from the disk image to the Application folder (which you will want to include as an alias in the DMG of course) removes the quarantine xattr flag and solved the app translocation problem from zipped apps opened from e.g. the
~/Downloads folder mentioned above.
It’s the best I’ve got thus far.
Alternatives and other notes for reference:
- Michael Tsai and Josh Wisenbaker revealed (to me) that you can omit notarization of the
.dmgis notarized, but then there’s all kinds of edge cases once you transfer the
.appbundle to another Mac that wasn’t privvy to the disk image’s notarization. The notarization ticket is stapled to the
.dmg, not the
.appinside, so you cannot re-distribute (e.g. copy via network) the app bundle extracted from the disk image independently in a notarized fashion, even though the notarization is recusively notarizing all contents. The stapling is key here. Since notarization happens rather quickly, I don’t see a point in bypassing it (except for academic curiosity) and personally prefer to err on the side of caution: Notarize both pieces so they can be used independently without caveats.
- Josh also mentioned that
fastlanecan automate this process. Haven’t tried Fastlane for Mac apps in, what, 4 years or so? So theres the
notarizeaction you might want to have a look at if you want Fastlane.
- Brendan Duddridge recommended the app DMG Canvas to create disk images, code-sign the image file, and then notarize it in one go. Haven’t tried that tool, yet, but if you don’t own DropDMG, you might also want to trial this app.