Skip to content

PLATFORMS

Quynn Bell edited this page Aug 24, 2026 · 1 revision

Platform detection, installers, and elevation

How the updater picks the right file for a machine, and what it does with it once verification passes.

Artifact selection

Picking an artifact happens at runtime, on the user's machine, based on OS, CPU architecture, and (on Linux) how the app was installed in the first place. A Debian box wants the .deb. A machine running as an AppImage wants the new AppImage, regardless of distro.

Platform Preferred order
Windows installer .exe, .msi, .zip
Debian / Ubuntu .deb, AppImage, tarball
Fedora / RHEL / SUSE .rpm, AppImage, tarball
Running as an AppImage AppImage, tarball
Other Linux AppImage, tarball, then native packages
macOS .dmg, .pkg, .zip, tarball

If a release doesn't publish a format yet, selection falls through to the next one on the list. An app shipping only .deb today keeps working unchanged when it adds .rpm and .AppImage later - no updater change needed.

Override this with assets.preference (global) or assets.linux.preference (OS-specific) in config, or force one exact asset with assets.pattern, a regex. See examples/config.yaml for the full list of settings.

Installers by platform

Windows

Handles Inno Setup and NSIS installers, MSI packages, and zips. Silent flags default to Inno Setup's (/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-), since that's what most small Windows apps ship, but the installer family is detected from the file itself where possible, so an NSIS installer gets NSIS flags (/S) instead. Override with install.installer_args if your installer needs something different.

MSI exit code 3010 ("success, reboot required") and 1641 count as success, not failure.

Linux

Handles .deb, .rpm, AppImage, and tarballs. Packages install through the system package manager (dpkg/apt, rpm/dnf) rather than by unpacking, so the package database stays consistent and the app stays uninstallable through normal means afterward. Only .deb is in wide production use today, but .rpm and AppImage support needs no updater change to start working - publish the asset and platform detection picks it up.

macOS

Handles .dmg, .pkg, and zipped .app bundles. A .dmg is mounted, the .app inside is copied into place, and the image is detached. A .pkg is handed to the system installer.

Downloaded bundles carry the com.apple.quarantine attribute, which macOS uses to show a scary warning or refuse to launch. That attribute is cleared once the bundle is in place - safe to do only because the artifact's signature was already verified before this code ever runs it (see SECURITY.md §1).

Archive safety

Every archive is attacker-controlled input until its signature is verified, and a verified archive can still be malformed. Before extraction the updater rejects absolute paths, path traversal (../..), and symlinks resolving outside the destination. Extraction always goes to a scratch directory first and is copied into place only once it completes, so a corrupt archive can't leave a half-replaced install. Details: SECURITY.md §5.

Silent vs. interactive installs

Two modes, both first-class:

  • silent (default) - unattended, no windows, no prompts. Right when the calling app already asked the user.
  • interactive - the real vendor installer UI runs and the user clicks through it. Right when a publisher wants users to see licence terms or pick components.

Set with install.mode.

Elevation

The updater never asks for admin rights speculatively. It checks whether the install directory is writable by the current user, and only re-launches elevated when it isn't - a per-user install updates silently, a Program Files install prompts once, exactly when needed.

  • Windows: ShellExecuteW with the runas verb (the UAC prompt)
  • Linux: pkexec in a desktop session, sudo on a terminal
  • No elevation path available (headless session, no pkexec/sudo): reported as an error rather than hanging on a password prompt nobody can answer

Set with install.elevate: auto | always | never. Full reasoning: SECURITY.md §4.

Stopping and relaunching the app

This is most of what separates a working updater from one that corrupts an install. The sequence is always: ask the process to exit politely, wait, escalate if needed, confirm the file locks are actually gone, install, then relaunch. Skipping "wait until the executable is unlocked" is the classic Windows failure mode where install fails because the old binary is still mapped into memory.

Controlled by process.calling_pid (usually passed as --calling-pid), process.stop_timeout, and process.relaunch.

See also

Clone this wiki locally