update-rs gives any Rust application a built-in "update yourself" command —
no installer, package manager, or external updater process required. It
downloads the newest release for the current platform, replaces the running
binary on disk, and relaunches into the new version.
This crate was extracted from the Sierra Softworks
Git-Tool project, where it powers
the gt update command. The design is described in
Building self-updating applications.
How it works: the three-phase update
A running executable can't reliably overwrite itself — on Windows the running image is locked, and on every platform you don't want to pull the binary out from under a process that's mid-flight. So the update runs across three phases, each executing from a different binary and relaunching the next:
- Prepare — the running application downloads the new release to a
temporary file next to it (
yourapp-<tag>in the temp directory), verifies it against the SHA-256 digest GitHub reports for the asset, marks it executable on Unix, then launches that temporary binary. - Replace — the temporary binary deletes the original application file and copies itself over it, retrying with a short backoff while the old process exits, then launches the freshly replaced original.
- Cleanup — the updated original deletes the leftover temporary binary and returns control to your application.
The phases are threaded together by relaunching the binary with a flag
(update_rs::RESUME_FLAG) followed by a serialized UpdateState. Your main()
detects the flag and hands control back to the library.
running app ──prepare──▶ temp binary ──replace──▶ updated app ──cleanup──▶ done
(download) (overwrite original) (remove temp file)
Features
- Self-relaunching three-phase updater that works even when the OS won't let a process overwrite its own image.
- Pluggable release sources — implement the single
Sourcetrait, or use the built-inGitHubSource. - Glob-based asset selection — point a pattern at your release assets and
name them however you like; there's no required naming scheme, with
naminghelpers for the common Go and Rust conventions. - SemVer-aware release listing, with a configurable tag prefix (
v→1.2.3). - Verified downloads — when GitHub reports a SHA-256 digest for an asset, the download is checked against it before the binary is swapped in, so a corrupted or tampered artifact is rejected.
- Friendly errors — every failure carries a description and actionable advice,
powered by
human-errors. - Async (Tokio) and cross-platform (Windows, Linux, macOS), with first-class handling of the awkward Windows cases.
Quick start
cargo add update-rs
Build an UpdateManager around a Source, detect the resume flag before any
other argument parsing, and otherwise offer the newest release:
use ;
async
Two parts of the contract are load-bearing:
- Detect
RESUME_FLAGbefore any other CLI parsing. The library relaunches your binary with this flag, and the JSON value that follows it must be passed straight toresume_from_arg. - Exit immediately when
updateorresume_from_argreturnsOk(true). A follow-up phase has been launched in a separate process, and it needs your process to release the binary so it can replace it.
Windows: avoiding UAC and "Error 740"
Windows has a legacy installer-detection heuristic that auto-requests UAC
elevation for any executable whose file name contains tokens like update,
setup, install, or patch. Because update-rs downloads the new release to
a temporary file named like yourapp-<tag>.exe — and because updater binaries
are often named similarly — the relaunched process can trigger an elevation
prompt. If elevation is unavailable or declined, CreateProcess fails with
ERROR_ELEVATION_REQUIRED (Win32 error 740) and the update breaks.
The fix is to ship your binary with an application manifest declaring
requestedExecutionLevel level="asInvoker", which opts the binary out of the
heuristic so it runs with the caller's token and never prompts. Since a library
crate can't embed a manifest into your executable, this is your binary's
responsibility — but update-rs ships ready-to-copy templates to make it a
two-minute job:
examples/windows-manifest/app.exe.manifest— the manifest (also enables long-path support and a UTF-8 active code page).examples/windows-manifest/build.rs— abuild.rsthat embeds it withwinresource.examples/windows-manifest/README.md— step-by-step instructions.
The same heuristic even affects Cargo's own test binaries (
yourapp-<hash>.exe). This repository sets__COMPAT_LAYER=RunAsInvokerin.cargo/config.tomlsocargo testcan launch them.
Setting up your release pipeline (consumer side)
update-rs's own release.yml only publishes
the library to crates.io. The application that consumes it is responsible for
publishing the platform binaries that GitHubSource downloads. To make that work:
-
Publish one binary per platform as a GitHub Release asset. You choose the naming scheme — just make sure the glob pattern you pass to
GitHubSource::newmatches the right asset on each platform. Thenaminghelpers cover two common conventions out of the box:naming::go("yourapp")→yourapp-linux-amd64,yourapp-windows-amd64.exe,yourapp-darwin-arm64, ... (Go'sGOOS/GOARCHnames);naming::rust("yourapp")→yourapp-x86_64-unknown-linux-gnu,yourapp-x86_64-pc-windows-msvc.exe, ... (the Rust target triple).
Or pass any glob yourself, e.g.
format!("yourapp-{OS}-{ARCH}{EXE_SUFFIX}")with [std::env::consts], or"*-linux-amd64". -
Tag releases as
vX.Y.Z(or whatever you pass towith_release_tag_prefix). Tags that don't parse as a SemVer version after the prefix is stripped are silently ignored.
The release pipeline guide
has a complete, copy-pasteable GitHub Actions workflow that builds a binary per
target and uploads it as <name>-<target-triple>[.exe] (matched by
naming::rust), along with the matching GitHubSource configuration. Git-Tool's
release workflow
is a worked example of the Go-style naming naming::go matches.
Documentation
- API reference on docs.rs.
- Guides and walkthroughs on the documentation website.
License
Licensed under the MIT License.
Copyright © Sierra Softworks.