Last week, I wanted to add an update to one of my mac apps Peachy after a long time. I realized that the atool used for notarizing mac apps has long been discontinued and it was time to switch to notarytool. It took me a while to read through Apple documentations and WWDC videos again to finally be able to distribute a working DMG for the new version.

I had some free time due to the Lunar new year holiday, so this was an opportunity to learn something new. It’s time to automate the distribution process for my mac apps, and a CLI tool is a perfect solution for this.

First thing first, I went to my trusted friend Google (not ChatGPT, surprise 👻) - and looked for a quick tutorial to build a CLI tool in Swift. Surprisingly, all of the tutorials I found were outdated, except for the bible documentation. This post will sum up things that I learned during the process.

Getting Started

The first thing to do is create a Swift package of executable type:

$ swift package init --name dmg-notary --type executable

I used Swift 5.9, and the newly created package contained the Package.swift file defining the main target as .executableTarget. I went ahead to add ArgumentParser as a dependency. This is essential for setting up and customizing a CLI tool.

The default package came with a main.swift file. We can create a struct in this file and trigger its .main() method. Since Swift 5.3, we can rename the file to the same as the type and mark it as the entry point with @main. This is the best practice since it makes the project structure cleaner and more organized.

The root struct needs to conform to the PassableCommand protocol and define the arguments and options for the CLI tool.

Another cool dependency is ShellOut which is helpful for triggering command lines from our Swift scripts.

Implementation highlights

dmg-notary was inspired by dmgdist, which also depends on create-dmg for generating a DMG from an app file. The first part of the script uses shellout to trigger the create-dmg command for this step.

The second part of the script triggers commands to notarytool to handle the notarization step. Due to the proxy, I had to handle the edge case when the user opts to enter their password separately instead of providing it in plain text in the command. The goal was to keep their password field hidden rather than plain text with readline(). I learned about getpass (and also the term shoulder surfing), which was the solution to this problem.

Conclusion

The sourcecode of dmg-notary can be found on my Github repo.

Alternative notarization method

A simpler way to notarize mac apps is to add a post-action script to the Archive step of your app’s scheme. More details can be found here.

References