diff --git a/plugins/process/permissions/schemas/schema.json b/plugins/process/permissions/schemas/schema.json index 1243c7d3..95f67149 100644 --- a/plugins/process/permissions/schemas/schema.json +++ b/plugins/process/permissions/schemas/schema.json @@ -322,4 +322,4 @@ ] } } -} +} \ No newline at end of file diff --git a/plugins/updater/src/error.rs b/plugins/updater/src/error.rs index d6d9b0ce..2d508a2e 100644 --- a/plugins/updater/src/error.rs +++ b/plugins/updater/src/error.rs @@ -30,6 +30,9 @@ pub enum Error { /// Operating system is not supported. #[error("Unsupported OS, expected one of `linux`, `darwin` or `windows`.")] UnsupportedOs, + /// Can't determine which type of installer was used for the app + #[error("Couldn't determinet installation method")] + UnknownInstaller, /// Failed to determine updater package extract path #[error("Failed to determine updater package extract path.")] FailedToDetermineExtractPath, @@ -42,6 +45,9 @@ pub enum Error { /// The platform was not found on the updater JSON response. #[error("the platform `{0}` was not found on the response `platforms` object")] TargetNotFound(String), + /// Neither the platform not the fallback platform was not found on the updater JSON response. + #[error("the platform `{0}` and `{1}` were not found on the response `platforms` object")] + TargetsNotFound(String, String), /// Download failed #[error("`{0}`")] Network(String), diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index e93f093a..de4cd40c 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -68,26 +68,30 @@ pub struct RemoteRelease { impl RemoteRelease { /// The release's download URL for the given target. - pub fn download_url(&self, target: &str) -> Result<&Url> { + pub fn download_url(&self, target: &str, fallback_target: &Option) -> Result<&Url> { match self.data { RemoteReleaseInner::Dynamic(ref platform) => Ok(&platform.url), RemoteReleaseInner::Static { ref platforms } => platforms .get(target) - .map_or(Err(Error::TargetNotFound(target.to_string())), |p| { - Ok(&p.url) - }), + .map_or_else( + || match fallback_target { + Some(fallback) => platforms.get(fallback).map_or(Err(Error::TargetsNotFound(target.to_string(), fallback.to_string())), |p| Ok(&p.url)), + None => Err(Error::TargetNotFound(target.to_string())) + }, |p| { Ok(&p.url) }) } } /// The release's signature for the given target. - pub fn signature(&self, target: &str) -> Result<&String> { + pub fn signature(&self, target: &str, fallback_target: &Option) -> Result<&String> { match self.data { RemoteReleaseInner::Dynamic(ref platform) => Ok(&platform.signature), RemoteReleaseInner::Static { ref platforms } => platforms .get(target) - .map_or(Err(Error::TargetNotFound(target.to_string())), |platform| { - Ok(&platform.signature) - }), + .map_or_else( + || match fallback_target { + Some(fallback) => platforms.get(fallback).map_or(Err(Error::TargetsNotFound(target.to_string(), fallback.to_string())), |p| Ok(&p.signature)), + None => Err(Error::TargetNotFound(target.to_string())) + }, |p| { Ok(&p.signature) }) } } } @@ -242,11 +246,16 @@ impl UpdaterBuilder { }; let arch = get_updater_arch().ok_or(Error::UnsupportedArch)?; - let (target, json_target) = if let Some(target) = self.target { - (target.clone(), target) + let (target, json_target, fallback_target) = if let Some(target) = self.target { + (target.clone(), target, None) } else { let target = get_updater_target().ok_or(Error::UnsupportedOs)?; - (target.to_string(), format!("{target}-{arch}")) + let installer = get_updater_installer()?; + let json_target = format!("{target}-{arch}"); + match installer { + Some(installer) => (target.to_owned(), format!("{json_target}-{installer}"), Some(json_target)), + None => (target.to_owned(), json_target, None) + } }; let executable_path = self.executable_path.clone().unwrap_or(current_exe()?); @@ -271,6 +280,7 @@ impl UpdaterBuilder { arch, target, json_target, + fallback_target, headers: self.headers, extract_path, on_before_exit: self.on_before_exit, @@ -299,10 +309,12 @@ pub struct Updater { proxy: Option, endpoints: Vec, 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, + // If target doesn't exist in the JSON check for this one + fallback_target: Option, headers: HeaderMap, extract_path: PathBuf, on_before_exit: Option, @@ -317,7 +329,6 @@ impl Updater { // we want JSON only let mut headers = self.headers.clone(); headers.insert("Accept", HeaderValue::from_str("application/json").unwrap()); - // Set SSL certs for linux if they aren't available. #[cfg(target_os = "linux")] { @@ -420,9 +431,9 @@ 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(), + download_url: release.download_url(&self.json_target, &self.fallback_target)?.to_owned(), body: release.notes.clone(), - signature: release.signature(&self.json_target)?.to_owned(), + signature: release.signature(&self.json_target, &self.fallback_target)?.to_owned(), raw_json: raw_json.unwrap(), timeout: self.timeout, proxy: self.proxy.clone(), @@ -1099,6 +1110,7 @@ pub(crate) fn get_updater_target() -> Option<&'static str> { } } + pub(crate) fn get_updater_arch() -> Option<&'static str> { if cfg!(target_arch = "x86") { Some("i686") @@ -1113,6 +1125,18 @@ pub(crate) fn get_updater_arch() -> Option<&'static str> { } } +pub(crate) fn get_updater_installer() -> Result> { + if cfg!(target_os = "linux") { + Ok(Some("deb")) + } else if cfg!(target_os = "windows") { + Ok(Some("wix")) + } else if cfg!(target_os = "macos") { + Ok(None) + } else { + Err(Error::UnknownInstaller) + } +} + pub fn extract_path_from_executable(executable_path: &Path) -> Result { // Return the path of the current executable by default // Example C:\Program Files\My App\ diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index 230ab376..bb248426 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -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)] @@ -45,7 +46,7 @@ struct Update { platforms: HashMap, } -fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTarget) { +fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, targets: Vec) { let mut command = Command::new("cargo"); command .args(["tauri", "build", "--debug", "--verbose"]) @@ -55,19 +56,20 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa .env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "") .current_dir(cwd); + command.args(["--bundles"]); #[cfg(target_os = "linux")] - command.args(["--bundles", target.name()]); + command.args(targets.into_iter().map(|t| t.name()).collect::>()); #[cfg(target_os = "macos")] - command.args(["--bundles", target.name()]); + command.args([target.name()]); if bundle_updater { #[cfg(windows)] - command.args(["--bundles", "msi", "nsis"]); + command.args(["msi", "nsis"]); - command.args(["--bundles", "updater"]); + command.args(["updater"]); } else { #[cfg(windows)] - command.args(["--bundles", target.name()]); + command.args([target.name()]); } let status = command @@ -82,6 +84,8 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa #[derive(Copy, Clone)] enum BundleTarget { AppImage, + Deb, + Rpm, App, @@ -93,6 +97,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", @@ -100,25 +106,73 @@ impl BundleTarget { } } -impl Default for BundleTarget { - fn default() -> Self { +impl BundleTarget { + fn get_targets() -> Vec { #[cfg(any(target_os = "macos", target_os = "ios"))] - return Self::App; + return vec![Self::App]; #[cfg(target_os = "linux")] - return Self::AppImage; + return vec![Self::AppImage, Self::Deb, Self::Rpm]; #[cfg(windows)] - return Self::Nsis; + return vec![Self::Nsis]; + } +} + +fn insert_plaforms( + bundle_target: BundleTarget, + platforms: &mut HashMap, + target: String, + signature: String, +) { + match bundle_target { + // Should use deb, no fallback + BundleTarget::Deb => { + platforms.insert( + format!("{target}-deb"), + PlatformUpdate { + signature, + url: "http://localhost:3007/download", + with_elevated_task: false, + }, + ); + } + // Should fail + BundleTarget::Rpm => {} + // AppImage should use fallback + _ => { + platforms.insert( + target, + PlatformUpdate { + signature, + url: "http://localhost:3007/download", + with_elevated_task: false, + }, + ); + } } } #[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" - )), - )] + vec![ + ( + BundleTarget::AppImage, + root_dir.join(format!( + "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" + )), + ), + ( + BundleTarget::Deb, + root_dir.join(format!( + "target/debug/bundle/deb/app-updater_{version}_amd64.deb" + )), + ), + ( + BundleTarget::Rpm, + root_dir.join(format!( + "target/debug/bundle/rpm/app-updater_{version}_amd64.rpm" + )), + ), + ] } #[cfg(target_os = "macos")] @@ -161,7 +215,6 @@ fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> } #[test] -#[ignore] fn update_app() { let target = tauri_plugin_updater::target().expect("running updater test in an unsupported platform"); @@ -188,7 +241,7 @@ fn update_app() { ); // bundle app update - build_app(&manifest_dir, &config, true, Default::default()); + build_app(&manifest_dir, &config, true, BundleTarget::get_targets()); let updater_zip_ext = if v1_compatible { if cfg!(windows) { @@ -249,14 +302,13 @@ fn update_app() { "/" => { let mut platforms = HashMap::new(); - platforms.insert( + insert_plaforms( + bundle_target, + &mut platforms, target.clone(), - PlatformUpdate { - signature: signature.clone(), - url: "http://localhost:3007/download", - with_elevated_task: false, - }, + signature.clone(), ); + let body = serde_json::to_vec(&Update { version: "1.0.0", date: time::OffsetDateTime::now_utc() @@ -293,11 +345,13 @@ fn update_app() { config.version = "0.1.0"; // bundle initial app version - build_app(&manifest_dir, &config, false, bundle_target); + build_app(&manifest_dir, &config, false, vec![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 if matches!(bundle_target, BundleTarget::Rpm) { + vec![ERROR_EXIT_CODE] } else { vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE] };