update_rs/lib.rs
1//! Self-contained, in-place self-updates for Rust applications.
2//!
3//! `update-rs` lets a Rust application replace its own binary on disk with a
4//! newer release and relaunch into it — without an installer, package manager,
5//! or external updater process. It was extracted from the Sierra Softworks
6//! [Git-Tool](https://github.com/SierraSoftworks/git-tool) project, where it
7//! powers the `gt update` command.
8//!
9//! # The three-phase update
10//!
11//! A running executable can't reliably overwrite itself (Windows holds an
12//! exclusive lock on a running image), so the update is performed across three
13//! phases, each running from a *different* binary and relaunching the next:
14//!
15//! 1. **Prepare** — the running application downloads the new release to a
16//! temporary file next to it, marks it executable (on Unix), and launches
17//! that temporary binary to perform the next phase.
18//! 2. **Replace** — the temporary binary deletes the original application file
19//! and copies itself over it (retrying while the old process exits), then
20//! launches the freshly replaced original.
21//! 3. **Cleanup** — the updated original deletes the leftover temporary binary
22//! and returns control to the application.
23//!
24//! The phases are threaded together by relaunching the binary with
25//! [`RESUME_FLAG`] followed by a serialized [`UpdateState`]. This design is
26//! described in more detail in
27//! [*Building self-updating applications*](https://sierrasoftworks.com/2019/10/15/app-updates/#cleanup).
28//!
29//! # Quick start
30//!
31//! Build an [`UpdateManager`] around a [`Source`] (the crate ships
32//! [`GitHubSource`]), detect the [`RESUME_FLAG`] **before** any other argument
33//! parsing, and otherwise offer the newest release. The asset to download is
34//! chosen by a glob pattern; the [`naming`] helpers build one for the current
35//! platform.
36//!
37//! ```no_run
38//! use update_rs::{naming, GitHubSource, Release, UpdateManager, RESUME_FLAG};
39//!
40//! #[tokio::main]
41//! async fn main() -> Result<(), update_rs::Error> {
42//! let manager = UpdateManager::new(
43//! // e.g. matches "yourapp-linux-amd64", "yourapp-windows-amd64.exe", ...
44//! GitHubSource::new("yourorg/yourapp", naming::go("yourapp"))
45//! .with_release_tag_prefix("v"), // strips the leading v in vX.Y.Z tags
46//! );
47//!
48//! // The updater relaunches your application between phases, passing the
49//! // serialized update state after `RESUME_FLAG`. Detect it first and hand
50//! // control back to the library.
51//! let args: Vec<String> = std::env::args().collect();
52//! if let Some(i) = args.iter().position(|a| a == RESUME_FLAG) {
53//! if manager.resume_from_arg(&args[i + 1]).await? {
54//! return Ok(()); // a phase was launched; exit so it can take over
55//! }
56//! }
57//!
58//! // Otherwise, look for the newest release with a binary for this platform.
59//! let releases = manager.get_releases().await?;
60//! let latest = Release::get_latest(releases.iter().filter(|r| r.get_variant().is_some()));
61//! if let Some(latest) = latest {
62//! if manager.update(latest).await? {
63//! println!("Shutting down to complete the update.");
64//! return Ok(()); // exit promptly so the new binary can take over
65//! }
66//! }
67//!
68//! // ... your normal application logic ...
69//! Ok(())
70//! }
71//! ```
72//!
73//! # Selecting the release asset
74//!
75//! [`GitHubSource::new`] takes a glob pattern (`*` and `?` wildcards) that is
76//! matched against each release's asset file names, so your project can name its
77//! assets however it likes. Build the pattern by hand —
78//! `format!("yourapp-{OS}-{ARCH}{EXE_SUFFIX}")` using [`std::env::consts`] — or
79//! with a [`naming`] helper: [`naming::go`] for Go-style names
80//! (`yourapp-linux-amd64`) or [`naming::rust`] for the Rust target triple
81//! (`yourapp-x86_64-unknown-linux-gnu`).
82//!
83//! # Windows: avoiding UAC / Error 740
84//!
85//! Because the temporary binary is named like `yourapp-<tag>.exe`, Windows'
86//! installer-detection heuristic can decide it's an installer and demand UAC
87//! elevation — which fails the relaunch with `ERROR_ELEVATION_REQUIRED`
88//! (Win32 error 740). The fix is to ship your binary with an `asInvoker`
89//! application manifest. This is a property of the consuming *binary* (a library
90//! can't embed a manifest), so `update-rs` ships a ready-to-copy `build.rs` and
91//! manifest template under `examples/windows-manifest/`; see the project README
92//! for details.
93
94mod cmd;
95mod fs;
96mod glob;
97mod manager;
98pub mod naming;
99mod release;
100mod source;
101mod state;
102
103pub use human_errors::Error;
104pub use manager::UpdateManager;
105pub use release::{Release, ReleaseVariant};
106pub use source::{GitHubSource, Source};
107pub use state::{UpdatePhase, UpdateState};
108
109/// The command-line flag the library uses to relaunch the consuming binary
110/// between update phases.
111///
112/// A consuming `main()` **must** detect this flag (before any other argument
113/// parsing), pass the JSON value that follows it to
114/// [`UpdateManager::resume_from_arg`], and exit immediately if it returns
115/// `Ok(true)` — so that the next phase's process can take ownership of the
116/// binary.
117pub const RESUME_FLAG: &str = "--update-resume-internal";
118
119/// The Rust target triple this crate was compiled for (e.g.
120/// `x86_64-unknown-linux-gnu`), captured at build time. Used by
121/// [`naming::rust`] to build release asset names.
122pub const TARGET: &str = env!("UPDATE_RS_TARGET");