mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
feat(desktop): wire macOS Developer ID signing + notarization
Add gated code-signing + notarization for the macOS desktop DMG so it can
clear Gatekeeper and stay in Homebrew's main cask (unsigned casks are
rejected after 2026-09-01).
- desktopApp/build.gradle.kts: macOS signing{}/notarization{} blocks, gated
on the AMETHYST_MAC_SIGN_IDENTITY env var. Absent => unsigned DMG, exactly
as before, so local dev and PR CI are unaffected.
- create-release.yml: import a Developer ID cert into a throwaway keychain on
the macOS leg and export the signing/notary env. Soft-gated on the
MAC_CERTIFICATE_P12 secret — no secret => unsigned build.
- BUILDING.md: document the six MAC_* secrets, how to generate them, and flip
the unsigned-cask fallback note to reflect the wiring is now in place
(pending Apple credentials).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
This commit is contained in:
@@ -101,8 +101,52 @@ jobs:
|
||||
fi
|
||||
chmod +x desktopApp/packaging/appimage/appimagetool-x86_64.AppImage
|
||||
|
||||
# macOS only: import the Developer ID Application cert into a throwaway
|
||||
# keychain so jpackage's codesign pass can find it. Soft-gated — if the
|
||||
# MAC_CERTIFICATE_P12 secret isn't set (forks, or before Apple creds are
|
||||
# provisioned) the DMG is built UNSIGNED, exactly as before. notarytool
|
||||
# runs as part of the gradle task when the identity env is exported below.
|
||||
- name: Import Apple Developer ID certificate (macOS leg, if configured)
|
||||
if: matrix.family == 'macos'
|
||||
id: mac_keychain
|
||||
env:
|
||||
CERT_P12: ${{ secrets.MAC_CERTIFICATE_P12 }}
|
||||
CERT_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${CERT_P12:-}" ]]; then
|
||||
echo "::notice::MAC_CERTIFICATE_P12 not set — building UNSIGNED DMG."
|
||||
echo "signing=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
KEYCHAIN="$RUNNER_TEMP/amethyst-signing.keychain-db"
|
||||
KEYCHAIN_PWD="$(openssl rand -base64 24)"
|
||||
CERT_PATH="$RUNNER_TEMP/developer_id.p12"
|
||||
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
|
||||
echo "$CERT_P12" | base64 --decode > "$CERT_PATH"
|
||||
security import "$CERT_PATH" -P "$CERT_PASSWORD" \
|
||||
-k "$KEYCHAIN" -T /usr/bin/codesign -T /usr/bin/productsign
|
||||
# Let codesign use the private key without an interactive UI prompt.
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
||||
-s -k "$KEYCHAIN_PWD" "$KEYCHAIN" >/dev/null
|
||||
# Prepend our keychain to the user search list so codesign sees it.
|
||||
security list-keychains -d user -s "$KEYCHAIN" \
|
||||
$(security list-keychains -d user | sed -e 's/[\"[:space:]]//g')
|
||||
rm -f "$CERT_PATH"
|
||||
echo "signing=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build desktop artifacts
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
env:
|
||||
# Empty on non-macOS legs and on the macOS leg when no cert is
|
||||
# configured — the gradle macOS{} block skips signing when the
|
||||
# identity is blank.
|
||||
AMETHYST_MAC_SIGN_IDENTITY: ${{ steps.mac_keychain.outputs.signing == 'true' && secrets.MAC_SIGN_IDENTITY || '' }}
|
||||
AMETHYST_NOTARY_APPLE_ID: ${{ secrets.MAC_NOTARY_APPLE_ID }}
|
||||
AMETHYST_NOTARY_PASSWORD: ${{ secrets.MAC_NOTARY_PASSWORD }}
|
||||
AMETHYST_NOTARY_TEAM_ID: ${{ secrets.MAC_NOTARY_TEAM_ID }}
|
||||
with:
|
||||
max_attempts: 2
|
||||
timeout_minutes: 15
|
||||
|
||||
+34
-5
@@ -250,13 +250,26 @@ provided automatically; everything else you set yourself.)
|
||||
| `SONATYPE_PASSWORD` | Maven Central user token password | Same |
|
||||
| `SIGNING_PRIVATE_KEY` | **GPG/PGP** private key, ASCII-armored | Signs the Maven artifacts (Central requires it) |
|
||||
| `SIGNING_PASSWORD` | Passphrase for that GPG key | Same |
|
||||
| `MAC_CERTIFICATE_P12` | Base64 of your **Apple Developer ID Application** cert (`.p12`, includes the private key) | Signs the macOS desktop **DMG** |
|
||||
| `MAC_CERTIFICATE_PASSWORD` | Password set when exporting the `.p12` | Imports the cert into the CI keychain |
|
||||
| `MAC_SIGN_IDENTITY` | Full identity string, e.g. `Developer ID Application: Your Name (TEAMID)` | The `codesign` identity to sign with |
|
||||
| `MAC_NOTARY_APPLE_ID` | Apple ID email of the notarization account | Apple notarization (`notarytool`) |
|
||||
| `MAC_NOTARY_PASSWORD` | **App-specific** password for that Apple ID (not the login password) | Same |
|
||||
| `MAC_NOTARY_TEAM_ID` | 10-char Apple Developer **Team ID** | Same |
|
||||
| `HOMEBREW_TOKEN` | PAT for `Homebrew/homebrew-cask` | Desktop cask bump (stable tags) |
|
||||
| `WINGET_TOKEN` | PAT for `microsoft/winget-pkgs` | Desktop winget bump (stable tags) |
|
||||
| `CROWDIN_PERSONAL_TOKEN`, `CROWDIN_PROJECT_ID` | Crowdin API creds | Translation sync (separate workflow, not the release) |
|
||||
|
||||
Note the **two distinct signing identities** people often conflate:
|
||||
Note the **three distinct signing identities** people often conflate:
|
||||
`SIGNING_KEY` + `KEY_*` is the **Android keystore**; `SIGNING_PRIVATE_KEY` +
|
||||
`SIGNING_PASSWORD` is the **GPG key** for Maven Central. They are unrelated.
|
||||
`SIGNING_PASSWORD` is the **GPG key** for Maven Central; `MAC_CERTIFICATE_*` +
|
||||
`MAC_SIGN_IDENTITY` + `MAC_NOTARY_*` is the **Apple Developer ID** for the macOS
|
||||
desktop DMG. They are unrelated — each comes from a different authority.
|
||||
|
||||
The macOS desktop signing secrets are **optional**: if `MAC_CERTIFICATE_P12` is
|
||||
unset the release workflow still builds the DMG, just **unsigned** (the previous
|
||||
behavior). Provision all six to switch signing + notarization on. Obtaining them
|
||||
requires Apple Developer Program membership ($99/yr).
|
||||
|
||||
Generating the values:
|
||||
|
||||
@@ -269,6 +282,14 @@ base64 -i upload.jks | tr -d '\n' # paste output into SIGNING_KEY
|
||||
# GPG key → armored private key for SIGNING_PRIVATE_KEY
|
||||
gpg --full-generate-key # create the key (once)
|
||||
gpg --armor --export-secret-keys <KEY_ID> # paste output into SIGNING_PRIVATE_KEY
|
||||
|
||||
# Apple Developer ID Application cert → base64 for MAC_CERTIFICATE_P12.
|
||||
# In Keychain Access, export the "Developer ID Application: ..." cert (with its
|
||||
# private key) as a .p12, setting an export password (-> MAC_CERTIFICATE_PASSWORD).
|
||||
base64 -i developer_id.p12 | tr -d '\n' # paste output into MAC_CERTIFICATE_P12
|
||||
security find-identity -v -p codesigning # shows the exact MAC_SIGN_IDENTITY string
|
||||
# MAC_NOTARY_PASSWORD is an app-specific password from https://appleid.apple.com
|
||||
# (Sign-In and Security -> App-Specific Passwords), NOT your Apple ID login.
|
||||
```
|
||||
|
||||
`SONATYPE_USERNAME`/`SONATYPE_PASSWORD` are a **user token** from
|
||||
@@ -480,9 +501,17 @@ for the deprecation date. When it hits:
|
||||
Homebrew has committed to disabling unsigned casks in `Homebrew/homebrew-cask`
|
||||
on 2026-09-01. Before that date:
|
||||
|
||||
**Option A**: Commit budget to Apple Developer Program ($99/yr), add
|
||||
`signing { sign.set(true) }` + `notarization {}` blocks to
|
||||
`desktopApp/build.gradle.kts`, wire Developer ID + notary creds into CI.
|
||||
**Option A (wiring done — needs Apple creds)**: The `signing { sign.set(true) }`
|
||||
+ `notarization {}` blocks are already in `desktopApp/build.gradle.kts` (gated on
|
||||
the `AMETHYST_MAC_SIGN_IDENTITY` env var), and the macOS leg of
|
||||
`create-release.yml` imports a Developer ID cert into a throwaway keychain and
|
||||
exports the signing/notary env. It all stays a **no-op until the six
|
||||
`MAC_*`/notary secrets are provisioned** (see [§ Secrets the CI
|
||||
needs](#secrets-the-ci-needs)) — until then the DMG builds unsigned. To turn it
|
||||
on: join the Apple Developer Program ($99/yr), create a *Developer ID
|
||||
Application* certificate, generate an app-specific password, and set the six
|
||||
secrets. The first signed+notarized DMG is best validated with a
|
||||
`workflow_dispatch` dry-run before a real tag.
|
||||
|
||||
**Option B**: Pivot to a private Homebrew tap:
|
||||
|
||||
|
||||
@@ -135,6 +135,36 @@ compose.desktop {
|
||||
macOS {
|
||||
bundleID = "com.vitorpamplona.amethyst.desktop"
|
||||
iconFile.set(project.file("src/jvmMain/resources/icon.icns"))
|
||||
|
||||
// --- Developer ID code signing + notarization ---
|
||||
// Required for Homebrew's main cask (unsigned casks rejected after
|
||||
// 2026-09-01) and to clear macOS Gatekeeper without the right-click
|
||||
// dance. Gated on the signing-identity env var so local dev builds
|
||||
// and PR CI keep producing plain UNSIGNED DMGs exactly as before —
|
||||
// signing only kicks in when the release workflow exports these
|
||||
// (which it does only when the Apple secrets are present):
|
||||
//
|
||||
// AMETHYST_MAC_SIGN_IDENTITY "Developer ID Application: NAME (TEAMID)"
|
||||
// AMETHYST_NOTARY_APPLE_ID Apple ID email of the notary account
|
||||
// AMETHYST_NOTARY_PASSWORD app-specific password for that Apple ID
|
||||
// AMETHYST_NOTARY_TEAM_ID 10-char Apple Developer Team ID
|
||||
//
|
||||
// The Developer ID Application certificate must already be in the
|
||||
// build host's keychain (CI imports it from a base64 .p12 secret).
|
||||
// Compose ships default hardened-runtime entitlements that permit
|
||||
// the JVM's JIT, so no custom entitlements file is needed.
|
||||
val macSignIdentity = System.getenv("AMETHYST_MAC_SIGN_IDENTITY")
|
||||
if (!macSignIdentity.isNullOrBlank()) {
|
||||
signing {
|
||||
sign.set(true)
|
||||
identity.set(macSignIdentity)
|
||||
}
|
||||
notarization {
|
||||
appleID.set(System.getenv("AMETHYST_NOTARY_APPLE_ID"))
|
||||
password.set(System.getenv("AMETHYST_NOTARY_PASSWORD"))
|
||||
teamID.set(System.getenv("AMETHYST_NOTARY_TEAM_ID"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
windows {
|
||||
|
||||
Reference in New Issue
Block a user