Structured counterparts:
This document explains how to build the BugNarrator release app, package it as a distributable macOS DMG, and validate it before publishing on GitHub Releases.
The packaging workflow creates:
- a Release build of
BugNarrator.app - a DMG containing
BugNarrator.app - an
Applicationsshortcut inside the DMG - a custom Finder layout with a drag-to-Applications presentation
- a mounted DMG volume icon that uses the BugNarrator app icon
- validation that branded icon resources are present in the app bundle
- validation that the mounted DMG contains the app plus the
Applicationsshortcut - two output filenames in
dist/
By default you will get:
BugNarrator-vX.Y.Z-macOS.dmgBugNarrator-macOS.dmg
The non-versioned filename is useful for a stable GitHub Releases download link.
- Xcode installed
- the generated project file
BugNarrator.xcodeproj hdiutil, which is included with macOSpython3, which the DMG builder uses to bootstrapbuild/dmg-venvwithdmgbuildautomatically if it is missing
If you changed project.yml, regenerate the Xcode project first:
xcodegen generateFrom the repository root:
./scripts/build_dmg.shFor a quick unsigned release-readiness pass before packaging:
./scripts/release_smoke_test.shThe release smoke path no longer launches the keychain startup probe by default. If you explicitly want to verify that startup does not trigger a new SecurityAgent prompt, opt in with:
RUN_STARTUP_KEYCHAIN_SMOKE=YES ./scripts/release_smoke_test.shThe script:
- builds the app in
Release - generates the DMG background art
- bootstraps
build/dmg-venvwithdmgbuildautomatically when that packaging environment is missing - uses
dmgbuildto package a styled HFS+ DMG with:BugNarrator.app- an
Applicationsshortcut - the BugNarrator mounted-volume icon
- a clean drag-to-Applications Finder layout
- verifies
AppIcon.icnsandAssets.carexist in the built app - mounts the DMG and verifies the expected layout resources
- writes the finished artifacts to
dist/ - when signing is enabled, verifies the built app and mounted DMG app still carry the microphone entitlement required for recording
By default the script writes:
- the DMG files to
dist/ - intermediate build data to
build/DerivedData
The built Release app is left at:
build/DerivedData/Build/Products/Release/BugNarrator.app
Use that path for extra manual checks if you want to inspect codesigning or bundle resources directly.
If you want to remove local build copies after testing so only /Applications/BugNarrator.app remains in Spotlight, Launch Services, and macOS privacy prompts, run:
./scripts/cleanup_local_build_apps.shThe script defaults to:
CODE_SIGNING_ALLOWED=NOThis makes the packaging workflow reproducible on machines that do not have Apple signing configured.
For a public release, you should normally:
- install a
Developer ID Applicationcertificate for your Apple Developer account - configure a notarization credential profile for
notarytool - build with Developer ID signing enabled
- notarize and staple the final DMG
The build scripts now pin the local macOS destination to the host architecture so the release path does not rely on Xcode's ambiguous default destination selection.
You can override the script behavior if needed. For a locally signed build using the configured Apple team:
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM=YOURTEAMID \
ALLOW_PROVISIONING_UPDATES=YES \
./scripts/build_dmg.shThat produces a signed app inside the DMG if your local machine has a valid signing identity for that team.
For broad public distribution outside your own Mac, you should use Developer ID Application plus notarization. An Apple Development signature is better than unsigned for local validation, but Gatekeeper will still reject it for normal public download flows.
BugNarrator's canonical notarization identity is:
- Apple ID:
abdeffenderfer@icloud.com - Team ID:
2R4WAH4R53 - Local keychain profile:
BugNarratorNotary
Do not store the app-specific password in the repo. Keep it only in:
- 1Password item
Apple Notarytool - BugNarratorNotary - GitHub repository secret
APPLE_APP_SPECIFIC_PASSWORD
If the password is rotated, update both of those locations before the next release.
First, store a notarization credential profile in your keychain:
xcrun notarytool store-credentials BugNarratorNotary \
--apple-id abdeffenderfer@icloud.com \
--team-id 2R4WAH4R53 \
--password YOUR_APP_SPECIFIC_PASSWORDThen build, notarize, staple, and validate in one command:
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_STYLE=Automatic \
CODE_SIGN_IDENTITY=\"Developer ID Application\" \
DEVELOPMENT_TEAM=2R4WAH4R53 \
ALLOW_PROVISIONING_UPDATES=YES \
NOTARIZE=YES \
NOTARY_PROFILE=BugNarratorNotary \
./scripts/build_dmg.shIf app signing succeeds but Apple's notarization service is temporarily unavailable or blocked by an expired Apple Developer agreement, the script now reports that explicitly instead of looking like a signing failure. You still have two safe options:
- use
NOTARIZE=NOfor a signed-only internal build - use
ALLOW_NOTARIZATION_FAILURE=YESif you want the script to preserve the signed DMG and checksum files even when notarization fails
Example signed-only internal build:
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_IDENTITY="Developer ID Application" \
DEVELOPMENT_TEAM=2R4WAH4R53 \
NOTARIZE=NO \
./scripts/build_dmg.shExample "try notarization, but keep the signed artifact if Apple blocks notarization":
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_IDENTITY="Developer ID Application" \
DEVELOPMENT_TEAM=2R4WAH4R53 \
NOTARIZE=YES \
NOTARY_PROFILE=BugNarratorNotary \
ALLOW_NOTARIZATION_FAILURE=YES \
./scripts/build_dmg.shThe packaging script will:
- build the Release app
- re-sign the app explicitly when a Developer ID build requires manual distribution signing
- verify the app is signed
- verify icon resources are present in the built app
- create a styled DMG with
dmgbuild - apply the custom volume icon and Finder window layout metadata directly
- mount the DMG and verify
BugNarrator.appplus theApplicationsshortcut are present - submit the DMG to Apple's notarization service
- staple the notarization ticket to the DMG
- run
stapler validateandspctlchecks
Because BugNarrator targets macOS 14 or later, the shipped app now uses ScreenCaptureKit for screenshot capture. No extra packaging step is required for that API, but your release smoke test should still verify that Screen Recording permission prompts only when the user requests a screenshot.
If your local Mac already has a Developer ID Application certificate and a BugNarratorNotary profile configured, this is the practical public-release command:
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_IDENTITY="Developer ID Application" \
DEVELOPMENT_TEAM=2R4WAH4R53 \
ALLOW_PROVISIONING_UPDATES=YES \
NOTARIZE=YES \
NOTARY_PROFILE=BugNarratorNotary \
./scripts/build_dmg.shThat produces:
dist/BugNarrator-vX.Y.Z-macOS.dmgdist/BugNarrator-macOS.dmg
Recommended flow:
- run
./scripts/release_smoke_test.sh - run the signed/notarized packaging command
- optionally run
./scripts/cleanup_local_build_apps.shafter publishing so local test builds do not linger inDerivedData - create a GitHub Release
- upload
dist/BugNarrator-macOS.dmg - optionally upload the versioned
dist/BugNarrator-vX.Y.Z-macOS.dmg - add release notes and link back to the changelog if needed
- verify the README top download link matches the uploaded stable DMG filename
- keep Apple signing credentials and the notary profile valid on the local release Mac before the next release
BugNarrator release builds are intentionally local-only. Do not rely on GitHub Actions to compile, sign, notarize, or package the macOS app.
After building:
- open the DMG in Finder
- confirm the DMG window opens to a clean drag-to-Applications layout with
BugNarrator.appon the left andApplicationson the right - confirm the mounted DMG shows the branded BugNarrator volume icon on the desktop and in Finder
- confirm
BugNarrator.appis present - confirm the
Applicationsshortcut is present - drag the app into
Applications - confirm the installed app shows the branded BugNarrator icon in Finder
- launch the installed app and verify first-run behavior
For a public release, also verify:
xcrun stapler validate dist/BugNarrator-vX.Y.Z-macOS.dmgspctl -a -vv build/DerivedData/Build/Products/Release/BugNarrator.app- Gatekeeper accepts the downloaded DMG on a second Mac that has never built the app locally
Note: spctl -a -vv -t open against a locally produced DMG can report Insufficient Context on the build machine because the file is not a quarantined download. Treat stapler validate plus an app-level spctl check as the reliable local validation, then do a real download/open smoke test before publishing.
The README intentionally points to:
- the stable direct asset path:
BugNarrator-macOS.dmg - the latest GitHub release page
If you change the stable DMG filename in the script, update the README download section in the same change so public visitors do not hit a broken link.