use anyhow::{Context, Result};
use std::env;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process::Command;
use crate::constants::{github, install_paths};
fn get_install_path() -> Result<PathBuf> {
if let Ok(output) = Command::new("which").arg("sticks").output() {
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
return Ok(PathBuf::from(path));
}
}
let paths = vec![
PathBuf::from(install_paths::USR_LOCAL_BIN),
PathBuf::from(install_paths::USR_BIN),
dirs::home_dir()
.map(|h| h.join(install_paths::CARGO_BIN_SUFFIX))
.unwrap_or_default(),
];
for path in paths {
if path.exists() {
return Ok(path);
}
}
anyhow::bail!("Could not determine sticks installation path")
}
fn get_architecture() -> &'static str {
#[cfg(target_arch = "x86_64")]
return "x86_64";
#[cfg(not(target_arch = "x86_64"))]
return "unsupported";
}
fn get_current_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
fn get_latest_version() -> Result<String> {
let output = Command::new("curl")
.args(["-s", github::RELEASE_API_URL])
.output()
.context("Failed to fetch latest release information")?;
if !output.status.success() {
anyhow::bail!("Failed to check for latest version");
}
let json = String::from_utf8_lossy(&output.stdout);
if let Some(tag_start) = json.find("\"tag_name\":\"") {
let version_str = &json[tag_start + 12..];
if let Some(tag_end) = version_str.find('"') {
let version = version_str[..tag_end].trim_start_matches('v').to_string();
return Ok(version);
}
}
anyhow::bail!("Could not parse version from GitHub API response")
}
pub fn update_project() -> Result<()> {
println!("đ Checking for updates...");
let current_version = get_current_version();
let arch = get_architecture();
if arch == "unsupported" {
anyhow::bail!(
"Unsupported architecture: only x86_64 is supported. Please update manually."
);
}
let install_path = get_install_path()?;
let is_system_install =
install_path.starts_with("/usr/bin") || install_path.starts_with("/usr/local/bin");
let latest_version = get_latest_version().context("Failed to check for updates")?;
if current_version == latest_version {
println!(
"â You're already on the latest version (v{})!",
current_version
);
return Ok(());
}
println!(
"đĻ Update available: v{} â v{}",
current_version, latest_version
);
if is_system_install {
println!();
println!("âšī¸ System installation detected.");
println!("đĻ Please use your package manager to update:");
println!();
println!(" Arch Linux: sudo pacman -Syu sticks");
println!(" Debian/Ubuntu: sudo apt update && sudo apt upgrade sticks");
println!();
println!("đĄ Or download the latest package from:");
println!(" https://github.com/mAmineChniti/sticks/releases/latest");
return Ok(());
}
println!("đĨ Downloading v{} from GitHub releases...", latest_version);
let temp_dir = env::temp_dir().join(format!("sticks-update-{}", std::process::id()));
fs::create_dir_all(&temp_dir).context("Failed to create temp directory")?;
let binary_name = format!("sticks-linux-{}", arch);
let download_url = format!("{}/{}", github::RELEASE_DOWNLOAD_URL, binary_name);
let temp_binary = temp_dir.join("sticks");
let status = Command::new("curl")
.args([
"-L",
"-f",
"-o",
temp_binary.to_str().unwrap(),
&download_url,
])
.status()
.context("Failed to download update. Is curl installed?")?;
if !status.success() {
fs::remove_dir_all(&temp_dir).ok();
anyhow::bail!(
"Failed to download update from {}. \
Please check your internet connection or download manually.",
download_url
);
}
let mut perms = fs::metadata(&temp_binary)
.context("Failed to get file permissions")?
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&temp_binary, perms).context("Failed to set executable permissions")?;
fs::copy(&temp_binary, &install_path)
.with_context(|| format!("Failed to replace binary at {:?}", install_path))?;
fs::remove_dir_all(&temp_dir).ok();
println!();
println!(
"â Successfully upgraded from v{} to v{}!",
current_version, latest_version
);
println!("đĄ Run 'sticks --version' to verify the installation.");
Ok(())
}