How tuicr Handles Self-Updates and Version Management Across Package Managers
The tuicr update mechanism detects how the binary was originally installed—Cargo, direct download, Homebrew, Nix, or other package managers—then routes version checks and updates through the appropriate channel, using Crates.io for Cargo installs and GitHub Releases for direct binary updates.
The tuicr project implements a sophisticated self-updating system in Rust that solves a common CLI tool problem: letting users update without reinstalling manually. This guide breaks down the complete update architecture, from version detection to package manager delegation, based on the actual source implementation in agavra/tuicr.
Update System Architecture Overview
The update subsystem lives in src/update/ and follows a three-stage pipeline: context detection, version checking, and execution routing. Each stage is decoupled, allowing tuicr to support multiple installation sources without hardcoding package manager specifics into the core logic.
The entry point for any update operation is the UpdateContext struct, which encapsulates everything needed to perform an update: the current version, detected install method, and runtime configuration.
Detecting the Installation Method
Before checking for updates, tuicr must determine how it was installed. This happens in UpdateContext::current() located in src/update/install.rs.
// Conceptual flow based on install.rs implementation
pub fn current() -> anyhow::Result<Self> {
let current_exe = std::env::current_exe()?;
let method = detect_install_method(¤t_exe)?;
// ... builds UpdateContext
}
The detect_install_method function (module installation.rs) inspects:
- Binary path location — binaries in
$CARGO_HOME/bin/indicate Cargo installation - System directories —
/usr/local/bin/or/opt/homebrew/bin/suggest Homebrew - Nix store paths —
/nix/store/paths indicate Nix installation - Direct downloads — standalone binaries in user directories or non-standard paths
The result is an InstallMethod enum that drives all downstream behavior:
| InstallMethod | Detection Heuristic | Update Strategy |
|---|---|---|
Cargo |
Binary in $CARGO_HOME/bin/ or ~/.cargo/bin/ |
cargo install --version <ver> --force |
Homebrew |
Path contains homebrew or Cellar |
brew upgrade tuicr |
Nix |
Path starts with /nix/store |
nix-env -iA nixpkgs.tuicr |
Direct |
No package manager indicators detected | GitHub Releases API + binary swap |
Apt/Yum/etc. |
Standard system binary paths | apt-get install --only-upgrade or yum update |
This detection is heuristic-based rather than relying on metadata files, making it resilient to cross-platform variations.
Checking for Available Updates
Once the install method is known, tuicr queries the authoritative version source. For Cargo-based installs, this is Crates.io. The implementation lives in src/update/check.rs.
// From src/update/check.rs
pub fn check_for_updates() -> anyhow::Result<UpdateCheckResult> {
let agent = ureq::AgentBuilder::new()
.timeout(Duration::from_secs(3))
.build();
let url = format!(
"https://crates.io/api/v1/crates/{}",
env!("CARGO_PKG_NAME")
);
let response = agent.get(&url).call()?;
let json: serde_json::Value = response.into_json()?;
let latest_version = json["crate"]["max_version"]
.as_str()
.ok_or_else(|| anyhow!("malformed crates.io response"))?;
classify_versions(
&Version::parse(env!("CARGO_PKG_VERSION"))?,
&Version::parse(latest_version)?
)
}
The classify_versions function returns one of four outcomes:
UpdateAvailable— newer version exists on Crates.ioUpToDate— running latest releaseAheadOfRelease— running pre-release or locally built version newer than publishedFailed— network or parsing error
Notably, only Cargo installs query Crates.io directly. For Direct install methods, the version check is deferred to the GitHub Releases API during the update execution phase.
Executing Updates by Install Method
The update_with_runtime function in src/update/install.rs routes to the appropriate update strategy based on InstallMethod.
Package Manager Delegation
When manager_command(method) returns a system command, tuicr delegates entirely to that package manager:
// Conceptual implementation from install.rs
match manager_command(&ctx.method) {
Some(cmd) => {
let status = Command::new(&cmd.binary)
.args(&cmd.args)
.status()?;
return Ok(UpdateOutcome::ManagerCompleted(cmd));
}
None => {} // Fall through to direct binary update
}
Supported manager commands include:
- Homebrew:
brew upgrade tuicr - Nix:
nix-env -iA nixpkgs.tuicr - Apt:
apt-get install --only-upgrade tuicr - Cargo:
cargo install tuicr --version {ver} --force
Direct Binary Updates
For installations without a package manager (downloaded directly from GitHub), tuicr performs a full binary replacement:
- Fetch release metadata from
https://api.github.com/repos/agavra/tuicr/releases/latest - Locate matching asset using
release_asset_name()(platform/architecture detection) - Verify integrity via SHA-256 digest comparison
- Extract binary from
.tar.gzor.zipusingarchive.rs - Atomic swap using
executable_swap.rsto replace the running executable safely
// Simplified direct update flow
fn update_direct(target_version: &Version) -> anyhow::Result<UpdateOutcome> {
let release = fetch_release_json(target_version)?;
let asset = select_asset(&release)?;
let archive = download_and_verify(&asset)?;
let new_binary = extract_binary(&archive)?;
swap_executable(&new_binary)?;
Ok(UpdateOutcome::Updated { from, to })
}
The atomic swap is critical—on Unix systems this uses renameat2 or equivalent; on Windows, it leverages MOVEFILE_REPLACE_EXISTING with appropriate permission handling.
Version Pinning and Specific Updates
Users can request specific versions rather than always updating to latest:
use semver::Version;
use tuicr::update::install::update_to_version;
// Pin to a specific release
let target = Version::parse("1.2.3").unwrap();
match update_to_version(&target) {
Ok(UpdateOutcome::Updated { from, to }) => {
println!("Updated from {} to {}", from, to);
}
Ok(UpdateOutcome::UpToDate) => {
println!("Already on requested version");
}
Err(e) => eprintln!("Update failed: {}", e),
}
Version pinning works for:
- Cargo installs: adds
--version <ver>to the install command - Direct binary installs: fetches specific GitHub release by tag
- Package manager installs: typically unsupported (managers don't expose version pinning uniformly)
Programmatic Update Check Example
Applications embedding tuicr can perform non-intrusive update checks:
use tuicr::update::check::{check_for_updates, UpdateCheckResult};
fn notify_if_update_available() {
match check_for_updates() {
Ok(UpdateCheckResult::UpdateAvailable(info)) => {
eprintln!(
"tuicr {} is available (you have {}). Run `tuicr update` to upgrade.",
info.latest_version, info.current_version
);
}
Ok(UpdateCheckResult::AheadOfRelease(info)) => {
log::debug!(
"Running development build {} ahead of {},fcio {}",
info.current_version, info.latest_version
);
}
Ok(_) => {} // Up to date, no notification needed
Err(e) => {
log::debug!("Could not check for updates: {}", e);
}
}
}
Key Source Files and Modules
| File | Responsibility | Key Functions |
|---|---|---|
src/update/check.rs |
Query Crates.io, classify version status | check_for_updates(), classify_versions() |
src/update/install.rs |
Orchestrate update execution | update_with_runtime(), update_installed(), update_to_version() |
src/update/installation.rs |
Detect install method, resolve manager commands | detect_install_method(), manager_command() |
src/update/source.rs |
Build GitHub API URLs, asset selection | release_api_url(), release_asset_name() |
src/update/archive.rs |
Extract binaries from release archives | extract_binary() |
src/update/executable_swap.rs |
Atomic executable replacement | swap_executable() |
Summary
- tuicr auto-detects installation method by examining binary path and environment, supporting Cargo, Homebrew, Nix, Apt/Yum, and direct GitHub downloads
- Version checks route to appropriate source: Crates.io API for Cargo installs, GitHub Releases API for direct binaries, skipped for system package managers
- Updates delegate to detected package manager when available, falling back to atomic binary replacement for standalone installs
- Direct binary updates include integrity verification via SHA-256 and safe executable swapping
- Version pinning is supported for Cargo and direct installs via explicit
--versionor release tag selection
Frequently Asked Questions
How does tuicr know which package manager installed it?
tuicr uses heuristics based on the binary's filesystem location. Binaries in $CARGO_HOME/bin/ indicate Cargo, paths containing homebrew or Cellar indicate Homebrew, /nix/store paths indicate Nix, and standard system directories trigger Apt/Yum detection. These rules are implemented in detect_install_method() within the installation module.
Can I update tuicr if I installed it directly from a GitHub release?
Yes. Direct binary installations trigger the full self-update path: tuicr queries the GitHub Releases API, downloads the matching asset for your platform, verifies its SHA-256 digest, extracts the new binary, and atomically replaces the running executable using swap_executable().
Why does tuicr query Crates.io instead of GitHub for version checks?
Crates.io serves as the canonical version registry for Rust ecosystem tools. Cargo-based installations inherently trust Crates.io versioning, so querying it directly ensures consistency with cargo install behavior. GitHub Releases are used only for direct binary installs where no package manager metadata exists.
Is it safe to update tuicr while it's running?
Yes. The executable_swap.rs module implements atomic replacement primitives. On Unix, this uses atomic rename operations; on Windows, it uses MOVEFILE_REPLACE_EXISTING with deferred deletion if the binary is locked. The old executable is removed on the next process restart, ensuring no corruption of the running instance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →