Desktop Installer Plan
Status: Planning
Issue: #530 — Desktop installer: one-click install with NSIS, DMG, AppImage + auto-update
Branch:
kalin/desktop-installer (this plan ships as commit 1; subsequent commits implement it)
Approach: Ground-up implementation using Lemonade Server’s installer as a reference architecture. Distributed via GitHub Releases initially; R2 / CDN deferred until the website lands.
Companion plan: installer.mdx covers the Python CLI install path. This document covers the Electron desktop app installer — which is the primary install path for non-developer end users.§1 Why this plan exists
GAIA today has three distribution channels — and the one most users actually want is the one that doesn’t work properly:| Channel | Audience | Status | First-run install? |
|---|---|---|---|
pip install amd-gaia | Python developers | ✅ Working | N/A (developer assumed to manage Python) |
npm install -g @amd-gaia/agent-ui | End users via Node | ✅ Working | ✅ bin/gaia-ui.mjs installs uv + Python venv + amd-gaia[ui] + gaia init |
Desktop installer (current Squirrel .exe) | End users via download | ❌ Broken UX | ❌ No — gives up if gaia not on PATH |
- Reliability is non-negotiable — every install failure is a lost user
- Polish matters — first impressions are formed in the first 60 seconds
- Documentation is a first-class deliverable, not an afterthought
- Code signing is required, not nice-to-have — antivirus / SmartScreen / Gatekeeper warnings are the leading cause of install abandonment for unsigned consumer apps
- Auto-update must work, because non-developers won’t manually re-download to get fixes
§2 Goals / non-goals
Goals
- One double-click install on Windows / macOS / Linux
- Under 10 minutes from download to first prompt (model download dominates; UX itself is < 1 minute and the rest is unattended progress)
- Auto-update — users get new fixes and features without manual reinstall
- Native conventions per platform: NSIS (Windows), DMG (macOS), DEB + AppImage (Linux)
- AMD-branded wizard with consistent visual identity
- Code-signed installers on Windows + macOS so users don’t see scary OS warnings (P0 because this is the primary path)
- Auto-start at login with sensible per-platform defaults so the tray-app pattern actually works
- Clean uninstall with sensible defaults — keep user data unless explicitly purged
- Single source of truth for first-run backend install logic, shared between npm CLI and Electron app
- Comprehensive documentation — quickstart, troubleshooting, FAQ, install/uninstall guides, all updated to lead with the desktop installer as the recommended path
- Robust failure recovery — every failure mode shows a useful error and offers a path forward (retry, manual install, quit), with logs the user can attach to a bug report
Non-goals
- Replacing the CLI install path entirely.
install.ps1,install.sh,gaia init, andgaia updatestill exist for developers, CI, headless deployments, and users who explicitly want the developer path. The desktop installer supplements them. - Bundling Python in the installer. The first-run logic downloads
uvand creates a Python 3.12 venv on demand. Bundling adds ~50 MB and complicates the upgrade story. - macOS Intel native build. Apple Silicon only. Intel Macs may work via Rosetta but are unsupported.
- Migrating users from the existing v0.17.1 Squirrel installer. The Squirrel installer is being removed entirely. Existing v0.17.1 users uninstall it manually via Apps & Features (release notes step), then install the new NSIS installer. No auto-detection, no migration scripting — that complexity isn’t worth the maintenance burden for one transition.
- Setting up the code-signing infrastructure itself. That’s a one-time admin task documented separately under
docs/deployment/. - Designing the visual assets. Asset generation (running
iconutil, ICO conversion, etc.) is in scope. Visual design is delivered separately by a designer; placeholder assets derived from existing GAIA PNGs are used until then. - R2 / CDN distribution. Deferred — ships via GitHub Releases. R2 migration becomes a one-line config change in
app.config.jsononce the website lands. - In-app behavioral telemetry / crash reporting. GAIA’s privacy-first positioning forbids this. Most consumer apps include opt-out crash reporting; we’re consciously breaking from the norm. Download / install metrics from GitHub Releases public stats are fine — they require no in-app code.
- A separate React-based first-run wizard UI. The smart install logic in
bin/gaia-ui.mjsalready covers this functionally. Issue #597 (polish-the-wizard-UI) stays scoped to its own work. - Implementing the
amd-gaia.ai/downloadpage. That’s a website project. This plan provides the content the download page needs (asset URLs, install instructions per platform) but doesn’t touch the website itself. Tracked as a follow-up issue.
§3 The key insight: reuse bin/gaia-ui.mjs install logic
src/gaia/apps/webui/bin/gaia-ui.mjs (572 lines) is the npm-installed CLI. It already does everything the desktop installer’s first-run wizard needs:
ensureUv()— installsuvif missinginstallBackend()— creates~/.gaia/venvwithuv venv --python 3.12, installsamd-gaia[ui]==<pinned-version>, runsgaia init --profile minimalgetInstalledVersion()+ensureBackend()— version-aware upgrade flow- Linux CPU-only PyTorch handling
- Detailed error messages with manual-fallback instructions for every failure mode
main.cjs (the Electron app) does none of this. It has a 20-line findGaiaCommand() that prints “Install with: pip install amd-gaia” and gives up.
The shape of the fix: extract the install logic from bin/gaia-ui.mjs into a shared module, then have both the npm CLI and the Electron main.cjs call it.
§3.1 Module format collision: .mjs is ESM, main.cjs is CommonJS
bin/gaia-ui.mjs uses import syntax (ES modules). main.cjs uses require (CommonJS). The shared module needs to work in both contexts.
The cleanest fix: rename bin/gaia-ui.mjs → bin/gaia-ui.cjs and have both the binary entry point and the Electron main process use require() to pull from services/backend-installer.cjs. The file extension change is invisible to users (the bin map in package.json is the only public reference). All install logic lives in plain CommonJS.
§3.2 Install state machine
The install isn’t atomic — it has multiple stages, takes 5–10 minutes, and can fail at any point. The Electron app needs to handle:| State | Meaning | Recovery |
|---|---|---|
idle | Backend not yet installed | Trigger install |
installing | Install in progress | Show progress dialog; persist state |
failed | Last install attempt failed | Show error dialog with Retry / Manual / Quit |
partial | Install was interrupted (window closed, app killed) | Detect on next launch; offer to resume or restart from scratch |
ready | Backend installed and version matches | Launch app normally |
~/.gaia/electron-install-state.json after each stage transition. On launch, the Electron app reads this file before doing anything else.
This eliminates: silent half-installed venvs, force-quit-mid-install confusion, and the “user closes the install dialog and doesn’t know what’s happening” footgun.
§4 Architecture (three layers)
§4.1 Tray and auto-start
The Electron app already runs as a tray application viaservices/tray-manager.cjs. The installer’s role is to integrate auto-start at login:
| Platform | Mechanism | Default |
|---|---|---|
| Windows NSIS | customInstall hook writes HKCU\Software\Microsoft\Windows\CurrentVersion\Run pointing at gaia-agent-ui.exe --minimized. No finish-page checkbox — MUI_FINISHPAGE_SHOWREADME is incompatible with electron-builder’s shared install/uninstall page flow (breaks the build). Users can disable autostart via the tray menu → Settings → “Launch at login”, Task Manager → Startup, or regedit. | ON (matches Slack/Discord/Zoom convention) |
| macOS DMG | No installer-time hook (DMG is just a copy). Default OFF; after first successful launch, the app shows a non-blocking banner: “GAIA can start when you sign in. Enable in Settings.” User can dismiss or enable via in-app Settings → app.setLoginItemSettings({ openAtLogin: true }). | OFF (in-app prompt on second launch) |
| Linux DEB | postinst does NOT write autostart by default (Debian convention). Setting available in-app, writes ~/.config/autostart/gaia-agent-ui.desktop. | OFF (in-app setting) |
| Linux AppImage | No installer-time hook. In-app setting only. | OFF (in-app setting) |
~/.config/autostart. The tray-app pattern still works because the user can enable it on any platform — we just don’t surprise them on macOS or Linux.
§4.2 Versioning across install paths
GAIA ships via three install paths that all need to share a version number:| Where | What | Source |
|---|---|---|
src/gaia/version.py | Python package version | Hand-edited / bump-ui-version.mjs |
src/gaia/apps/webui/package.json | npm package version + Electron app version | Hand-edited / bump-ui-version.mjs |
| NSIS / DMG / DEB metadata | Desktop installer artifact version | Read from package.json at build time |
gaia init / gaia update checks | Backend version pin | Read from package.json at runtime by bin/gaia-ui.cjs |
src/gaia/version.py. installer/version/bump-ui-version.mjs propagates it to package.json. The Electron app reads package.json at runtime to know what backend version to install. The desktop installer build pipeline reads package.json to label artifacts.
A release tag v0.17.2 triggers:
version.pyis at0.17.2package.jsonis at0.17.2(kept in sync bybump-ui-version.mjs)- PyPI package
amd-gaia==0.17.2is published - npm package
@amd-gaia/[email protected]is published - NSIS / DMG / DEB artifacts at
gaia-0.17.2-<platform>.<ext>are published to GitHub Releases - The Electron app
0.17.2knows to install backendamd-gaia[ui]==0.17.2
bin/gaia-ui.cjs’s version-check logic catches it on next launch and triggers an upgrade.
§5 Uninstall (parity with install)
Three tiers, default to keeping user data.| Tier | What’s removed | Default? |
|---|---|---|
| 1. App-only | Electron app binaries, shortcuts, registry entries, auto-start hook | YES — default for all uninstall flows |
| 2. App + Python venv | Tier 1 + ~/.gaia/venv/ | Opt-in checkbox / --venv flag |
| 3. Purge | Tier 2 + ~/.gaia/chat/, ~/.gaia/documents/, ~/.gaia/electron-config.json, ~/.gaia/electron-install*.{log,json} | Opt-in checkbox / --purge flag |
--purge):
- Lemonade Server — installed via Lemonade’s own installer; treated as a separate package. Opt-in flag
--purge-lemonadeavailable for users who explicitly want it. - Models — live in Lemonade’s cache (
~/.cache/lemonade/). Opt-in flag--purge-models. uvbinary — system-wide tool that may be used by other apps.
§5.1 Per-platform implementation
| Platform | Default uninstall flow | Purge access |
|---|---|---|
| Windows NSIS | Standard “Add or Remove Programs” entry. NSIS uninstaller wizard with optional “Also remove user data” checkbox that maps to gaia uninstall --purge | Wizard checkbox + silent flag /PURGE=1 |
| macOS DMG | Drag the GAIA app from /Applications to the Trash. This is what users already know how to do — the most familiar Mac UX. User data in ~/.gaia/ is left behind (same as every other Mac app). | Documented one-liner in README, FAQ, and release notes: ~/.gaia/venv/bin/gaia uninstall --purge |
| Linux DEB | apt remove gaia-agent-ui → tier 1; apt purge gaia-agent-ui → tier 3. prerm script stops the GAIA Agent UI Electron process only (does NOT kill the user-launched gaia chat --ui backend, which the user may want to keep running). postrm handles purge cleanup. | Native apt purge |
| Linux AppImage | Delete the .AppImage file. User data orphaned (consistent with how all AppImages work). | ~/.gaia/venv/bin/gaia uninstall --purge from CLI |
§5.2 The unifying piece: gaia uninstall CLI
A single CLI command implements the actual cleanup logic. All four platforms delegate to it:
src/gaia/installer/uninstall_command.py. Already listed as a “future command” in the existing installer.mdx (lines 437–442); this plan implements it for real.
§6 Approach: ground-up using Lemonade as a reference
We are not vendoring Lemonade’s installer files. We are reading them, understanding what they do, and writing GAIA’s installer fresh — clean-room implementation against Lemonade’s architecture as a reference.Why ground-up over vendoring
| Vendor / fork | Ground-up | |
|---|---|---|
| Long-term maintenance | High — diverges from upstream | Low — clean code we own |
| Lemonade-specific cruft | Inherited (CMake, C++, lemond service) | Excluded |
| Format choices | Inherits Lemonade’s (MSI, PKG) | Optimal for our use case (NSIS, DMG) |
| Risk of upstream breakage | High | Zero |
| Code clarity | Low — unrelated patterns mixed in | High — only what GAIA needs |
What we learn from Lemonade (per-area)
| Area | Lemonade reference | What we learn |
|---|---|---|
| Branded installer wizard | src/cpp/installer/Product.wxs.in | Banner BMP layout (top + side), per-user install scope, ARP properties pattern |
| Custom auto-start property | Property Id="ADDTOSTARTUP" in WiX | NSIS equivalent: a Section with WriteRegStr HKCU |
| macOS notarization | .github/actions/build-macos-dmg/action.yml | Notarization flow with electron-builder, notarytool log fetching on failure |
| Debian packaging conventions | contrib/debian/ (whole tree) | control, rules, changelog.in template, prerm/postrm script structure, lintian-clean conventions |
| GitHub composite actions | .github/actions/* | Pattern for composite actions wrapping platform-specific build steps |
| SignPath integration | .signpath/policies/ | Free OSS code signing for Windows; policy file structure |
| Build env bootstrap | setup.sh | Template for “make sure the build environment is sane” script |
| Per-user vs per-machine | MSIINSTALLPERUSER property | Default to per-user (no UAC); allow per-machine via opt-in flag |
lemonade-server.preinst (Lemonade-specific systemd unit management), or launchpad-ppa workflow.
Maintenance philosophy
We do not auto-sync from Lemonade. If they change their installer significantly and we want to incorporate the change, that’s a manual review-and-port effort, no different from any other open-source reference. Documented ininstaller/README.md.
§7 Phases
The work is broken into nine phases (A–I). Phase A is the smallest, highest-value, and ships first. The rest can land in roughly the dependency order shown. No time estimates — these depend on review cycles, parallel work, and external dependencies (SignPath approval timeline) that are too variable to predict honestly.Phase A — Extract bin/gaia-ui install logic into shared module
The smallest, highest-value change. Lands as its own PR before any installer migration work. Fixes the primary user-facing bug (broken Squirrel install UX) immediately.
Deliverables:
- New module:
src/gaia/apps/webui/services/backend-installer.cjs- Pure CommonJS so it can be
require()’d from both contexts - Exports:
ensureUv(),installBackend(opts),ensureBackend(opts),getInstalledVersion(gaiaBin),findGaiaBin() - State machine (§3.2) persisted to
~/.gaia/electron-install-state.json - Pre-checks before starting:
- Disk space — at least 3 GB free at
~/.gaia/’s parent. Friendly dialog if insufficient. - Network — basic reachability check (HEAD to
https://astral.shor similar). Friendly “you appear to be offline” dialog with Retry / Quit if no network. - Existing partial install — read state file; if
partial, offer Resume or Restart from scratch.
- Disk space — at least 3 GB free at
- Logging — all output also written to
~/.gaia/electron-install.log. Error dialogs show the log path with a “Copy log path” button. - Progress callbacks — caller passes
onProgress(stage, percent, message)so the UI can stream updates without the module knowing about Electron
- Pure CommonJS so it can be
- Rename
bin/gaia-ui.mjs→bin/gaia-ui.cjs(§3.1). Updatepackage.jsonbinmap. - Refactor
bin/gaia-ui.cjsto import from the new shared module instead of duplicating logic - Refactor
main.cjs:- Remove the dead-end
findGaiaCommand()warning path - On
app.whenReady(), callensureBackend()from the new module - Show a progress dialog (borderless
BrowserWindowwith custom HTML) while the install runs - On failure, show a friendly error dialog with Retry / Manual install instructions / Quit buttons + log path
- Remove the dead-end
- Add
services/backend-installer-progress-dialog.cjs— owns the Electron-specific progress UI and IPC plumbing
bin/gaia-ui.cjsworks exactly asbin/gaia-ui.mjsdid- A fresh Windows machine with NO
gaiaon PATH can install the existing Squirrel.exeand on first launch, the Electron app installs the Python backend automatically with visible progress - The progress dialog updates as install steps complete
- Closing the install dialog mid-install: state is persisted; next launch resumes or restarts cleanly
- Insufficient disk space → friendly dialog before any install begins
- Offline → friendly dialog with Retry
- Failed install → error dialog with log path, retry option, manual fallback link
- All log output goes to
~/.gaia/electron-install.log - Existing
npm teststill passes
Phase B — Restructure scripts/ → installer/
Move existing build/install/release scripts from scripts/ into a focused installer/ tree. Folds in PR #341 (BOM removal, log location fix, permission fallback).
Deliverables:
- New top-level directory:
installer/installer/nsis/— Windows NSIS scripts and assetsinstaller/debian/— DEB packaging filesinstaller/macos/— DMG layout, entitlements,Info.plistinstaller/linux/—.desktopfile, AppImage assetsinstaller/scripts/— build orchestration scriptsinstaller/version/— version normalization helpersinstaller/README.md— how to build installers locally + Lemonade reference notes
- Move from
scripts/:build-ui-installer.{ps1,sh}→installer/scripts/install.{ps1,sh}→installer/scripts/(still referenced frominstaller.mdx)start-agent-ui.{ps1,sh}→installer/scripts/start-lemonade.{ps1,sh,bat}→installer/scripts/release-ui.mjs,bump-ui-version.mjs→installer/version/
- Fold in PR #341: BOM fix in
install.ps1, logger fallback to~/.gaia/gaia.log, PermissionError handling - Update all references in
package.json, CI workflows, docs - Delete the old entries from
scripts/
- All previously-working scripts still work from their new homes
- CI workflows still pass
grep -r "scripts/build-ui" . --include="*.yml" --include="*.json" --include="*.md*"returns no hits- PR #341’s three fixes are present in the new locations
Phase D — gaia uninstall CLI
Phase D ships before Phase C because Phase C’s NSIS uninstaller calls gaia uninstall for the optional purge flow. D is a Python-only change with no Electron dependencies and can land in parallel with B.
Deliverables:
- New module:
src/gaia/installer/uninstall_command.py - New CLI subcommand registered in
src/gaia/cli.py - Implements the full flag set from §5.2
- Confirmation prompts skippable via
--yesAND via stdin-not-a-tty detection (so silent uninstall flows work) - Cross-platform path handling via
pathlib - Unit tests in
tests/unit/installer/test_uninstall_command.pyusingpyfakefsto verify each tier removes the right files and leaves the right files alone - Update
installer.mdxto promotegaia uninstallfrom “future” to “implemented”
- All flag combinations work as documented
--dry-runshows accurate output- Tests cover happy path + each opt-in flag combination
- Linux DEB
postrmcan callgaia uninstall --purge --yesreliably - Windows NSIS uninstaller can call
gaia uninstall --purge --yesreliably
Phase C — Migrate electron-forge → electron-builder
Replaceforge.config.cjs with electron-builder’s build section. The existing Squirrel installer is removed entirely — no migration logic, no auto-uninstall of legacy installs. Existing v0.17.1 users uninstall manually via Apps & Features (release-notes step).
Why migrate:
- electron-forge’s only Windows maker is Squirrel, which produces a non-standard installer with no visible UI and no Start Menu entry
- electron-forge has no AppImage maker
- electron-forge can’t produce a DMG with custom layout
- electron-builder has first-class support for NSIS / DMG / DEB / AppImage and integrates with electron-updater
- electron-builder is the de-facto standard for Electron apps
- Remove
@electron-forge/*devDependencies fromsrc/gaia/apps/webui/package.json - Add
electron-builder(^26.4.0) andelectron-updater(^6.3.0) - Add a
"build"section topackage.json:appId: "ai.amd.gaia"productName: "GAIA Agent UI"directories.output: "dist-app"win.target: ['nsis']with NSIS-specific config (per-user install, custom installer pages, SignPath integration)mac.target: ['dmg']with DMG layoutlinux.target: ['deb', 'AppImage']with desktop file integration- File globs that exclude dev/test/source paths (replicate the current
forge.config.cjs.packagerConfig.ignorepatterns) - Code signing identity references via env vars
publish: { provider: 'github', owner: 'amd', repo: 'gaia' }
- New npm scripts:
package,package:win,package:mac,package:linux - Delete
forge.config.cjs - Remove
electron-squirrel-startupfromdependenciesand frommain.cjs - Move locale-pruning logic to
installer/scripts/after-pack.cjs(electron-builderafterPackhook) - Move 4-part → 3-part SemVer normalizer to
installer/version/normalize.mjs - NSIS installer template at
installer/nsis/installer.nsh—customInstall/customUnInstallhooks that write and remove the autostart HKCU Run key - No Squirrel migration code. Release notes document the manual uninstall step for v0.17.1 users.
npm run package:winproduces a working.exe(NSIS) on Windows that installs and launchesnpm run package:macproduces a working.dmgon macOS that mounts and installs via drag-to-Applicationsnpm run package:linuxproduces working.deband.AppImageartifacts on Linux- Locale pruning still strips Chromium translations (~45 MB savings)
- The 4-part → 3-part version normalization still works
- Existing GAIA UI launches cleanly (no missing
electron-squirrel-startuperrors)
Phase E — Installer assets and branding
Generate platform-specific installer assets that match GAIA’s visual identity. Asset generation only — visual design is delivered separately. Deliverables:installer/nsis/installer-banner.bmp— 150×57 BMP (NSIS top banner)installer/nsis/installer-sidebar.bmp— 164×314 BMP (NSIS sidebar)installer/nsis/icon.ico— multi-size.ico(16/32/48/256)installer/macos/icon.icns— macOS icon set (generated viaiconutil)installer/macos/dmg-background.png— DMG background image (660×400) with drag-to-Applications arrowinstaller/macos/entitlements.mac.plist— explicit list of entitlements:These are required because the app spawns child processes (uv,python,gaia). Without them, the signed-and-notarized app installs successfully but crashes on first launch when it tries to run the bootstrap.installer/macos/Info.plist— app metadata (bundle identifier, minimum macOS version, etc.)installer/linux/gaia-ui.desktop—.desktopfileinstaller/linux/gaia-ui.png— 512×512 icon used by AppImage and DEB- Tray-specific assets verified at this stage:
iconTemplate.png+[email protected]for macOS retinatray-icon-22.pngfor Linux- Confirm
icon.icoincludes 16/32 sizes for Windows system tray
- All installers display the new branding
- Icons render at all sizes without aliasing
lintianpasses on the.deb- A signed and notarized DMG installs and launches on a clean macOS Sonoma machine and successfully spawns the backend installer subprocess
Phase F — Auto-update via electron-updater
Deliverables:- Add
electron-updaterimport + configuration tomain.cjs - GitHub provider configuration:
{ provider: 'github', owner: 'amd', repo: 'gaia' }(works for public repos with no auth) - Provider URL is read from
app.config.jsonso swapping to R2 later is a one-line change - Update check on app launch (10-second delay) and every 4 hours
- Concurrent-check guard
- Event handlers for
update-available,download-progress,update-downloaded,error - “Restart to update” dialog using Electron’s native
dialog.showMessageBox - IPC channel
update:statusexposing the current state to the renderer - Frontend: small “update available — restart to apply” toast in the status bar
GAIA_DISABLE_UPDATE=1env var to skip all checks (CI / dev / corporate environments)
- Launching an old version with a new release published triggers download + restart prompt
- Failed update attempts are logged and surfaced gracefully (no crash)
- Update check respects offline mode
GAIA_DISABLE_UPDATE=1skips all checks- Manual smoke test: install version
0.17.2-rc.1, publish0.17.2-rc.2, verify the older app picks it up
Phase G — CI/CD workflow (build-installers.yml)
GitHub Actions workflow that builds and uploads installers on tag push. Integrates with the existing 431-line publish.yml rather than replacing it.
Deliverables:
- New workflow:
.github/workflows/build-installers.yml - Matrix:
windows-latest→ NSIS.exemacos-latest(Apple Silicon) →.dmgubuntu-latest→.deb+.AppImage
- Each job:
- Restores
~/.cache/electronand~/.cache/electron-builder - Installs Node 20
- Runs
npm ci && npm run package:<platform>fromsrc/gaia/apps/webui/ - Code-signs (Phase H)
- Uploads
.blockmapfiles alongside installers (delta updates) - Uploads to GitHub Releases via
softprops/action-gh-release@v2
- Restores
- Composite action
prepare-gaia-debian-buildadapted from Lemonade’s pattern (clean-room rewrite) - Workflow can run manually via
workflow_dispatchfor testing - Integration with
publish.yml:build-installers.ymlis invoked as a job after the validate step. Artifacts feed into the existing GitHub Release publish step. Single approval gate frompublish.ymlcovers the whole release including installers.
- Pushing a tag
v0.17.2-rc.1triggers the workflow and produces all four artifacts - Artifacts appear on the GitHub Release page
- Each installer can be downloaded and run successfully on the target platform
- Subsequent tag pushes produce delta
.blockmapfiles - The single
publish.ymlapproval gate still works end-to-end
Phase I — Documentation
Documentation is a first-class deliverable. Because the desktop installer is the primary install path for non-developers, the docs need to lead with it, not bury it. Deliverables:- Quickstart rewrite (
docs/quickstart.mdx) — lead with the desktop installer download. CLI/pip/npm install paths are documented as “developer alternatives” further down. - Download guide (
docs/guides/install.mdx— new): per-platform install instructions, screenshots of the wizard, what to expect during first-run setup, how to enable auto-start - Troubleshooting guide (
docs/reference/install-troubleshooting.mdx— new):- “First-launch backend install fails” — common causes (firewall, proxy, disk space, antivirus)
- “GAIA Agent UI won’t start” — log file locations, how to attach to bug report
- “Update isn’t being detected” — manual update check, version mismatch
- “Uninstall doesn’t remove all data” —
gaia uninstall --purgeinstructions - “macOS Gatekeeper blocks the app” — how to bypass for unsigned builds
- “Windows SmartScreen blocks the installer” — how to bypass for unsigned builds
- FAQ updates (
docs/reference/faq.mdx):- Where are my chats stored?
- How do I uninstall?
- Why is the first launch slow?
- Does GAIA send my data anywhere?
- How do I install on a machine without internet?
- README badges — download buttons for Win/Mac/Linux pointing at the latest GitHub Release
- Release notes template — must include the manual uninstall step for v0.17.1 Squirrel users
- Content for the future
amd-gaia.ai/downloadpage — Markdown snippets the website project can consume (asset URLs, install commands, screenshots). The actual website page is a separate project but blocks on this content existing. - Update
installer.mdx(the existing CLI install plan) to cross-reference the new desktop installer plan and clarify the audience split (CLI = developers, desktop = end users)
- New user can navigate from
amd-gaia.ai→ “Download” → installer in under 30 seconds - Every install failure mode in §13 has a corresponding troubleshooting entry
- Quickstart no longer leads with
pip install - README has working download badges
- Release notes for v0.17.2 include the Squirrel manual-uninstall step
Phase H — Code signing setup
Apply for SignPath OSS for Windows; configure Apple Developer ID for macOS. Partially blocked on AMD admin decisions (Apple Developer ID) and external SignPath approval. Deliverables:- SignPath OSS application submitted (one-time, ~1 week external approval)
.signpath/policies/gaia.policyconfigured for the Windows NSIS installer- GitHub secret
SIGNPATH_API_TOKENset - Apple Developer ID configuration:
- Decision needed (Open Question §1): AMD-owned or personal cert
- GitHub secrets
APPLE_ID,APPLE_APP_SPECIFIC_PASSWORD,APPLE_TEAM_ID,CSC_LINK,CSC_KEY_PASSWORDconfigured
build-installers.ymlupdated to invoke signing on the appropriate platforms- Until both are set up: artifacts are unsigned, troubleshooting guide documents the SmartScreen / Gatekeeper bypass steps
- A signed
.exeshows the AMD publisher in the SmartScreen prompt instead of “Unknown publisher” - A signed and notarized
.dmgopens without the Gatekeeper warning on a fresh macOS install - Both signing flows are documented in
docs/deployment/
§8 File-by-file change plan
New files
Modified files
Deleted files
Removed dependencies
@electron-forge/cli@electron-forge/maker-deb@electron-forge/maker-squirrelelectron-squirrel-startup
Added dependencies
electron-builder(^26.4.0)electron-updater(^6.3.0)
§9 Output matrix
| Platform | Format | Auto-Update | Shortcut | Delta Updates | Code Signed | Auto-start | Size (est.) |
|---|---|---|---|---|---|---|---|
| Windows x64 | .exe (NSIS) | Yes | Start Menu + Desktop | Yes (.blockmap) | Yes (SignPath) | Unconditional via customInstall (default ON, disable via tray menu / Task Manager) | ~85 MB |
| macOS Apple Silicon | .dmg | Yes | /Applications | Yes (.blockmap) | Yes (Apple Developer ID + notarized) | In-app prompt on second launch (default OFF) | ~95 MB |
| Linux x64 | .AppImage | Yes | Manual .desktop | Yes (.blockmap) | GPG-signed only | In-app setting (default OFF) | ~90 MB |
| Linux x64 | .deb | No (apt) | Yes (.desktop) | No | GPG-signed | In-app setting (default OFF) | ~85 MB |
§10 Open questions
1. Apple Developer ID ownership
Question: AMD-ownedDeveloper ID Application: Advanced Micro Devices, Inc. or personal cert?
Recommendation: AMD-owned long-term. Until decided, ship unsigned .dmg with Gatekeeper bypass instructions in the troubleshooting guide.
Action needed: Maintainer escalation to AMD legal/IT.
2. SignPath OSS application timing
Question: When do we apply for SignPath OSS? Recommendation: Apply early, in parallel with Phase A–C work. Approval typically takes ~1 week. If approval slips, Phase G ships unsigned and we backfill in Phase H. Action needed: Submit application at https://signpath.io/solutions/open-source-community.3. NSIS license screen content
Question: Show the full MIT license, or a short summary + link to GitHub? Recommendation: Full MIT license text (under 200 lines). Standard wizard convention.4. State recovery model for partial installs
Question: When the Electron app launches and finds apartial install state, what’s the resume strategy?
Options:
- A. Restart from scratch every time (simplest, slowest, safest)
- B. Resume from the last completed stage (faster, more code, more failure modes)
- C. Verify existing artifacts and only re-run missing steps (best UX, most code, hardest to test)
uv venv and pip install is idempotent and only adds a few minutes. The complexity of B/C isn’t worth it for the first release. Revisit in v0.17.3+ if user feedback says first-launch retries are too slow.
5. npm-vs-desktop dual-install detection
Question: What if a user installs via bothnpm install -g @amd-gaia/agent-ui AND the desktop installer?
Options:
- A. Don’t detect, both work, two icons in the user’s launcher
- B. Detect via
~/.gaia/install-source.json; warn user; prefer the desktop install - C. Refuse to install if the other path is detected
~/.gaia/venv/, so they don’t actually conflict — they just produce two app icons. Document the dual-install scenario in the FAQ as a known edge case.
6. Test machine availability for cross-platform validation
Question: Do we have access to clean Windows, macOS, and Linux machines for manual testing? Action needed: Confirm before Phase G smoke testing.§11 Acceptance criteria
User-facing
A non-developer user can:- Visit GitHub Releases (or eventually
amd-gaia.ai/download), see Windows / macOS / Linux assets, click their platform - Download a single file under 100 MB
- Run the file (NSIS wizard on Windows, drag-to-Applications on Mac,
apt installon Debian) without ever opening a terminal - Get a Start Menu / Applications /
.desktopshortcut - Launch the app from the shortcut
- See a clear progress dialog while the Python backend (uv + venv + amd-gaia + Lemonade + minimal model) installs automatically on first launch
- If the install fails, see an error dialog with a Retry button, a “Manual install instructions” link, and a log file path
- Send their first prompt within 10 minutes of starting the download (assuming minimal profile and reasonable connection)
- On Windows, get auto-start at sign-in configured automatically (default ON via
customInstall, can be disabled via Task Manager → Startup or the in-app tray menu) - Get a notification when a new version is available
- Click “Restart to update” and have the app update without losing chat history
- Uninstall via the OS-native flow (Add or Remove Programs / drag to Trash /
apt remove) and have the app cleanly removed - Optionally remove all GAIA data with one extra checkbox or
--purgeflag - Find every install failure mode in the troubleshooting guide
Internal
- All artifacts (
.exe,.dmg,.deb,.AppImage) build successfully in CI on every release tag - All artifacts are code-signed by the time the desktop installer is announced as the recommended path
- The Electron app boots without
electron-squirrel-startup - The build pipeline produces
.blockmapfiles for delta updates - Auto-update successfully delivers a new version end-to-end in a manual smoke test
-
bin/gaia-ui.cjsandmain.cjsshare a singleservices/backend-installer.cjsmodule — no duplicate install logic -
installer/README.mddocuments how to build each artifact locally and references the Lemonade patterns -
gaia uninstallworks with all flag combinations and is unit-tested - All documentation deliverables in Phase I are complete
- The cross-platform test matrix (§11.1) passes on a fresh machine for each platform
§11.1 Cross-platform test matrix
Every release candidate must pass this matrix on a clean machine before announcement:| Test | Win NSIS | macOS DMG | Linux DEB | Linux AppImage |
|---|---|---|---|---|
Install on clean machine (no Python, no gaia) | ✅ required | ✅ required | ✅ required | ✅ required |
| First-run backend install completes successfully | ✅ required | ✅ required | ✅ required | ✅ required |
| First prompt returns a sensible response | ✅ required | ✅ required | ✅ required | ✅ required |
| Auto-start behaves per platform default (§4.1) | ✅ required | ✅ required | ✅ required | ✅ required |
| Update from previous version triggers and completes | ✅ required | ✅ required | N/A (apt) | ✅ required |
| Uninstall (tier 1) cleanly removes the app | ✅ required | ✅ required | ✅ required | ✅ required |
| Uninstall (purge) cleanly removes app + user data | ✅ required | ✅ required | ✅ required | ✅ required |
| Crash recovery: kill mid-install, relaunch resumes/restarts | ✅ required | ✅ required | ✅ required | ✅ required |
| Offline first-launch shows friendly error | ✅ required | ✅ required | ✅ required | ✅ required |
| Insufficient disk space shows friendly error | ✅ required | ✅ required | ✅ required | ✅ required |
| Signed installer shows AMD publisher | ✅ Phase H | ✅ Phase H | N/A | N/A |
§12 Success metrics (post-launch)
| Metric | Target |
|---|---|
| Time from download → first prompt (minimal profile, 100 Mbps) | < 10 minutes |
First-run backend install success rate (clean machine, no gaia on PATH) | > 90% |
| First-prompt completion rate (% of installs that successfully send their first prompt) | > 80% |
| Installer size per platform | < 100 MB |
| Auto-update success rate (rolling 30-day) | > 95% |
| User-reported install failures (first month) | < 1% of downloads |
| Tray-app auto-start opt-in retention rate (Windows) | > 70% (fewer than 30% disable it) |
§13 Risks and mitigations
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| electron-builder migration breaks the dev build | Medium | High | Ship Phase A first (independently mergeable); test Phase C carefully on all 3 platforms before merge |
| Removing Squirrel installer leaves v0.17.1 users orphaned | High | Low | Release notes call out the manual uninstall step; small user base affected; recoverable |
| SignPath OSS approval delayed | Medium | High | Apply early in parallel with Phase A–C; ship Phase G unsigned with troubleshooting docs; sign retroactively when approved |
| macOS notarization fails intermittently | Medium | Medium | Add notarytool log fetching to the build action (Lemonade pattern); retry logic in the workflow |
| First-run backend install fails on a user’s machine (proxy, firewall, no internet) | Medium | High | Pre-checks (network, disk space) before install; detailed error messages; troubleshooting guide; the desktop installer is the primary path so reliability is critical |
| User antivirus flags unsigned NSIS as a threat | High | High | Sign as soon as SignPath is approved; document SmartScreen bypass in troubleshooting; consider delaying public announcement until signed |
| Backend install fails due to missing C++ build tools (rare with uv prebuilt wheels but possible) | Low | Medium | Document recommendation to install Visual Studio Build Tools in troubleshooting; uv typically uses prebuilt wheels |
| Disk space exhaustion mid-install on small SSDs | Medium | Medium | Pre-check 3 GB free at install start; friendly dialog if insufficient |
| Auto-start ON default upsets some Windows users | Low | Low | In-app tray menu lets users disable it; Task Manager → Startup is the Windows-standard opt-out; matches Slack/Discord/Zoom convention |
| User installs via npm AND via desktop installer simultaneously | Medium | Low | Both share ~/.gaia/venv/; they don’t conflict; documented in FAQ |
| NSIS scripting learning curve blocks Phase C | Medium | Medium | Allocate explicit learning time; reference electron-builder examples + community templates; NSIS is well-documented |
| macOS hardened runtime entitlements wrong → app crashes on first launch when spawning child processes | Medium | High | Explicit entitlements list in Phase E; manual cross-platform test required (§11.1) before signing rollout |
electron-updater GitHub provider hits API rate limits at scale | Low | Low | Cached metadata; switch to R2 when website lands |
| macOS Sonoma point releases break notarization | Low | Medium | Pin notarytool version; monitor Apple Developer forums |
electron-updater requires non-draft releases with assets uploaded before publish | Medium | Medium | Workflow ordering: build → upload → publish in publish.yml |
| Documentation drifts behind code changes | Medium | Medium | Phase I documentation is part of release acceptance criteria; PR template requires docs updates |
§14 Implementation order
Dependency-only ordering. No time estimates — actual cadence depends on review cycles, parallel work, SignPath external approval, and access to test machines.§15 References
- Issue: #530 — Desktop installer: one-click install with NSIS, DMG, AppImage + auto-update
- Companion plan:
installer.mdx— Python CLI install,gaia init,gaia update, and (post-Phase D)gaia uninstall - Related issue (left as polish, not folded in): #597 — First-run setup wizard
- Related stale PR (folded into Phase B): #341 — Fix installer BOM and logger permissions
- Lemonade installer source (reference architecture): lemonade-sdk/lemonade
- electron-builder docs: https://www.electron.build/
- electron-updater docs: https://www.electron.build/auto-update
- NSIS docs: https://nsis.sourceforge.io/Docs/
- SignPath OSS program: https://signpath.io/solutions/open-source-community
- Apple notarization: https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution