Skip to content

Release Process

mrdulasolutions edited this page May 13, 2026 · 1 revision

Release Process

How releases get cut. Fully automated via GitHub Actions — you don't run codesign or notarytool locally.

TL;DR

# Bump version in four files, commit, tag, push.
# That's it. Workflow does everything else.
git commit -am "chore(release): bump to v0.1.9"
git tag v0.1.9
git push origin main v0.1.9
gh run watch  # follow the workflow
# Then: review the draft release, click Publish.

Pipeline overview

A tag push matching v* triggers .github/workflows/release.yml. The workflow runs a matrix:

  • aarch64-apple-darwin on macos-latest — actively used
  • x86_64-apple-darwin on macos-13 — configured but unreliable (Intel runner pool is constrained; jobs frequently sit queued for hours)

Each matrix job is 14 steps:

  1. Checkout the tagged commit.
  2. Setup Node + Rust toolchains.
  3. Cache cargo registry and src-tauri/target/.
  4. npm ci root + sidecar.
  5. Pre-import Apple Developer ID cert into a fresh keychain in $RUNNER_TEMP. Necessary so copy-runtime-modules.mjs can codesign better_sqlite3.node with a secure timestamp — without that, notarytool rejects the .app. (PR #9, PR #10)
  6. Build sidecarprepare-node (bundle real Node binary, ad-hoc sign), build (esbuild → dist/index.cjs), package (wrap in self-extracting bash stub), runtime-modules (copy + sign .node files).
  7. Probe Apple signing secrets — logs whether codesign and notarization will run. Doesn't gate later steps.
  8. Strip AppleDouble metadata from src-tauri/target, sidecar/, node_modules. Without this, gtar-restored cache files include ._* companions that get bundled into the .app and break tauri-plugin-updater extraction on client. (PR #2)
  9. Wipe cached bundle outputsrm -rf src-tauri/target/<triple>/release/bundle/ so tauri-bundler regenerates the .app.tar.gz from the freshly-built .app rather than reusing a stale cached tarball. (PR #12)
  10. tauri-action@v0 — Rust compile, .app bundle, codesign with Developer ID, notarize via Apple, .dmg build, updater bundle sign with minisign, upload all to a draft GitHub release.
  11. Verify update tarball — raw-parses the produced .app.tar.gz tar stream (BSD tar -t hides AppleDouble; can't trust it). Fails the build if any ._* entries leak through.
  12. Summarize artifacts in the run log.

Required secrets

All eight at https://github.com/mrdulasolutions/AOS-Mail/settings/secrets/actions:

Secret What it is How to set
APPLE_CERTIFICATE Base64-encoded .p12 of the Developer ID Application cert security export -k login.keychain-db -t identities -f pkcs12 -P <pw> -o cert.p12 && base64 -i cert.p12 | gh secret set APPLE_CERTIFICATE
APPLE_CERTIFICATE_PASSWORD The password you set when exporting the .p12 echo -n '<pw>' | gh secret set APPLE_CERTIFICATE_PASSWORD
APPLE_SIGNING_IDENTITY Exact CN: Developer ID Application: <Name> (<Team ID>) Look up with security find-identity -v -p codesigning
APPLE_ID Apple ID email enrolled in the Developer Program Plain string
APPLE_PASSWORD App-specific password for notarization (xxxx-xxxx-xxxx-xxxx) https://account.apple.com → Sign-In and Security → App-Specific Passwords
APPLE_TEAM_ID 10-char Team ID Visible in the signing identity CN; also at https://developer.apple.com/account
TAURI_SIGNING_PRIVATE_KEY Contents of ~/.tauri/aos-mail-v2.key (minisign private key) cat ~/.tauri/aos-mail-v2.key | gh secret set TAURI_SIGNING_PRIVATE_KEY
TAURI_SIGNING_PRIVATE_KEY_PASSWORD Password for the minisign key echo -n '<pw>' | gh secret set TAURI_SIGNING_PRIVATE_KEY_PASSWORD

If any signing secret is missing, the build produces an unsigned .app (won't pass Gatekeeper on a fresh Mac). If notarization secrets are missing, the build is signed but not notarized — users see the "unidentified developer" warning. If TAURI_SIGNING_PRIVATE_KEY is missing, no .app.tar.gz.sig and no latest.json — auto-update breaks.

Cutting a release — step by step

1. Bump the version in four files

They MUST agree, or tauri build warns about version mismatch and notarytool sometimes rejects.

  • package.json"version"
  • sidecar/package.json"version" (optional but conventional)
  • src-tauri/tauri.conf.json"version"
  • src-tauri/Cargo.toml[package] version
  • src-tauri/Cargo.lock[[package]] name = "aos-mail"version
git commit -am "chore(release): bump to v0.1.9"
git push origin main

2. Tag and push

git tag v0.1.9
git push origin v0.1.9

The tag push triggers release.yml.

3. Watch the workflow

gh run watch
# or open https://github.com/mrdulasolutions/AOS-Mail/actions

arm64 typically completes in ~10-15 minutes. x64 may sit queued indefinitely depending on macos-13 runner availability — see "Intel matrix" below.

4. Review the draft release

When green, GitHub Releases has a draft with:

  • AOS.Mail_<version>_aarch64.dmg — signed + notarized installer.
  • AOS.Mail_aarch64.app.tar.gz — auto-update bundle.
  • AOS.Mail_aarch64.app.tar.gz.sig — minisign signature for the bundle.
  • latest.json — updater manifest (darwin-aarch64 entry only currently).

Quick sanity check:

mkdir /tmp/verify && cd /tmp/verify
gh release download v0.1.9 --pattern '*aarch64*.tar.gz'
tar -xzf AOS.Mail_aarch64.app.tar.gz
spctl -a -vv "AOS Mail.app"   # expect: accepted / Notarized Developer ID
xcrun stapler validate "AOS Mail.app"   # expect: The validate action worked!

5. Publish

gh release edit v0.1.9 --draft=false
# or click "Publish release" in the UI

The moment the draft is published, releases/latest/download/latest.json flips, and every running install picks up the new version on its next update check.

If something fails

The workflow tries each step idempotently — you can re-tag (delete and re-push the tag) to re-run. To move a tag:

git tag -d v0.1.9
git push origin :refs/tags/v0.1.9
git tag v0.1.9 <new-commit-sha>
git push origin v0.1.9

Before re-tagging, delete the draft release (if any) — otherwise tauri-action appends artifacts on top of the broken ones:

gh release delete v0.1.9 --yes

See Troubleshooting for common build failures and their fixes.

Intel matrix

The workflow includes x86_64-apple-darwin on macos-13 runners, but GitHub's Intel macOS runner pool has been constrained — jobs frequently sit queued for hours and time out. For now, arm64-only is the practical default.

latest.json only contains a darwin-aarch64 entry. The matrix runs both arms and each uploads its own latest.json to the same draft release; whichever uploaded last wins. A workflow patch to merge both architectures' entries before publishing is on the roadmap. Until then, Intel users have no auto-update channel.

What's NOT in this pipeline

  • gh release create — tauri-action creates the draft for us.
  • Manual codesign — tauri-action handles it.
  • Manual xcrun notarytool submit — tauri-action handles it.
  • Manual stapler staple — tauri-action handles it.
  • Manual npx @tauri-apps/cli signer sign — tauri-action handles it.

If you find yourself running any of those locally as part of a release, something's wrong with the secrets or the workflow.

See also

Clone this wiki locally