How to quickly package macOS apps for distribution outside the App Store

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.

macOS app package icon

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:

  1. Archive the finished release in Xcode, which handles the code signing.
  2. Submit the app for notarisation.
  3. Package the notarised app for installation.
  4. Notarise the package.
  5. Add the notarised package to a disk image.
  6. Sign the .dmg file.

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.

My script, 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 -u/--user and -c/--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 --user switch.

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.

macOS disk image installer icon

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.

1 thought on “How to quickly package macOS apps for distribution outside the App Store

  1. Vincent

    Hi. From my experience, there are several improvements I think you could make.

    [1] if you install your app using an installer package, you probably don’t need to wrap the package inside a disk image. Distributing the notarized package is suffificent.

    [2] if you want to distribute just a plain app, it’s probably better to distribute the app inside a disk image (no need to put it in an installer package); if so, you don’t need to notarize the app bundle (just codesign it) because the notarization service will inspect the disk image and find a codesigned app and go ahead and notarize the image. When the image is downloaded, it will pass the notarization check and the app can then be put in /Applications.

    [3] to create an optimised disk image, first create a sparebundle disk image then convert that image (in reality a folder on your disk) into a compressed read-only disk image. When converting it, I recommend the ULFO format and not the UDZO nor UDBZ formats.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s