feat: add update command to CLI, relay, and client

This commit is contained in:
Helios Agent 2026-03-06 12:16:10 +01:00
parent 835d20f734
commit 6345209538
No known key found for this signature in database
GPG key ID: C8259547CD8309B5
9 changed files with 302 additions and 0 deletions

View file

@ -337,6 +337,12 @@ enum Commands {
device: String,
},
/// Update all components (relay, client, CLI) if behind
Update {
/// Device label
device: String,
},
/// Fetch last N lines of client log file
Logs {
/// Device label
@ -713,6 +719,139 @@ fn main() {
);
}
Commands::Update { device } => {
validate_label(&device);
// Fetch all three commits
let relay_commit = match reqwest::blocking::get(&format!("{}/version", cfg.base_url)) {
Ok(r) => r
.json::<Value>()
.ok()
.and_then(|v| v["commit"].as_str().map(String::from))
.unwrap_or_else(|| "?".into()),
Err(e) => format!("error: {}", e),
};
let client_commit = {
let data = req(&cfg, "GET", &format!("/devices/{}/version", device), None, 10);
data["commit"].as_str().unwrap_or("?").to_string()
};
let cli_commit = GIT_COMMIT;
println!(" relay {}", relay_commit);
println!(" cli {}", cli_commit);
println!(" client {}", client_commit);
let all_same = relay_commit == cli_commit && cli_commit == client_commit;
if all_same {
println!(" ✅ Already up to date (commit: {})", cli_commit);
return;
}
println!();
let mut updated_any = false;
// Update relay if needed
if relay_commit != cli_commit {
println!(" → Updating relay...");
let data = req(&cfg, "POST", "/relay/update", None, 15);
println!(" {}", data["message"].as_str().unwrap_or("triggered"));
updated_any = true;
}
// Update client if needed
if client_commit != cli_commit {
println!(" → Updating client on {}...", device);
let data = req(
&cfg,
"POST",
&format!("/devices/{}/update", device),
None,
65,
);
println!(
" {}",
data["message"].as_str().unwrap_or(
if data["success"].as_bool() == Some(true) { "triggered" } else { "failed" }
)
);
updated_any = true;
}
// Self-update CLI if needed
// (relay_commit is the "canonical" latest — if we differ from it, we're outdated)
if cli_commit != relay_commit {
println!(" → Updating CLI...");
#[cfg(target_os = "windows")]
let url = "https://agent-helios.me/downloads/helios-remote/helios-remote-cli-windows.exe";
#[cfg(not(target_os = "windows"))]
let url = "https://agent-helios.me/downloads/helios-remote/helios-remote-cli-linux";
let bytes = match reqwest::blocking::get(url) {
Ok(r) => match r.bytes() {
Ok(b) => b,
Err(e) => {
eprintln!("[helios-remote] CLI update: read failed: {}", e);
process::exit(1);
}
},
Err(e) => {
eprintln!("[helios-remote] CLI update: download failed: {}", e);
process::exit(1);
}
};
let exe = std::env::current_exe().unwrap_or_else(|e| {
eprintln!("[helios-remote] CLI update: current_exe failed: {}", e);
process::exit(1);
});
#[cfg(not(target_os = "windows"))]
{
let tmp = exe.with_extension("new");
std::fs::write(&tmp, &bytes).unwrap_or_else(|e| {
eprintln!("[helios-remote] CLI update: write failed: {}", e);
process::exit(1);
});
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&tmp, std::fs::Permissions::from_mode(0o755));
std::fs::rename(&tmp, &exe).unwrap_or_else(|e| {
eprintln!("[helios-remote] CLI update: rename failed: {}", e);
process::exit(1);
});
println!(" CLI updated. Re-executing...");
use std::os::unix::process::CommandExt;
let args: Vec<String> = std::env::args().collect();
let err = std::process::Command::new(&exe).args(&args[1..]).exec();
eprintln!("[helios-remote] CLI re-exec failed: {}", err);
process::exit(1);
}
#[cfg(target_os = "windows")]
{
let tmp = exe.with_extension("update.exe");
std::fs::write(&tmp, &bytes).unwrap_or_else(|e| {
eprintln!("[helios-remote] CLI update: write failed: {}", e);
process::exit(1);
});
let old = exe.with_extension("old.exe");
let _ = std::fs::remove_file(&old);
let _ = std::fs::rename(&exe, &old);
std::fs::rename(&tmp, &exe).unwrap_or_else(|e| {
eprintln!("[helios-remote] CLI update: rename failed: {}", e);
process::exit(1);
});
println!(" CLI updated. Please restart the command.");
}
updated_any = true;
}
if updated_any {
println!();
println!(" ✅ Update(s) triggered.");
}
}
Commands::Logs { device, lines } => {
validate_label(&device);
let data = req(