use std::io::Read;
use sha2::{Digest, Sha256};
use super::provider::LatestRelease;
use super::{UpdateError, platform};
const CHECKSUMS_ASSET: &str = "checksums-sha256.txt";
pub fn self_update(latest: &LatestRelease) -> Result<(), UpdateError> {
let asset_name =
platform::binary_asset_name(&latest.version).ok_or(UpdateError::UnsupportedPlatform)?;
let binary_asset = latest
.asset(&asset_name)
.ok_or_else(|| UpdateError::MissingAsset(asset_name.clone()))?;
let checksums_asset = latest
.asset(CHECKSUMS_ASSET)
.ok_or_else(|| UpdateError::MissingAsset(CHECKSUMS_ASSET.to_string()))?;
let checksums_bytes = download_bytes(&checksums_asset.url)?;
let checksums = String::from_utf8_lossy(&checksums_bytes);
let expected = checksum_for(&checksums, &asset_name)
.ok_or_else(|| UpdateError::NoChecksum(asset_name.clone()))?;
let bytes = download_bytes(&binary_asset.url)?;
let actual = hex_sha256(&bytes);
if !actual.eq_ignore_ascii_case(&expected) {
return Err(UpdateError::ChecksumMismatch { expected, actual });
}
let exe = std::env::current_exe()?;
let dir = exe.parent().unwrap_or_else(|| std::path::Path::new("."));
let staged = dir.join(format!(".{asset_name}.new"));
kimun_core::system::replace_atomically(&staged, &bytes)
.map_err(|e| std::io::Error::other(e.to_string()))?;
kimun_core::system::make_executable(&staged)
.map_err(|e| std::io::Error::other(e.to_string()))?;
let result = self_replace::self_replace(&staged).map_err(UpdateError::Replace);
let _ = kimun_core::system::remove_file(&staged);
result
}
const MAX_DOWNLOAD_BYTES: u64 = 256 * 1024 * 1024;
fn download_bytes(url: &str) -> Result<Vec<u8>, UpdateError> {
let mut buf = Vec::new();
super::http_get(url)?
.into_reader()
.take(MAX_DOWNLOAD_BYTES)
.read_to_end(&mut buf)?;
Ok(buf)
}
fn checksum_for(checksums: &str, asset_name: &str) -> Option<String> {
checksums.lines().find_map(|line| {
let (digest, name) = line.split_once(char::is_whitespace)?;
(name.trim() == asset_name).then(|| digest.trim().to_string())
})
}
fn hex_sha256(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
let mut out = String::with_capacity(digest.len() * 2);
for byte in digest {
use std::fmt::Write;
let _ = write!(out, "{byte:02x}");
}
out
}