Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/workflows/macos-signing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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: 22
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:
# 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 }}
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
10 changes: 10 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
43 changes: 29 additions & 14 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,20 @@ 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 |
|---|---|
| `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).
Expand Down Expand Up @@ -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)

Expand Down
Loading