Preparing a macOS app for distribution through the App Store is fairly easy using Xcode, but to do so for apps that you plan to distribute as a binary by other means — as a download from your own website, for example — isn’t straightforward, and it has got more complicated over recent macOS releases.
Code signing and app notarisation are, I think, good requirements in a connected world where apps are no longer distributed on robust write-once, read-many media. From a security perspective, the less physical a medium is, the more vulnerable to attack it is. Apple’s requirements may be a pain, but there’s (mostly) good reasons for them, and the App Store is possible the safest way to distribute macOS apps there is. Only the fact that it mandates App Sandbox, which overly restricts an app’s access to the host’s resources, prevents me from distributing more of my apps that way.
Even beyond the App Store, code signing and notarisation help potential users download and install them with some confidence — event though they increase the complexity of packaging apps for distribution. Fortunately, it’s a very scriptable process.
Here’s what needs to be done:
- Archive the finished release in Xcode, which handles the code signing.
- Submit the app for notarisation.
- Package the notarised app for installation.
- Notarise the package.
- Add the notarised package to a disk image.
- Sign the
The last two steps are optional, but I do them anyway: belts and braces.
Step 1 is achieved entirely in Xcode, but the other steps require other tools, all accessible from the command line. Which makes them scriptable.
packapp.zsh, is available from my GitHub repo. It performs all but the last two steps — I’ll explain why in a moment — leaving you with an Installer package ready for distribution. The ability to build the steps into a script also gives you a great deal of scope for automation.
The script takes the path to the notarized
.app file plus a number of switches. The essential ones are
--cert, which respectively take a Keychain account name and the name of your Developer ID Installer certificate, which you’ll require if you’re to create a signed Installer package. And you have to do that to get it notarised.
The script parses the command line arguments and does some checking to make sure it the information it needs. It then creates the signed installed package, which it then uploads to Apple. For this is requires Developer Account Apple ID credentials, which it expects to find in your keychain stored under the name AC_PASSWORD (edit the script to change this). For the keychain item, you’ll need to visit appleid.apple.com, sign in and create an App Key — this key is stored in AC_PASSWORD where it’s associated with an account name, which could be your Apple ID or something else. Whatever you choose, pass it into
packapp.zsh using the
The upload complete,
packapp.zsh now periodically polls the App Store server until the notarisation process is complete, either successfully or not. If the package is notarised, the script staples the notarisation notice to the package, verifies that it’s good for distribution and exits.
I currently create a
.dmg file manually: open Disk Utility, make a 100MB image, copy over the Installer package, unmount the image, and then use Disk Utility to create a compressed version. I sign this manually using:
codesign -s '<DEVELOPER_ID_APPLICATION_CERT>' <APP_NAME>.dmg
Why not script these steps? I’d like to, and it is possible, but I’ve not yet figured out how to compress the final
.dmg as much as Disk Utility does. Here are the CLI steps:
hdiutil create "$dmg_path" -megabytes 100 -fs HFS+J \ -volname "$volume_name" hdiutil attach "$dmg_path" cp -r "$package_path" "/Volumes/$volume_name" hdiutil detach "/Volumes/$volume_name" hdiutil convert "$dmg_path" -format UDZO -imagekey 9 \ -o "$dmg_path"
The key issue is the final line. All of the possible
-format values (check out hdiutil’s man page for the details) yield larger
.dmg files than Disk Utility does. I continue to experiment.