Electron 代码签名和公证
本文的方式仅限用于 macOS
最近尝试入门了下 Electron 开发,写了一个可以自动匹配弹幕的动漫播放器,期间遇到挺多坑的,写篇文章来记录一下。
代码签名和公证
想要在 macOS 上运行一个桌面端应用,那就必须对它代码进行签名,否则是无法打开,会出现如下错误。
Apple Developer 注册
想要进行代码签名就得花 688 元去注册苹果开发者,这里注册也比较玄学,注册过程中运气不好就容易出现 联系我们以继续流程
的弹窗,这我在用 MacBook 注册时候出现过一次,然后换 iPhone 上注册就没有这个问题了。
注册到最后一步,付完费用之后,你会发现还是没办法使用,打开 Developer APP 账户页面里面,会显示一个灰色的现在注册按钮,然后显示将很快收到相关邮件,打开邮箱会发现两封名为你的订阅确认
和 Apple 提供的收据
的邮件,但这个其实并不是上文 Apple 所提到的相关邮件
。这里不用慌张,这其实就是 Apple 正在审核的意思,我是晚上注册的,等隔天早上 9 点之后,就会收到一份 欢迎加入 Apple Developer Program
的邮件,这才表明注册成功了。
代码签名
关于具体如何生成和上传证书,网上相关教程有很多,这里就不展开说明了。之后就是把 Developer ID certificates 的私钥 .p12 文件,配置到终端环境变量中,填写 CSC_LINK
和 CSC_KEY_PASSWORD
。
这里 Electron 打包我选择使用 electron-builder ,执行 electron-vite build && electron-builder --mac --publish never
的时候,它会自动读取上方配置好的两个环境变量从而就行签名,无需进行额外配置。
下面是我的 electron-builder.yml
appId: com.suemor.Marchen
productName: Marchen
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
asarUnpack:
- resources/**
win:
executableName: Marchen
nsis:
artifactName: ${productName}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
allowToChangeInstallationDirectory: true
oneClick: false
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false
target:
- target: dmg
arch:
- arm64
- x64
- target: zip
arch:
- arm64
- x64
dmg:
artifactName: ${productName}-${version}-${arch}.${ext}
linux:
target:
- target: AppImage
arch:
- arm64
- x64
maintainer: github.com/suemor233
category: Utility
appImage:
artifactName: ${productName}-${version}-${arch}.${ext}
npmRebuild: false
publish:
provider: github
owner: marchen-dev
repo: MarchenPlay
afterSign: scripts/notarize.js
releaseInfo:
releaseNotes: |
本次更新:
可以切换动漫内嵌字幕和手动导入字幕
新增视频播放器设置功能
可以对历史记录动漫进行删除和从新识别弹幕库
公证
代码签完名之后,我们需要把打包后的程序上传给苹果,来确保我们程序的安全性和没有被其他人篡改。
这里我们安装 @electron/notarize
和 dotenv
这两个包, 然后创建一个 scripts/notarize.js
文件,写法如下,他可以读取我们 .env 里面的相关变量,然后进行公证。
import { notarize } from '@electron/notarize'
import { config } from 'dotenv'
config()
export default async function notarizing(context) {
if (context.electronPlatformName !== 'darwin') {
return
}
const appBundleId = process.env.APPLE_APP_BUNDLE_ID // 随便填一个,类似 com.suemor.Marchen
const appleId = process.env.APPLE_ID // 你的 Apple ID
const appleIdPassword = process.env.APPLE_APP_SPECIFIC_PASSWORD // https://account.apple.com/account/manage 然后点击 App 专用密码
const teamId = process.env.APPLE_TEAM_ID // https://developer.apple.com/account 里面 会员资格详细信息 卡片里面,有个 团队 ID
if (!appBundleId || !appleId || !appleIdPassword || !teamId) {
return
}
const appName = context.packager.appInfo.productFilename
const appPath = `${context.appOutDir}/${appName}.app`
// eslint-disable-next-line no-console
console.log('Notarizing app:', appPath)
await notarize({
appPath,
appBundleId,
appleId,
appleIdPassword,
teamId,
})
}
之后在 electron-builder.yml
里面导入这个路径 afterSign: scripts/notarize.js
。
然后执行"build:mac": "electron-vite build && electron-builder --mac --publish never",
就可以完成公证了。
这里公证速度一般都挺慢的,因为要把你的应用上传到苹果服务器上去,取决于你宽带的上传速度,得耐心等待。另外我看别人说公证成功之后收到 Apple Developer 的相关邮件,不清楚什么原因,我从来没有收到过。
那么如何校验我们这个应用是否公证成功了呢?安装完 App 之后,我们只要去终端执行下方的命令,显示The validate action worked!
就代表成功了。
stapler validate /Applications/xxxx.app
配置 GitHub Actions
为了方便发布版本,我们可能会利用 Github Actions 进行自动化打包,为了 Github Actions 能够在打包过程中进行签名和公证,如下图所示,我们需要把上文所配置的环境变量放到 Github Repository secrets 里面。
CSC_LINK // 填写 base64
CSC_KEY_PASSWORD
APPLE_ID
APPLE_APP_SPECIFIC_PASSWORD
APPLE_TEAM_ID
APPLE_APP_BUNDLE_ID
这里有个麻烦的地方,就是 CSC_LINK 这个字段的填写。之前我们在配置终端环境变量的时候,是直接使用绝对路径的方式来链接到 .p12 文件的,但这里的 Repository secrets 是不支持上传文件的,所以得把之前 Developer ID certificates 的私钥 .p12 文件转换为 base64 然后复制到 Repository secrets 的 CSC_LINK 的变量里面去。
base64 -i xxxx.p12 | pbcopy
下方是我完整的 release.yml
name: Build/release Electron app
on:
push:
tags:
- v*.*.*
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [20.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: true
- name: Checkout LFS objects
run: git lfs checkout
- name: Setup pnpm
uses: pnpm/action-setup@v4.0.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install
- name: Install snapcraft
if: matrix.os == 'ubuntu-latest'
run: sudo snap install snapcraft --classic
- name: Build for Linux
if: matrix.os == 'ubuntu-latest'
run: pnpm run build:linux
- name: Build for macOS
if: matrix.os == 'macos-latest'
run: pnpm run build:mac
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_BUNDLE_ID: ${{ secrets.APPLE_APP_BUNDLE_ID }}
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Build for Windows
if: matrix.os == 'windows-latest'
run: pnpm run build:win
- name: Generate Changelog
if: github.event_name == 'push'
run: npx @suemor/changelogithub
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release
uses: softprops/action-gh-release@v2
with:
draft: false
prerelease: true
files: |
dist/*.exe
dist/*.zip
dist/*.dmg
dist/*.AppImage
dist/*.snap
dist/*.deb
dist/*.rpm
dist/*.tar.gz
dist/latest*.yml
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
下期预告
下篇文章准备来讲一下如何打包 FFmpeg 到 Electron 里面,这里面的坑还是挺多的,特别是利用 Github Actions 打包的时候,得实现根据不同的平台打包不同 FFmpeg 才行,比如用 arm64 mac 打包 x64 的应用,如何正确打包 x64 版本的 FFmpeg。