For Mac developers selling outside the App Store
Licence keys that survive a flight and a refund. Signed, notarised, auto-updating DMGs from one command. And 35 checks that stop the release which would have failed silently on a Mac you don't own.
One-time purchase · Unlimited apps · Kept current as Apple moves
Free tool, no signup: eighteen checks against a Mac app you already shipped and its update feed. It tells you what is quietly broken for the people who downloaded it, and fine for you.
The full run from certificate to notarised DMG, in order.
The log is the tool's own output, word for word.
Sell from your own site and you keep 100% minus payment fees, own your customers, and escape the sandbox. In exchange, Apple hands you this list and no tooling.
| The job | Handled alone | With the kit |
|---|---|---|
| Menu bar app shell | No Xcode template exists; tutorials are outdated, LLMs suggest deprecated APIs | Working skeleton, macOS 13+ |
| Notarization | Xcode notarises the app. The DMG you actually ship is a separate submission, and every release repeats the GUI by hand | One script, both submissions, signature flags checked before the round-trip |
| DMG packaging | hdiutil incantations; the DMG itself must be notarized too, which most guides skip | Signed, notarized, stapled |
| Auto-updates | Sparkle setup, EdDSA keys, appcast hosting, three silent failure modes | Wired, with delta updates |
| License keys | Verification API, offline handling, refund handling, keychain storage | Polar flow, 14-day grace, provider in one file |
Config.xcconfigThe one file you edit. Name, bundle id, team, versions, update feed, product id. Everything follows it.ship.shBuild → sign → notarize → staple → DMG. Verifies its own signature flags before wasting a round-trip to Apple, and refuses to ship what would silently fail.appcast.shGenerates your signed update feed. Rejects an unsigned one, a failure Sparkle's own tooling lets through without a word.# you edit one file: Config.xcconfig ShipKit.xcodeproj Sources/ ExampleFeatureView ← or point ship.sh at your own project SettingsView launch-at-login, tabs UpdaterView Sparkle wired License* Polar + grace period scripts/ ship.sh one command → DMG appcast.sh signed update feed docs/ 01-quickstart … 05-troubleshooting
Every constraint is here rather than in the small print. If one of the right-hand rows is you, close the tab now and keep your $99. We would rather lose the sale than the afternoon you spend finding out.
ship.sh at your own Xcode project and it ships that one, without modifying it. That is how Hawser itself is shipped.ship.sh runs on your machine, deliberately. There are no GitHub Actions templates.One more, because it is the question we get asked instead of being told: the kit ships a Polar licence client, and the provider is not welded in. LicenseClient.swift is 72 lines of code whose entire job is one HTTP call. The 14-day offline grace, the keychain storage, the refund and revocation handling and the licensed / grace / unlicensed state machine all sit behind it and never learn who answered. Pointing it at Stripe, Paddle, Lemon Squeezy or your own server is rewriting one function, not unpicking the kit.
Three of the ten. Any one of these costs a lost afternoon, or worse, ships broken silently.
Apple's certificate page pre-selects "Previous Sub-CA". Accept it and your Developer ID dies in February 2027 no matter what the expiry says. The guide walks you past it; ship.sh's error text warns about it too.
Add Sparkle after your first release and its own tool generates an unsigned feed with no warning. Updates then fail silently on every user's machine. appcast.sh verifies the signature and refuses to let it through.
errSecInternalComponent is codesign's way of saying "keychain permissions". It just never mentions keychains, or permissions. You get the one-line fix in the error message itself, when it happens, not after two hours of searching.
A number is easy to write and easy to doubt. These three are real stopping points in ship.sh and appcast.sh: 24 in the first, 11 in the second. Every one of them halts the run and prints the fix rather than a status code, and the three below share a property that makes them worth $99 on their own. Nothing on your machine looks wrong when they fire.
Before signing your update feed, the script reads the Sparkle public key baked into the app inside each archive and compares it with the private key in your keychain. If they are not a pair, it stops and prints both.
Without it: the feed publishes, the app fetches it, and Sparkle refuses every update with no visible error. Not a failed update dialog. Nothing. Your users simply stay on the version they have, and you find out from the ones who eventually email to ask why the app never updates.
Each update archive is unpacked and the app inside it is checked for a stapled notarisation ticket. The usual cause of a failure here is specific enough that the script names it: the archive was copied out of dist/ by hand, from the build made to send to Apple, before the ticket existed.
Without it: you ship an update that Gatekeeper refuses on any Mac that is offline or behind a captive portal. It works perfectly on yours, because yours has already seen the ticket.
After the app is signed, the script reads the entitlements back out of the signature and refuses to continue if get-task-allow is still there. It also verifies the Hardened Runtime flag and the secure timestamp the same way, by reading the signature rather than trusting the command that made it.
Without it: Apple rejects the notarisation, but only after you have uploaded, waited, and lost the round trip. The check costs milliseconds and happens before the upload.
Counted in the shipping scripts on 4 September 2026, not estimated. The remaining 32 are the same shape: read back what actually happened, refuse to continue on a mismatch, and say what to do about it.
Eighteen of them run against an app you have already shipped, as a free tool →
Apple has changed the rules of this pipeline before, and macOS 27 is in beta right now. Every change arrives the same way: a release that worked last month stops working, and nothing tells you why.
Our scripts stay current because we ship our own apps through them. When Apple moves, it breaks our releases first. We find it, fix it, and you get the fix.
A pipeline you assembled yourself gets updated when a customer emails you to say your app won't open.
Fixes and improvements to v1 are free, delivered through your original download link. Every release so far is on the changelog.
No subscription, no per-app fee, no account. The full breakdown is on the pricing page: what the price buys, what you need on your side, and what the kit deliberately does not do. Before you pay rather than after.
Not ready? Verify the sample or read the FAQ first. There is no trial, and the FAQ says why.