How Herdr Checks for New Versions: Inside the Rust Self-Update Mechanism
Herdr checks for new versions by fetching a JSON manifest from https://herdr.dev/latest.json, comparing semantic versions using the semver crate, and atomically replacing the running binary if a newer release exists.
The self-update mechanism in the ogulcancelik/herdr repository is implemented entirely in Rust and provides a platform-aware way to keep the CLI tool current. Unlike package manager-dependent updates, Herdr implements an in-place binary replacement strategy that works across Linux, macOS, and other supported platforms. Understanding how this update mechanism checks for new versions helps developers debug deployment issues and ensures CI/CD pipelines remain up to date.
How the Herdr Update Mechanism Works
The update logic resides in src/update.rs and follows a sequential pipeline from manifest retrieval to atomic installation. Each stage is designed to be resilient, with clear error handling for network failures or malformed version strings.
Define the Update Source
The updater begins by referencing a constant URL that always points to the current release manifest. According to the source code at src/update.rs#L20, this URL is hardcoded as https://herdr.dev/latest.json, ensuring every Herdr instance checks the same canonical source for version metadata.
Fetch and Parse the Manifest
The function fetch_update_manifest() performs a blocking HTTP GET request to the manifest URL. As implemented around line 178 in src/update.rs, this function:
- Downloads the raw JSON from
herdr.dev/latest.json - Deserializes the response into an
UpdateManifeststruct (defined at line 97) - Returns a structured object containing the latest version string, optional release notes, and a map of per-platform assets
The manifest format mirrors the RemoteUpdateManifest found in src/remote.rs, allowing both local and remote bootstrap instances to share identical version-checking logic.
Compare Versions Semantically
Version comparison relies on strict semantic versioning. The mechanism:
- Parses the current binary's version using
env!("CARGO_PKG_VERSION")at compile time - Converts both the local version and the manifest's version string into
semver::Versionobjects - Performs a standard comparison at lines 225-226 in
src/update.rs
If the manifest version is greater than the running binary's version, the update proceeds. Otherwise, the process exits early with a "already up to date" message.
Select the Platform-Specific Binary
Not every release contains identical binaries. The method UpdateManifest::download_url_for(os, arch) (line 138 in src/update.rs) looks up the release assets using std::env::consts to detect the current operating system and architecture. This ensures an ARM64 macOS binary never attempts to download an x86_64 Linux replacement.
The manifest schema uses a nested structure where each version contains an assets map keyed by strings like "linux-x86_64" or "macos-aarch64".
Download and Atomic Installation
Once the correct asset URL is identified, download_update(&release) (line 333) handles the actual transfer. This function spawns a subprocess (typically curl or platform-native downloader) to stream the new executable to a temporary file while validating exit status codes.
Finally, install_downloaded_update(temp_file) at line 382 performs an atomic file swap. It locates the currently running executable path via std::env::current_exe() and replaces it with the downloaded binary while preserving file permissions. This atomicity prevents corruption if the process is interrupted mid-write.
The Update Manifest Format
The manifest hosted at https://herdr.dev/latest.json (and checked into the repository as website/latest.json) follows a strict schema matching the UpdateManifest struct:
{
"version": "0.6.5",
"notes": "### Added ...",
"releases": {
"0.6.5": {
"assets": {
"linux-x86_64": "https://github.com/ogulcancelik/herdr/releases/download/v0.6.5/herdr-linux-x86_64",
"macos-aarch64": "https://github.com/ogulcancelik/herdr/releases/download/v0.6.5/herdr-macos-aarch64"
},
"download_url": "https://github.com/ogulcancelik/herdr/releases/download/v0.6.5/herdr-linux-x86_64"
}
}
}
The download_url_for method extracts the appropriate URL from the assets map based on runtime detection of the OS and architecture constants.
Remote Bootstrap Updates
When Herdr initializes a remote instance (via herdr --remote), the same version-checking logic applies but through src/remote.rs. This file defines an identical UPDATE_MANIFEST_URL constant and a RemoteUpdateManifest struct with matching fields. The remote bootstrap path uses the same semantic comparison logic to decide whether to download a newer binary for headless server deployment, ensuring consistency between local CLI updates and remote server provisioning.
Code Examples
Checking for Updates via CLI
Run the built-in update command from any terminal to trigger the full check-and-replace workflow:
# Standard update check and automatic installation
herdr update
# Update with live handoff flag (recommended if a server is running)
herdr update --handoff
The --handoff flag signals Herdr to coordinate with a running server process to minimize downtime during the binary swap.
Programmatic Version Check in Rust
Integrate the update mechanism into your own Rust applications using Herdr's internal modules:
use herdr::update::{fetch_update_manifest, CURRENT_VERSION};
use semver::Version;
async fn check_for_herdr_update() -> Result<(), String> {
// Fetch the latest manifest from herdr.dev/latest.json
let manifest = fetch_update_manifest().await
.map_err(|e| format!("Failed to fetch manifest: {e}"))?;
// Parse versions using semver
let current = Version::parse(CURRENT_VERSION)
.map_err(|e| format!("Invalid local version: {e}"))?;
let latest = Version::parse(&manifest.version)
.map_err(|e| format!("Invalid manifest version: {e}"))?;
// Compare and proceed if newer version exists
if latest > current {
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;
let url = manifest.download_url_for(os, arch)
.ok_or_else(|| format!("No binary available for {}-{}", os, arch))?;
println!("Update available: {} -> {}", current, latest);
println!("Download URL: {}", url);
// Proceed with download_update() and install_downloaded_update()...
} else {
println!("Herdr is up to date ({})", current);
}
Ok(())
}
Debugging the Manifest Fetch
To verify connectivity or inspect the raw manifest without triggering an installation:
use herdr::update::fetch_update_manifest;
#[tokio::main]
async fn main() {
match fetch_update_manifest().await {
Ok(manifest) => {
println!("Latest version: {}", manifest.version);
println!("Release notes: {}", manifest.notes.unwrap_or_default());
}
Err(e) => eprintln!("Manifest fetch failed: {}", e),
}
}
This pattern is useful for CI environments where you need to validate that https://herdr.dev/latest.json is reachable before attempting an upgrade.
Summary
- Herdr checks for updates by downloading a JSON manifest from
https://herdr.dev/latest.json, defined as a constant insrc/update.rs#L20. - Semantic comparison occurs using the
semvercrate to compareenv!("CARGO_PKG_VERSION")against the manifest's version string at lines 225-226. - Platform detection via
download_url_for(os, arch)ensures the correct binary is selected for the current OS and architecture. - Atomic installation prevents corruption by using
install_downloaded_updateto swap executables only after successful download validation. - Remote bootstrap shares identical logic in
src/remote.rs, enabling version-aware deployment for headless servers.
Frequently Asked Questions
Where does Herdr look for version information?
Herdr queries the HTTPS endpoint https://herdr.dev/latest.json to retrieve the canonical version manifest. This URL is hardcoded as UPDATE_MANIFEST_URL in src/update.rs and src/remote.rs. The JSON file contains the latest semantic version string and a map of downloadable assets for each supported platform.
How does Herdr handle different operating systems and architectures?
The UpdateManifest::download_url_for method inspects std::env::consts::OS and std::env::consts::ARCH at runtime to construct a platform key like "linux-x86_64" or "macos-aarch64". It then looks up this key in the manifest's assets map to return the correct binary URL, ensuring architecture-specific updates are downloaded even when the manifest contains multiple release artifacts.
What happens if the update download is interrupted?
The download_update function streams data to a temporary file first and validates the exit status of the download command before returning. Only after successful completion does install_downloaded_update perform the atomic replacement of the running binary. If the download fails or is interrupted, the temporary file is discarded and the original binary remains untouched at its current path returned by std::env::current_exe().
Can I run Herdr update while a server is active?
Yes, but you should use the --handoff flag. According to the user-facing messages in src/update.rs around lines 700-800, running herdr update --handoff signals the updater to coordinate with a running Herdr server process. This allows for a live handoff where the server can gracefully transition to the new binary version without dropping active connections or requiring a manual restart.
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 →