This gist provides details of how to build, sign and notarize Electron apps for macOS and Windows using Electron Builder via a GitHub Action.
Electron Builder takes care of a lot of the signing process. But part of the process to notarize the app with Apple involves submitting the app to Apple for review. This is done through the electron-notarize
package. Add the following code to your repository as afterSignHook.js
.
"use strict";
const { notarize } = require("@electron/notarize");
const {
ELECTRON_SKIP_NOTARIZATION,
XCODE_APP_LOADER_EMAIL,
XCODE_APP_LOADER_PASSWORD,
} = process.env;
async function main(context) {
const { electronPlatformName, appOutDir } = context;
if (
electronPlatformName !== "darwin" ||
ELECTRON_SKIP_NOTARIZATION === "true" ||
!XCODE_APP_LOADER_EMAIL ||
!XCODE_APP_LOADER_PASSWORD
) {
console.log("Skipping Apple notarization.");
return;
}
console.log("Starting Apple notarization.");
const appName = context.packager.appInfo.productFilename;
await notarize({
appBundleId: "io.balena.starterinterface",
appPath: `${appOutDir}/${appName}.app`,
appleId: XCODE_APP_LOADER_EMAIL,
appleIdPassword: XCODE_APP_LOADER_PASSWORD,
});
}
exports.default = main;
You will then need to add the following configuration to your Electron Builder config file:
"build": {
"afterSign": "scripts/notarize.js"
}
The following GitHub secrets need to be added for Apple signing. GitHub's own docs are helpful on generating these:
BUILD_CERTIFICATE_BASE64
P12_PASSWORD
BUILD_PROVISION_PROFILE_BASE64
KEYCHAIN_PASSWORD
Your Apple developer user account details also need adding into the following secrets. You can generate an App Specific password through your developer account and your username needs to be set the same as your developer account username (i.e. the email address you signed in with):
XCODE_APP_LOADER_EMAIL
XCODE_APP_LOADER_PASSWORD
Windows signing key secrets are required for Windows builds. Details on generating a Code Signing Certificate can be found on the Electron Builder site and Windows pages. Once generated, add the following secrets:
CSC_LINK # The base64-encoded data of the *.p12 or *.pfx certificate file.
CSC_KEY_PASSWORD # The password to decrypt the certificate given in CSC_LINK.
The following GitHub action will build and deploy the built app as GitHub releases.
But first configure your Electron Builder config file as follows:
artifactName: 'Your App Name.${ext}',
mac: {
target: 'dmg',
artifactName: 'Your App Name ${arch}.${ext}'
}
Electron Builder will replace ${arch}.${ext}
for you with the architecture and file extension of each build. Without this the Apple amd64 and aarch64 builds will have the same name and one will overwrite the other. Your installed application will still be set as your normal artifact name, it is only the .dmg
installed file that is changed.
You will also need to replace the ELECTRON_OUTPUT_PATH
env variable in the below with the output path of Electron Builder. This can vary depending on your setup.
Then insert the file in ./github/workflows/deploy.yml
of your GitHub repository.
# deploy.yml
name: Deploy to production
on:
push:
# This limits the run of the workflow to when a tag is pushed
tags:
- "*"
env:
ELECTRON_OUTPUT_PATH: "your/output/path"
jobs:
build-electron-app:
matrix:
job_name: ["linux", "mac_amd64", "mac_arm64", "windows"]
include:
- job_name: linux
os: ubuntu-latest
- job_name: mac_amd64
os: macos-latest
apple_signing: true
- job_name: mac_arm64
os: macos-latest
build_arch: --arch arm64
apple_signing: true
- job_name: windows
os: windows-latest
name: ${{ matrix.job_name }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18.12.1"
cache: "yarn"
- name: Install the Apple certificates
if: ${{ matrix.apple_signing }}
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- name: Install packages
run: |
yarn install --immutable
- name: Build Electron App
env:
# Windows signing keys
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
# Apple signing keys
XCODE_APP_LOADER_EMAIL: ${{ secrets.XCODE_APP_LOADER_EMAIL }}
XCODE_APP_LOADER_PASSWORD: ${{ secrets.XCODE_APP_LOADER_PASSWORD }}
run: yarn build ${{ matrix.build_arch }}
- name: Upload artifacts to GitHub temporary storage
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.job_name }}
path: |
${{ env.ELECTRON_OUTPUT_PATH }}/*.exe
${{ env.ELECTRON_OUTPUT_PATH }}/*.dmg
${{ env.ELECTRON_OUTPUT_PATH }}/*.AppImage
# An example deploy action. You can replace this with your own deployment actions or steps as required.
create-release:
needs: [build-electron-app]
runs-on: ubuntu-22.04
steps:
- name: Download all workflow artifacts
uses: actions/download-artifact@v3
- name: Create production changeLog and release with the artifacts
uses: "marvinpinto/action-automatic-releases@d68defdd11f9dcc7f52f35c1b7c236ee7513bcc1"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
files: |
mac_amd64/*
mac_arm64/*
linux/*
windows/*