Skip to content

Commit 8c3cb02

Browse files
committed
feat: add macOS signing and notarization support for Electron builds
1 parent d4a1757 commit 8c3cb02

3 files changed

Lines changed: 140 additions & 0 deletions

File tree

.github/workflows/release-electron.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,42 @@ jobs:
5858
- name: Install workspace dependencies
5959
run: pnpm install --frozen-lockfile
6060

61+
- name: Prepare Apple signing certificate
62+
if: runner.os == 'macOS' && secrets.APPLE_SIGN_CERTIFICATE_P12_BASE64 != ''
63+
shell: bash
64+
run: |
65+
CERTIFICATE_PATH="$RUNNER_TEMP/starquery-macos-signing.p12"
66+
KEYCHAIN_PATH="$RUNNER_TEMP/starquery-signing.keychain-db"
67+
KEYCHAIN_PASSWORD="$(uuidgen)"
68+
69+
echo "${{ secrets.APPLE_SIGN_CERTIFICATE_P12_BASE64 }}" | base64 --decode > "$CERTIFICATE_PATH"
70+
71+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
72+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
73+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
74+
security import "$CERTIFICATE_PATH" \
75+
-k "$KEYCHAIN_PATH" \
76+
-P "${{ secrets.APPLE_SIGN_CERTIFICATE_PASSWORD }}" \
77+
-T /usr/bin/codesign \
78+
-T /usr/bin/security \
79+
-T /usr/bin/productbuild
80+
security set-key-partition-list \
81+
-S apple-tool:,apple:,codesign: \
82+
-s \
83+
-k "$KEYCHAIN_PASSWORD" \
84+
"$KEYCHAIN_PATH"
85+
security default-keychain -d user -s "$KEYCHAIN_PATH"
86+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
87+
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
88+
89+
- name: Prepare Apple notarization API key
90+
if: runner.os == 'macOS' && secrets.APPLE_NOTARY_API_KEY_P8_BASE64 != ''
91+
shell: bash
92+
run: |
93+
API_KEY_PATH="$RUNNER_TEMP/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8"
94+
echo "${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }}" | base64 --decode > "$API_KEY_PATH"
95+
echo "APPLE_API_KEY=$API_KEY_PATH" >> "$GITHUB_ENV"
96+
6197
- name: Prepare optional Windows code-signing certificate
6298
if: runner.os == 'Windows' && secrets.STARQUERY_WINDOWS_CERTIFICATE_BASE64 != ''
6399
shell: pwsh
@@ -71,6 +107,16 @@ jobs:
71107
72108
- name: Build Electron distributables
73109
env:
110+
STARQUERY_MAC_SIGN: ${{ runner.os == 'macOS' && secrets.APPLE_SIGN_CERTIFICATE_P12_BASE64 != '' && 'true' || 'false' }}
111+
STARQUERY_MAC_NOTARIZE: ${{ runner.os == 'macOS' && ((secrets.APPLE_NOTARY_API_KEY_P8_BASE64 != '' && secrets.APPLE_API_KEY_ID != '' && secrets.APPLE_API_ISSUER != '') || (secrets.APPLE_ID != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD != '' && secrets.APPLE_TEAM_ID != '')) && 'true' || 'false' }}
112+
STARQUERY_MAC_BUNDLE_ID: ${{ vars.STARQUERY_MAC_BUNDLE_ID }}
113+
STARQUERY_MAC_APP_CATEGORY: ${{ vars.STARQUERY_MAC_APP_CATEGORY }}
114+
APPLE_SIGN_IDENTITY: ${{ secrets.APPLE_SIGN_IDENTITY }}
115+
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
116+
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
117+
APPLE_ID: ${{ secrets.APPLE_ID }}
118+
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
119+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
74120
STARQUERY_MSIX_PUBLISHER: ${{ vars.STARQUERY_MSIX_PUBLISHER }}
75121
STARQUERY_MSIX_PUBLISHER_DISPLAY_NAME: ${{ vars.STARQUERY_MSIX_PUBLISHER_DISPLAY_NAME }}
76122
STARQUERY_MSIX_IDENTITY_NAME: ${{ vars.STARQUERY_MSIX_IDENTITY_NAME }}

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,40 @@ These are only relevant for Windows Electron builds. The Electron Forge setup no
277277
| `WINDOWS_CERTIFICATE_FILE` | unset | Optional `.pfx` path for signed Windows/MSIX builds outside the Store. |
278278
| `WINDOWS_CERTIFICATE_PASSWORD` | unset | Password for `WINDOWS_CERTIFICATE_FILE`. |
279279

280+
## Electron macOS Signing And Notarization
281+
282+
The Electron Forge setup can now sign and notarize the regular macOS ZIP distribution without enabling any Mac App Store packaging path.
283+
284+
Build-time environment variables:
285+
286+
| Variable | Default | Description |
287+
| --- | --- | --- |
288+
| `STARQUERY_MAC_SIGN` | `false` | Enables macOS code signing for the Electron build. |
289+
| `STARQUERY_MAC_NOTARIZE` | `false` | Enables notarization for the macOS Electron build. |
290+
| `STARQUERY_MAC_BUNDLE_ID` | `com.interaapps.starquery` | Bundle identifier used for the regular macOS app build. |
291+
| `STARQUERY_MAC_APP_CATEGORY` | `public.app-category.developer-tools` | App category written into the macOS app metadata. |
292+
| `APPLE_SIGN_IDENTITY` | unset | Optional explicit signing identity, for example `Developer ID Application: Your Name (TEAMID)`. If unset, Electron signing can auto-detect a suitable identity from the keychain. |
293+
| `APPLE_API_KEY` | unset | Path to the App Store Connect API key `.p8` file used for notarization. |
294+
| `APPLE_API_KEY_ID` | unset | App Store Connect API key ID used for notarization. |
295+
| `APPLE_API_ISSUER` | unset | App Store Connect API issuer ID used for notarization. |
296+
| `APPLE_ID` | unset | Optional fallback Apple ID for notarization when not using API-key-based notarization. |
297+
| `APPLE_APP_SPECIFIC_PASSWORD` | unset | App-specific password for `APPLE_ID` notarization. |
298+
| `APPLE_TEAM_ID` | unset | Apple team ID required for Apple-ID-based notarization. |
299+
300+
GitHub Actions secrets for the macOS release job:
301+
302+
| Secret | Required | Description |
303+
| --- | --- | --- |
304+
| `APPLE_SIGN_CERTIFICATE_P12_BASE64` | yes for signing | Base64-encoded exported `.p12` containing the `Developer ID Application` certificate. |
305+
| `APPLE_SIGN_CERTIFICATE_PASSWORD` | yes for signing | Password used when exporting the `.p12` certificate. |
306+
| `APPLE_SIGN_IDENTITY` | recommended | Exact signing identity name, if you want to avoid auto-detection ambiguity. |
307+
| `APPLE_NOTARY_API_KEY_P8_BASE64` | recommended for notarization | Base64-encoded App Store Connect API key `.p8`. |
308+
| `APPLE_API_KEY_ID` | recommended for notarization | App Store Connect API key ID. |
309+
| `APPLE_API_ISSUER` | recommended for notarization | App Store Connect API issuer ID. |
310+
| `APPLE_ID` | optional fallback | Apple ID email if you want to use the older Apple-ID notarization flow instead of API keys. |
311+
| `APPLE_APP_SPECIFIC_PASSWORD` | optional fallback | App-specific password for the Apple-ID notarization flow. |
312+
| `APPLE_TEAM_ID` | optional fallback | Team ID for the Apple-ID notarization flow. |
313+
280314
## Backend Configuration Examples
281315

282316
### Hosted with MySQL meta database

packages/electron/forge.config.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const iconBasePath = path.resolve(__dirname, 'images', 'icon');
77
const pngIconPath = path.resolve(__dirname, 'images', 'icon.png');
88
const icoIconPath = path.resolve(__dirname, 'images', 'icon.ico');
99
const msixAssetsPath = path.resolve(__dirname, 'images', 'msix-assets');
10+
const isDarwin = process.platform === 'darwin';
1011

1112
function readBooleanEnv(name, fallback = false) {
1213
const value = process.env[name];
@@ -55,10 +56,69 @@ function buildMsixMakerConfig() {
5556
return config;
5657
}
5758

59+
function buildMacSignConfig() {
60+
if (!isDarwin || !readBooleanEnv('STARQUERY_MAC_SIGN', false)) {
61+
return undefined;
62+
}
63+
64+
const config = {
65+
hardenedRuntime: true,
66+
gatekeeperAssess: false,
67+
};
68+
69+
const identity = process.env.APPLE_SIGN_IDENTITY || process.env.CSC_NAME;
70+
if (identity) {
71+
config.identity = identity;
72+
}
73+
74+
return config;
75+
}
76+
77+
function buildMacNotarizeConfig() {
78+
if (!isDarwin || !readBooleanEnv('STARQUERY_MAC_NOTARIZE', false)) {
79+
return undefined;
80+
}
81+
82+
const appleApiKey = process.env.APPLE_API_KEY;
83+
const appleApiKeyId = process.env.APPLE_API_KEY_ID;
84+
const appleApiIssuer = process.env.APPLE_API_ISSUER;
85+
86+
if (appleApiKey && appleApiKeyId && appleApiIssuer) {
87+
return {
88+
appleApiKey,
89+
appleApiKeyId,
90+
appleApiIssuer,
91+
};
92+
}
93+
94+
const appleId = process.env.APPLE_ID;
95+
const appleIdPassword =
96+
process.env.APPLE_APP_SPECIFIC_PASSWORD || process.env.APPLE_PASSWORD;
97+
const teamId = process.env.APPLE_TEAM_ID;
98+
99+
if (appleId && appleIdPassword && teamId) {
100+
return {
101+
appleId,
102+
appleIdPassword,
103+
teamId,
104+
};
105+
}
106+
107+
return undefined;
108+
}
109+
110+
const macSignConfig = buildMacSignConfig();
111+
const macNotarizeConfig = buildMacNotarizeConfig();
112+
58113
module.exports = {
59114
packagerConfig: {
60115
icon: iconBasePath,
61116
asar: true,
117+
appBundleId: process.env.STARQUERY_MAC_BUNDLE_ID || 'com.interaapps.starquery',
118+
appCategoryType:
119+
process.env.STARQUERY_MAC_APP_CATEGORY || 'public.app-category.developer-tools',
120+
...(macSignConfig ? { osxSign: macSignConfig } : {}),
121+
...(macNotarizeConfig ? { osxNotarize: macNotarizeConfig } : {}),
62122
},
63123
rebuildConfig: {},
64124
makers: [

0 commit comments

Comments
 (0)