Cutting a release
No Allomer release has been cut yet. The local app uses an ad hoc signature. It is suitable for development on this Mac. It is not suitable for distribution.
A public release needs a Developer ID signature, Apple notarization, and stapled tickets on both the app and disk image. The release scripts keep the local preview path working when those credentials are absent.
Release configuration
tools/release.json contains the product and release values used by all packaging scripts.
| Key | Purpose |
|---|---|
product_name | App, volume, and disk image name. |
bundle_identifier | App bundle identifier. |
disk_image_identifier | Code-signing identifier for the disk image. |
version | Semantic release version. A prerelease suffix is allowed. The numeric core becomes CFBundleShortVersionString. |
build | Positive integer used by CFBundleVersion. Increase it for every distributed build. |
minimum_system_version | Oldest supported macOS version. Every bundled binary must match it. |
copyright | Copyright text stored in the app. |
signing_identity | Full Developer ID Application: ... identity. null selects ad hoc signing. |
team_identifier | Ten-character Apple team identifier. |
notary_keychain_profile | Local profile created by notarytool store-credentials. |
release_url | HTTPS page where the release and source are published. |
The scripts reject missing keys, invalid identifiers, a nonnumeric build, and partial signing configuration.
Local preview
python3 tools/build-app.py
python3 tools/check-app.py
python3 tools/build-dmg.py
python3 tools/release.py --verify
The last command checks the seal, every Mach-O file, ARM64 architecture, the macOS 14 deployment target, the app metadata, and the disk image layout. It writes dist/preview/release-manifest.json. It reports the release checks that an ad hoc signature cannot pass.
Inputs needed for a public release
The repository still needs these external inputs:
- A committed, clean Git revision.
- An Apple Developer Program account and a Developer ID Application certificate with its private key.
- A notary credential stored in the login keychain.
- The final HTTPS release address in
tools/release.json. - A clean Apple silicon Mac or virtual machine running macOS 14 for the first-launch checks in Release checks.
- Source-path remapping in the native, Rust, and Swift builders. The current preview embeds its checkout path. Signed builds refuse this until the builders remove it.
Confirm the signing identity with:
security find-identity -v -p codesigning
Create the notary profile once. The command prompts for an app-specific password. The password is stored in Keychain and does not enter the repository.
xcrun notarytool store-credentials allomer-notary --apple-id '<apple-id>' --team-id '<TEAMID>'
Then set the identity, team, profile, and release URL in tools/release.json.
Public release flow
Start from the committed revision that will be tagged.
python3 tools/build-app.py
python3 tools/check-app.py
python3 tools/release.py
The scripts perform these steps:
- Build every executable for ARM64 and macOS 14.
- Sign nested code and the app with the hardened runtime and a secure timestamp.
- Read every built binary back and verify its target and signing team.
- Submit the app to Apple, save the response and log, and staple its ticket.
- Build and sign the disk image from the stapled app.
- Submit the disk image and staple its ticket.
- Run Gatekeeper checks on the app and a quarantined copy of the disk image.
- Mount the final image and run the packaged conversion check against the app inside it.
- Write the release manifest and final SHA-256 digest.
The app must be stapled before it enters the image. A ticket attached only to the disk image does not follow the app when the user drags it to Applications.
The notary response and full log stay beside the artifacts in dist/preview/. Read the log even when Apple accepts the submission.
Privacy and entitlements
The app has usage descriptions for Desktop, Documents, Downloads, removable volumes, and network volumes. Folder monitoring can read those locations without an Open panel, so macOS can ask for access and explain why.
The app has no entitlements file. Its code does not request JIT, unsigned executable memory, disabled library validation, or another hardened-runtime exception. Whole-system monitoring can require Full Disk Access. The user grants that access in System Settings.
Update delivery
Allomer does not ship an update checker yet. After the first signed release establishes the GitHub release channel, add a small version check that opens that release page.
Independent installation check
The local quarantine check is a smoke test. It cannot reproduce a browser download's full provenance record.
Use a clean Apple silicon Mac running macOS 14. Download the disk image over HTTPS. Disconnect the network. Mount it, drag Allomer to Applications, and launch it. Then run the format and Finder checks in Release checks.
Publish the GitHub release
The first public build is v0.1.0-beta.1. Mark it as a GitHub prerelease. Create a draft release for tag v<version> and upload these files before publishing it:
Allomer-<version>.dmgrelease-manifest.json- The source for the exact Git revision in the manifest
Use the SHA-256 from the release manifest. The package-size report is written before the disk image ticket is attached, so it describes different bytes. Publish the release only after the uploaded names and digest match the manifest.
Publish through Homebrew
The public install command is:
brew install --cask ashbench/tap/allomer
Homebrew gets this command from the ashbench/homebrew-tap repository and its Casks/ directory. The fully qualified install grants trust only to this cask. Users who tap first can make that trust explicit with brew trust --cask ashbench/tap/allomer; the documentation does not ask them to trust the whole tap.
The homebrew-release environment requires owner approval. Its HOMEBREW_TAP_DEPLOY_KEY secret can write only to the tap repository. The workflow cannot read it until the environment approval succeeds.
Only a repository maintainer can publish a GitHub release or manually run this workflow. It does not run for pull requests, and fork workflows do not receive the environment secret. Publishing a GitHub release starts .github/workflows/homebrew.yml. The workflow downloads the DMG and release manifest, checks the Git revision, Developer ID signature, hardened runtime, secure timestamps, file name, and SHA-256, then generates Casks/allomer.rb. It runs brew style before it commits the cask to the tap. A preview or ad hoc build is refused.
To retry an existing release, run Publish Homebrew cask from GitHub Actions and enter its tag. To inspect the generated cask before publishing, run this beside a signed release manifest and DMG:
python3 tools/build-homebrew-cask.py \
--download-url https://github.com/ashbench/allomer/releases/download/v0.1.0-beta.1/Allomer-0.1.0-beta.1.dmg
brew tap-new --no-git allomer-check/tap
mkdir -p "$(brew --repository allomer-check/tap)/Casks"
cp dist/preview/allomer.rb "$(brew --repository allomer-check/tap)/Casks/allomer.rb"
brew style --cask allomer-check/tap/allomer
The cask installs Allomer.app and links Contents/MacOS/allomer into Homebrew's binary directory. brew uninstall --cask --zap ashbench/tap/allomer also removes settings, history, and recovery files, so users should use --zap only when they no longer need Undo.