# How tuicr Handles Self-Updates and Version Management Across Package Managers

> Discover how tuicr seamlessly handles self-updates and version management across Cargo, Homebrew, Nix, and more. Learn its intelligent update mechanism for effortless software maintenance.

- Repository: [Almog Gavra/tuicr](https://github.com/agavra/tuicr)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/agavra/tuicr/blob/main/src/update/install.rs).

```rust
// 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(&current_exe)?;
    // ... builds UpdateContext
}

```

The `detect_install_method` function (module [`installation.rs`](https://github.com/agavra/tuicr/blob/main/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`](https://github.com/agavra/tuicr/blob/main/src/update/check.rs).

```rust
// 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.io
- **`UpToDate`** — running latest release
- **`AheadOfRelease`** — running pre-release or locally built version newer than published
- **`Failed`** — 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`](https://github.com/agavra/tuicr/blob/main/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:

```rust
// 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**:

1. **Fetch release metadata** from `https://api.github.com/repos/agavra/tuicr/releases/latest`
2. **Locate matching asset** using `release_asset_name()` (platform/architecture detection)
3. **Verify integrity** via SHA-256 digest comparison
4. **Extract binary** from `.tar.gz` or `.zip` using [`archive.rs`](https://github.com/agavra/tuicr/blob/main/archive.rs)
5. **Atomic swap** using [`executable_swap.rs`](https://github.com/agavra/tuicr/blob/main/executable_swap.rs) to replace the running executable safely

```rust
// 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:

```rust
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:

```rust
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`](https://github.com/agavra/tuicr/blob/main/src/update/check.rs) | Query Crates.io, classify version status | `check_for_updates()`, `classify_versions()` |
| [`src/update/install.rs`](https://github.com/agavra/tuicr/blob/main/src/update/install.rs) | Orchestrate update execution | `update_with_runtime()`, `update_installed()`, `update_to_version()` |
| [`src/update/installation.rs`](https://github.com/agavra/tuicr/blob/main/src/update/installation.rs) | Detect install method, resolve manager commands | `detect_install_method()`, `manager_command()` |
| [`src/update/source.rs`](https://github.com/agavra/tuicr/blob/main/src/update/source.rs) | Build GitHub API URLs, asset selection | `release_api_url()`, `release_asset_name()` |
| [`src/update/archive.rs`](https://github.com/agavra/tuicr/blob/main/src/update/archive.rs) | Extract binaries from release archives | `extract_binary()` |
| [`src/update/executable_swap.rs`](https://github.com/agavra/tuicr/blob/main/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 `--version` or 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`](https://github.com/agavra/tuicr/blob/main/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.