From 1db34dfb2d3427723ce75dc2ded11df7178b760d Mon Sep 17 00:00:00 2001 From: Srinivas Gowda Date: Tue, 27 Oct 2026 22:16:03 +0530 Subject: [PATCH 1/4] ci: sign and notarize macOS builds with Developer ID; signing check workflow --- .github/workflows/macos-signing.yml | 84 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 10 ++++ 2 files changed, 94 insertions(+) create mode 100644 .github/workflows/macos-signing.yml diff --git a/.github/workflows/macos-signing.yml b/.github/workflows/macos-signing.yml new file mode 100644 index 0000000..347e68c --- /dev/null +++ b/.github/workflows/macos-signing.yml @@ -0,0 +1,84 @@ +name: macOS signing check + +# Builds the macOS app the way release.yml does, signed and notarized, without +# publishing anything, then checks it the way Gatekeeper will. Runs when the +# signing setup changes, or by hand to test the Apple secrets before a release. +on: + workflow_dispatch: + pull_request: + paths: + - .github/workflows/macos-signing.yml + - .github/workflows/release.yml + - src-tauri/tauri.conf.json + - src-tauri/Entitlements.plist + +jobs: + sign: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + # Fails in seconds on a wrong Apple ID, app-specific password or team, + # instead of after a 20-minute build. + - name: Check the notarization credentials + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: xcrun notarytool history --apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID" > /dev/null + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-apple-darwin,x86_64-apple-darwin + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: ./src-tauri -> target + + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile + + # Same as release.yml; see the comments there. + - name: Link clang's runtime for ggml's Metal @available checks + run: echo "RUSTFLAGS=-C link-arg=$(clang -print-file-name=libclang_rt.osx.a)" >> "$GITHUB_ENV" + + - name: Build, sign and notarize + uses: tauri-apps/tauri-action@v0 + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + TRANSCRIBE_CMAKE_ARGS: "-DGGML_NATIVE=OFF -DGGML_AVX=ON -DGGML_AVX2=ON -DGGML_FMA=ON -DGGML_F16C=ON -DGGML_AVX512=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15" + with: + args: --target universal-apple-darwin --bundles app,dmg + + - name: Check the app as Gatekeeper will + run: | + bundle=src-tauri/target/universal-apple-darwin/release/bundle + app=$(ls -d "$bundle"/macos/*.app) + dmg=$(ls "$bundle"/dmg/*.dmg) + codesign --verify --deep --strict --verbose=2 "$app" + codesign -dvv "$app" 2>&1 | grep -E '^(Authority|TeamIdentifier|Runtime)' + spctl --assess --type execute --verbose=4 "$app" + xcrun stapler validate "$app" + codesign --verify --verbose=2 "$dmg" || echo "::warning::the .dmg itself isn't signed (the app inside is)" + + - uses: actions/upload-artifact@v4 + with: + name: sarala-macos-signed + path: src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg + retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9696b67..7bd10a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,6 +110,16 @@ jobs: # Updater signing (minisign). Set these as repo secrets. TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + # macOS code signing (Developer ID) and notarization. tauri-action + # imports the certificate into a temporary keychain, and + # APPLE_SIGNING_IDENTITY overrides the ad-hoc "-" in tauri.conf.json, + # which local builds keep. The other platforms ignore these. + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} # Voice typing's speech engine (ggml) would otherwise be tuned to this # runner's CPU and crash on older machines. Portable baseline: x86-64 # with AVX2/FMA/F16C (2013+; the app checks at runtime and explains if From 3ea17ccc9f592cb72925665a04d4fcdc9d7090ee Mon Sep 17 00:00:00 2001 From: Srinivas Gowda Date: Tue, 27 Oct 2026 22:16:19 +0530 Subject: [PATCH 2/4] ci: match release.yml's Node version --- .github/workflows/macos-signing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos-signing.yml b/.github/workflows/macos-signing.yml index 347e68c..df1842c 100644 --- a/.github/workflows/macos-signing.yml +++ b/.github/workflows/macos-signing.yml @@ -33,7 +33,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm - name: Install Rust From 6c9d3a2f39c17a0521b0bd720b6f477d16ff1cf0 Mon Sep 17 00:00:00 2001 From: Srinivas Gowda Date: Tue, 27 Oct 2026 22:20:17 +0530 Subject: [PATCH 3/4] docs: macOS builds are signed and notarized; document the Apple secrets --- README.md | 8 +------- RELEASING.md | 43 +++++++++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 27ffcbb..89dec69 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,7 @@ brew install --cask --yes sarala > > If `brew install` fails with ``Calling `depends_on macos: :catalina` is disabled``, your tap holds an old copy of the cask: run `brew update` (or `brew untap solancer/sarala` and tap it again), then install. -Or download the universal `.dmg` (Apple Silicon and Intel, macOS 10.15+) from the [latest release](https://github.com/solancer/sarala/releases/latest). Sarala is ad-hoc signed but not notarized by Apple, so macOS blocks the first launch. After dragging it into Applications, run this once: - -```bash -xattr -dr com.apple.quarantine /Applications/Sarala.app -``` - -(Or open it, click **Done**, then **System Settings › Privacy & Security › Open Anyway**.) Sarala registers for `.md`, `.markdown` and `.mdown`, so **Open With › Sarala** works in Finder, including when it's already running. +Or download the universal `.dmg` (Apple Silicon and Intel, macOS 10.15+) from the [latest release](https://github.com/solancer/sarala/releases/latest), open it and drag Sarala into Applications. It's signed and notarized by Apple, so it opens like any other app. Sarala registers for `.md`, `.markdown` and `.mdown`, so **Open With › Sarala** works in Finder, including when it's already running. ### Windows diff --git a/RELEASING.md b/RELEASING.md index 6691787..97eb466 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -44,7 +44,7 @@ CI rewrites this gist's `latest.json` on every release (see below). ### 3. GitHub Actions secrets -The release workflow (`.github/workflows/release.yml`) needs three repo secrets +The release workflow (`.github/workflows/release.yml`) needs these repo secrets (Settings ▸ Secrets and variables ▸ Actions): | Secret | Value | @@ -52,6 +52,12 @@ The release workflow (`.github/workflows/release.yml`) needs three repo secrets | `TAURI_SIGNING_PRIVATE_KEY` | Contents of `~/.tauri/sarala.key` (the minisign private key). | | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | The password you set when generating it. | | `GIST_TOKEN` | A GitHub PAT with the **`gist`** scope. The default `GITHUB_TOKEN` is repo-scoped and **cannot** edit a user-owned gist. | +| `APPLE_CERTIFICATE` | The Developer ID Application certificate and its private key, exported from Keychain as `.p12`, base64 encoded. | +| `APPLE_CERTIFICATE_PASSWORD` | The `.p12` export password. | +| `APPLE_SIGNING_IDENTITY` | `Developer ID Application: Lenzo International Private Limited (6WUKC69WYX)` | +| `APPLE_TEAM_ID` | `6WUKC69WYX` | +| `APPLE_ID` | An Apple ID on that team, for notarization. | +| `APPLE_PASSWORD` | An app-specific password for that Apple ID (account.apple.com ▸ Sign-In and Security). | > Building locally instead? Export the two `TAURI_SIGNING_*` values in your shell > before `pnpm tauri build` (see the manual fallback at the bottom). @@ -215,19 +221,28 @@ Notes: - `signature` is the **contents** of the `.sig` file (a base64 blob), not a path or URL. -## Code-signing caveat (macOS) - -The minisign signature above is **not** Apple code-signing. On macOS, Gatekeeper -blocks an unsigned/un-notarized auto-update the same way it blocks a first -install. For a real public release you need **both**: - -1. The updater's minisign signature (above), and -2. An Apple Developer ID **codesigned + notarized** `.app`. - -Until the Apple Developer account is set up, auto-updates will install but -Gatekeeper may refuse to launch the replaced app on other machines. Windows has -an analogous (softer) SmartScreen story; an Authenticode cert removes the -warning. +## Code signing (macOS) + +The minisign signature above only lets the updater trust a download. Gatekeeper +needs the app itself signed with a Developer ID and notarized by Apple, which +the macOS build does with the `APPLE_*` secrets. Local `pnpm tauri build`s stay +ad-hoc signed (`"signingIdentity": "-"` in `tauri.conf.json`); the +`APPLE_SIGNING_IDENTITY` variable overrides it in CI. + +To test the Apple secrets without releasing, run the **macOS signing check** +workflow (Actions tab, or it runs on PRs that touch the signing setup). It +builds a signed, notarized app and checks it with `codesign`, `spctl` and +`stapler`. + +The certificate expires on **26 November 2027**. Before then, the Account Holder +creates a new Developer ID Application certificate from a fresh signing request +(Keychain Access ▸ Certificate Assistant ▸ Request a Certificate From a +Certificate Authority), and the new `.p12` replaces `APPLE_CERTIFICATE` and +`APPLE_CERTIFICATE_PASSWORD`. Apps already shipped keep working after it +expires. On a Mac without Xcode, `security find-identity -v -p codesigning` +only lists the certificate once Apple's +[Developer ID G2 intermediate](https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer) +is in the keychain. ## Manual fallback (build locally, no CI) From 0ff5c294fb144fe1625a5be0f7dfc8417bb39bd4 Mon Sep 17 00:00:00 2001 From: Srinivas Gowda Date: Tue, 27 Oct 2026 22:57:00 +0530 Subject: [PATCH 4/4] ci: give the signing check the updater key --- .github/workflows/macos-signing.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/macos-signing.yml b/.github/workflows/macos-signing.yml index df1842c..f6f6d77 100644 --- a/.github/workflows/macos-signing.yml +++ b/.github/workflows/macos-signing.yml @@ -56,6 +56,9 @@ jobs: - name: Build, sign and notarize uses: tauri-apps/tauri-action@v0 env: + # The build also signs the updater bundle, so it needs the updater key. + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}