Skip to content

macOS signing and notarization, plus the Homebrew cask - #1

Merged
RusEu merged 2 commits into
mainfrom
macos-signing-and-cask
Aug 9, 2026
Merged

RusEu merged 2 commits into
mainfrom
macos-signing-and-cask

Conversation

@RusEu

@RusEu RusEu commented Aug 9, 2026

Copy link
Copy Markdown
Member

macOS builds went out unsigned, so every download opened to "mandalo is damaged and can't be opened". This signs the .app, the .dmg, the standalone CLI and the copy inside the darwin VSIXs with the Developer ID certificate, and notarizes all of them.

Verified against Apple with the real certificate, not just linted:

Artifact Result
CLI .tar.gz notarization Accepted, runs with the quarantine bit set
.app (sidecar included) codesign --verify --deep --strict valid, spctl → source=Notarized Developer ID
.dmg notarized and stapled, spctl -a -t open → accepted
Cask brew style --cask clean

Three things a real build turned up

  • Tauri rejects a sidecar named after the src-tauri crate. The bundled CLI is mandalo-cli, and the cask's target: puts it back on PATH as mandalo. This would have failed all five desktop jobs on a tag.
  • Tauri notarizes and staples the .app but not the .dmg carrying it, and Gatekeeper assesses the downloaded disk image on its own: spctl -a -t open answered Unnotarized Developer ID and refused it. Users would have hit the exact error this PR set out to remove. The release job now notarizes the .dmg and re-uploads the stapled copy before publish hashes it into SHA256SUMS.
  • The Linux desktop job had no pkg-config/libssl-dev/cmake, which building the CLI sidecar there needs.

Behaviour

A tagged release fails loudly when the Apple credentials are missing rather than publishing artifacts Gatekeeper will reject. Other runs build unsigned as before.

externalBin is global, so the CLI now also travels inside the .msi, .deb, .rpm and AppImage (~13 MB each) without landing on PATH there. Tauri has no per-platform externalBin; splitting it needs a separate merged config.

Homebrew

The app bundle carries the CLI, so one command installs both:

brew install --cask De-Rus/tap/mandalo   # macOS: app + CLI
brew install De-Rus/tap/mandalo          # Linux: CLI

The formula becomes depends_on :linux, matching the sixtysix tap — a formula without a bottle takes the build-from-source path, which demands Command Line Tools to install an already-compiled binary. The two can then never both provide bin/mandalo, so no conflicts_with is needed. The trade-off: on macOS there is no brew route to the CLI alone; that is install.sh or the tarball.

Still blocked on

Both releases are Drafts. Until one is published, neither the cask nor the formula nor the updater nor install.sh resolves for a real user, and sync-packaging.sh cannot read SHA256SUMS to fill the digests.

Secrets are set: APPLE_CERTIFICATE, APPLE_CERTIFICATE_PASSWORD, APPLE_SIGNING_IDENTITY, APPLE_API_KEY, APPLE_API_ISSUER, APPLE_API_KEY_P8. See docs/macos-signing.md.

🤖 Generated with Claude Code

macOS builds went out unsigned, so every download opened to "mandalo is
damaged and can't be opened". The Developer ID certificate now signs the
.app, the .dmg, the standalone CLI and the copy inside the darwin VSIXs,
and Apple notarizes all of them.

Three things this turned up that only a real build shows:

- Tauri rejects a sidecar named after the src-tauri crate, so the bundled
  CLI is `mandalo-cli` and the cask's `target:` puts it back on PATH as
  `mandalo`.
- Tauri notarizes and staples the .app but leaves the .dmg carrying it
  merely signed, and Gatekeeper assesses the downloaded .dmg on its own:
  `spctl -a -t open` answered "Unnotarized Developer ID" and refused it.
  The release job now notarizes the disk image too and re-uploads the
  stapled copy before SHA256SUMS is computed over it.
- The Linux desktop job had no pkg-config/libssl-dev/cmake, which building
  the CLI sidecar there needs.

A tagged release fails loudly when the Apple credentials are missing rather
than publishing artifacts Gatekeeper will reject; other runs stay unsigned.

The app bundle now carries the CLI, so the Homebrew cask installs both from
one command. The formula becomes `depends_on :linux` to match the sixtysix
tap: a formula without a bottle takes the build-from-source path, which
demands Command Line Tools to install an already-compiled binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 9, 2026 03:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds end-to-end macOS signing/notarization to the release pipeline (including DMG stapling) and introduces a Homebrew cask that installs the notarized app while exposing the bundled CLI via a symlink, aligning distribution artifacts with Gatekeeper expectations.

Changes:

  • Bundle the CLI into the Tauri desktop app via externalBin and stage it in CI with a new sidecar build script.
  • Add scripts and workflow steps to sign and notarize macOS artifacts (CLI tarball, VSIX-bundled CLI, app/DMG), including DMG notarization + re-upload.
  • Add/extend packaging automation and docs: Homebrew cask + Linux-only formula, and sync tooling to rewrite versions/digests from SHA256SUMS.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src-tauri/tauri.conf.json Declares the CLI as a bundled Tauri sidecar via externalBin.
scripts/build-sidecar.sh Builds/stages the CLI in the filename/location Tauri expects for sidecars.
scripts/macos-sign.sh Signs standalone macOS binaries (e.g., CLI + VSIX-bundled CLI) with Developer ID.
scripts/macos-notarize.sh Submits signed artifacts to Apple notarization and staples tickets where applicable.
scripts/sync-packaging.sh Extends packaging sync to also rewrite the Homebrew cask version/sha256 from SHA256SUMS.
packaging/homebrew/Casks/mandalo.rb New Homebrew cask for the universal DMG; exposes bundled mandalo CLI via binary ... target:.
packaging/homebrew/Formula/mandalo.rb Converts formula to Linux-only CLI distribution (depends_on :linux).
packaging/README.md Updates publishing instructions for formula+cask and documents universal DMG requirement.
docs/macos-signing.md Documents required secrets and the signing/notarization behavior and verification steps.
.github/actions/apple-credentials/action.yml Composite action to gate signing and materialize the App Store Connect .p8 on disk.
.github/workflows/release.yml Wires in sidecar staging; adds macOS signing/notarization (including DMG stapling + re-upload).
.github/workflows/ci.yml Extends shellcheck coverage to the new scripts.
.gitignore Ignores the staged sidecar output directory under src-tauri/binaries/.
Suppressed comments (2)

scripts/macos-sign.sh:35

  • base64 --decode is a GNU option and is not available on the default macOS/BSD base64, so this will fail on macOS runners (the primary place this script runs). Use a portable decoder (e.g., openssl base64 -d).
    printf '%s' "$APPLE_CERTIFICATE" | base64 --decode > "$p12"

.github/actions/apple-credentials/action.yml:49

  • base64 --decode is not portable on macOS (BSD base64 typically uses -D). Since this action runs on macOS runners, decoding the .p8 should use a portable method (e.g., openssl base64 -d).
        printf '%s' "$API_KEY_P8" | base64 --decode > "$key_path"

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +38 to +45
if [ -z "$CERTIFICATE" ] || [ -z "$API_KEY_P8" ]; then
if [ "$REQUIRED" = "true" ]; then
echo "::error::A tagged release must ship signed macOS artifacts, but APPLE_CERTIFICATE and/or APPLE_API_KEY_P8 are missing. See docs/macos-signing.md."
exit 1
fi
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "No Apple credentials — this run produces unsigned macOS artifacts."
exit 0
Comment thread scripts/macos-sign.sh
Comment on lines +23 to +26
if [ ! -f "$keychain" ]; then
umask 077
openssl rand -hex 24 > "$password_file"
password=$(cat "$password_file")
src-tauri is in this workspace, so `cargo test --workspace` compiles it, and
its build script refuses to run while the externalBin it declares is missing:

    resource path `binaries/mandalo-cli-x86_64-unknown-linux-gnu` doesn't exist

Build it in the dev profile rather than release-cli — the test run then reuses
those debug artifacts instead of paying for a second full compile of the CLI
and its dependency tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@RusEu
RusEu merged commit 1956a52 into main Aug 9, 2026
4 of 5 checks passed
@RusEu
RusEu deleted the macos-signing-and-cask branch August 9, 2026 11:43
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.

2 participants