Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
148 lines
4.1 KiB
Rust
148 lines
4.1 KiB
Rust
use std::{fs, path::PathBuf, process::Command, time::Duration};
|
|
|
|
use anyhow::{anyhow, Context, Result};
|
|
use reqwest::blocking::Client;
|
|
use serde::{Deserialize, Serialize};
|
|
use sha2::{Digest, Sha256};
|
|
use uuid::Uuid;
|
|
|
|
const RELEASE_FEED: &str =
|
|
"https://git.arrowstack.dev/Admin/fieldlens-desktop/raw/branch/main/downloads/release.json";
|
|
const LINUX_DEB_URL: &str =
|
|
"https://fieldlens.arrowstack.dev/downloads/fieldlens-print-linux-x64.deb";
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ReleaseFeed {
|
|
version: String,
|
|
downloads: std::collections::HashMap<String, ReleaseDownload>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ReleaseDownload {
|
|
url: String,
|
|
sha256: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct UpdateInstallResult {
|
|
pub status: String,
|
|
pub version: String,
|
|
}
|
|
|
|
fn newer(candidate: &str, current: &str) -> bool {
|
|
let parse = |value: &str| {
|
|
value
|
|
.split('.')
|
|
.map(|part| part.parse::<u64>().unwrap_or(0))
|
|
.collect::<Vec<_>>()
|
|
};
|
|
let left = parse(candidate);
|
|
let right = parse(current);
|
|
let width = left.len().max(right.len());
|
|
|
|
for index in 0..width {
|
|
let a = *left.get(index).unwrap_or(&0);
|
|
let b = *right.get(index).unwrap_or(&0);
|
|
if a != b {
|
|
return a > b;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub fn install_linux_update() -> Result<UpdateInstallResult> {
|
|
let client = Client::builder()
|
|
.timeout(Duration::from_secs(120))
|
|
.build()
|
|
.context("create update HTTP client")?;
|
|
|
|
let release: ReleaseFeed = client
|
|
.get(RELEASE_FEED)
|
|
.header("cache-control", "no-cache")
|
|
.send()
|
|
.context("fetch FieldLens release feed")?
|
|
.error_for_status()
|
|
.context("FieldLens release feed returned an error")?
|
|
.json()
|
|
.context("decode FieldLens release feed")?;
|
|
|
|
if !newer(&release.version, env!("CARGO_PKG_VERSION")) {
|
|
return Ok(UpdateInstallResult {
|
|
status: "up-to-date".into(),
|
|
version: release.version,
|
|
});
|
|
}
|
|
|
|
let download = release
|
|
.downloads
|
|
.get("linux-x86_64-deb")
|
|
.ok_or_else(|| anyhow!("release feed has no Linux DEB"))?;
|
|
|
|
if download.url != LINUX_DEB_URL {
|
|
return Err(anyhow!("refusing unexpected Linux update URL"));
|
|
}
|
|
if download.sha256.len() != 64
|
|
|| !download.sha256.chars().all(|value| value.is_ascii_hexdigit())
|
|
{
|
|
return Err(anyhow!("release feed contains an invalid SHA-256"));
|
|
}
|
|
|
|
let package = client
|
|
.get(&download.url)
|
|
.send()
|
|
.context("download FieldLens update")?
|
|
.error_for_status()
|
|
.context("FieldLens update download returned an error")?
|
|
.bytes()
|
|
.context("read FieldLens update")?;
|
|
|
|
let actual = format!("{:x}", Sha256::digest(&package));
|
|
if !actual.eq_ignore_ascii_case(&download.sha256) {
|
|
return Err(anyhow!("FieldLens update checksum verification failed"));
|
|
}
|
|
|
|
let path: PathBuf = std::env::temp_dir().join(format!(
|
|
"fieldlens-print-update-{}-{}.deb",
|
|
release.version,
|
|
Uuid::new_v4()
|
|
));
|
|
fs::write(&path, &package).context("stage FieldLens update")?;
|
|
|
|
let installer = if std::path::Path::new("/usr/bin/apt-get").is_file() {
|
|
"/usr/bin/apt-get"
|
|
} else {
|
|
"/usr/bin/dpkg"
|
|
};
|
|
|
|
let mut command = Command::new("pkexec");
|
|
command.arg(installer);
|
|
if installer.ends_with("apt-get") {
|
|
command.args(["install", "-y"]).arg(&path);
|
|
} else {
|
|
command.arg("-i").arg(&path);
|
|
}
|
|
|
|
let status = command
|
|
.status()
|
|
.context("run elevated FieldLens update installer")?;
|
|
let _ = fs::remove_file(&path);
|
|
|
|
if !status.success() {
|
|
return Err(anyhow!(
|
|
"FieldLens update installation was cancelled or failed"
|
|
));
|
|
}
|
|
|
|
Ok(UpdateInstallResult {
|
|
status: "installed".into(),
|
|
version: release.version,
|
|
})
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
pub fn install_linux_update() -> Result<UpdateInstallResult> {
|
|
Err(anyhow!("Linux DEB updates are only available on Linux"))
|
|
}
|