gpui-updater 0.4.1

Observable updater adapter for official GPUI
Documentation

gpui-updater

crates.io docs.rs CI

Cross-platform self-update for GPUI desktop apps, hosted on GitHub Releases, static JSON manifests, or Sparkle appcasts, and on macOS optionally driven by Sparkle itself.

GPUI ships no updater of its own, and Zed's auto_update crate is GPL-licensed and wired to Zed's private update server. gpui-updater is an independent, MIT/Apache implementation of the same idea: check a release source, download the platform artifact, verify it, and swap it into place.

Features

Feature What it does Guide
Update sources GitHub Releases, static JSON manifests on S3, R2, or any CDN, and Sparkle or WinSparkle appcasts. A fallback source tries a second host; implement UpdateSource for your own service. source
Verification Size, SHA-256, minisign and Sparkle Ed25519 signatures, version-bound signatures, key rotation, and signed manifests that expire. On macOS, the bundle version and code-signing team; on Windows, the Authenticode publisher. verify
Downloads HTTPS only, into a private directory. Retries resume with HTTP Range, a verified earlier download is reused, and a download can be cancelled. Proxies and request headers are configurable. Retries, proxies
Delta updates Optional delta feature: per-file bsdiff deltas, chained across releases, with a fallback to the full download. On OpenLogi's macOS releases, a delta was 7–12% of the full download. delta
Install macOS .dmg; Linux .tar.gz, app directories, and AppImages; Windows bare .exe and .msi. install
Location and ownership Rejects App Translocation, read-only, and unwritable install locations before downloading, and detects installs that Homebrew, apt, Flatpak, Snap, Nix, Scoop, winget, or an app store owns. Location, ownership
Errors Every failure carries an ErrorKind, so the app can choose what to tell the user. ErrorKind
GPUI entity An observable Entity<Updater> that runs the work on the background executor. There is no background polling: the app triggers each check. gpui_updater
Sparkle driver Hands the whole macOS flow to Sparkle 2, behind the same entity and status. gpui_updater_sparkle

Crates

Crate Use it for Rust
gpui-updater-core The blocking engine, without GPUI 1.85
gpui-updater Apps on registry gpui ^0.2.2 1.98
gpui-updater-pre Apps on gpui-pre ^0.3.4 1.98
gpui-updater-sparkle Sparkle 2 on macOS, next to either adapter 1.85

The core and the two adapters are alternatives, not dependencies to combine; gpui-updater-sparkle is additive. Both adapters re-export the entire core API, and only the adapters export Updater. The Rust versions are tested support floors (1.85.0 and 1.98.1), not a claim that older compilers fail.

Installation

[dependencies]
# Core only (blocking engine, no GPUI):
gpui-updater-core = "0.4"

# OR official GPUI (Entity<Updater>):
gpui = "0.2.2"
gpui-updater = "0.4"

# OR gpui-pre (keep existing gpui_updater imports via an alias):
gpui = { package = "gpui-pre", version = "0.3.4" }
gpui-updater = { package = "gpui-updater-pre", version = "0.4" }

# Optional, next to either adapter: hand the macOS flow to Sparkle 2.
gpui-updater-sparkle = "0.4"

Add features = ["delta"] to the core or adapter dependency for delta updates. Without the alias, pre imports use gpui_updater_pre.

Quick start

In a GPUI app, build the entity once, observe it, and drive it from a button:

use gpui_updater::{EngineConfig, GitHubSource, UpdateStatus, Updater, Version};

let updater = cx.new(|cx| {
    Updater::new(
        GitHubSource::new("AprilNEA", "OpenLogi")
            .asset_contains("macos")
            .asset_contains(".dmg")
            .with_checksums("SHA256SUMS"),
        EngineConfig::new(Version::parse(env!("CARGO_PKG_VERSION")).unwrap()),
        cx,
    )
});
cx.observe(&updater, |_, _, cx| cx.notify()).detach();

// In the "Check for updates" button handler:
updater.update(cx, |updater, cx| match updater.status() {
    UpdateStatus::Available(_) => updater.download_and_install(cx),
    UpdateStatus::Downloading { .. } => updater.cancel(cx),
    UpdateStatus::Staged(_) => updater.restart(cx),
    _ => updater.check(cx),
});

Without GPUI, the core runs the same steps as blocking calls:

use gpui_updater_core::{EngineConfig, GitHubSource, UpdateEngine, Version};

let engine = UpdateEngine::new(
    GitHubSource::new("AprilNEA", "OpenLogi").with_checksums("SHA256SUMS"),
    EngineConfig::new(Version::parse(env!("CARGO_PKG_VERSION"))?),
);
if let Some(release) = engine.check()? {
    let artifact = engine.download(&release, |done, total| { /* progress */ })?;
    engine.install(&artifact)?;
}

The gpui_updater documentation covers status values, errors, cancelling, and the Sparkle driver. The gpui_updater_core documentation lists a guide for each topic.

Platform notes

Platform Artifacts Code signature check Notes
macOS .dmg Bundle version and code-signing team A standard user cannot replace an app in /Applications; the engine reports ErrorKind::Permission before downloading. Sparkle's installer handles this case.
Linux .tar.gz, app directories, .AppImage None: sign the artifacts Set EngineConfig::install_root for an app directory.
Windows bare .exe, .msi Authenticode publisher Call UpdateEngine::remove_leftovers at startup. Deltas are not supported.

The install guide has the details. The adapters need their GPUI backend's native toolchain, including Xcode and Metal on macOS; the core needs neither GPUI nor a graphics toolchain.

Known issue: on Linux with registry gpui 0.2.2, libc 0.2.190 removed ENOATTR, which breaks xattr 0.2.3 (pulled in by gpui_http_client → zed-async-tar). If a fresh lockfile picks that libc, hold it back with cargo update -p libc --precise 0.2.189. gpui-pre is not affected.

Upgrading

MIGRATING.md lists the breaking changes of each release: from 0.3, from 0.2, from 0.1, and from 0.0.x. The releases page lists what each release adds.

Contributing

CONTRIBUTING.md lists the checks to run, the release order, and where documentation lives.

License

Licensed under either of

at your option.