CLI: fieldcraft-cli update --check | --install (#231)

--check runs app.update.check and prints its JSON; --install downloads the latest release for
this install, verifies the checksum list's signature and the asset's checksum, and swaps it in
(or, for .deb/.rpm, prints the package manager command for the verified download). Tested
against a local release server through FIELDCRAFT_UPDATE_HOST.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 01:48:30 -04:00
1 parent 12487794a3
commit 69c1440cbe
5 files changed
+103

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
//! `fieldcraft-cli update --check | --install` (docs/updates.md): the same check as the app's
//! Help › Check for Updates (`app.update.check`), and installing the latest release over this
//! copy of the CLI's install, verified against the release key first. `FIELDCRAFT_UPDATE_HOST`
//! points both at a local test server.
use fieldcraft_engine::Session;
use fieldcraft_update::install::{self, Finish};
use fieldcraft_update::net::Client;
use fieldcraft_update::stage::{self, Options};
use serde_json::json;
pub const USAGE: &str = "usage: fieldcraft-cli update --check | --install";
pub fn run(args: &[String]) -> Result<(), String> {
match args.iter().map(String::as_str).collect::<Vec<_>>().as_slice() {
["--check"] => check(),
["--install"] => install(),
_ => Err(USAGE.into()),
}
}
fn check() -> Result<(), String> {
fieldcraft_engine::enable_update_checks(true);
let report = Session::default().execute("app.update.check", &json!({})).map_err(|e| e.to_string())?;
println!("{}", serde_json::to_string_pretty(&report).map_err(|e| e.to_string())?);
Ok(())
}
fn install() -> Result<(), String> {
let client = Client::from_env();
let check = client.check().map_err(|e| e.to_string())?;
if !check.newer() {
println!("FieldCraft {} is up to date (the latest release is {}).", check.current, check.latest.version);
return Ok(());
}
let here = install::here();
let downloads = fieldcraft_io::recovery::data_dir().map(|d| d.join("updates")).unwrap_or_else(|| std::env::temp_dir().join("fieldcraft-updates"));
eprintln!("Downloading FieldCraft {} for {}…", check.latest.version, here.describe());
let progress = |done: u64, total: u64| {
if total > 0 && done == total {
eprintln!("Downloaded {} MB; verifying…", total / (1024 * 1024));
}
};
let options = Options { key: fieldcraft_update::PUBLIC_KEY, downloads: &downloads, arch: std::env::consts::ARCH, progress: &progress };
let staged = stage::stage(&client, &check.latest, &here, &options).map_err(|e| e.to_string())?;
match install::plan(&staged, false).map_err(|e| e.to_string())? {
Finish::Swap(plan) => {
stage::apply(&plan).map_err(|e| e.to_string())?;
println!("Installed FieldCraft {} ({}). A running FieldCraft app uses it from its next start.", staged.version, here.describe());
}
Finish::Package { file, command } => {
println!("Downloaded and verified {}.\nInstall it with the system's package manager:\n {command}", file.display());
}
}
Ok(())
}