ADR 0022: Application Update Delivery
- Status: Accepted (amended by D364 / D603 / D628 / ADR 0197, D450 / ADR 0289; issue #1098)
- Date: 2026-07-26
- Deciders: PI-Desktop core
- Related: D120, D126, D364, D603, D628, D010, D450, ADR 0021, ADR 0197, ADR 0289
Context
PI-Desktop needs a release-update path that preserves the renderer sandbox, does not expose feed configuration to untrusted UI code, and reflects each platform's actual installer capabilities. A single automatic-install policy is not appropriate: unsigned macOS packages cannot provide a qualified in-app upgrade lane, while Windows NSIS and Linux AppImage support electron-updater's download-and-install flow.
Decision
- Electron Main exclusively owns
electron-updater, the fixed GitHub Releases feed, update polling, and install lifecycle. Renderer IPC exposes only allowlisted check, state, release-link, and install operations; callers cannot supply a feed URL. - Development builds keep updates disabled. Packaged macOS, Windows NSIS, and Linux AppImage use in-app download and quit-and-install delivery. Non-AppImage Linux (deb/rpm) and Windows ZIP builds (
piDistribution = "zip") use notify-and-link delivery so an NSIS installer cannot replace a no-install run. Legacy portable executables remain manual whenPORTABLE_EXECUTABLE_FILEis present. (Packaged macOS was notify-and-link until D450 / ADR 0289 qualified the signed in-app channel.) - The updater always sets
allowPrerelease = false. electron-updater would otherwise pin prerelease installs (for example0.2.0-rc.6) to the same custom channel (rc) and never offer a newer stable GitHub latest release. Prerelease-channel policy remains an operational follow-up if a dedicated RC feed is needed later. - Automatic checks run after startup and periodically. Automatic failures remain ambient; explicit checks expose status and errors through the menu and Settings -> Info. A downloaded update remains actionable until install or normal shutdown.
- Feed manifests and artifact hashes are generated by electron-builder. The client ships no GitHub credential and fails closed when the feed or package cannot be validated.
- D126 later lifts D010's macOS-only publication scope and publishes all platform artifacts and update manifests produced by the release matrix. D450 / ADR 0289 qualifies the signed macOS in-app channel on that same feed.
- Dual-locale product "what's new" text (D164) is maintained in
packages/sharedas EN + zh-CN catalogs. Main formats notes for the discovered version using the product UI locale and attaches them as optionalUpdateState.releaseNoteson the existing updates path. GitHub auto-generated release bodies remain web-only; the renderer never supplies a notes URL.
Consequences
- Update state is process-owned and consistent across the application menu, Settings, and the ambient banner.
- The sandboxed renderer cannot redirect update traffic or install arbitrary packages.
- Windows NSIS, Linux AppImage, and packaged macOS can update in-app from published tag feeds; Linux deb and Windows ZIP users install from the release page after extracting the archive.
- Prerelease installs graduate to newer stable releases through the same latest feed; a dedicated RC channel is not active.
- In-app bilingual release highlights ship with the build and follow the product locale without a second network surface.
- Rollback, staged rollout, and optional prerelease-channel policy remain operational follow-ups rather than renderer capabilities.
Alternatives
- Renderer-owned updater: rejected because it violates the process and sandbox boundary.
- Caller-provided feed URLs: rejected because it creates an arbitrary package installation path.
- Force one delivery mode on every platform: rejected because installer and signing guarantees differ by target.
Amendment (D450 / ADR 0289)
Official GitHub tag macOS artifacts are Developer ID-signed, notarized, and stapled. Packaged macOS therefore uses the same in-app electron-updater lane as Windows NSIS and Linux AppImage. Local unsigned packaging without a certificate remains available (D078).
Amendment (D603)
The Windows no-install lane now publishes a normal ZIP archive instead of electron-builder's self-extracting portable executable. The release helper builds NSIS and ZIP separately and stamps the ZIP app metadata with piDistribution = "zip"; the updater uses that marker to keep the ZIP manual even though ordinary ZIP launches do not set PORTABLE_EXECUTABLE_FILE. Users extract the archive and run PI-Desktop.exe; the NSIS lane and existing data directory remain unchanged.
Amendment (issue #1098)
For Windows NSIS installs, PI_DESKTOP_UPDATE_CACHE_DIR may set an absolute, writable base for electron-updater's download cache. Main reads the cache's subdirectory name from the packaged app-update.yml, adopts the installer and block-map differential baselines plus any staged update from %LOCALAPPDATA% on startup, then removes the legacy cache directory only when empty. Unknown files and already-populated destinations are preserved rather than overwritten or recursively deleted. Once the feed confirms the running version is current, Main deletes only the obsolete pending/ staging directory and keeps the baselines for the next differential update. The default cache location remains unchanged unless the variable is set. Main does not write to the installation directory: the NSIS script still writes its installer.exe baseline to %LOCALAPPDATA%, which Main adopts on the next launch.
Amendment (D628)
Settings → Info exposes a persisted per-install updatePreference with automatic and manual values. Automatic preserves the existing in-app installer lane. Manual continues stable-release checks but disables automatic download and install-on-quit, and raises one reminder per discovered version. The last reminded version is stored in the existing Host-owned app settings JSON and is intentionally omitted from portable configuration sync.
An update-banner dismissal is also persisted for its available version. In-app dismissal cancels an active download and disables install-on-quit for that version. Scheduled and explicit checks continue; rediscovering the dismissed version does not restart its download, while discovering a newer version clears the dismissal and restores automatic delivery.
Installed Windows NSIS, packaged macOS, and Linux AppImage default to Automatic. Windows ZIP/portable packages default to Manual; opting into Automatic is available on supported Windows packages only after the UI warns that NSIS may replace the extracted no-install copy. Unsupported installer formats remain Manual. This additive setting requires no database schema or host protocol version change.