feat: Developer-ID sign + notarize macOS builds
- tauri.conf.json: enable hardenedRuntime + entitlements (keep ad-hoc signingIdentity as the dev default; release script overrides it) - add desktop/src-tauri/entitlements.plist (JIT / unsigned-exec-mem / dyld-env-vars for the hardened runtime) - scripts/release-mac.sh: auto-detect Developer ID identity from the keychain, load notarization creds from ~/.config/gridline/notarize.env (App Store Connect API key), re-sign bundled pg tools after install_name_tool (invalidated signatures would fail notarization), and verify codesign + stapled notarization before upload - README/AGENTS.md: replace the unsigned/xattr instructions with the macOS signing & notarization setup guide
This commit is contained in:
@@ -7,16 +7,11 @@ name: Release
|
|||||||
# git checkout prod && git pull
|
# git checkout prod && git pull
|
||||||
# git tag v0.5.0 && git push origin v0.5.0
|
# git tag v0.5.0 && git push origin v0.5.0
|
||||||
#
|
#
|
||||||
# SIGNING STATUS: macOS bundles are AD-HOC signed via tauri.conf.json
|
# SIGNING STATUS: macOS is Developer-ID signed + notarized via
|
||||||
# (bundle.macOS.signingIdentity: "-", hardenedRuntime: false). That replaces
|
# scripts/release-mac.sh (built manually on Adrian's machine). NOTE: this
|
||||||
# the linker-only signature that made macOS report the app as "damaged" and
|
# GitHub workflow is NOT the active release path — Gitea Actions uses
|
||||||
# refuse Finder launches — users now get the standard "Open Anyway" prompt
|
# .gitea/workflows/release.yml (Linux/Windows only). Linux/Windows installers
|
||||||
# (or run `sudo xattr -dr com.apple.quarantine` for the damaged-error case, see
|
# remain unsigned.
|
||||||
# README). Still NOT Developer-ID signed / notarized (no certs yet).
|
|
||||||
# tauri-action automatically signs + notarizes when the signing secrets are
|
|
||||||
# present, so the moment we add APPLE_CERTIFICATE / APPLE_API_KEY /
|
|
||||||
# WINDOWS_CERTIFICATE (or Azure Trusted Signing) to repo secrets, future
|
|
||||||
# builds are properly signed — no changes to this file required.
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ Cut a release by tagging the **`prod`** branch once the PR is merged — `git ta
|
|||||||
- `desktop/src/lib/version.test.ts` and `desktop/src/lib/docs-coverage.test.ts` if they assert the version
|
- `desktop/src/lib/version.test.ts` and `desktop/src/lib/docs-coverage.test.ts` if they assert the version
|
||||||
- **README download links are static (versioned)** — both download tables (top **Download** section + **Which file should I download?**) link directly to the release-tag assets (`releases/download/v0.7.9/<file>`). tauri-action uses default versioned asset names (`Gridline_<ver>_aarch64.dmg`, `Gridline-<ver>-1.x86_64.rpm`, etc.) — update BOTH tables to the new names on every release (see the MAINTENANCE comment in README.md).
|
- **README download links are static (versioned)** — both download tables (top **Download** section + **Which file should I download?**) link directly to the release-tag assets (`releases/download/v0.7.9/<file>`). tauri-action uses default versioned asset names (`Gridline_<ver>_aarch64.dmg`, `Gridline-<ver>-1.x86_64.rpm`, etc.) — update BOTH tables to the new names on every release (see the MAINTENANCE comment in README.md).
|
||||||
- **Bundled pg tools:** `tauri.conf.json` `bundle.resources` lists `resources/pg_tools/*`; the `release.yml` matrix builds/downloads + checksum-verifies the static binaries before the Tauri build step.
|
- **Bundled pg tools:** `tauri.conf.json` `bundle.resources` lists `resources/pg_tools/*`; the `release.yml` matrix builds/downloads + checksum-verifies the static binaries before the Tauri build step.
|
||||||
- **Release notes must include the macOS first-launch instructions** — until a Developer ID signing certificate is secured, releases are ad-hoc signed only (not notarized), so every macOS user hits a Gatekeeper prompt. The draft release body must include: right-click → **Open** → **Open** (or System Settings → Privacy & Security → **Open Anyway**) for "developer cannot be verified", and `sudo xattr -dr com.apple.quarantine /Applications/Gridline.app` (with the re-run-after-every-upgrade note) for "damaged and can't be opened".
|
- **macOS is Developer-ID signed + notarized** via `scripts/release-mac.sh` (built manually on Adrian's machine; not in CI). The script auto-detects the Developer ID identity from the login keychain, reads notarization credentials from `~/.config/gridline/notarize.env` (App Store Connect API key), and verifies codesign + stapled notarization before uploading. If the cert or credentials are missing it fails with setup instructions (see the README's "macOS signing & notarization" section).
|
||||||
|
|
||||||
### Adding a Tauri Command
|
### Adding a Tauri Command
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ brew install --cask AdrianBonpin/gridline/gridline
|
|||||||
brew install --cask gridline
|
brew install --cask gridline
|
||||||
```
|
```
|
||||||
|
|
||||||
The cask strips the macOS quarantine flag automatically during install, so **no manual `xattr` step is needed** on the Homebrew path — the app just launches. (Direct-DMG downloads still need the [first-launch instructions](#installers-are-unsigned-for-now) below.)
|
The cask installs the app directly. The DMGs are Developer-ID signed and notarized, so macOS trusts them on first launch — no `xattr` or "Open Anyway" step needed.
|
||||||
|
|
||||||
> **macOS first launch:** macOS may say *"Gridline is damaged and can't be opened"* or *"the developer cannot be verified"* — this is expected; the app isn't Developer-ID signed/notarized yet. See the [macOS first-launch instructions](#installers-are-unsigned-for-now) (right-click → Open, or the one-time `xattr` fix). Not sure if your Mac is Intel or Apple Silicon? See [Which file should I download?](#which-file-should-i-download) below.
|
> **macOS first launch:** Gridline is Developer-ID signed and notarized, so macOS opens it without a Gatekeeper prompt. Not sure if your Mac is Intel or Apple Silicon? See [Which file should I download?](#which-file-should-i-download) below.
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
MAINTENANCE: These links are STATIC (versioned) — they point at the v0.7.12
|
MAINTENANCE: These links are STATIC (versioned) — they point at the v0.7.12
|
||||||
@@ -307,24 +307,27 @@ Pre-built installers for macOS, Windows, and Linux are published on the [Release
|
|||||||
|
|
||||||
> ⚠️ Gridline is under active development. Expect rough edges and please [open issues](https://git.ranio.xyz/adrianbonpin/gridline/issues/new) when you hit them.
|
> ⚠️ Gridline is under active development. Expect rough edges and please [open issues](https://git.ranio.xyz/adrianbonpin/gridline/issues/new) when you hit them.
|
||||||
|
|
||||||
#### Installers are unsigned (for now)
|
#### macOS signing & notarization
|
||||||
|
|
||||||
Gridline is currently distributed **unsigned** — it doesn't pay for code-signing certificates yet (macOS builds are *ad-hoc signed*, so they pass Apple Silicon's launch checks but aren't Developer-ID signed or notarized). Your OS will warn you the first time you open it. This is expected — the app is safe, it just hasn't paid the signing fee:
|
Gridline's macOS builds are **Developer-ID signed and notarized**, so macOS opens them without a Gatekeeper prompt. This is configured on the machine that runs `scripts/release-mac.sh` (macOS is built manually, not in CI).
|
||||||
|
|
||||||
> **Homebrew users:** the [Homebrew cask](#download) strips the quarantine flag automatically during install, so you skip all of the steps below — the app just launches.
|
**One-time setup** (only the repo owner needs to do this):
|
||||||
|
|
||||||
- **macOS:** if you see *"Gridline can't be opened because the developer cannot be verified"* (or the app simply won't open from Finder), right-click the app → **Open** → **Open** (or System Settings → Privacy & Security → **Open Anyway**). Do this once per version.
|
1. **Create a Developer ID Application certificate** — [developer.apple.com](https://developer.apple.com) → Certificates, Identifiers & Profiles → **+** → **Developer ID Application**. Generate a Certificate Signing Request from Keychain Access (Certificate Assistant → *Request a Certificate From a Certificate Authority*), upload it, download the `.cer`, and double-click to install into your login keychain. Verify with `security find-identity -v -p codesigning`.
|
||||||
- **macOS — "Gridline is damaged and can't be opened":** remove the quarantine flag macOS attaches to downloaded apps, then launch normally:
|
2. **Create an App Store Connect API key** for notarization — [appstoreconnect.apple.com](https://appstoreconnect.apple.com) → Users and Access → **Integrations** → **App Store Connect API** → **+** (name it, *Developer* access). Download the `.p8` (shown once) and note the **Key ID** and **Issuer ID**.
|
||||||
|
3. **Save the notarization credentials** to `~/.config/gridline/notarize.env` (never committed):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo xattr -dr com.apple.quarantine /Applications/Gridline.app
|
APPLE_API_KEY=<Key ID>
|
||||||
```
|
APPLE_API_ISSUER=<Issuer ID>
|
||||||
|
APPLE_API_KEY_PATH=/absolute/path/to/AuthKey_<KeyID>.p8
|
||||||
|
```
|
||||||
|
|
||||||
(`sudo` is required — some files inside the bundle are read-only, so removing the flag needs admin rights.) Re-run after **every upgrade** — each freshly-downloaded copy gets re-quarantined.
|
(Alternatively, drop the `.p8` at `~/.appstoreconnect/private_keys/AuthKey_<KeyID>.p8` and omit `APPLE_API_KEY_PATH`.)
|
||||||
- **Windows:** on the SmartScreen prompt, click **More info** → **Run anyway**.
|
|
||||||
- **Linux:** no warning — install and run normally.
|
|
||||||
|
|
||||||
Code signing **will be added in the future** (Apple Developer Program + a Windows signing cert, e.g. Azure Trusted Signing) — the CI workflow is already wired to pick up the signing secrets automatically the moment they exist, no workflow changes needed.
|
`scripts/release-mac.sh` auto-detects the Developer ID identity from your keychain, signs both architectures, notarizes + staples them, and verifies the result before uploading. If the certificate or credentials are missing it fails with instructions (set `GRIDLINE_SKIP_NOTARIZE=1` to build signed-but-unnotarized for testing only).
|
||||||
|
|
||||||
|
**Windows** still shows a SmartScreen prompt (no Windows signing cert yet); **Linux** installs without a warning.
|
||||||
|
|
||||||
#### Which file should I download?
|
#### Which file should I download?
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<!-- Required for the WKWebView (JIT-compiled JavaScript). Without this the
|
||||||
|
app crashes under the hardened runtime. -->
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<!-- The WebView allocates executable memory for JIT; allow it. -->
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<!-- Tauri's dev server / debug builds rely on DYLD_* environment variables. -->
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -29,7 +29,8 @@
|
|||||||
"resources": ["resources/pg_tools/*", "resources/mysql_tools/*"],
|
"resources": ["resources/pg_tools/*", "resources/mysql_tools/*"],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"signingIdentity": "-",
|
"signingIdentity": "-",
|
||||||
"hardenedRuntime": false
|
"hardenedRuntime": true,
|
||||||
|
"entitlements": "entitlements.plist"
|
||||||
},
|
},
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ for b in pg_dump pg_restore psql; do
|
|||||||
libpq=$(otool -L "$OUT_PG/$b" | awk '/libpq/ {print $1; exit}')
|
libpq=$(otool -L "$OUT_PG/$b" | awk '/libpq/ {print $1; exit}')
|
||||||
install_name_tool -change "$libpq" "@loader_path/libpq.5.dylib" "$OUT_PG/$b"
|
install_name_tool -change "$libpq" "@loader_path/libpq.5.dylib" "$OUT_PG/$b"
|
||||||
done
|
done
|
||||||
|
# install_name_tool invalidates the linker's ad-hoc signature; re-sign so the
|
||||||
|
# bundle passes notarization (every code object inside must be signed).
|
||||||
|
for f in "$OUT_PG"/pg_dump "$OUT_PG"/pg_restore "$OUT_PG"/psql "$OUT_PG"/libpq.5.dylib; do
|
||||||
|
codesign --force --sign - "$f" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
echo ">> Building MariaDB client tools..."
|
echo ">> Building MariaDB client tools..."
|
||||||
mkdir -p "$OUT_MY"
|
mkdir -p "$OUT_MY"
|
||||||
@@ -116,6 +121,58 @@ for pass in 1 2 3 4 5 6; do
|
|||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# ---- macOS signing + notarization -----------------------------------------
|
||||||
|
# Requires a "Developer ID Application" certificate in the login keychain and
|
||||||
|
# an App Store Connect API key for notarization. See the README's "macOS
|
||||||
|
# signing" section for how to create both.
|
||||||
|
#
|
||||||
|
# Notarization credentials are read from ~/.config/gridline/notarize.env
|
||||||
|
# (never committed). Create it with:
|
||||||
|
# APPLE_API_KEY=<Key ID>
|
||||||
|
# APPLE_API_ISSUER=<Issuer ID>
|
||||||
|
# APPLE_API_KEY_PATH=/absolute/path/to/AuthKey_<KeyID>.p8
|
||||||
|
# (or drop the .p8 at ~/.appstoreconnect/private_keys/AuthKey_<KeyID>.p8 and
|
||||||
|
# omit APPLE_API_KEY_PATH — tauri auto-searches that location.)
|
||||||
|
|
||||||
|
# 1. Resolve the Developer ID Application signing identity from the keychain.
|
||||||
|
SIGNING_IDENTITY="$(security find-identity -v -p codesigning 2>/dev/null \
|
||||||
|
| grep -i 'Developer ID Application' \
|
||||||
|
| sed -E 's/.*"([^"]+)".*/\1/' \
|
||||||
|
| head -1 || true)"
|
||||||
|
if [ -z "$SIGNING_IDENTITY" ]; then
|
||||||
|
echo "ERROR: No 'Developer ID Application' certificate found in the login keychain." >&2
|
||||||
|
echo " Create one at https://developer.apple.com/account/resources/certificates/list" >&2
|
||||||
|
echo " (Certificates, IDs & Profiles -> + -> Developer ID Application), download the" >&2
|
||||||
|
echo " .cer, and double-click it to install into your login keychain." >&2
|
||||||
|
echo " Verify with: security find-identity -v -p codesigning" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export APPLE_SIGNING_IDENTITY="$SIGNING_IDENTITY"
|
||||||
|
echo ">> Signing identity: $SIGNING_IDENTITY"
|
||||||
|
|
||||||
|
# 2. Load notarization credentials.
|
||||||
|
NOTARIZE_ENV="${HOME}/.config/gridline/notarize.env"
|
||||||
|
if [ -f "$NOTARIZE_ENV" ]; then
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$NOTARIZE_ENV"
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
if [ -z "${APPLE_API_KEY:-}" ] || [ -z "${APPLE_API_ISSUER:-}" ]; then
|
||||||
|
if [ "${GRIDLINE_SKIP_NOTARIZE:-0}" = "1" ]; then
|
||||||
|
echo ">> WARNING: notarization credentials missing; GRIDLINE_SKIP_NOTARIZE=1 set." >&2
|
||||||
|
echo " Building signed but NOT notarized (test-only — users will still see a prompt)." >&2
|
||||||
|
else
|
||||||
|
echo "ERROR: Notarization credentials not configured." >&2
|
||||||
|
echo " Create ${NOTARIZE_ENV} with APPLE_API_KEY and APPLE_API_ISSUER" >&2
|
||||||
|
echo " (and APPLE_API_KEY_PATH, or place the .p8 at ~/.appstoreconnect/private_keys/)." >&2
|
||||||
|
echo " See the README's 'macOS signing' section for the App Store Connect API key setup." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo ">> Notarization: API key ${APPLE_API_KEY} (issuer ${APPLE_API_ISSUER})"
|
||||||
|
fi
|
||||||
|
|
||||||
# ---- build the app (both arches) ------------------------------------------
|
# ---- build the app (both arches) ------------------------------------------
|
||||||
cd "${WORKSPACE}/desktop"
|
cd "${WORKSPACE}/desktop"
|
||||||
echo ">> Installing frontend dependencies..."
|
echo ">> Installing frontend dependencies..."
|
||||||
@@ -130,6 +187,24 @@ VERSION="${TAG#v}"
|
|||||||
BUNDLE_ARM="${WORKSPACE}/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle"
|
BUNDLE_ARM="${WORKSPACE}/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle"
|
||||||
BUNDLE_INTEL="${WORKSPACE}/desktop/src-tauri/target/x86_64-apple-darwin/release/bundle"
|
BUNDLE_INTEL="${WORKSPACE}/desktop/src-tauri/target/x86_64-apple-darwin/release/bundle"
|
||||||
|
|
||||||
|
# ---- verify signing + notarization -----------------------------------------
|
||||||
|
# Confirm both .app bundles are properly signed and carry a stapled
|
||||||
|
# notarization ticket before we upload them. spctl can lag right after
|
||||||
|
# notarization (assessment cache), so stapler validate is the authoritative
|
||||||
|
# check; a pending spctl is only a warning.
|
||||||
|
for APP in \
|
||||||
|
"${BUNDLE_ARM}/macos/Gridline.app" \
|
||||||
|
"${BUNDLE_INTEL}/macos/Gridline.app"; do
|
||||||
|
[ -d "$APP" ] || { echo "missing $APP" >&2; exit 1; }
|
||||||
|
echo ">> Verifying $APP"
|
||||||
|
codesign --verify --deep --strict --verbose=2 "$APP" \
|
||||||
|
|| { echo "codesign verify FAILED" >&2; exit 1; }
|
||||||
|
xcrun stapler validate "$APP" \
|
||||||
|
|| { echo "notarization ticket not stapled" >&2; exit 1; }
|
||||||
|
spctl --assess --type execute --verbose=4 "$APP" \
|
||||||
|
|| echo " (spctl assessment pending — normal right after notarization; stapler validate passed)"
|
||||||
|
done
|
||||||
|
|
||||||
# ---- create/ensure release + upload DMGs -----------------------------------
|
# ---- create/ensure release + upload DMGs -----------------------------------
|
||||||
API="${GITEA_SERVER}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}"
|
API="${GITEA_SERVER}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}"
|
||||||
AUTH="Authorization: token ${GITEA_TOKEN}"
|
AUTH="Authorization: token ${GITEA_TOKEN}"
|
||||||
|
|||||||
Reference in New Issue
Block a user