Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ docs/
│ ├── api-endpoints.md
│ ├── configuration-reference.md
│ ├── database-schema.md
│ ├── desktop-build-and-distribution.md
│ ├── memory-coordination-prompt.md
│ └── skillsmith.md
Expand Down Expand Up @@ -65,3 +66,4 @@ docs/
- [Testing](testing-reference.md)
- [Data Contracts](domains/data-contracts-reference.md)
- [User Workflows](product/user-workflows-reference.md)
- [Desktop Build & Distribution](reference/desktop-build-and-distribution.md)
4 changes: 2 additions & 2 deletions docs/docs-index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,6 @@ docs_index:
tags: [releases, desktop, electron, windows, linux, macos, installers]
anchors:
- id: ci
summary: "Desktop installers workflow: manual or on v* tag; download .exe, .deb, .dmg"
summary: "Desktop installers workflow: manual or on v* tag; artifacts + release flow; unsigned-by-default signing status and troubleshooting"
depends_on: [doc.releases-distribution]
last_verified: "2026-03-06"
last_verified: "2026-03-09"
93 changes: 78 additions & 15 deletions docs/reference/desktop-build-and-distribution.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Desktop build and distribution

How to produce signed/notarized Windows (.exe), Linux (.deb), and macOS (.dmg) installers and enable auto-updates from GitHub Releases.
How Dossier currently produces desktop installers (Windows `.exe`, Linux `.deb`, macOS `.dmg`) and publishes them for update checks.

## Artifact formats

Expand All @@ -12,6 +12,36 @@ How to produce signed/notarized Windows (.exe), Linux (.deb), and macOS (.dmg) i

Version `X.Y.Z` comes from `package.json` `version`.

## Current behavior (as implemented)

The source of truth is:

- `.github/workflows/desktop-build.yml`
- `package.json` (`electron:make`)
- `forge.config.js`
- `electron/main.ts` (`updateElectronApp`)

### What CI does today

- Builds installers on three runners (`ubuntu-latest`, `windows-latest`, `macos-latest`).
- Uses `pnpm install --frozen-lockfile`.
- Runs `pnpm run electron:make`, which runs:
1. `pnpm run build`
2. `pnpm run electron:prepare-node`
3. `pnpm run electron:compile`
4. `electron-forge make`
- Uploads each runner's `out/make/` as an artifact.
- Creates a GitHub Release only for `v*` tag pushes.

### Signing/notarization status

Signing and notarization are currently **disabled by default** in source:

- CI signing env and prep steps are commented out in `.github/workflows/desktop-build.yml`.
- Forge signing/notarization options are commented out in `forge.config.js`.

Result: default CI outputs are unsigned installers unless signing is explicitly re-enabled in code.

## Auto-update

The app uses [update.electronjs.org](https://update.electronjs.org) (free for open-source apps on GitHub). When you create a **GitHub Release** with the built installers:
Expand All @@ -21,9 +51,12 @@ The app uses [update.electronjs.org](https://update.electronjs.org) (free for op

**To enable auto-update:** Push a version tag (e.g. `v0.5.3`) so the workflow runs. The **release** job creates a GitHub Release and attaches the Windows, Linux, and macOS artifacts. Ensure `package.json` `version` matches the tag (e.g. `0.5.3` for tag `v0.5.3`).

## Signing and notarization
## Signing and notarization (prepared, currently disabled)

When the following **repository secrets** are set, the CI build signs and (on macOS) notarizes the installers. If secrets are missing, builds still run but produce **unsigned** installers (fine for local/testing; stores and auto-update on macOS prefer signed/notarized).
The workflow and Forge config include commented templates for signing/notarization. If you decide to enable them, use the following secrets and uncomment the corresponding blocks in:

- `.github/workflows/desktop-build.yml`
- `forge.config.js`

### macOS (code sign + notarization)

Expand All @@ -45,7 +78,7 @@ Choose **one** of these two methods.
| `APPLE_API_ISSUER` | Issuer UUID from App Store Connect → Users and Access → Integrations → API Keys |
| `APPLE_API_KEY_BASE64` | Base64-encoded contents of your `.p8` key file (download from App Store Connect; only available once) |

The workflow decodes this to a temporary file and sets `APPLE_API_KEY_PATH` before the build. Do not commit the `.p8` file.
The template workflow decodes this to a temporary file and sets `APPLE_API_KEY_PATH` before the build. Do not commit the `.p8` file.

### Windows (Squirrel code signing)

Expand All @@ -54,12 +87,7 @@ The workflow decodes this to a temporary file and sets `APPLE_API_KEY_PATH` befo
| `WINDOWS_CERTIFICATE_PFX_BASE64` | Base64-encoded `.pfx` (or `.p12`) code signing certificate |
| `WINDOWS_CERTIFICATE_PASSWORD` | Password for the PFX |

The workflow decodes the base64 to `cert.pfx` on the Windows runner before building. Do not commit `cert.pfx`; it is in `.gitignore`.

### Local signed builds

- **macOS:** Set `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID` (or the API key vars) in your environment and run `pnpm run electron:make` on a Mac. Signing and notarization run automatically when credentials are present.
- **Windows:** Put your `.pfx` at the repo root as `cert.pfx` (or set `certificateFile` in `forge.config.js`), set `WINDOWS_CERTIFICATE_PASSWORD`, and run `pnpm run electron:make` on Windows. For CI we use base64 + secret instead of a committed file.
The template workflow decodes the base64 to `cert.pfx` on the Windows runner before building. Do not commit `cert.pfx`; it is in `.gitignore`.

## Building on demand (CI)

Expand All @@ -74,18 +102,53 @@ The **Desktop installers** workflow builds all three platforms and (on tag push)
- Download `dossier-windows-<version>`, `dossier-linux-<version>`, `dossier-macos-<version>`.
- For tagged runs, the same files are attached to the GitHub Release.

3. **Use for stores**
- Attach the installer files from the release (or artifacts) to your web site or app store submission. Signing/notarization is applied when the secrets above are set.
3. **Use for distribution**
- Attach installer files from artifacts/release to your website or release process.
- If you need signed/notarized builds, enable signing in workflow + Forge first (see section above).

## Building locally

Electron Forge only produces installers for the **current OS** (no cross-compilation).

- **macOS:** `pnpm run electron:make` → `out/make/*.dmg` (signed/notarized if Apple env vars are set).
- **Windows:** Same command on Windows → `out/make/*.exe` (+ Squirrel artifacts; signed if `cert.pfx` and password are set).
- **macOS:** `pnpm run electron:make` → `out/make/*.dmg`.
- **Windows:** Same command on Windows → `out/make/*.exe` (+ Squirrel artifacts).
- **Linux:** Same command on Linux → `out/make/*.deb`.

To get all three from one place, use the CI workflow.
By default, local outputs are unsigned because signing/notarization config is commented out in `forge.config.js`.

To get all three platforms from one place, use the CI workflow.

## Troubleshooting and common pitfalls

### Linux `.deb` maker fails on CI

Symptom: Debian packaging step fails on Ubuntu runners.

Cause: `fakeroot` missing.

Current fix in workflow: Ubuntu job installs `fakeroot` before `electron:make`.

### Windows build exits with heap/OOM errors

Symptom: `electron:make` fails on Windows runner with memory-related errors.

Cause: Windows builds may require higher Node heap. Also, setting `NODE_OPTIONS` via `GITHUB_ENV` is blocked by GitHub Actions.

Current fix in workflow: set `NODE_OPTIONS=--max-old-space-size=4096` only on the **Build desktop installers** step for Windows.

### Tag pushed but release artifacts mismatch

Symptom: release job cannot find artifacts (download step fails).

Cause: release job expects artifacts named with the version extracted from `refs/tags/v*`. If the tag/version flow is inconsistent, names can diverge.

Check:

- `package.json` version matches tag (`0.5.2` -> `v0.5.2`)
- build artifacts exist:
- `dossier-windows-<version>`
- `dossier-linux-<version>`
- `dossier-macos-<version>`

## Configuration

Expand Down