311 lines
11 KiB
Markdown
311 lines
11 KiB
Markdown
# 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](#architecture-overview)
|
|
- [Current Provider: GitHub Releases](#current-provider-github-releases)
|
|
- [Release Workflow](#release-workflow)
|
|
- [CI/CD Environment Variables](#cicd-environment-variables)
|
|
- [Switching to AWS S3](#switching-to-aws-s3)
|
|
- [Switching to a Generic File Server](#switching-to-a-generic-file-server)
|
|
- [Code Signing Requirements](#code-signing-requirements)
|
|
- [Troubleshooting](#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
|
|
|
|
1. **App starts** → After a 3-second delay, `autoUpdater.checkForUpdatesAndNotify()` is called.
|
|
2. **Update available** → If `autoDownload` is `true` (default), downloads automatically.
|
|
3. **Download progress** → `download-progress` events are forwarded to the renderer.
|
|
4. **Update downloaded** → The renderer shows a "Restart to Update" prompt.
|
|
5. **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`:
|
|
|
|
```yaml
|
|
publish:
|
|
provider: github
|
|
owner: YOUR_GITHUB_ORG
|
|
repo: YOUR_REPO_NAME
|
|
```
|
|
|
|
### How it Works
|
|
|
|
1. 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), or `latest-linux.yml` (Linux).
|
|
|
|
2. When the packaged app calls `checkForUpdates()`, `electron-updater`:
|
|
- Reads `app-update.yml` from the app's `resources/` directory (auto-generated during build).
|
|
- Fetches the appropriate `latest*.yml` from the configured GitHub release.
|
|
- Compares versions and downloads the update if a newer version exists.
|
|
|
|
### `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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```yaml
|
|
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](https://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
|
|
|
|
1. Go to **Settings → Secrets and variables → Actions** in your repository.
|
|
2. Add each variable as a **Repository secret**.
|
|
3. 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`:
|
|
|
|
```yaml
|
|
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.):
|
|
|
|
```yaml
|
|
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
|
|
|
|
```nginx
|
|
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-updater` will 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.yml` is already configured with:
|
|
```yaml
|
|
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
|
|
<?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`, and `APPLE_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 `publish` block in `electron-builder.yml` is missing or misconfigured.
|
|
- **Fix**: Ensure the `publish` block exists. Run `electron-builder --publish never` first to verify the file is generated in `release/*/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_LINK` and `CSC_KEY_PASSWORD` are 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.hardenedRuntime` and entitlements are configured.
|