11 KiB
Auto-Update System
This document covers the Electron auto-update system powered by electron-updater, including the release workflow, provider configuration, CI/CD requirements, and code signing.
Table of Contents
- Architecture Overview
- Current Provider: GitHub Releases
- Release Workflow
- CI/CD Environment Variables
- Switching to AWS S3
- Switching to a Generic File Server
- Code Signing Requirements
- Troubleshooting
Architecture Overview
The auto-update flow involves three layers:
┌─────────────────────────────────────────────────────────┐
│ Main Process (src/main/index.ts) │
│ │
│ autoUpdater.checkForUpdatesAndNotify() │
│ ↓ │
│ Events: checking → available → progress → downloaded │
│ ↓ │
│ sendToRenderer('updater:*', data) │
├─────────────────────────────────────────────────────────┤
│ Preload (src/preload/index.ts) │
│ │
│ contextBridge: onUpdateAvailable, onDownloadProgress, │
│ onUpdateDownloaded, checkForUpdates, │
│ installUpdate │
├─────────────────────────────────────────────────────────┤
│ Renderer / React (apps/web) │
│ │
│ useElectronUpdater() hook │
│ → status, progress, updateInfo, errorMessage │
│ → checkForUpdates(), installUpdate() │
└─────────────────────────────────────────────────────────┘
Lifecycle
- App starts → After a 3-second delay,
autoUpdater.checkForUpdatesAndNotify()is called. - Update available → If
autoDownloadistrue(default), downloads automatically. - Download progress →
download-progressevents are forwarded to the renderer. - Update downloaded → The renderer shows a "Restart to Update" prompt.
- User clicks install →
autoUpdater.quitAndInstall()restarts the app with the new version.
Current Provider: GitHub Releases
The update provider is configured in electron-builder.yml:
publish:
provider: github
owner: YOUR_GITHUB_ORG
repo: YOUR_REPO_NAME
How it Works
-
When you run
electron-builder --publish always, it:- Builds the app for your target platform.
- Uploads the installer(s) to a GitHub Release tagged with the version from
package.json. - Generates and uploads
latest.yml(Windows),latest-mac.yml(macOS), orlatest-linux.yml(Linux).
-
When the packaged app calls
checkForUpdates(),electron-updater:- Reads
app-update.ymlfrom the app'sresources/directory (auto-generated during build). - Fetches the appropriate
latest*.ymlfrom the configured GitHub release. - Compares versions and downloads the update if a newer version exists.
- Reads
app-update.yml
This file is automatically generated by electron-builder during the build process. It contains the provider configuration and is placed in the packaged app's resources/ directory. You do NOT need to create or manage this file manually.
Release Workflow
Manual Release
# 1. Bump the version
cd apps/desktop
npm version patch # or minor, major
# 2. Build the web app
cd ../..
pnpm build --filter=web
# 3. Build & publish the Electron app
cd apps/desktop
pnpm run prebuild
GH_TOKEN=your_token electron-builder --publish always --config electron-builder.yml
Automated Release (GitHub Actions)
A typical CI workflow:
name: Release Desktop
on:
push:
tags:
- 'desktop-v*'
jobs:
release:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install
- run: pnpm build --filter=web
- run: cd apps/desktop && pnpm run prebuild
- run: cd apps/desktop && pnpm run build
- name: Publish
run: cd apps/desktop && npx electron-builder --publish always --config electron-builder.yml
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CI/CD Environment Variables
| Variable | Required | Platform | Description |
|---|---|---|---|
GH_TOKEN |
Yes | All | GitHub personal access token with repo scope. Used by electron-builder to create/upload releases. |
CSC_LINK |
macOS/Windows | macOS, Windows | Base64-encoded .p12 code signing certificate. Generate with: base64 -i cert.p12 | pbcopy |
CSC_KEY_PASSWORD |
macOS/Windows | macOS, Windows | Password for the .p12 certificate. |
APPLE_ID |
macOS only | macOS | Apple ID email for notarization. |
APPLE_APP_SPECIFIC_PASSWORD |
macOS only | macOS | App-specific password generated at appleid.apple.com. |
APPLE_TEAM_ID |
macOS only | macOS | Your Apple Developer Team ID. |
WIN_CSC_LINK |
Windows only | Windows | Separate Windows code signing certificate (if different from CSC_LINK). |
WIN_CSC_KEY_PASSWORD |
Windows only | Windows | Password for the Windows certificate. |
Setting Secrets in GitHub Actions
- Go to Settings → Secrets and variables → Actions in your repository.
- Add each variable as a Repository secret.
- Reference them in workflow files as
${{ secrets.VARIABLE_NAME }}.
Setting Variables in Turborepo
In turbo.json, the build task already has env awareness via "inputs": ["$TURBO_DEFAULT$", ".env*"]. For CI-specific variables, pass them through the environment — Turborepo does NOT manage CI secrets.
Switching to AWS S3
To use a private S3 bucket instead of GitHub Releases, update electron-builder.yml:
publish:
provider: s3
bucket: your-bucket-name
region: ap-southeast-1
path: /desktop-releases
acl: private
Additional AWS Environment Variables
| Variable | Description |
|---|---|
AWS_ACCESS_KEY_ID |
IAM access key with S3 write permissions |
AWS_SECRET_ACCESS_KEY |
IAM secret key |
S3 Bucket Policy
The bucket must allow public read access to the update files, or you must configure a CloudFront distribution in front of it. electron-updater needs to GET the latest*.yml files without authentication.
Recommended bucket structure:
your-bucket/desktop-releases/
├── latest.yml (Windows)
├── latest-mac.yml (macOS)
├── latest-linux.yml (Linux)
├── EigenDesktop-Setup-0.2.0.exe
├── EigenDesktop-0.2.0.dmg
├── EigenDesktop-0.2.0-mac.zip
└── EigenDesktop-0.2.0.AppImage
Switching to a Generic File Server
For a self-hosted server (Nginx, Caddy, etc.):
publish:
provider: generic
url: https://updates.your-domain.com/desktop
Your server must host the same file structure as S3 above. On each release, upload the installer files and latest*.yml to the server.
Nginx Example
server {
listen 443 ssl;
server_name updates.your-domain.com;
location /desktop/ {
alias /var/www/desktop-releases/;
autoindex off;
add_header Cache-Control "no-cache";
}
}
Code Signing Requirements
Warning
macOS auto-updates will FAIL without code signing. Apple's Gatekeeper will block unsigned apps, and
electron-updaterwill refuse to apply updates to unsigned builds. This is enforced by the OS, not by Electron.
macOS
- Requires an Apple Developer ID Application certificate.
- The
electron-builder.ymlis already configured with:mac: hardenedRuntime: true gatekeeperAssess: false entitlements: build/entitlements.mac.plist entitlementsInherit: build/entitlements.mac.plist - You must create
apps/desktop/build/entitlements.mac.plist:<?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> <key>com.apple.security.cs.allow-jit</key> <true/> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> <key>com.apple.security.cs.allow-dyld-environment-variables</key> <true/> </dict> </plist> - Notarization is required for macOS 10.15+. Provide
APPLE_ID,APPLE_APP_SPECIFIC_PASSWORD, andAPPLE_TEAM_ID.
Windows
- Requires an EV Code Signing Certificate or a standard code signing certificate.
- Without signing, Windows SmartScreen will show a warning to users.
- EV certificates eliminate SmartScreen warnings immediately; standard certificates build reputation over time.
Linux
- Code signing is not required for Linux.
- AppImage files work without signatures. However, you can optionally sign with GPG for package managers that support it.
Troubleshooting
"Update check failed" on startup
- Cause: The app is offline, or the update server is unreachable.
- Impact: None — the error is caught and logged. The app continues to function normally.
- Verification: Check the main process console for
[AutoUpdater] Startup check failed (possibly offline).
app-update.yml not found in production build
- Cause: The
publishblock inelectron-builder.ymlis missing or misconfigured. - Fix: Ensure the
publishblock exists. Runelectron-builder --publish neverfirst to verify the file is generated inrelease/*/resources/app-update.yml.
"Cannot update: code signature is invalid" (macOS)
- Cause: The app was not signed or the signature is broken.
- Fix: Ensure
CSC_LINKandCSC_KEY_PASSWORDare set correctly in CI. Verify with:codesign --verify --deep --strict release/mac*/EigenDesktop.app.
Updates work on Windows/Linux but not macOS
- Cause: macOS requires both a signed app AND notarization.
- Fix: Provide all Apple credential environment variables and ensure the
mac.hardenedRuntimeand entitlements are configured.