pull/2624/merge
kandrelczyk 1 day ago committed by GitHub
commit 1e9c1cc89a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

2
.gitignore vendored

@ -58,4 +58,4 @@ pids
.idea
debug.log
TODO.md
.aider*
.aider.*

159
Cargo.lock generated

@ -213,7 +213,7 @@ dependencies = [
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-build 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-plugin-barcode-scanner",
"tauri-plugin-biometric",
"tauri-plugin-cli",
@ -245,7 +245,7 @@ dependencies = [
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-build 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-plugin-updater",
"time",
"tiny_http",
@ -258,7 +258,7 @@ dependencies = [
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-build 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-plugin-updater",
"tiny_http",
]
@ -270,7 +270,7 @@ dependencies = [
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-build 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-plugin-store",
]
@ -1428,7 +1428,7 @@ dependencies = [
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-build 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-plugin-deep-link",
"tauri-plugin-log",
"tauri-plugin-single-instance",
@ -3615,9 +3615,9 @@ dependencies = [
[[package]]
name = "muda"
version = "0.16.1"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492"
checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988"
dependencies = [
"crossbeam-channel",
"dpi",
@ -5729,7 +5729,7 @@ dependencies = [
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-build 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-plugin-cli",
"tauri-plugin-single-instance",
]
@ -6348,16 +6348,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7a0f4019c80391d143ee26cd7cd1ed271ac241d3087d333f99f3269ba90812"
version = "2.6.2"
source = "git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1#232265c70e1c213bbb3f84b5541ddc07d330fce1"
dependencies = [
"anyhow",
"bytes",
"dirs 6.0.0",
"dunce",
"embed_plist",
"getrandom 0.2.15",
"getrandom 0.3.2",
"glob",
"gtk",
"heck 0.5.0",
@ -6383,11 +6382,11 @@ dependencies = [
"serialize-to-javascript",
"specta",
"swift-rs",
"tauri-build",
"tauri-build 2.3.0 (git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1)",
"tauri-macros",
"tauri-runtime",
"tauri-runtime-wry",
"tauri-utils",
"tauri-utils 2.5.0 (git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1)",
"thiserror 2.0.12",
"tokio",
"tray-icon",
@ -6417,8 +6416,29 @@ dependencies = [
"semver",
"serde",
"serde_json",
"tauri-codegen",
"tauri-utils",
"tauri-codegen 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-utils 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-winres",
"toml",
"walkdir",
]
[[package]]
name = "tauri-build"
version = "2.3.0"
source = "git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1#232265c70e1c213bbb3f84b5541ddc07d330fce1"
dependencies = [
"anyhow",
"cargo_toml",
"dirs 6.0.0",
"glob",
"heck 0.5.0",
"json-patch",
"schemars",
"semver",
"serde",
"serde_json",
"tauri-utils 2.5.0 (git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1)",
"tauri-winres",
"toml",
"walkdir",
@ -6442,7 +6462,7 @@ dependencies = [
"serde_json",
"sha2",
"syn 2.0.100",
"tauri-utils",
"tauri-utils 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 2.0.12",
"time",
"url",
@ -6451,17 +6471,41 @@ dependencies = [
]
[[package]]
name = "tauri-macros"
name = "tauri-codegen"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f59e1d1fa9651212dcb890a0c66226d819b716490b0cf43c078514da3591705"
source = "git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1#232265c70e1c213bbb3f84b5541ddc07d330fce1"
dependencies = [
"base64 0.22.1",
"ico",
"json-patch",
"plist",
"png",
"proc-macro2",
"quote",
"semver",
"serde",
"serde_json",
"sha2",
"syn 2.0.100",
"tauri-utils 2.5.0 (git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1)",
"thiserror 2.0.12",
"time",
"url",
"uuid",
"walkdir",
]
[[package]]
name = "tauri-macros"
version = "2.3.1"
source = "git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1#232265c70e1c213bbb3f84b5541ddc07d330fce1"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.100",
"tauri-codegen",
"tauri-utils",
"tauri-codegen 2.3.0 (git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1)",
"tauri-utils 2.5.0 (git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1)",
]
[[package]]
@ -6476,7 +6520,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"tauri-utils",
"tauri-utils 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"toml",
"walkdir",
]
@ -6554,7 +6598,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-plugin",
"tauri-utils",
"tauri-utils 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 2.0.12",
"tracing",
"url",
@ -6594,7 +6638,7 @@ dependencies = [
"serde_repr",
"tauri",
"tauri-plugin",
"tauri-utils",
"tauri-utils 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 2.0.12",
"toml",
"url",
@ -6915,7 +6959,7 @@ dependencies = [
"tokio",
"url",
"windows-sys 0.60.2",
"zip 4.0.0",
"zip 4.2.0",
]
[[package]]
@ -6969,8 +7013,7 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
source = "git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1#232265c70e1c213bbb3f84b5541ddc07d330fce1"
dependencies = [
"cookie",
"dpi",
@ -6982,7 +7025,7 @@ dependencies = [
"raw-window-handle",
"serde",
"serde_json",
"tauri-utils",
"tauri-utils 2.5.0 (git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1)",
"thiserror 2.0.12",
"url",
"windows 0.61.1",
@ -6990,9 +7033,8 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe52ed0ef40fd7ad51a620ecb3018e32eba3040bb95025216a962a37f6f050c5"
version = "2.7.1"
source = "git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1#232265c70e1c213bbb3f84b5541ddc07d330fce1"
dependencies = [
"gtk",
"http",
@ -7007,7 +7049,7 @@ dependencies = [
"softbuffer",
"tao",
"tauri-runtime",
"tauri-utils",
"tauri-utils 2.5.0 (git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1)",
"url",
"webkit2gtk",
"webview2-com",
@ -7055,6 +7097,45 @@ dependencies = [
"walkdir",
]
[[package]]
name = "tauri-utils"
version = "2.5.0"
source = "git+https://github.com/tauri-apps/tauri?rev=232265c70e1c213bbb3f84b5541ddc07d330fce1#232265c70e1c213bbb3f84b5541ddc07d330fce1"
dependencies = [
"aes-gcm",
"anyhow",
"cargo_metadata",
"ctor",
"dunce",
"getrandom 0.3.2",
"glob",
"html5ever",
"http",
"infer",
"json-patch",
"kuchikiki",
"log",
"memchr",
"phf 0.11.3",
"proc-macro2",
"quote",
"regex",
"schemars",
"semver",
"serde",
"serde-untagged",
"serde_json",
"serde_with",
"serialize-to-javascript",
"swift-rs",
"thiserror 2.0.12",
"toml",
"url",
"urlpattern",
"uuid",
"walkdir",
]
[[package]]
name = "tauri-winres"
version = "0.3.0"
@ -7462,9 +7543,9 @@ dependencies = [
[[package]]
name = "tray-icon"
version = "0.20.0"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5"
checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a"
dependencies = [
"crossbeam-channel",
"dirs 6.0.0",
@ -7653,7 +7734,7 @@ dependencies = [
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-build 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-plugin-updater",
"time",
"tiny_http",
@ -8069,7 +8150,7 @@ dependencies = [
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-build 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tauri-plugin-websocket",
"tokio",
"tokio-tungstenite",
@ -9068,9 +9149,9 @@ dependencies = [
[[package]]
name = "zip"
version = "4.0.0"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "153a6fff49d264c4babdcfa6b4d534747f520e56e8f0f384f3b808c4b64cc1fd"
checksum = "95ab361742de920c5535880f89bbd611ee62002bf11341d16a5f057bb8ba6899"
dependencies = [
"arbitrary",
"crc32fast",

@ -39,3 +39,6 @@ codegen-units = 1
lto = true
incremental = false
opt-level = "s"
[patch.crates-io]
tauri = { git = "https://github.com/tauri-apps/tauri", rev = "232265c70e1c213bbb3f84b5541ddc07d330fce1" }

@ -39,9 +39,12 @@ pub enum Error {
/// `reqwest` crate errors.
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
/// The platform was not found on the updater JSON response.
#[error("the platform `{0}` was not found on the response `platforms` object")]
/// The platform was not found in the updater JSON response.
#[error("the platform `{0}` was not found in the response `platforms` object")]
TargetNotFound(String),
/// Neither the platform not the fallback platform was not found in the updater JSON response.
#[error("the platform `{0}` and `{1}` were not found in the response `platforms` object")]
TargetsNotFound(String, String),
/// Download failed
#[error("`{0}`")]
Network(String),
@ -67,8 +70,8 @@ pub enum Error {
TempDirNotFound,
#[error("Authentication failed or was cancelled")]
AuthenticationFailed,
#[error("Failed to install .deb package")]
DebInstallFailed,
#[error("Failed to install package")]
PackageInstallFailed,
#[error("invalid updater binary format")]
InvalidUpdaterFormat,
#[error(transparent)]

@ -26,7 +26,13 @@ use reqwest::{
};
use semver::Version;
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize};
use tauri::{utils::platform::current_exe, AppHandle, Resource, Runtime};
use tauri::{
utils::{
config::BundleType,
platform::{bundle_type, current_exe},
},
AppHandle, Resource, Runtime,
};
use time::OffsetDateTime;
use url::Url;
@ -37,6 +43,31 @@ use crate::{
const UPDATER_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
#[derive(Copy, Clone)]
pub enum Installer {
AppImage,
Deb,
Rpm,
App,
Msi,
Nsis,
}
impl Installer {
fn suffix(self) -> &'static str {
match self {
Self::AppImage => "appimage",
Self::Deb => "deb",
Self::Rpm => "rpm",
Self::App => "app",
Self::Msi => "msi",
Self::Nsis => "nsis",
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ReleaseManifestPlatform {
/// Download URL for the platform
@ -270,7 +301,8 @@ impl UpdaterBuilder {
(target.clone(), target)
} else {
let target = get_updater_target().ok_or(Error::UnsupportedOs)?;
(target.to_string(), format!("{target}-{arch}"))
let json_target = format!("{target}-{arch}");
(target.to_owned(), json_target)
};
let executable_path = self.executable_path.clone().unwrap_or(current_exe()?);
@ -327,7 +359,7 @@ pub struct Updater {
proxy: Option<Url>,
endpoints: Vec<Url>,
arch: &'static str,
// The `{{target}}` variable we replace in the endpoint
// The `{{target}}` variable we replace in the endpoint and serach for in the JSON
target: String,
// The value we search if the updater server returns a JSON with the `platforms` object
json_target: String,
@ -342,6 +374,18 @@ pub struct Updater {
}
impl Updater {
fn get_updater_installer(&self) -> Option<Installer> {
bundle_type().and_then(|t| match t {
BundleType::Deb => Some(Installer::Deb),
BundleType::Rpm => Some(Installer::Rpm),
BundleType::AppImage => Some(Installer::AppImage),
BundleType::Msi => Some(Installer::Msi),
BundleType::Nsis => Some(Installer::Nsis),
BundleType::App => Some(Installer::App),
BundleType::Dmg => None,
})
}
pub async fn check(&self) -> Result<Option<Update>> {
// we want JSON only
let mut headers = self.headers.clone();
@ -466,6 +510,31 @@ impl Updater {
None => release.version > self.current_version,
};
let mut download_url = release.download_url(&self.json_target);
let mut signature = release.signature(&self.json_target);
let installer = self.get_updater_installer();
if installer.is_none() && (download_url.is_err() || signature.is_err()) {
return Err(Error::TargetNotFound(self.json_target.clone()));
}
if let Some(installer) = installer {
let target = &format!("{}-{}", &self.json_target, installer.suffix());
download_url =
release
.download_url(target)
.or(download_url.or(Err(Error::TargetsNotFound(
self.json_target.clone(),
target.clone(),
))));
signature = release
.signature(target)
.or(signature.or(Err(Error::TargetsNotFound(
self.json_target.clone(),
target.clone(),
))));
}
let update = if should_update {
Some(Update {
run_on_main_thread: self.run_on_main_thread.clone(),
@ -477,9 +546,10 @@ impl Updater {
extract_path: self.extract_path.clone(),
version: release.version.to_string(),
date: release.pub_date,
download_url: release.download_url(&self.json_target)?.to_owned(),
signature: release.signature(&self.json_target)?.to_owned(),
body: release.notes,
download_url: download_url?.to_owned(),
body: release.notes.clone(),
signature: signature?.to_owned(),
installer,
raw_json: raw_json.unwrap(),
timeout: None,
proxy: self.proxy.clone(),
@ -513,6 +583,8 @@ pub struct Update {
pub date: Option<OffsetDateTime>,
/// Target
pub target: String,
/// Current installer
pub installer: Option<Installer>,
/// Download URL announced
pub download_url: Url,
/// Signature announced
@ -852,11 +924,10 @@ impl Update {
/// └── ...
///
fn install_inner(&self, bytes: &[u8]) -> Result<()> {
if self.is_deb_package() {
self.install_deb(bytes)
} else {
// Handle AppImage or other formats
self.install_appimage(bytes)
match self.installer {
Some(Installer::Deb) => self.install_deb(bytes),
Some(Installer::Rpm) => self.install_rpm(bytes),
_ => self.install_appimage(bytes),
}
}
@ -933,39 +1004,6 @@ impl Update {
Err(Error::TempDirNotOnSameMountPoint)
}
fn is_deb_package(&self) -> bool {
// First check if we're in a typical Debian installation path
let in_system_path = self
.extract_path
.to_str()
.map(|p| p.starts_with("/usr"))
.unwrap_or(false);
if !in_system_path {
return false;
}
// Then verify it's actually a Debian-based system by checking for dpkg
let dpkg_exists = std::path::Path::new("/var/lib/dpkg").exists();
let apt_exists = std::path::Path::new("/etc/apt").exists();
// Additional check for the package in dpkg database
let package_in_dpkg = if let Ok(output) = std::process::Command::new("dpkg")
.args(["-S", &self.extract_path.to_string_lossy()])
.output()
{
output.status.success()
} else {
false
};
// Consider it a deb package only if:
// 1. We're in a system path AND
// 2. We have Debian package management tools AND
// 3. The binary is tracked by dpkg
dpkg_exists && apt_exists && package_in_dpkg
}
fn install_deb(&self, bytes: &[u8]) -> Result<()> {
// First verify the bytes are actually a .deb package
if !infer::archive::is_deb(bytes) {
@ -973,6 +1011,18 @@ impl Update {
return Err(Error::InvalidUpdaterFormat);
}
self.try_tmp_locations(bytes, "dpkg", "-i")
}
fn install_rpm(&self, bytes: &[u8]) -> Result<()> {
// First verify the bytes are actually a .rpm package
if !infer::archive::is_rpm(bytes) {
return Err(Error::InvalidUpdaterFormat);
}
self.try_tmp_locations(bytes, "rpm", "-U")
}
fn try_tmp_locations(&self, bytes: &[u8], install_cmd: &str, install_arg: &str) -> Result<()> {
// Try different temp directories
let tmp_dir_locations = vec![
Box::new(|| Some(std::env::temp_dir())) as Box<dyn FnOnce() -> Option<PathBuf>>,
@ -984,15 +1034,19 @@ impl Update {
for tmp_dir_location in tmp_dir_locations {
if let Some(path) = tmp_dir_location() {
if let Ok(tmp_dir) = tempfile::Builder::new()
.prefix("tauri_deb_update")
.prefix("tauri_rpm_update")
.tempdir_in(path)
{
let deb_path = tmp_dir.path().join("package.deb");
let pkg_path = tmp_dir.path().join("package.rpm");
// Try writing the .deb file
if std::fs::write(&deb_path, bytes).is_ok() {
if std::fs::write(&pkg_path, bytes).is_ok() {
// If write succeeds, proceed with installation
return self.try_install_with_privileges(&deb_path);
return self.try_install_with_privileges(
&pkg_path,
install_cmd,
install_arg,
);
}
// If write fails, continue to next temp location
}
@ -1003,12 +1057,17 @@ impl Update {
Err(Error::TempDirNotFound)
}
fn try_install_with_privileges(&self, deb_path: &Path) -> Result<()> {
fn try_install_with_privileges(
&self,
pkg_path: &Path,
install_cmd: &str,
install_arg: &str,
) -> Result<()> {
// 1. First try using pkexec (graphical sudo prompt)
if let Ok(status) = std::process::Command::new("pkexec")
.arg("dpkg")
.arg("-i")
.arg(deb_path)
.arg(install_cmd)
.arg(install_arg)
.arg(pkg_path)
.status()
{
if status.success() {
@ -1019,7 +1078,7 @@ impl Update {
// 2. Try zenity or kdialog for a graphical sudo experience
if let Ok(password) = self.get_password_graphically() {
if self.install_with_sudo(deb_path, &password)? {
if self.install_with_sudo(pkg_path, &password, install_cmd, install_arg)? {
log::debug!("installed deb with GUI sudo");
return Ok(());
}
@ -1027,16 +1086,16 @@ impl Update {
// 3. Final fallback: terminal sudo
let status = std::process::Command::new("sudo")
.arg("dpkg")
.arg("-i")
.arg(deb_path)
.arg(install_cmd)
.arg(install_arg)
.arg(pkg_path)
.status()?;
if status.success() {
log::debug!("installed deb with sudo");
Ok(())
} else {
Err(Error::DebInstallFailed)
Err(Error::PackageInstallFailed)
}
}
@ -1070,15 +1129,21 @@ impl Update {
Err(Error::AuthenticationFailed)
}
fn install_with_sudo(&self, deb_path: &Path, password: &str) -> Result<bool> {
fn install_with_sudo(
&self,
pkg_path: &Path,
password: &str,
install_cmd: &str,
install_arg: &str,
) -> Result<bool> {
use std::io::Write;
use std::process::{Command, Stdio};
let mut child = Command::new("sudo")
.arg("-S") // read password from stdin
.arg("dpkg")
.arg("-i")
.arg(deb_path)
.arg(install_cmd)
.arg(install_arg)
.arg(pkg_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
@ -1086,7 +1151,7 @@ impl Update {
if let Some(mut stdin) = child.stdin.take() {
// Write password to stdin
writeln!(stdin, "{}", password)?;
writeln!(stdin, "{password}")?;
}
let status = child.wait()?;

@ -9,7 +9,6 @@ use tauri_plugin_updater::UpdaterExt;
fn main() {
#[allow(unused_mut)]
let mut context = tauri::generate_context!();
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new().build())
.setup(|app| {

@ -2,6 +2,7 @@
"identifier": "com.tauri.updater",
"plugins": {
"updater": {
"dangerousInsecureTransportProtocol": true,
"endpoints": ["http://localhost:3007"],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK",
"windows": {

@ -17,6 +17,7 @@ use tauri::utils::config::{Updater, V1Compatible};
const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5TlFOMFpXYzJFOUdjeHJEVXY4WE1TMUxGNDJVUjNrMmk1WlR3UVJVUWwva0FBQkFBQUFBQUFBQUFBQUlBQUFBQUpVK3ZkM3R3eWhyN3hiUXhQb2hvWFVzUW9FbEs3NlNWYjVkK1F2VGFRU1FEaGxuRUtlell5U0gxYS9DbVRrS0YyZVJGblhjeXJibmpZeGJjS0ZKSUYwYndYc2FCNXpHalM3MHcrODMwN3kwUG9SOWpFNVhCSUd6L0E4TGRUT096TEtLR1JwT1JEVFU9Cg==";
const UPDATED_EXIT_CODE: i32 = 0;
const ERROR_EXIT_CODE: i32 = 1;
const UP_TO_DATE_EXIT_CODE: i32 = 2;
#[derive(Serialize)]
@ -48,7 +49,7 @@ struct Update {
fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTarget) {
let mut command = Command::new("cargo");
command
.args(["tauri", "build", "--debug", "--verbose"])
.args(["tauri", "build", "--verbose"])
.arg("--config")
.arg(serde_json::to_string(config).unwrap())
.env("TAURI_SIGNING_PRIVATE_KEY", UPDATER_PRIVATE_KEY)
@ -80,6 +81,8 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa
#[derive(Copy, Clone)]
enum BundleTarget {
AppImage,
Deb,
Rpm,
App,
@ -91,6 +94,8 @@ impl BundleTarget {
fn name(self) -> &'static str {
match self {
Self::AppImage => "appimage",
Self::Deb => "deb",
Self::Rpm => "rpm",
Self::App => "app",
Self::Msi => "msi",
Self::Nsis => "nsis",
@ -98,7 +103,7 @@ impl BundleTarget {
}
}
impl Default for BundleTarget {
impl BundleTarget {
fn default() -> Self {
#[cfg(any(target_os = "macos", target_os = "ios"))]
return Self::App;
@ -109,57 +114,168 @@ impl Default for BundleTarget {
}
}
fn target_to_platforms(
update_platform: Option<String>,
signature: String,
) -> HashMap<String, PlatformUpdate> {
let mut platforms = HashMap::new();
if let Some(platform) = update_platform {
platforms.insert(
platform,
PlatformUpdate {
signature,
url: "http://localhost:3007/download",
with_elevated_task: false,
},
);
}
platforms
}
#[cfg(target_os = "linux")]
fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> {
vec![(
BundleTarget::AppImage,
root_dir.join(format!(
"target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage"
)),
)]
fn test_cases(
root_dir: &Path,
version: &str,
target: String,
) -> Vec<(BundleTarget, PathBuf, Option<String>, Vec<i32>)> {
vec![
// update using fallback
(
BundleTarget::AppImage,
root_dir.join(format!(
"target/release/bundle/appimage/app-updater_{version}_amd64.AppImage"
)),
Some(target.clone()),
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE],
),
// update using full name
(
BundleTarget::AppImage,
root_dir.join(format!(
"target/release/bundle/appimage/app-updater_{version}_amd64.AppImage"
)),
Some(format!("{target}-{}", BundleTarget::AppImage.name())),
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE],
),
// no update
(
BundleTarget::AppImage,
root_dir.join(format!(
"target/release/bundle/appimage/app-updater_{version}_amd64.AppImage"
)),
None,
vec![ERROR_EXIT_CODE],
),
]
}
#[cfg(target_os = "macos")]
fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> {
vec![(
BundleTarget::App,
root_dir.join("target/debug/bundle/macos/app-updater.app"),
)]
fn test_cases(
root_dir: &Path,
_version: &str,
target: String,
) -> Vec<(BundleTarget, PathBuf, Option<String>, Vec<i32>)> {
vec![
(
BundleTarget::App,
root_dir.join("target/release/bundle/macos/app-updater.app"),
Some(target.clone()),
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE],
),
// update with installer
(
BundleTarget::App,
root_dir.join("target/release/bundle/macos/app-updater.app"),
Some(format!("{target}-{}", BundleTarget::App.name())),
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE],
),
// no update
(
BundleTarget::App,
root_dir.join("target/release/bundle/macos/app-updater.app"),
None,
vec![ERROR_EXIT_CODE],
),
]
}
#[cfg(target_os = "ios")]
fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> {
fn bundle_paths(
root_dir: &Path,
_version: &str,
v1compatible: bool,
) -> Vec<(BundleTarget, PathBuf)> {
vec![(
BundleTarget::App,
root_dir.join("target/debug/bundle/ios/app-updater.ipa"),
root_dir.join("target/release/bundle/ios/app-updater.ipa"),
)]
}
#[cfg(target_os = "android")]
fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf {
root_dir.join("target/debug/bundle/android/app-updater.apk")
fn bundle_path(root_dir: &Path, _version: &str, v1compatible: bool) -> PathBuf {
root_dir.join("target/release/bundle/android/app-updater.apk")
}
#[cfg(windows)]
fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> {
fn test_cases(
root_dir: &Path,
version: &str,
target: String,
) -> Vec<(BundleTarget, PathBuf, Option<String>, Vec<i32>)> {
vec![
(
BundleTarget::Nsis,
root_dir.join(format!(
"target/debug/bundle/nsis/app-updater_{version}_x64-setup.exe"
"target/release/bundle/nsis/app-updater_{version}_x64-setup.exe"
)),
Some(target.clone()),
vec![UPDATED_EXIT_CODE],
),
(
BundleTarget::Nsis,
root_dir.join(format!(
"target/release/bundle/nsis/app-updater_{version}_x64-setup.exe"
)),
Some(format!("{target}-{}", BundleTarget::Nsis.name())),
vec![UPDATED_EXIT_CODE],
),
(
BundleTarget::Nsis,
root_dir.join(format!(
"target/release/bundle/nsis/app-updater_{version}_x64-setup.exe"
)),
None,
vec![ERROR_EXIT_CODE],
),
(
BundleTarget::Msi,
root_dir.join(format!(
"target/debug/bundle/msi/app-updater_{version}_x64_en-US.msi"
"target/release/bundle/msi/app-updater_{version}_x64_en-US.msi"
)),
Some(target.clone()),
vec![UPDATED_EXIT_CODE],
),
(
BundleTarget::Msi,
root_dir.join(format!(
"target/release/bundle/msi/app-updater_{version}_x64_en-US.msi"
)),
Some(format!("{target}-{}", BundleTarget::Msi.name())),
vec![UPDATED_EXIT_CODE],
),
(
BundleTarget::Msi,
root_dir.join(format!(
"target/release/bundle/msi/app-updater_{version}_x64_en-US.msi"
)),
None,
vec![ERROR_EXIT_CODE],
),
]
}
#[test]
#[ignore]
fn update_app() {
let target =
tauri_plugin_updater::target().expect("running updater test in an unsupported platform");
@ -185,9 +301,6 @@ fn update_app() {
Updater::String(V1Compatible::V1Compatible)
);
// bundle app update
build_app(&manifest_dir, &config, true, Default::default());
let updater_zip_ext = if v1_compatible {
if cfg!(windows) {
Some("zip")
@ -200,7 +313,13 @@ fn update_app() {
None
};
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
for (bundle_target, out_bundle_path, update_platform, status_checks) in
test_cases(&root_dir, "1.0.0", target.clone())
{
// bundle app update
config.version = "1.0.0";
build_app(&manifest_dir, &config, true, BundleTarget::default());
let bundle_updater_ext = if v1_compatible {
out_bundle_path
.extension()
@ -228,13 +347,11 @@ fn update_app() {
});
let out_updater_path = out_bundle_path.with_extension(updater_extension);
let updater_path = root_dir.join(format!(
"target/debug/{}",
"target/release/{}",
out_updater_path.file_name().unwrap().to_str().unwrap()
));
std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");
let target = target.clone();
// start the updater server
let server = Arc::new(
tiny_http::Server::http("localhost:3007").expect("failed to start updater server"),
@ -245,16 +362,9 @@ fn update_app() {
for request in server_.incoming_requests() {
match request.url() {
"/" => {
let mut platforms = HashMap::new();
platforms.insert(
target.clone(),
PlatformUpdate {
signature: signature.clone(),
url: "http://localhost:3007/download",
with_elevated_task: false,
},
);
let platforms =
target_to_platforms(update_platform.clone(), signature.clone());
let body = serde_json::to_vec(&Update {
version: "1.0.0",
date: time::OffsetDateTime::now_utc()
@ -293,19 +403,12 @@ fn update_app() {
// bundle initial app version
build_app(&manifest_dir, &config, false, bundle_target);
let status_checks = if matches!(bundle_target, BundleTarget::Msi) {
// for msi we can't really check if the app was updated, because we can't change the install path
vec![UPDATED_EXIT_CODE]
} else {
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE]
};
for expected_exit_code in status_checks {
let mut binary_cmd = if cfg!(windows) {
Command::new(root_dir.join("target/debug/app-updater.exe"))
Command::new(root_dir.join("target/release/app-updater.exe"))
} else if cfg!(target_os = "macos") {
Command::new(
bundle_paths(&root_dir, "0.1.0")
test_cases(&root_dir, "0.1.0", target.clone())
.first()
.unwrap()
.1
@ -313,11 +416,20 @@ fn update_app() {
)
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
let mut c = Command::new("xvfb-run");
c.arg("--auto-servernum")
.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
c.arg("--auto-servernum").arg(
&test_cases(&root_dir, "0.1.0", target.clone())
.first()
.unwrap()
.1,
);
c
} else {
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
Command::new(
&test_cases(&root_dir, "0.1.0", target.clone())
.first()
.unwrap()
.1,
)
};
binary_cmd.env("TARGET", bundle_target.name());
@ -327,7 +439,7 @@ fn update_app() {
if code != expected_exit_code {
panic!(
"failed to run app, expected exit code {expected_exit_code}, got {code}"
"failed to run app bundled as {}, expected exit code {expected_exit_code}, got {code}", bundle_target.name()
);
}
#[cfg(windows)]

Loading…
Cancel
Save