Skip to content

Ship MetersApp as a notarized download on every merge - #3

Closed
gotwalt wants to merge 1 commit into
mainfrom
ci/notarize-metersapp
Closed

Ship MetersApp as a notarized download on every merge#3
gotwalt wants to merge 1 commit into
mainfrom
ci/notarize-metersapp

Conversation

@gotwalt

@gotwalt gotwalt commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Every merge to main now produces a signed, notarized, stapled download of MetersApp.

What lands

swift/Scripts/build_app.sh — SwiftPM only emits a bare executable, so this wraps the same product in a real .app:

  • universal (--arch arm64 --arch x86_64), so one download runs on Apple silicon and Intel
  • Info.plist with bundle id com.gotwalt.libkp.MetersApp, display name "KP Meters", LSMinimumSystemVersion 13.0
  • NSLocalNetworkUsageDescription — without it macOS 15+ silently drops the discovery broadcast once the app is bundled
  • marketing version defaults to spec/version.toml; MARKETING_VERSION / BUILD_NUMBER / BUNDLE_ID override
swift/Scripts/build_app.sh          # -> swift/.build/dist/MetersApp.app

.github/workflows/metersapp-release.yml — on every push to main (plus workflow_dispatch), on macos-15:

build the bundle → import the Developer ID cert into a throwaway keychain → codesign --options runtime --timestampditto zip → notarytool submit --waitstapler staple + stapler validate + spctl --assess → re-zip the stapled app → delete and recreate the metersapp-latest prerelease at that commit.

The tag rolls, so the download URL is stable:

https://github.com/gotwalt/libkp/releases/download/metersapp-latest/MetersApp.app.zip

A .sha256 is published alongside it, and the zip is uploaded as a workflow artifact on every run. concurrency: metersapp-release keeps two merges from racing on the rolling tag; the keychain and the .p8 are torn down in an always() step.

Before this does anything

Six repository secrets:

Secret What it is
MACOS_CERT_P12_BASE64 Developer ID Application cert + key, base64 -i DeveloperID.p12
MACOS_CERT_PASSWORD the password used for that .p12 export
MACOS_SIGNING_IDENTITY Developer ID Application: Your Name (TEAMID)
APP_STORE_CONNECT_KEY_ID App Store Connect API key id
APP_STORE_CONNECT_ISSUER_ID issuer id (a UUID)
APP_STORE_CONNECT_KEY_CONTENT the AuthKey_<KEY_ID>.p8 contents

Until they exist the job still builds and uploads an unsigned artifact and emits a warning rather than failing — so merging this will not turn main red.

Verification

Locally: the script builds a working universal bundle (lipo -archsx86_64 arm64, plutil -lint clean); an ad-hoc codesign --options runtime run binds the Info.plist and reports Identifier=com.gotwalt.libkp.MetersApp with the runtime flag set; ditto produces a 762K zip. The workflow YAML parses and every run block passes bash -n.

The signing and notarization steps themselves are untested — they need the real secrets, and the first merge to main is what exercises them.

Not done here

  • No app icon (.icns in Contents/Resources + CFBundleIconFile).
  • The release job runs in parallel with CI rather than gating on it. Switching the trigger to workflow_run on CI success would publish only builds that passed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FUC9inEb9CrygvatVez5qD

SwiftPM builds a bare executable, which Gatekeeper will not accept and
which macOS 15 will not grant local-network access to. Scripts/build_app.sh
wraps the same product in a universal .app with an Info.plist that carries
NSLocalNetworkUsageDescription, so discovery keeps working once the app is
bundled.

The new workflow builds that bundle on every push to main, signs it with a
Developer ID under the hardened runtime, notarizes it, staples the ticket,
and republishes the rolling metersapp-latest prerelease at that commit, so
the download URL is stable. Until the six signing secrets are configured it
skips signing and uploads an unsigned workflow artifact rather than failing
the run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUC9inEb9CrygvatVez5qD
@gotwalt gotwalt closed this Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant