From 72a4670ac54c6e5c348bc4ac6d334b0f29620652 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 12:32:51 +1300 Subject: [PATCH 01/37] Added deb update support --- plugins/updater/src/error.rs | 2 ++ plugins/updater/src/updater.rs | 60 ++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/plugins/updater/src/error.rs b/plugins/updater/src/error.rs index 1f5d6a15f..6f90bcfc6 100644 --- a/plugins/updater/src/error.rs +++ b/plugins/updater/src/error.rs @@ -63,6 +63,8 @@ pub enum Error { TempDirNotOnSameMountPoint, #[error("binary for the current target not found in the archive")] BinaryNotFoundInArchive, + #[error("Failed to install .deb package")] + DebInstallFailed, #[error("invalid updater binary format")] InvalidUpdaterFormat, #[error(transparent)] diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index b1dadd6d2..1396e5b9b 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -748,7 +748,7 @@ impl Update { } } -/// Linux (AppImage) +/// Linux (AppImage and Deb) #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -760,13 +760,18 @@ impl Update { /// ### Expected structure: /// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler /// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage + /// ├── [AppName]_[version]_amd64.deb.tar.gz # GZ generated by tauri-bundler + /// │ └──[AppName]_[version]_amd64.deb # Debian package /// └── ... - /// - /// We should have an AppImage already installed to be able to copy and install - /// the extract_path is the current AppImage path - /// tmp_dir is where our new AppImage is found fn install_inner(&self, bytes: &[u8]) -> Result<()> { use std::os::unix::fs::{MetadataExt, PermissionsExt}; + + // Check if it's a .deb package by examining the file extension + if self.extract_path.extension() == Some(OsStr::new("deb")) { + return self.install_deb(bytes); + } + + // Existing AppImage installation logic let extract_path_metadata = self.extract_path.metadata()?; let tmp_dir_locations = vec![ @@ -797,24 +802,20 @@ impl Update { #[cfg(feature = "zip")] if infer::archive::is_gz(bytes) { // extract the buffer to the tmp_dir - // we extract our signed archive into our final directory without any temp file let archive = Cursor::new(bytes); let decoder = flate2::read::GzDecoder::new(archive); let mut archive = tar::Archive::new(decoder); for mut entry in archive.entries()?.flatten() { if let Ok(path) = entry.path() { if path.extension() == Some(OsStr::new("AppImage")) { - // if something went wrong during the extraction, we should restore previous app if let Err(err) = entry.unpack(&self.extract_path) { std::fs::rename(tmp_app_image, &self.extract_path)?; return Err(err.into()); } - // early finish we have everything we need here return Ok(()); } } } - // if we have not returned early we should restore the backup std::fs::rename(tmp_app_image, &self.extract_path)?; return Err(Error::BinaryNotFoundInArchive); } @@ -823,7 +824,6 @@ impl Update { .and_then(|_| std::fs::set_permissions(&self.extract_path, permissions)) { Err(err) => { - // if something went wrong during the extraction, we should restore previous app std::fs::rename(tmp_app_image, &self.extract_path)?; Err(err.into()) } @@ -835,6 +835,46 @@ impl Update { Err(Error::TempDirNotOnSameMountPoint) } + + fn install_deb(&self, bytes: &[u8]) -> Result<()> { + use std::process::Command; + + // Create a temporary directory to store the .deb file + let tmp_dir = tempfile::Builder::new() + .prefix("tauri_deb_update") + .tempdir()?; + + let deb_path = tmp_dir.path().join("package.deb"); + + // Write the .deb file to the temporary directory + std::fs::write(&deb_path, bytes)?; + + // First, try using pkexec (graphical sudo prompt) + let status = Command::new("pkexec") + .arg("dpkg") + .arg("-i") + .arg(&deb_path) + .status(); + + match status { + Ok(exit_status) if exit_status.success() => Ok(()), + _ => { + // If pkexec fails, try with regular sudo + // Note: This requires the user to have a terminal open + let status = Command::new("sudo") + .arg("dpkg") + .arg("-i") + .arg(&deb_path) + .status()?; + + if status.success() { + Ok(()) + } else { + Err(Error::DebInstallFailed) + } + } + } + } } /// MacOS From c890ec639e4e39f19e3bea8e8f4918456123e500 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 12:42:25 +1300 Subject: [PATCH 02/37] WIP --- Cargo.lock | 1 + plugins/updater/Cargo.toml | 1 + plugins/updater/src/error.rs | 2 + plugins/updater/src/updater.rs | 157 +++++++++++++++++++++++---------- 4 files changed, 116 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2044a052f..69b6b9a37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7026,6 +7026,7 @@ dependencies = [ "futures-util", "http", "infer", + "log", "minisign-verify", "percent-encoding", "reqwest", diff --git a/plugins/updater/Cargo.toml b/plugins/updater/Cargo.toml index 9a1944e8a..29f7c8678 100644 --- a/plugins/updater/Cargo.toml +++ b/plugins/updater/Cargo.toml @@ -26,6 +26,7 @@ ios = { level = "none", notes = "" } tauri-plugin = { workspace = true, features = ["build"] } [dependencies] +log = "0.4" tauri = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/plugins/updater/src/error.rs b/plugins/updater/src/error.rs index 6f90bcfc6..5f6b7d8f3 100644 --- a/plugins/updater/src/error.rs +++ b/plugins/updater/src/error.rs @@ -63,6 +63,8 @@ pub enum Error { TempDirNotOnSameMountPoint, #[error("binary for the current target not found in the archive")] BinaryNotFoundInArchive, + #[error("Authentication failed or was cancelled")] + AuthenticationFailed, #[error("Failed to install .deb package")] DebInstallFailed, #[error("invalid updater binary format")] diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 1396e5b9b..f372cb4e7 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -757,48 +757,46 @@ impl Update { target_os = "openbsd" ))] impl Update { - /// ### Expected structure: - /// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler - /// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage - /// ├── [AppName]_[version]_amd64.deb.tar.gz # GZ generated by tauri-bundler - /// │ └──[AppName]_[version]_amd64.deb # Debian package - /// └── ... fn install_inner(&self, bytes: &[u8]) -> Result<()> { + if self.is_deb_package() { + self.install_deb_update(bytes) + } else { + // Handle AppImage or other formats + self.install_appimage_update(bytes) + } + } + + // Separate the AppImage logic into its own function + fn install_appimage_update(&self, bytes: &[u8]) -> Result<()> { use std::os::unix::fs::{MetadataExt, PermissionsExt}; - // Check if it's a .deb package by examining the file extension - if self.extract_path.extension() == Some(OsStr::new("deb")) { - return self.install_deb(bytes); - } - - // Existing AppImage installation logic let extract_path_metadata = self.extract_path.metadata()?; - + let tmp_dir_locations = vec![ Box::new(|| Some(std::env::temp_dir())) as Box Option>, Box::new(dirs::cache_dir), Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())), ]; - + for tmp_dir_location in tmp_dir_locations { if let Some(tmp_dir_location) = tmp_dir_location() { let tmp_dir = tempfile::Builder::new() .prefix("tauri_current_app") .tempdir_in(tmp_dir_location)?; let tmp_dir_metadata = tmp_dir.path().metadata()?; - + if extract_path_metadata.dev() == tmp_dir_metadata.dev() { let mut perms = tmp_dir_metadata.permissions(); perms.set_mode(0o700); std::fs::set_permissions(tmp_dir.path(), perms)?; - + let tmp_app_image = &tmp_dir.path().join("current_app.AppImage"); - + let permissions = std::fs::metadata(&self.extract_path)?.permissions(); - + // create a backup of our current app image std::fs::rename(&self.extract_path, tmp_app_image)?; - + #[cfg(feature = "zip")] if infer::archive::is_gz(bytes) { // extract the buffer to the tmp_dir @@ -819,7 +817,7 @@ impl Update { std::fs::rename(tmp_app_image, &self.extract_path)?; return Err(Error::BinaryNotFoundInArchive); } - + return match std::fs::write(&self.extract_path, bytes) .and_then(|_| std::fs::set_permissions(&self.extract_path, permissions)) { @@ -832,48 +830,117 @@ impl Update { } } } - + Err(Error::TempDirNotOnSameMountPoint) } - fn install_deb(&self, bytes: &[u8]) -> Result<()> { + fn is_deb_package(&self) -> bool { + // Check if we're running from a .deb installation + // Typically installed in /usr/bin or /usr/local/bin + self.extract_path + .to_str() + .map(|p| p.starts_with("/usr")) + .unwrap_or(false) + } + + fn install_deb_update(&self, bytes: &[u8]) -> Result<()> { use std::process::Command; - // Create a temporary directory to store the .deb file + // Create a temporary directory in /tmp (which is typically writable by all users) let tmp_dir = tempfile::Builder::new() .prefix("tauri_deb_update") - .tempdir()?; + .tempdir_in("/tmp")?; let deb_path = tmp_dir.path().join("package.deb"); - // Write the .deb file to the temporary directory + // Write the .deb file std::fs::write(&deb_path, bytes)?; + + log::info!("Preparing to install .deb update from: {}", deb_path.display()); + + // Try different privilege escalation methods + let installation_result = self.try_install_with_privileges(&deb_path); + + // Clean up the temporary file regardless of installation result + let _ = std::fs::remove_file(&deb_path); + + installation_result + } - // First, try using pkexec (graphical sudo prompt) - let status = Command::new("pkexec") + fn try_install_with_privileges(&self, deb_path: &Path) -> 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) - .status(); + .arg(deb_path) + .status() + { + if status.success() { + log::info!("Successfully installed update using pkexec"); + return Ok(()); + } + } - match status { - Ok(exit_status) if exit_status.success() => Ok(()), - _ => { - // If pkexec fails, try with regular sudo - // Note: This requires the user to have a terminal open - let status = Command::new("sudo") - .arg("dpkg") - .arg("-i") - .arg(&deb_path) - .status()?; - - if status.success() { - Ok(()) - } else { - Err(Error::DebInstallFailed) - } + // 2. Try zenity for a more user-friendly graphical sudo experience + if let Ok(password) = self.get_password_graphically() { + if self.install_with_sudo(deb_path, &password)? { + log::info!("Successfully installed update using sudo (graphical)"); + return Ok(()); } } + + // 3. Final fallback: terminal sudo + log::info!("Falling back to terminal sudo for installation"); + let status = std::process::Command::new("sudo") + .arg("dpkg") + .arg("-i") + .arg(deb_path) + .status()?; + + if status.success() { + Ok(()) + } else { + Err(Error::DebInstallFailed) + } + } + + fn get_password_graphically(&self) -> Result { + let output = std::process::Command::new("zenity") + .args([ + "--password", + "--title=Authentication Required", + "--text=Enter your password to install the update:" + ]) + .output()?; + + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) + } else { + Err(Error::AuthenticationFailed) + } + } + + fn install_with_sudo(&self, deb_path: &Path, password: &str) -> Result { + use std::process::{Command, Stdio}; + use std::io::Write; + + let mut child = Command::new("sudo") + .arg("-S") // read password from stdin + .arg("dpkg") + .arg("-i") + .arg(deb_path) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + if let Some(mut stdin) = child.stdin.take() { + // Write password to stdin + writeln!(stdin, "{}", password)?; + } + + let status = child.wait()?; + Ok(status.success()) } } From 552c7b86c1ff1656a596ce4c8e882ffdfc98ea2d Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 12:59:42 +1300 Subject: [PATCH 03/37] WIP --- plugins/updater/src/updater.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index f372cb4e7..6266a8428 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -758,10 +758,20 @@ impl Update { ))] impl Update { fn install_inner(&self, bytes: &[u8]) -> Result<()> { + log::debug!("Starting Linux update installation"); + log::debug!("Extract path: {:?}", self.extract_path); + + // Check if it's a tar.gz archive + if infer::archive::is_gz(bytes) { + log::debug!("Detected tar.gz archive, attempting to extract"); + return self.handle_targz(bytes); + } + + // If not a tar.gz, try direct installation + log::debug!("Not a tar.gz archive, attempting direct installation"); if self.is_deb_package() { self.install_deb_update(bytes) } else { - // Handle AppImage or other formats self.install_appimage_update(bytes) } } From 0be9dbb2e6ffe39abad948c1a4f429f794360972 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 13:01:28 +1300 Subject: [PATCH 04/37] WIP --- plugins/updater/src/updater.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 6266a8428..eb754a87c 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -758,23 +758,18 @@ impl Update { ))] impl Update { fn install_inner(&self, bytes: &[u8]) -> Result<()> { + log::warn!("=========== UPDATE =================="); log::debug!("Starting Linux update installation"); log::debug!("Extract path: {:?}", self.extract_path); - - // Check if it's a tar.gz archive - if infer::archive::is_gz(bytes) { - log::debug!("Detected tar.gz archive, attempting to extract"); - return self.handle_targz(bytes); - } - // If not a tar.gz, try direct installation - log::debug!("Not a tar.gz archive, attempting direct installation"); if self.is_deb_package() { self.install_deb_update(bytes) } else { + // Handle AppImage or other formats self.install_appimage_update(bytes) } } + // Separate the AppImage logic into its own function fn install_appimage_update(&self, bytes: &[u8]) -> Result<()> { From a0ad60bfb03ee8789b5b2473b016be35f7e03abd Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 13:04:02 +1300 Subject: [PATCH 05/37] updated logging --- plugins/updater/src/updater.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index eb754a87c..6a914dc13 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -759,8 +759,8 @@ impl Update { impl Update { fn install_inner(&self, bytes: &[u8]) -> Result<()> { log::warn!("=========== UPDATE =================="); - log::debug!("Starting Linux update installation"); - log::debug!("Extract path: {:?}", self.extract_path); + log::warn!("Starting Linux update installation"); + log::warn!("Extract path: {:?}", self.extract_path); if self.is_deb_package() { self.install_deb_update(bytes) @@ -861,7 +861,7 @@ impl Update { // Write the .deb file std::fs::write(&deb_path, bytes)?; - log::info!("Preparing to install .deb update from: {}", deb_path.display()); + log::warn!("Preparing to install .deb update from: {}", deb_path.display()); // Try different privilege escalation methods let installation_result = self.try_install_with_privileges(&deb_path); @@ -881,7 +881,7 @@ impl Update { .status() { if status.success() { - log::info!("Successfully installed update using pkexec"); + log::warn!("Successfully installed update using pkexec"); return Ok(()); } } @@ -889,13 +889,13 @@ impl Update { // 2. Try zenity for a more user-friendly graphical sudo experience if let Ok(password) = self.get_password_graphically() { if self.install_with_sudo(deb_path, &password)? { - log::info!("Successfully installed update using sudo (graphical)"); + log::warn!("Successfully installed update using sudo (graphical)"); return Ok(()); } } // 3. Final fallback: terminal sudo - log::info!("Falling back to terminal sudo for installation"); + log::warn!("Falling back to terminal sudo for installation"); let status = std::process::Command::new("sudo") .arg("dpkg") .arg("-i") From fb269f0fb6bd96d9790636c13da2b9faa088c5f8 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 14:55:40 +1300 Subject: [PATCH 06/37] add tarball support --- plugins/updater/src/updater.rs | 43 ++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 6a914dc13..d2fb047ec 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -850,23 +850,52 @@ impl Update { fn install_deb_update(&self, bytes: &[u8]) -> Result<()> { use std::process::Command; + use flate2::read::GzDecoder; + + log::warn!("Starting DEB package installation"); - // Create a temporary directory in /tmp (which is typically writable by all users) + // Create a temporary directory let tmp_dir = tempfile::Builder::new() .prefix("tauri_deb_update") .tempdir_in("/tmp")?; let deb_path = tmp_dir.path().join("package.deb"); - // Write the .deb file - std::fs::write(&deb_path, bytes)?; + // Check if we need to extract from tar.gz first + if infer::archive::is_gz(bytes) { + log::warn!("Detected tar.gz archive, extracting DEB package..."); + let decoder = GzDecoder::new(Cursor::new(bytes)); + let mut archive = tar::Archive::new(decoder); + + // Look for .deb file in archive + let mut found_deb = false; + for mut entry in archive.entries()?.flatten() { + if let Ok(path) = entry.path() { + if path.extension() == Some(OsStr::new("deb")) { + log::warn!("Found DEB package in archive, extracting to: {}", deb_path.display()); + entry.unpack(&deb_path)?; + found_deb = true; + break; + } + } + } + + if !found_deb { + log::error!("No .deb package found in tar.gz archive"); + return Err(Error::BinaryNotFoundInArchive); + } + } else { + // Direct .deb file + log::warn!("Writing DEB package directly to: {}", deb_path.display()); + std::fs::write(&deb_path, bytes)?; + } - log::warn!("Preparing to install .deb update from: {}", deb_path.display()); - + log::warn!("Preparing to install DEB package from: {}", deb_path.display()); + // Try different privilege escalation methods let installation_result = self.try_install_with_privileges(&deb_path); - - // Clean up the temporary file regardless of installation result + + // Clean up let _ = std::fs::remove_file(&deb_path); installation_result From b178029850be750d6b6369c26a0ed35be3b86f85 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 15:00:45 +1300 Subject: [PATCH 07/37] reverted to deb only --- plugins/updater/src/updater.rs | 45 ++++++---------------------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index d2fb047ec..14ab93343 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -849,53 +849,22 @@ impl Update { } fn install_deb_update(&self, bytes: &[u8]) -> Result<()> { - use std::process::Command; - use flate2::read::GzDecoder; - - log::warn!("Starting DEB package installation"); - - // Create a temporary directory + // Create a temporary directory in /tmp (which is typically writable by all users) let tmp_dir = tempfile::Builder::new() .prefix("tauri_deb_update") .tempdir_in("/tmp")?; let deb_path = tmp_dir.path().join("package.deb"); - // Check if we need to extract from tar.gz first - if infer::archive::is_gz(bytes) { - log::warn!("Detected tar.gz archive, extracting DEB package..."); - let decoder = GzDecoder::new(Cursor::new(bytes)); - let mut archive = tar::Archive::new(decoder); - - // Look for .deb file in archive - let mut found_deb = false; - for mut entry in archive.entries()?.flatten() { - if let Ok(path) = entry.path() { - if path.extension() == Some(OsStr::new("deb")) { - log::warn!("Found DEB package in archive, extracting to: {}", deb_path.display()); - entry.unpack(&deb_path)?; - found_deb = true; - break; - } - } - } - - if !found_deb { - log::error!("No .deb package found in tar.gz archive"); - return Err(Error::BinaryNotFoundInArchive); - } - } else { - // Direct .deb file - log::warn!("Writing DEB package directly to: {}", deb_path.display()); - std::fs::write(&deb_path, bytes)?; - } + // Write the .deb file + std::fs::write(&deb_path, bytes)?; - log::warn!("Preparing to install DEB package from: {}", deb_path.display()); - + log::warn!("Preparing to install .deb update from: {}", deb_path.display()); + // Try different privilege escalation methods let installation_result = self.try_install_with_privileges(&deb_path); - - // Clean up + + // Clean up the temporary file regardless of installation result let _ = std::fs::remove_file(&deb_path); installation_result From a3f8462ec178ea86612775b99d45ce63948f43ed Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 15:05:55 +1300 Subject: [PATCH 08/37] add tarball support --- plugins/updater/src/updater.rs | 45 ++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 14ab93343..d2fb047ec 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -849,22 +849,53 @@ impl Update { } fn install_deb_update(&self, bytes: &[u8]) -> Result<()> { - // Create a temporary directory in /tmp (which is typically writable by all users) + use std::process::Command; + use flate2::read::GzDecoder; + + log::warn!("Starting DEB package installation"); + + // Create a temporary directory let tmp_dir = tempfile::Builder::new() .prefix("tauri_deb_update") .tempdir_in("/tmp")?; let deb_path = tmp_dir.path().join("package.deb"); - // Write the .deb file - std::fs::write(&deb_path, bytes)?; + // Check if we need to extract from tar.gz first + if infer::archive::is_gz(bytes) { + log::warn!("Detected tar.gz archive, extracting DEB package..."); + let decoder = GzDecoder::new(Cursor::new(bytes)); + let mut archive = tar::Archive::new(decoder); + + // Look for .deb file in archive + let mut found_deb = false; + for mut entry in archive.entries()?.flatten() { + if let Ok(path) = entry.path() { + if path.extension() == Some(OsStr::new("deb")) { + log::warn!("Found DEB package in archive, extracting to: {}", deb_path.display()); + entry.unpack(&deb_path)?; + found_deb = true; + break; + } + } + } + + if !found_deb { + log::error!("No .deb package found in tar.gz archive"); + return Err(Error::BinaryNotFoundInArchive); + } + } else { + // Direct .deb file + log::warn!("Writing DEB package directly to: {}", deb_path.display()); + std::fs::write(&deb_path, bytes)?; + } - log::warn!("Preparing to install .deb update from: {}", deb_path.display()); - + log::warn!("Preparing to install DEB package from: {}", deb_path.display()); + // Try different privilege escalation methods let installation_result = self.try_install_with_privileges(&deb_path); - - // Clean up the temporary file regardless of installation result + + // Clean up let _ = std::fs::remove_file(&deb_path); installation_result From 7f7b6fd5f5edacf8726b58326ea35324f9df3307 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 15:36:25 +1300 Subject: [PATCH 09/37] Removed logs --- Cargo.lock | 1 - plugins/updater/Cargo.toml | 1 - plugins/updater/src/updater.rs | 23 ++++++++--------------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69b6b9a37..2044a052f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7026,7 +7026,6 @@ dependencies = [ "futures-util", "http", "infer", - "log", "minisign-verify", "percent-encoding", "reqwest", diff --git a/plugins/updater/Cargo.toml b/plugins/updater/Cargo.toml index 29f7c8678..9a1944e8a 100644 --- a/plugins/updater/Cargo.toml +++ b/plugins/updater/Cargo.toml @@ -26,7 +26,6 @@ ios = { level = "none", notes = "" } tauri-plugin = { workspace = true, features = ["build"] } [dependencies] -log = "0.4" tauri = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index d2fb047ec..b79851680 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -757,11 +757,15 @@ impl Update { target_os = "openbsd" ))] impl Update { + /// ### Expected structure: + /// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler + /// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage + /// ├── [AppName]_[version]_amd64.deb.tar.gz # GZ generated by tauri-bundler + /// │ └──[AppName]_[version]_amd64.deb # Debian package + /// ├── [AppName]_[version]_amd64.deb # Debian package + /// └── ... + /// fn install_inner(&self, bytes: &[u8]) -> Result<()> { - log::warn!("=========== UPDATE =================="); - log::warn!("Starting Linux update installation"); - log::warn!("Extract path: {:?}", self.extract_path); - if self.is_deb_package() { self.install_deb_update(bytes) } else { @@ -849,11 +853,8 @@ impl Update { } fn install_deb_update(&self, bytes: &[u8]) -> Result<()> { - use std::process::Command; use flate2::read::GzDecoder; - log::warn!("Starting DEB package installation"); - // Create a temporary directory let tmp_dir = tempfile::Builder::new() .prefix("tauri_deb_update") @@ -863,7 +864,6 @@ impl Update { // Check if we need to extract from tar.gz first if infer::archive::is_gz(bytes) { - log::warn!("Detected tar.gz archive, extracting DEB package..."); let decoder = GzDecoder::new(Cursor::new(bytes)); let mut archive = tar::Archive::new(decoder); @@ -872,7 +872,6 @@ impl Update { for mut entry in archive.entries()?.flatten() { if let Ok(path) = entry.path() { if path.extension() == Some(OsStr::new("deb")) { - log::warn!("Found DEB package in archive, extracting to: {}", deb_path.display()); entry.unpack(&deb_path)?; found_deb = true; break; @@ -886,11 +885,8 @@ impl Update { } } else { // Direct .deb file - log::warn!("Writing DEB package directly to: {}", deb_path.display()); std::fs::write(&deb_path, bytes)?; } - - log::warn!("Preparing to install DEB package from: {}", deb_path.display()); // Try different privilege escalation methods let installation_result = self.try_install_with_privileges(&deb_path); @@ -910,7 +906,6 @@ impl Update { .status() { if status.success() { - log::warn!("Successfully installed update using pkexec"); return Ok(()); } } @@ -918,13 +913,11 @@ impl Update { // 2. Try zenity for a more user-friendly graphical sudo experience if let Ok(password) = self.get_password_graphically() { if self.install_with_sudo(deb_path, &password)? { - log::warn!("Successfully installed update using sudo (graphical)"); return Ok(()); } } // 3. Final fallback: terminal sudo - log::warn!("Falling back to terminal sudo for installation"); let status = std::process::Command::new("sudo") .arg("dpkg") .arg("-i") From c88064b4b9768bde026fe39850b3cda04f340961 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 15:37:50 +1300 Subject: [PATCH 10/37] removed spaces --- plugins/updater/src/updater.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index b79851680..eedad632b 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -774,38 +774,28 @@ impl Update { } } - - // Separate the AppImage logic into its own function fn install_appimage_update(&self, bytes: &[u8]) -> Result<()> { use std::os::unix::fs::{MetadataExt, PermissionsExt}; - let extract_path_metadata = self.extract_path.metadata()?; - let tmp_dir_locations = vec![ Box::new(|| Some(std::env::temp_dir())) as Box Option>, Box::new(dirs::cache_dir), Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())), ]; - for tmp_dir_location in tmp_dir_locations { if let Some(tmp_dir_location) = tmp_dir_location() { let tmp_dir = tempfile::Builder::new() .prefix("tauri_current_app") .tempdir_in(tmp_dir_location)?; let tmp_dir_metadata = tmp_dir.path().metadata()?; - if extract_path_metadata.dev() == tmp_dir_metadata.dev() { let mut perms = tmp_dir_metadata.permissions(); perms.set_mode(0o700); std::fs::set_permissions(tmp_dir.path(), perms)?; - let tmp_app_image = &tmp_dir.path().join("current_app.AppImage"); - let permissions = std::fs::metadata(&self.extract_path)?.permissions(); - // create a backup of our current app image std::fs::rename(&self.extract_path, tmp_app_image)?; - #[cfg(feature = "zip")] if infer::archive::is_gz(bytes) { // extract the buffer to the tmp_dir @@ -826,7 +816,6 @@ impl Update { std::fs::rename(tmp_app_image, &self.extract_path)?; return Err(Error::BinaryNotFoundInArchive); } - return match std::fs::write(&self.extract_path, bytes) .and_then(|_| std::fs::set_permissions(&self.extract_path, permissions)) { From 9b5d30a1a6ea81f9171111d6509186d141c1841a Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 15:39:48 +1300 Subject: [PATCH 11/37] fixed comments --- plugins/updater/src/updater.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index eedad632b..b47958b55 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -777,42 +777,52 @@ impl Update { fn install_appimage_update(&self, bytes: &[u8]) -> Result<()> { use std::os::unix::fs::{MetadataExt, PermissionsExt}; let extract_path_metadata = self.extract_path.metadata()?; + let tmp_dir_locations = vec![ Box::new(|| Some(std::env::temp_dir())) as Box Option>, Box::new(dirs::cache_dir), Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())), ]; + for tmp_dir_location in tmp_dir_locations { if let Some(tmp_dir_location) = tmp_dir_location() { let tmp_dir = tempfile::Builder::new() .prefix("tauri_current_app") .tempdir_in(tmp_dir_location)?; let tmp_dir_metadata = tmp_dir.path().metadata()?; + if extract_path_metadata.dev() == tmp_dir_metadata.dev() { let mut perms = tmp_dir_metadata.permissions(); perms.set_mode(0o700); std::fs::set_permissions(tmp_dir.path(), perms)?; let tmp_app_image = &tmp_dir.path().join("current_app.AppImage"); + let permissions = std::fs::metadata(&self.extract_path)?.permissions(); + // create a backup of our current app image std::fs::rename(&self.extract_path, tmp_app_image)?; + #[cfg(feature = "zip")] if infer::archive::is_gz(bytes) { // extract the buffer to the tmp_dir + // we extract our signed archive into our final directory without any temp file let archive = Cursor::new(bytes); let decoder = flate2::read::GzDecoder::new(archive); let mut archive = tar::Archive::new(decoder); for mut entry in archive.entries()?.flatten() { if let Ok(path) = entry.path() { if path.extension() == Some(OsStr::new("AppImage")) { + // if something went wrong during the extraction, we should restore previous app if let Err(err) = entry.unpack(&self.extract_path) { std::fs::rename(tmp_app_image, &self.extract_path)?; return Err(err.into()); } + // early finish we have everything we need here return Ok(()); } } } + // if we have not returned early we should restore the backup std::fs::rename(tmp_app_image, &self.extract_path)?; return Err(Error::BinaryNotFoundInArchive); } @@ -820,6 +830,7 @@ impl Update { .and_then(|_| std::fs::set_permissions(&self.extract_path, permissions)) { Err(err) => { + // if something went wrong during the extraction, we should restore previous app std::fs::rename(tmp_app_image, &self.extract_path)?; Err(err.into()) } @@ -828,7 +839,7 @@ impl Update { } } } - + Err(Error::TempDirNotOnSameMountPoint) } From 408ccba534eaa71913882908433bd898fbaa74d2 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 15:41:07 +1300 Subject: [PATCH 12/37] Clean up comments --- plugins/updater/src/updater.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index b47958b55..0a9529692 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -795,6 +795,7 @@ impl Update { let mut perms = tmp_dir_metadata.permissions(); perms.set_mode(0o700); std::fs::set_permissions(tmp_dir.path(), perms)?; + let tmp_app_image = &tmp_dir.path().join("current_app.AppImage"); let permissions = std::fs::metadata(&self.extract_path)?.permissions(); @@ -826,6 +827,7 @@ impl Update { std::fs::rename(tmp_app_image, &self.extract_path)?; return Err(Error::BinaryNotFoundInArchive); } + return match std::fs::write(&self.extract_path, bytes) .and_then(|_| std::fs::set_permissions(&self.extract_path, permissions)) { @@ -839,7 +841,7 @@ impl Update { } } } - + Err(Error::TempDirNotOnSameMountPoint) } From 4d4f2f89015d7337a8ad97517911a770aaeb162e Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 15:49:19 +1300 Subject: [PATCH 13/37] Fixed console log --- plugins/updater/src/updater.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 0a9529692..9307a9bc0 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -882,7 +882,6 @@ impl Update { } if !found_deb { - log::error!("No .deb package found in tar.gz archive"); return Err(Error::BinaryNotFoundInArchive); } } else { From 37ec6294f834960469a933f7d219d13bd496a7c9 Mon Sep 17 00:00:00 2001 From: jLynx Date: Mon, 4 Nov 2024 10:29:36 +1300 Subject: [PATCH 14/37] Removed gz support --- plugins/updater/src/updater.rs | 50 ++++++++-------------------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 9307a9bc0..a4d082060 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -760,11 +760,9 @@ impl Update { /// ### Expected structure: /// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler /// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage - /// ├── [AppName]_[version]_amd64.deb.tar.gz # GZ generated by tauri-bundler - /// │ └──[AppName]_[version]_amd64.deb # Debian package /// ├── [AppName]_[version]_amd64.deb # Debian package /// └── ... - /// + /// fn install_inner(&self, bytes: &[u8]) -> Result<()> { if self.is_deb_package() { self.install_deb_update(bytes) @@ -795,7 +793,7 @@ impl Update { let mut perms = tmp_dir_metadata.permissions(); perms.set_mode(0o700); std::fs::set_permissions(tmp_dir.path(), perms)?; - + let tmp_app_image = &tmp_dir.path().join("current_app.AppImage"); let permissions = std::fs::metadata(&self.extract_path)?.permissions(); @@ -855,46 +853,22 @@ impl Update { } fn install_deb_update(&self, bytes: &[u8]) -> Result<()> { - use flate2::read::GzDecoder; - // Create a temporary directory let tmp_dir = tempfile::Builder::new() .prefix("tauri_deb_update") .tempdir_in("/tmp")?; - + let deb_path = tmp_dir.path().join("package.deb"); - - // Check if we need to extract from tar.gz first - if infer::archive::is_gz(bytes) { - let decoder = GzDecoder::new(Cursor::new(bytes)); - let mut archive = tar::Archive::new(decoder); - - // Look for .deb file in archive - let mut found_deb = false; - for mut entry in archive.entries()?.flatten() { - if let Ok(path) = entry.path() { - if path.extension() == Some(OsStr::new("deb")) { - entry.unpack(&deb_path)?; - found_deb = true; - break; - } - } - } - - if !found_deb { - return Err(Error::BinaryNotFoundInArchive); - } - } else { - // Direct .deb file - std::fs::write(&deb_path, bytes)?; - } - + + // Direct .deb file + std::fs::write(&deb_path, bytes)?; + // Try different privilege escalation methods let installation_result = self.try_install_with_privileges(&deb_path); - + // Clean up let _ = std::fs::remove_file(&deb_path); - + installation_result } @@ -937,7 +911,7 @@ impl Update { .args([ "--password", "--title=Authentication Required", - "--text=Enter your password to install the update:" + "--text=Enter your password to install the update:", ]) .output()?; @@ -949,11 +923,11 @@ impl Update { } fn install_with_sudo(&self, deb_path: &Path, password: &str) -> Result { - use std::process::{Command, Stdio}; use std::io::Write; + use std::process::{Command, Stdio}; let mut child = Command::new("sudo") - .arg("-S") // read password from stdin + .arg("-S") // read password from stdin .arg("dpkg") .arg("-i") .arg(deb_path) From be6031500596dd3809199637b79cf1ea870b6fdc Mon Sep 17 00:00:00 2001 From: jLynx Date: Tue, 12 Nov 2024 14:30:52 +1300 Subject: [PATCH 15/37] Updated detection method --- plugins/updater/src/updater.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index a4d082060..8db0e3f3e 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -844,12 +844,35 @@ impl Update { } fn is_deb_package(&self) -> bool { - // Check if we're running from a .deb installation - // Typically installed in /usr/bin or /usr/local/bin - self.extract_path + // 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) + .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_update(&self, bytes: &[u8]) -> Result<()> { From a8866b9533c32c7284f2205fe42c4e12a7ab3227 Mon Sep 17 00:00:00 2001 From: jLynx Date: Tue, 12 Nov 2024 14:52:55 +1300 Subject: [PATCH 16/37] Fixed formatting --- plugins/updater/src/updater.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 8db0e3f3e..64288b449 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -845,19 +845,20 @@ impl Update { fn is_deb_package(&self) -> bool { // First check if we're in a typical Debian installation path - let in_system_path = self.extract_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()]) @@ -867,7 +868,7 @@ impl Update { } 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 From 337e23eb7818f9c995cbda3a90caca379a081d5c Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 11:13:00 +1300 Subject: [PATCH 17/37] refactor --- plugins/updater/src/error.rs | 2 ++ plugins/updater/src/updater.rs | 33 +++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/plugins/updater/src/error.rs b/plugins/updater/src/error.rs index 5f6b7d8f3..d6d9b0ced 100644 --- a/plugins/updater/src/error.rs +++ b/plugins/updater/src/error.rs @@ -63,6 +63,8 @@ pub enum Error { TempDirNotOnSameMountPoint, #[error("binary for the current target not found in the archive")] BinaryNotFoundInArchive, + #[error("failed to create temporary directory")] + TempDirNotFound, #[error("Authentication failed or was cancelled")] AuthenticationFailed, #[error("Failed to install .deb package")] diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 64288b449..9e4f0fed2 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -765,14 +765,14 @@ impl Update { /// fn install_inner(&self, bytes: &[u8]) -> Result<()> { if self.is_deb_package() { - self.install_deb_update(bytes) + self.install_deb(bytes) } else { // Handle AppImage or other formats - self.install_appimage_update(bytes) + self.install_appimage(bytes) } } - fn install_appimage_update(&self, bytes: &[u8]) -> Result<()> { + fn install_appimage(&self, bytes: &[u8]) -> Result<()> { use std::os::unix::fs::{MetadataExt, PermissionsExt}; let extract_path_metadata = self.extract_path.metadata()?; @@ -876,23 +876,28 @@ impl Update { dpkg_exists && apt_exists && package_in_dpkg } - fn install_deb_update(&self, bytes: &[u8]) -> Result<()> { - // Create a temporary directory - let tmp_dir = tempfile::Builder::new() - .prefix("tauri_deb_update") - .tempdir_in("/tmp")?; - + fn install_deb(&self, bytes: &[u8]) -> Result<()> { + // Try different temp directories, similar to AppImage handling + let tmp_dir_locations = ["/tmp", "/var/tmp", "/dev/shm"]; + + let tmp_dir = tmp_dir_locations + .iter() + .find_map(|dir| { + tempfile::Builder::new() + .prefix("tauri_deb_update") + .tempdir_in(dir) + .ok() + }) + .ok_or_else(|| Error::TempDirNotFound)?; + let deb_path = tmp_dir.path().join("package.deb"); - + // Direct .deb file std::fs::write(&deb_path, bytes)?; - + // Try different privilege escalation methods let installation_result = self.try_install_with_privileges(&deb_path); - // Clean up - let _ = std::fs::remove_file(&deb_path); - installation_result } From d8dcfef823f2b6bdeb0c3dbc3641f57138c00962 Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 11:19:46 +1300 Subject: [PATCH 18/37] Added changes file --- .changes/deb-update-support.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/deb-update-support.md diff --git a/.changes/deb-update-support.md b/.changes/deb-update-support.md new file mode 100644 index 000000000..60d8cd56d --- /dev/null +++ b/.changes/deb-update-support.md @@ -0,0 +1,5 @@ +--- +"updater": "minor" +--- + +Added support for .deb package updates on Linux systems, including new error types (TempDirNotFound, AuthenticationFailed, DebInstallFailed) and implementation of .deb package installation with privilege escalation handling. \ No newline at end of file From f9f9297c78e94d02b2d8ab7a3dcbd345acf29350 Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 11:25:10 +1300 Subject: [PATCH 19/37] Added update with kdialog support --- plugins/updater/src/updater.rs | 36 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 9e4f0fed2..91af4d4c9 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -879,7 +879,7 @@ impl Update { fn install_deb(&self, bytes: &[u8]) -> Result<()> { // Try different temp directories, similar to AppImage handling let tmp_dir_locations = ["/tmp", "/var/tmp", "/dev/shm"]; - + let tmp_dir = tmp_dir_locations .iter() .find_map(|dir| { @@ -889,12 +889,12 @@ impl Update { .ok() }) .ok_or_else(|| Error::TempDirNotFound)?; - + let deb_path = tmp_dir.path().join("package.deb"); - + // Direct .deb file std::fs::write(&deb_path, bytes)?; - + // Try different privilege escalation methods let installation_result = self.try_install_with_privileges(&deb_path); @@ -914,7 +914,7 @@ impl Update { } } - // 2. Try zenity for a more user-friendly graphical sudo experience + // 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)? { return Ok(()); @@ -936,19 +936,33 @@ impl Update { } fn get_password_graphically(&self) -> Result { - let output = std::process::Command::new("zenity") + // Try zenity first + let zenity_result = std::process::Command::new("zenity") .args([ "--password", "--title=Authentication Required", "--text=Enter your password to install the update:", ]) - .output()?; + .output(); - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { - Err(Error::AuthenticationFailed) + if let Ok(output) = zenity_result { + if output.status.success() { + return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()); + } } + + // Fall back to kdialog if zenity fails or isn't available + let kdialog_result = std::process::Command::new("kdialog") + .args(["--password", "Enter your password to install the update:"]) + .output(); + + if let Ok(output) = kdialog_result { + if output.status.success() { + return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()); + } + } + + Err(Error::AuthenticationFailed) } fn install_with_sudo(&self, deb_path: &Path, password: &str) -> Result { From 466cdaefa53ea84d785b1ca17e6753bc1cbf2154 Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 11:30:15 +1300 Subject: [PATCH 20/37] Check if file is deb --- plugins/updater/src/updater.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 91af4d4c9..ab38941a1 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -877,7 +877,12 @@ impl Update { } fn install_deb(&self, bytes: &[u8]) -> Result<()> { - // Try different temp directories, similar to AppImage handling + // First verify the bytes are actually a .deb package + if !infer::archive::is_deb(bytes) { + return Err(Error::InvalidUpdaterFormat); + } + + // Try different temp directories let tmp_dir_locations = ["/tmp", "/var/tmp", "/dev/shm"]; let tmp_dir = tmp_dir_locations From 7d70f62a583563689777992caf799b33de379a92 Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 12:07:29 +1300 Subject: [PATCH 21/37] Updated as per comments --- .changes/deb-update-support.md | 2 +- plugins/updater/src/updater.rs | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.changes/deb-update-support.md b/.changes/deb-update-support.md index 60d8cd56d..e71c9e77d 100644 --- a/.changes/deb-update-support.md +++ b/.changes/deb-update-support.md @@ -2,4 +2,4 @@ "updater": "minor" --- -Added support for .deb package updates on Linux systems, including new error types (TempDirNotFound, AuthenticationFailed, DebInstallFailed) and implementation of .deb package installation with privilege escalation handling. \ No newline at end of file +Added support for `.deb` package updates on Linux systems. \ No newline at end of file diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index ab38941a1..1bbf06bd4 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -883,15 +883,23 @@ impl Update { } // Try different temp directories - let tmp_dir_locations = ["/tmp", "/var/tmp", "/dev/shm"]; + let tmp_dir_locations = vec![ + Box::new(|| Some(std::env::temp_dir())) as Box Option>, + Box::new(dirs::cache_dir), + Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())), + ]; let tmp_dir = tmp_dir_locations - .iter() - .find_map(|dir| { - tempfile::Builder::new() - .prefix("tauri_deb_update") - .tempdir_in(dir) - .ok() + .into_iter() + .find_map(|loc| { + if let Some(path) = loc() { + tempfile::Builder::new() + .prefix("tauri_deb_update") + .tempdir_in(path) + .ok() + } else { + None + } }) .ok_or_else(|| Error::TempDirNotFound)?; @@ -901,9 +909,7 @@ impl Update { std::fs::write(&deb_path, bytes)?; // Try different privilege escalation methods - let installation_result = self.try_install_with_privileges(&deb_path); - - installation_result + self.try_install_with_privileges(&deb_path) } fn try_install_with_privileges(&self, deb_path: &Path) -> Result<()> { From ffec0edaa50d91deec8db2e11e17e42f78081d4f Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 13:25:49 +1300 Subject: [PATCH 22/37] Added appimage implementaion --- plugins/updater/src/updater.rs | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 1bbf06bd4..d66af3dc0 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -889,27 +889,27 @@ impl Update { Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())), ]; - let tmp_dir = tmp_dir_locations - .into_iter() - .find_map(|loc| { - if let Some(path) = loc() { - tempfile::Builder::new() - .prefix("tauri_deb_update") - .tempdir_in(path) - .ok() - } else { - None + // Try writing to multiple temp locations until one succeeds + 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") + .tempdir_in(path) + { + let deb_path = tmp_dir.path().join("package.deb"); + + // Try writing the .deb file + if std::fs::write(&deb_path, bytes).is_ok() { + // If write succeeds, proceed with installation + return self.try_install_with_privileges(&deb_path); + } + // If write fails, continue to next temp location } - }) - .ok_or_else(|| Error::TempDirNotFound)?; - - let deb_path = tmp_dir.path().join("package.deb"); - - // Direct .deb file - std::fs::write(&deb_path, bytes)?; + } + } - // Try different privilege escalation methods - self.try_install_with_privileges(&deb_path) + // If we get here, all temp locations failed + Err(Error::TempDirNotFound) } fn try_install_with_privileges(&self, deb_path: &Path) -> Result<()> { From f79dfbf81af392786c40d59ac168dca0a9defaae Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 14:42:31 +1300 Subject: [PATCH 23/37] WIP test for deb --- .../updater/tests/app-updater/tests/update.rs | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index 230ab3763..6deb21e4c 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -82,9 +82,8 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa #[derive(Copy, Clone)] enum BundleTarget { AppImage, - + Deb, App, - Msi, Nsis, } @@ -93,6 +92,7 @@ impl BundleTarget { fn name(self) -> &'static str { match self { Self::AppImage => "appimage", + Self::Deb => "deb", Self::App => "app", Self::Msi => "msi", Self::Nsis => "nsis", @@ -105,7 +105,15 @@ impl Default for BundleTarget { #[cfg(any(target_os = "macos", target_os = "ios"))] return Self::App; #[cfg(target_os = "linux")] - return Self::AppImage; + { + // Check if we're on a Debian-based system + if std::path::Path::new("/var/lib/dpkg").exists() + && std::path::Path::new("/etc/apt").exists() + { + return Self::Deb; + } + return Self::AppImage; + } #[cfg(windows)] return Self::Nsis; } @@ -113,12 +121,23 @@ impl Default for BundleTarget { #[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" - )), - )] + // Check if we're on a Debian-based system + if std::path::Path::new("/var/lib/dpkg").exists() + && std::path::Path::new("/etc/apt").exists() { + vec![( + BundleTarget::Deb, + root_dir.join(format!( + "target/debug/bundle/deb/app-updater_{version}_amd64.deb" + )), + )] + } else { + vec![( + BundleTarget::AppImage, + root_dir.join(format!( + "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" + )), + )] + } } #[cfg(target_os = "macos")] From 7346ade1325132bd75ff8b1124275351f6c23c19 Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 15:25:21 +1300 Subject: [PATCH 24/37] got deb running --- .github/workflows/integration-tests.yml | 4 +- .../updater/tests/app-updater/tests/update.rs | 410 ++++++++++-------- 2 files changed, 237 insertions(+), 177 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index fbbca96ab..10403a8b4 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -46,7 +46,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: install Tauri CLI - run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev + run: sudo cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev - name: run integration tests - run: cargo test --test '*' -- --ignored + run: sudo cargo test --test '*' -- --ignored diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index 6deb21e4c..f96bff33b 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -79,7 +79,7 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] enum BundleTarget { AppImage, Deb, @@ -106,12 +106,6 @@ impl Default for BundleTarget { return Self::App; #[cfg(target_os = "linux")] { - // Check if we're on a Debian-based system - if std::path::Path::new("/var/lib/dpkg").exists() - && std::path::Path::new("/etc/apt").exists() - { - return Self::Deb; - } return Self::AppImage; } #[cfg(windows)] @@ -121,23 +115,21 @@ impl Default for BundleTarget { #[cfg(target_os = "linux")] fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { - // Check if we're on a Debian-based system - if std::path::Path::new("/var/lib/dpkg").exists() - && std::path::Path::new("/etc/apt").exists() { - vec![( - BundleTarget::Deb, - root_dir.join(format!( - "target/debug/bundle/deb/app-updater_{version}_amd64.deb" - )), - )] - } else { - vec![( + // Return both AppImage and Deb paths + 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" + )), + ), + ] } #[cfg(target_os = "macos")] @@ -187,179 +179,247 @@ fn update_app() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let root_dir = manifest_dir.join("../../../.."); - for mut config in [ - Config { - version: "1.0.0", - bundle: BundleConfig { - create_updater_artifacts: Updater::Bool(true), + // Set up cleanup on panic + let cleanup = std::panic::AssertUnwindSafe(|| { + #[cfg(target_os = "linux")] + { + let _ = std::process::Command::new("sudo") + .arg("dpkg") + .arg("-r") + .arg("app-updater") + .status(); + } + }); + + let test_result = std::panic::catch_unwind(|| { + for mut config in [ + Config { + version: "1.0.0", + bundle: BundleConfig { + create_updater_artifacts: Updater::Bool(true), + }, }, - }, - Config { - version: "1.0.0", - bundle: BundleConfig { - create_updater_artifacts: Updater::String(V1Compatible::V1Compatible), + Config { + version: "1.0.0", + bundle: BundleConfig { + create_updater_artifacts: Updater::String(V1Compatible::V1Compatible), + }, }, - }, - ] { - let v1_compatible = matches!( - config.bundle.create_updater_artifacts, - 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") - } else { + ] { + let v1_compatible = matches!( + config.bundle.create_updater_artifacts, + 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") + } else { + Some("tar.gz") + } + } else if cfg!(target_os = "macos") { Some("tar.gz") - } - } else if cfg!(target_os = "macos") { - Some("tar.gz") - } else { - None - }; - - for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { - let bundle_updater_ext = if v1_compatible { - out_bundle_path - .extension() - .unwrap() - .to_str() - .unwrap() - .replace("exe", "nsis") - } else { - out_bundle_path - .extension() - .unwrap() - .to_str() - .unwrap() - .to_string() - }; - let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext { - format!("{bundle_updater_ext}.{updater_zip_ext}") } else { - bundle_updater_ext + None }; - let signature_extension = format!("{updater_extension}.sig"); - let signature_path = out_bundle_path.with_extension(signature_extension); - let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| { - panic!("failed to read signature file {}", signature_path.display()) - }); - let out_updater_path = out_bundle_path.with_extension(updater_extension); - let updater_path = root_dir.join(format!( - "target/debug/{}", - 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"), - ); - let server_ = server.clone(); - std::thread::spawn(move || { - 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 body = serde_json::to_vec(&Update { - version: "1.0.0", - date: time::OffsetDateTime::now_utc() - .format(&time::format_description::well_known::Rfc3339) - .unwrap(), - platforms, - }) - .unwrap(); - let len = body.len(); - let response = tiny_http::Response::new( - tiny_http::StatusCode(200), - Vec::new(), - std::io::Cursor::new(body), - Some(len), - None, - ); - let _ = request.respond(response); + for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { + let bundle_updater_ext = if v1_compatible { + out_bundle_path + .extension() + .unwrap() + .to_str() + .unwrap() + .replace("exe", "nsis") + } else { + out_bundle_path + .extension() + .unwrap() + .to_str() + .unwrap() + .to_string() + }; + let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext { + format!("{bundle_updater_ext}.{updater_zip_ext}") + } else { + bundle_updater_ext + }; + let signature_extension = format!("{updater_extension}.sig"); + let signature_path = out_bundle_path.with_extension(signature_extension); + let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| { + panic!("failed to read signature file {}", signature_path.display()) + }); + let out_updater_path = out_bundle_path.with_extension(updater_extension); + let updater_path = root_dir.join(format!( + "target/debug/{}", + 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"), + ); + + let server_ = server.clone(); + std::thread::spawn(move || { + 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 body = serde_json::to_vec(&Update { + version: "1.0.0", + date: time::OffsetDateTime::now_utc() + .format(&time::format_description::well_known::Rfc3339) + .unwrap(), + platforms, + }) + .unwrap(); + let len = body.len(); + let response = tiny_http::Response::new( + tiny_http::StatusCode(200), + Vec::new(), + std::io::Cursor::new(body), + Some(len), + None, + ); + let _ = request.respond(response); + } + "/download" => { + let _ = request.respond(tiny_http::Response::from_file( + File::open(&updater_path).unwrap_or_else(|_| { + panic!( + "failed to open updater bundle {}", + updater_path.display() + ) + }), + )); + } + _ => (), } - "/download" => { - let _ = request.respond(tiny_http::Response::from_file( - File::open(&updater_path).unwrap_or_else(|_| { - panic!( - "failed to open updater bundle {}", - updater_path.display() - ) - }), - )); + } + }); + + config.version = "0.1.0"; + + // bundle initial app version + build_app(&manifest_dir, &config, false, bundle_target); + + // Set appropriate permissions and install package if needed + #[cfg(target_os = "linux")] + { + let paths = bundle_paths(&root_dir, "0.1.0"); + let bundle_path = &paths.first().unwrap().1; + + if bundle_target == BundleTarget::AppImage { + std::process::Command::new("sudo") + .arg("chmod") + .arg("+x") + .arg(bundle_path) + .status() + .expect("failed to change permissions"); + } else if bundle_target == BundleTarget::Deb { + // Install the .deb package + let install_status = std::process::Command::new("sudo") + .arg("dpkg") + .arg("-i") + .arg(bundle_path) + .status() + .expect("failed to install .deb package"); + + if !install_status.success() { + panic!("Failed to install .deb package"); } - _ => (), } } - }); - - config.version = "0.1.0"; - // 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")) - } else if cfg!(target_os = "macos") { - Command::new( - bundle_paths(&root_dir, "0.1.0") - .first() - .unwrap() - .1 - .join("Contents/MacOS/app-updater"), - ) - } 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 + let status_checks = if matches!(bundle_target, BundleTarget::Msi) { + vec![UPDATED_EXIT_CODE] } else { - Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) + vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE] }; - binary_cmd.env("TARGET", bundle_target.name()); + for expected_exit_code in status_checks { + let mut binary_cmd = if cfg!(windows) { + Command::new(root_dir.join("target/debug/app-updater.exe")) + } else if cfg!(target_os = "macos") { + Command::new( + bundle_paths(&root_dir, "0.1.0") + .first() + .unwrap() + .1 + .join("Contents/MacOS/app-updater"), + ) + } else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() { + let mut c = Command::new("xvfb-run"); + c.arg("--auto-servernum"); + #[cfg(target_os = "linux")] + if bundle_target == BundleTarget::Deb { + c.arg("/usr/bin/app-updater"); + } else { + c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); + } + c + } else { + #[cfg(target_os = "linux")] + { + let mut c = Command::new("sudo"); + if bundle_target == BundleTarget::Deb { + c.arg("/usr/bin/app-updater"); + } else { + c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); + } + c + } + #[cfg(not(target_os = "linux"))] + { + Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) + } + }; + + binary_cmd.env("TARGET", bundle_target.name()); - let status = binary_cmd.status().expect("failed to run app"); - let code = status.code().unwrap_or(-1); + let status = binary_cmd.status().expect("failed to run app"); + let code = status.code().unwrap_or(-1); - if code != expected_exit_code { - panic!( - "failed to run app, expected exit code {expected_exit_code}, got {code}" - ); - } - #[cfg(windows)] - if code == UPDATED_EXIT_CODE { - // wait for the update to finish - std::thread::sleep(std::time::Duration::from_secs(5)); + if code != expected_exit_code { + panic!( + "failed to run app, expected exit code {expected_exit_code}, got {code}" + ); + } + #[cfg(windows)] + if code == UPDATED_EXIT_CODE { + // wait for the update to finish + std::thread::sleep(std::time::Duration::from_secs(5)); + } } - } - // graceful shutdown - server.unblock(); + // graceful shutdown + server.unblock(); + } } + }); + + // Always run cleanup + cleanup(); + + // Re-panic if there was an error + if let Err(e) = test_result { + std::panic::resume_unwind(e); } } From 5a95db813b8c39db6d9c12eb4e74d3c91e8cc849 Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 15:29:23 +1300 Subject: [PATCH 25/37] Now runs both appimage and deb tests --- .../updater/tests/app-updater/tests/update.rs | 367 +++++++++--------- 1 file changed, 187 insertions(+), 180 deletions(-) diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index f96bff33b..f60094b17 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -211,206 +211,213 @@ 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") - } else { + #[cfg(target_os = "linux")] + let bundle_targets = vec![BundleTarget::AppImage, BundleTarget::Deb]; + #[cfg(not(target_os = "linux"))] + let bundle_targets = vec![BundleTarget::default()]; + + for bundle_target in bundle_targets { + // bundle app update + build_app(&manifest_dir, &config, true, bundle_target); + + let updater_zip_ext = if v1_compatible { + if cfg!(windows) { + Some("zip") + } else { + Some("tar.gz") + } + } else if cfg!(target_os = "macos") { Some("tar.gz") - } - } else if cfg!(target_os = "macos") { - Some("tar.gz") - } else { - None - }; - - for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { - let bundle_updater_ext = if v1_compatible { - out_bundle_path - .extension() - .unwrap() - .to_str() - .unwrap() - .replace("exe", "nsis") } else { - out_bundle_path - .extension() - .unwrap() - .to_str() - .unwrap() - .to_string() + None }; - let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext { - format!("{bundle_updater_ext}.{updater_zip_ext}") - } else { - bundle_updater_ext - }; - let signature_extension = format!("{updater_extension}.sig"); - let signature_path = out_bundle_path.with_extension(signature_extension); - let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| { - panic!("failed to read signature file {}", signature_path.display()) - }); - let out_updater_path = out_bundle_path.with_extension(updater_extension); - let updater_path = root_dir.join(format!( - "target/debug/{}", - 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"), - ); - - let server_ = server.clone(); - std::thread::spawn(move || { - 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 body = serde_json::to_vec(&Update { - version: "1.0.0", - date: time::OffsetDateTime::now_utc() - .format(&time::format_description::well_known::Rfc3339) - .unwrap(), - platforms, - }) - .unwrap(); - let len = body.len(); - let response = tiny_http::Response::new( - tiny_http::StatusCode(200), - Vec::new(), - std::io::Cursor::new(body), - Some(len), - None, - ); - let _ = request.respond(response); - } - "/download" => { - let _ = request.respond(tiny_http::Response::from_file( - File::open(&updater_path).unwrap_or_else(|_| { - panic!( - "failed to open updater bundle {}", - updater_path.display() - ) - }), - )); + + for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { + let bundle_updater_ext = if v1_compatible { + out_bundle_path + .extension() + .unwrap() + .to_str() + .unwrap() + .replace("exe", "nsis") + } else { + out_bundle_path + .extension() + .unwrap() + .to_str() + .unwrap() + .to_string() + }; + let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext { + format!("{bundle_updater_ext}.{updater_zip_ext}") + } else { + bundle_updater_ext + }; + let signature_extension = format!("{updater_extension}.sig"); + let signature_path = out_bundle_path.with_extension(signature_extension); + let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| { + panic!("failed to read signature file {}", signature_path.display()) + }); + let out_updater_path = out_bundle_path.with_extension(updater_extension); + let updater_path = root_dir.join(format!( + "target/debug/{}", + 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"), + ); + + let server_ = server.clone(); + std::thread::spawn(move || { + 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 body = serde_json::to_vec(&Update { + version: "1.0.0", + date: time::OffsetDateTime::now_utc() + .format(&time::format_description::well_known::Rfc3339) + .unwrap(), + platforms, + }) + .unwrap(); + let len = body.len(); + let response = tiny_http::Response::new( + tiny_http::StatusCode(200), + Vec::new(), + std::io::Cursor::new(body), + Some(len), + None, + ); + let _ = request.respond(response); + } + "/download" => { + let _ = request.respond(tiny_http::Response::from_file( + File::open(&updater_path).unwrap_or_else(|_| { + panic!( + "failed to open updater bundle {}", + updater_path.display() + ) + }), + )); + } + _ => (), } - _ => (), } - } - }); - - config.version = "0.1.0"; - - // bundle initial app version - build_app(&manifest_dir, &config, false, bundle_target); - - // Set appropriate permissions and install package if needed - #[cfg(target_os = "linux")] - { - let paths = bundle_paths(&root_dir, "0.1.0"); - let bundle_path = &paths.first().unwrap().1; - - if bundle_target == BundleTarget::AppImage { - std::process::Command::new("sudo") - .arg("chmod") - .arg("+x") - .arg(bundle_path) - .status() - .expect("failed to change permissions"); - } else if bundle_target == BundleTarget::Deb { - // Install the .deb package - let install_status = std::process::Command::new("sudo") - .arg("dpkg") - .arg("-i") - .arg(bundle_path) - .status() - .expect("failed to install .deb package"); - - if !install_status.success() { - panic!("Failed to install .deb package"); + }); + + config.version = "0.1.0"; + + // bundle initial app version + build_app(&manifest_dir, &config, false, bundle_target); + + // Set appropriate permissions and install package if needed + #[cfg(target_os = "linux")] + { + let bundle_path = &out_bundle_path; + + if bundle_target == BundleTarget::AppImage { + std::process::Command::new("sudo") + .arg("chmod") + .arg("+x") + .arg(bundle_path) + .status() + .expect("failed to change permissions"); + } else if bundle_target == BundleTarget::Deb { + // Install the .deb package + let install_status = std::process::Command::new("sudo") + .arg("dpkg") + .arg("-i") + .arg(bundle_path) + .status() + .expect("failed to install .deb package"); + + if !install_status.success() { + panic!("Failed to install .deb package"); + } } } - } - - let status_checks = if matches!(bundle_target, BundleTarget::Msi) { - 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")) - } else if cfg!(target_os = "macos") { - Command::new( - bundle_paths(&root_dir, "0.1.0") - .first() - .unwrap() - .1 - .join("Contents/MacOS/app-updater"), - ) - } else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() { - let mut c = Command::new("xvfb-run"); - c.arg("--auto-servernum"); - #[cfg(target_os = "linux")] - if bundle_target == BundleTarget::Deb { - c.arg("/usr/bin/app-updater"); - } else { - c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); - } - c + let status_checks = if matches!(bundle_target, BundleTarget::Msi) { + vec![UPDATED_EXIT_CODE] } else { - #[cfg(target_os = "linux")] - { - let mut c = Command::new("sudo"); + 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")) + } else if cfg!(target_os = "macos") { + Command::new( + bundle_paths(&root_dir, "0.1.0") + .first() + .unwrap() + .1 + .join("Contents/MacOS/app-updater"), + ) + } else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() { + let mut c = Command::new("xvfb-run"); + c.arg("--auto-servernum"); + #[cfg(target_os = "linux")] if bundle_target == BundleTarget::Deb { c.arg("/usr/bin/app-updater"); } else { c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); } c - } - #[cfg(not(target_os = "linux"))] - { - Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) - } - }; + } else { + #[cfg(target_os = "linux")] + { + let mut c = Command::new("sudo"); + if bundle_target == BundleTarget::Deb { + c.arg("/usr/bin/app-updater"); + } else { + c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); + } + c + } + #[cfg(not(target_os = "linux"))] + { + Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) + } + }; - binary_cmd.env("TARGET", bundle_target.name()); + binary_cmd.env("TARGET", bundle_target.name()); - let status = binary_cmd.status().expect("failed to run app"); - let code = status.code().unwrap_or(-1); + let status = binary_cmd.status().expect("failed to run app"); + let code = status.code().unwrap_or(-1); - if code != expected_exit_code { - panic!( - "failed to run app, expected exit code {expected_exit_code}, got {code}" - ); - } - #[cfg(windows)] - if code == UPDATED_EXIT_CODE { - // wait for the update to finish - std::thread::sleep(std::time::Duration::from_secs(5)); + if code != expected_exit_code { + panic!( + "failed to run app, expected exit code {expected_exit_code}, got {code}" + ); + } + #[cfg(windows)] + if code == UPDATED_EXIT_CODE { + // wait for the update to finish + std::thread::sleep(std::time::Duration::from_secs(5)); + } } - } - // graceful shutdown - server.unblock(); + // graceful shutdown + server.unblock(); + } } } }); From cbb08130a79c69d5f4ce13b6f0618322ecba5738 Mon Sep 17 00:00:00 2001 From: jLynx Date: Wed, 13 Nov 2024 16:05:00 +1300 Subject: [PATCH 26/37] Adjusted order --- plugins/updater/tests/app-updater/tests/update.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index f60094b17..a4524b5f9 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -105,9 +105,7 @@ impl Default for BundleTarget { #[cfg(any(target_os = "macos", target_os = "ios"))] return Self::App; #[cfg(target_os = "linux")] - { - return Self::AppImage; - } + return Self::Deb; #[cfg(windows)] return Self::Nsis; } @@ -118,15 +116,15 @@ fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> // Return both AppImage and Deb paths vec![ ( - BundleTarget::AppImage, + BundleTarget::Deb, root_dir.join(format!( - "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" + "target/debug/bundle/deb/app-updater_{version}_amd64.deb" )), ), ( - BundleTarget::Deb, + BundleTarget::AppImage, root_dir.join(format!( - "target/debug/bundle/deb/app-updater_{version}_amd64.deb" + "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" )), ), ] @@ -212,7 +210,7 @@ fn update_app() { ); #[cfg(target_os = "linux")] - let bundle_targets = vec![BundleTarget::AppImage, BundleTarget::Deb]; + let bundle_targets = vec![BundleTarget::Deb, BundleTarget::AppImage]; #[cfg(not(target_os = "linux"))] let bundle_targets = vec![BundleTarget::default()]; From a13d5b4c413a68884a95daf5623f72322f89b356 Mon Sep 17 00:00:00 2001 From: jLynx Date: Thu, 14 Nov 2024 10:15:44 +1300 Subject: [PATCH 27/37] Got the build working --- plugins/updater/tests/app-updater/tests/update.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index a4524b5f9..527320631 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -261,8 +261,8 @@ fn update_app() { "target/debug/{}", out_updater_path.file_name().unwrap().to_str().unwrap() )); - std::fs::rename(&out_updater_path, &updater_path) - .expect("failed to rename bundle"); + // std::fs::rename(&out_updater_path, &updater_path) + // .expect("failed to rename bundle"); let target = target.clone(); From 8f07405acb8d7ef2ec679a55fedaf95d123fde65 Mon Sep 17 00:00:00 2001 From: jLynx Date: Thu, 14 Nov 2024 13:08:06 +1300 Subject: [PATCH 28/37] Got deb tests passing --- .../updater/tests/app-updater/tests/update.rs | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index 527320631..62bdfe53b 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -208,6 +208,7 @@ fn update_app() { config.bundle.create_updater_artifacts, Updater::String(V1Compatible::V1Compatible) ); + println!("== IS v1_compatible: {}", v1_compatible); #[cfg(target_os = "linux")] let bundle_targets = vec![BundleTarget::Deb, BundleTarget::AppImage]; @@ -215,6 +216,12 @@ fn update_app() { let bundle_targets = vec![BundleTarget::default()]; for bundle_target in bundle_targets { + // Skip test for Linux .deb with v1 compatibility + #[cfg(target_os = "linux")] + if v1_compatible && bundle_target == BundleTarget::Deb { + println!("Skipping test for .deb with v1 compatibility mode"); + continue; + } // bundle app update build_app(&manifest_dir, &config, true, bundle_target); @@ -261,8 +268,12 @@ fn update_app() { "target/debug/{}", out_updater_path.file_name().unwrap().to_str().unwrap() )); - // std::fs::rename(&out_updater_path, &updater_path) - // .expect("failed to rename bundle"); + println!("Rename operation paths:"); + println!(" From: {}", out_updater_path.display()); + println!(" To: {}", updater_path.display()); + // Note, this may need to still be here, but also may need to move the sig too + std::fs::rename(&out_updater_path, &updater_path) + .expect("failed to rename bundle"); let target = target.clone(); @@ -328,21 +339,29 @@ fn update_app() { // Set appropriate permissions and install package if needed #[cfg(target_os = "linux")] { - let bundle_path = &out_bundle_path; + let initial_bundle_path = bundle_paths(&root_dir, "0.1.0") + .iter() + .find(|(t, _)| *t == bundle_target) + .map(|(_, path)| path.clone()) + .unwrap(); if bundle_target == BundleTarget::AppImage { std::process::Command::new("sudo") .arg("chmod") .arg("+x") - .arg(bundle_path) + .arg(initial_bundle_path) .status() .expect("failed to change permissions"); } else if bundle_target == BundleTarget::Deb { + println!( + "Installing Deb package from: {}", + initial_bundle_path.display() + ); // Install the .deb package let install_status = std::process::Command::new("sudo") .arg("dpkg") .arg("-i") - .arg(bundle_path) + .arg(initial_bundle_path) .status() .expect("failed to install .deb package"); @@ -382,6 +401,10 @@ fn update_app() { } else { #[cfg(target_os = "linux")] { + println!( + "RUNNING LINUX BUILD - Expected exit code: {}", + expected_exit_code + ); let mut c = Command::new("sudo"); if bundle_target == BundleTarget::Deb { c.arg("/usr/bin/app-updater"); @@ -406,6 +429,7 @@ fn update_app() { "failed to run app, expected exit code {expected_exit_code}, got {code}" ); } + println!("===== CODE WAS SUCCESSFUL! {}", expected_exit_code); #[cfg(windows)] if code == UPDATED_EXIT_CODE { // wait for the update to finish From 562297213bea1e534b647cfa6e9caf06660910f4 Mon Sep 17 00:00:00 2001 From: jLynx Date: Thu, 14 Nov 2024 13:40:25 +1300 Subject: [PATCH 29/37] Attempt at buidling with GH Actions --- .github/workflows/integration-tests.yml | 5 +++- .../updater/tests/app-updater/tests/update.rs | 23 ++++++------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 10403a8b4..5d1a01e29 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -45,8 +45,11 @@ jobs: - uses: Swatinem/rust-cache@v2 + # - name: install Tauri CLI + # run: sudo cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev + - name: install Tauri CLI - run: sudo cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev + run: sudo $(which cargo) install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev - name: run integration tests run: sudo cargo test --test '*' -- --ignored diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index 62bdfe53b..0702e8ca9 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -208,7 +208,6 @@ fn update_app() { config.bundle.create_updater_artifacts, Updater::String(V1Compatible::V1Compatible) ); - println!("== IS v1_compatible: {}", v1_compatible); #[cfg(target_os = "linux")] let bundle_targets = vec![BundleTarget::Deb, BundleTarget::AppImage]; @@ -219,7 +218,6 @@ fn update_app() { // Skip test for Linux .deb with v1 compatibility #[cfg(target_os = "linux")] if v1_compatible && bundle_target == BundleTarget::Deb { - println!("Skipping test for .deb with v1 compatibility mode"); continue; } // bundle app update @@ -237,7 +235,10 @@ fn update_app() { None }; - for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { + for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") + .into_iter() + .filter(|(t, _)| *t == bundle_target) + { let bundle_updater_ext = if v1_compatible { out_bundle_path .extension() @@ -260,6 +261,9 @@ fn update_app() { }; let signature_extension = format!("{updater_extension}.sig"); let signature_path = out_bundle_path.with_extension(signature_extension); + + println!("SIG PATH {:?} | {}", signature_path, updater_extension); + let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| { panic!("failed to read signature file {}", signature_path.display()) }); @@ -268,10 +272,6 @@ fn update_app() { "target/debug/{}", out_updater_path.file_name().unwrap().to_str().unwrap() )); - println!("Rename operation paths:"); - println!(" From: {}", out_updater_path.display()); - println!(" To: {}", updater_path.display()); - // Note, this may need to still be here, but also may need to move the sig too std::fs::rename(&out_updater_path, &updater_path) .expect("failed to rename bundle"); @@ -353,10 +353,6 @@ fn update_app() { .status() .expect("failed to change permissions"); } else if bundle_target == BundleTarget::Deb { - println!( - "Installing Deb package from: {}", - initial_bundle_path.display() - ); // Install the .deb package let install_status = std::process::Command::new("sudo") .arg("dpkg") @@ -401,10 +397,6 @@ fn update_app() { } else { #[cfg(target_os = "linux")] { - println!( - "RUNNING LINUX BUILD - Expected exit code: {}", - expected_exit_code - ); let mut c = Command::new("sudo"); if bundle_target == BundleTarget::Deb { c.arg("/usr/bin/app-updater"); @@ -429,7 +421,6 @@ fn update_app() { "failed to run app, expected exit code {expected_exit_code}, got {code}" ); } - println!("===== CODE WAS SUCCESSFUL! {}", expected_exit_code); #[cfg(windows)] if code == UPDATED_EXIT_CODE { // wait for the update to finish From cf506595a55de3dc1fd7a64692488ebfba48c74b Mon Sep 17 00:00:00 2001 From: jLynx Date: Thu, 14 Nov 2024 13:50:51 +1300 Subject: [PATCH 30/37] WIP GH Actions --- .github/workflows/integration-tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 5d1a01e29..7e650829e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -34,8 +34,13 @@ jobs: with: fetch-depth: 0 + # - name: install stable + # uses: dtolnay/rust-toolchain@stable + - name: install stable - uses: dtolnay/rust-toolchain@stable + run: | + sudo curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sudo sh -s -- -y + sudo rustup default stable - name: install Linux dependencies if: matrix.platform == 'ubuntu-22.04' From 44d2e778ad40c465aba8a8fdaf6a9974440b15fa Mon Sep 17 00:00:00 2001 From: jLynx Date: Thu, 14 Nov 2024 14:23:04 +1300 Subject: [PATCH 31/37] WIP --- plugins/updater/tests/app-updater/tests/update.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index 0702e8ca9..036b212c4 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -53,7 +53,8 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa .arg(serde_json::to_string(config).unwrap()) .env("TAURI_SIGNING_PRIVATE_KEY", UPDATER_PRIVATE_KEY) .env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "") - .current_dir(cwd); + .current_dir(cwd) + .stdout(std::process::Stdio::null()); #[cfg(target_os = "linux")] command.args(["--bundles", target.name()]); From 6b21e8866964a4ee01a905f9d5b6ffd602333d5b Mon Sep 17 00:00:00 2001 From: jLynx Date: Thu, 14 Nov 2024 14:23:14 +1300 Subject: [PATCH 32/37] wip --- plugins/updater/tests/app-updater/tests/update.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index 036b212c4..b28bad334 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -53,8 +53,7 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa .arg(serde_json::to_string(config).unwrap()) .env("TAURI_SIGNING_PRIVATE_KEY", UPDATER_PRIVATE_KEY) .env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "") - .current_dir(cwd) - .stdout(std::process::Stdio::null()); + .current_dir(cwd); #[cfg(target_os = "linux")] command.args(["--bundles", target.name()]); @@ -117,15 +116,15 @@ fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> // Return both AppImage and Deb paths vec![ ( - BundleTarget::Deb, + BundleTarget::AppImage, root_dir.join(format!( - "target/debug/bundle/deb/app-updater_{version}_amd64.deb" + "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" )), ), ( - BundleTarget::AppImage, + BundleTarget::Deb, root_dir.join(format!( - "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" + "target/debug/bundle/deb/app-updater_{version}_amd64.deb" )), ), ] @@ -211,7 +210,7 @@ fn update_app() { ); #[cfg(target_os = "linux")] - let bundle_targets = vec![BundleTarget::Deb, BundleTarget::AppImage]; + let bundle_targets = vec![BundleTarget::AppImage, BundleTarget::Deb]; #[cfg(not(target_os = "linux"))] let bundle_targets = vec![BundleTarget::default()]; From 628a35553b6090ff3f588b21fe7c53b984da018d Mon Sep 17 00:00:00 2001 From: jLynx Date: Mon, 25 Nov 2024 10:03:54 +1300 Subject: [PATCH 33/37] Reverted tests --- .github/workflows/integration-tests.yml | 14 +- .../updater/tests/app-updater/tests/update.rs | 439 +++++++----------- 2 files changed, 173 insertions(+), 280 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 7e650829e..cfdb91c2e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -34,13 +34,8 @@ jobs: with: fetch-depth: 0 - # - name: install stable - # uses: dtolnay/rust-toolchain@stable - - name: install stable - run: | - sudo curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sudo sh -s -- -y - sudo rustup default stable + uses: dtolnay/rust-toolchain@stable - name: install Linux dependencies if: matrix.platform == 'ubuntu-22.04' @@ -50,11 +45,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - # - name: install Tauri CLI - # run: sudo cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev - - name: install Tauri CLI - run: sudo $(which cargo) install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev + run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev - name: run integration tests - run: sudo cargo test --test '*' -- --ignored + run: cargo test --test '*' -- --ignored \ No newline at end of file diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index b28bad334..812a317f1 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -79,11 +79,12 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa } } -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone)] enum BundleTarget { AppImage, - Deb, + App, + Msi, Nsis, } @@ -92,7 +93,6 @@ impl BundleTarget { fn name(self) -> &'static str { match self { Self::AppImage => "appimage", - Self::Deb => "deb", Self::App => "app", Self::Msi => "msi", Self::Nsis => "nsis", @@ -105,7 +105,7 @@ impl Default for BundleTarget { #[cfg(any(target_os = "macos", target_os = "ios"))] return Self::App; #[cfg(target_os = "linux")] - return Self::Deb; + return Self::AppImage; #[cfg(windows)] return Self::Nsis; } @@ -113,21 +113,12 @@ impl Default for BundleTarget { #[cfg(target_os = "linux")] fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> { - // Return both AppImage and Deb paths - 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" - )), - ), - ] + vec![( + BundleTarget::AppImage, + root_dir.join(format!( + "target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage" + )), + )] } #[cfg(target_os = "macos")] @@ -177,269 +168,179 @@ fn update_app() { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let root_dir = manifest_dir.join("../../../.."); - // Set up cleanup on panic - let cleanup = std::panic::AssertUnwindSafe(|| { - #[cfg(target_os = "linux")] - { - let _ = std::process::Command::new("sudo") - .arg("dpkg") - .arg("-r") - .arg("app-updater") - .status(); - } - }); - - let test_result = std::panic::catch_unwind(|| { - for mut config in [ - Config { - version: "1.0.0", - bundle: BundleConfig { - create_updater_artifacts: Updater::Bool(true), - }, + for mut config in [ + Config { + version: "1.0.0", + bundle: BundleConfig { + create_updater_artifacts: Updater::Bool(true), }, - Config { - version: "1.0.0", - bundle: BundleConfig { - create_updater_artifacts: Updater::String(V1Compatible::V1Compatible), - }, + }, + Config { + version: "1.0.0", + bundle: BundleConfig { + create_updater_artifacts: Updater::String(V1Compatible::V1Compatible), }, - ] { - let v1_compatible = matches!( - config.bundle.create_updater_artifacts, - Updater::String(V1Compatible::V1Compatible) + }, + ] { + let v1_compatible = matches!( + config.bundle.create_updater_artifacts, + 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") + } else { + Some("tar.gz") + } + } else if cfg!(target_os = "macos") { + Some("tar.gz") + } else { + None + }; + + for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") { + let bundle_updater_ext = if v1_compatible { + out_bundle_path + .extension() + .unwrap() + .to_str() + .unwrap() + .replace("exe", "nsis") + } else { + out_bundle_path + .extension() + .unwrap() + .to_str() + .unwrap() + .to_string() + }; + let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext { + format!("{bundle_updater_ext}.{updater_zip_ext}") + } else { + bundle_updater_ext + }; + let signature_extension = format!("{updater_extension}.sig"); + let signature_path = out_bundle_path.with_extension(signature_extension); + let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| { + panic!("failed to read signature file {}", signature_path.display()) + }); + let out_updater_path = out_bundle_path.with_extension(updater_extension); + let updater_path = root_dir.join(format!( + "target/debug/{}", + 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"), ); - #[cfg(target_os = "linux")] - let bundle_targets = vec![BundleTarget::AppImage, BundleTarget::Deb]; - #[cfg(not(target_os = "linux"))] - let bundle_targets = vec![BundleTarget::default()]; - - for bundle_target in bundle_targets { - // Skip test for Linux .deb with v1 compatibility - #[cfg(target_os = "linux")] - if v1_compatible && bundle_target == BundleTarget::Deb { - continue; - } - // bundle app update - build_app(&manifest_dir, &config, true, bundle_target); - - let updater_zip_ext = if v1_compatible { - if cfg!(windows) { - Some("zip") - } else { - Some("tar.gz") + let server_ = server.clone(); + std::thread::spawn(move || { + 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 body = serde_json::to_vec(&Update { + version: "1.0.0", + date: time::OffsetDateTime::now_utc() + .format(&time::format_description::well_known::Rfc3339) + .unwrap(), + platforms, + }) + .unwrap(); + let len = body.len(); + let response = tiny_http::Response::new( + tiny_http::StatusCode(200), + Vec::new(), + std::io::Cursor::new(body), + Some(len), + None, + ); + let _ = request.respond(response); + } + "/download" => { + let _ = request.respond(tiny_http::Response::from_file( + File::open(&updater_path).unwrap_or_else(|_| { + panic!( + "failed to open updater bundle {}", + updater_path.display() + ) + }), + )); + } + _ => (), } - } else if cfg!(target_os = "macos") { - Some("tar.gz") - } else { - None - }; - - for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") - .into_iter() - .filter(|(t, _)| *t == bundle_target) - { - let bundle_updater_ext = if v1_compatible { - out_bundle_path - .extension() - .unwrap() - .to_str() - .unwrap() - .replace("exe", "nsis") - } else { - out_bundle_path - .extension() - .unwrap() - .to_str() - .unwrap() - .to_string() - }; - let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext { - format!("{bundle_updater_ext}.{updater_zip_ext}") - } else { - bundle_updater_ext - }; - let signature_extension = format!("{updater_extension}.sig"); - let signature_path = out_bundle_path.with_extension(signature_extension); - - println!("SIG PATH {:?} | {}", signature_path, updater_extension); - - let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| { - panic!("failed to read signature file {}", signature_path.display()) - }); - let out_updater_path = out_bundle_path.with_extension(updater_extension); - let updater_path = root_dir.join(format!( - "target/debug/{}", - 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"), - ); + } + }); - let server_ = server.clone(); - std::thread::spawn(move || { - 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 body = serde_json::to_vec(&Update { - version: "1.0.0", - date: time::OffsetDateTime::now_utc() - .format(&time::format_description::well_known::Rfc3339) - .unwrap(), - platforms, - }) - .unwrap(); - let len = body.len(); - let response = tiny_http::Response::new( - tiny_http::StatusCode(200), - Vec::new(), - std::io::Cursor::new(body), - Some(len), - None, - ); - let _ = request.respond(response); - } - "/download" => { - let _ = request.respond(tiny_http::Response::from_file( - File::open(&updater_path).unwrap_or_else(|_| { - panic!( - "failed to open updater bundle {}", - updater_path.display() - ) - }), - )); - } - _ => (), - } - } - }); + config.version = "0.1.0"; - config.version = "0.1.0"; + // bundle initial app version + build_app(&manifest_dir, &config, false, bundle_target); - // 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] + }; - // Set appropriate permissions and install package if needed - #[cfg(target_os = "linux")] - { - let initial_bundle_path = bundle_paths(&root_dir, "0.1.0") - .iter() - .find(|(t, _)| *t == bundle_target) - .map(|(_, path)| path.clone()) - .unwrap(); + for expected_exit_code in status_checks { + let mut binary_cmd = if cfg!(windows) { + Command::new(root_dir.join("target/debug/app-updater.exe")) + } else if cfg!(target_os = "macos") { + Command::new( + bundle_paths(&root_dir, "0.1.0") + .first() + .unwrap() + .1 + .join("Contents/MacOS/app-updater"), + ) + } 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 + } else { + Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) + }; - if bundle_target == BundleTarget::AppImage { - std::process::Command::new("sudo") - .arg("chmod") - .arg("+x") - .arg(initial_bundle_path) - .status() - .expect("failed to change permissions"); - } else if bundle_target == BundleTarget::Deb { - // Install the .deb package - let install_status = std::process::Command::new("sudo") - .arg("dpkg") - .arg("-i") - .arg(initial_bundle_path) - .status() - .expect("failed to install .deb package"); - - if !install_status.success() { - panic!("Failed to install .deb package"); - } - } - } + binary_cmd.env("TARGET", bundle_target.name()); - let status_checks = if matches!(bundle_target, BundleTarget::Msi) { - 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")) - } else if cfg!(target_os = "macos") { - Command::new( - bundle_paths(&root_dir, "0.1.0") - .first() - .unwrap() - .1 - .join("Contents/MacOS/app-updater"), - ) - } else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() { - let mut c = Command::new("xvfb-run"); - c.arg("--auto-servernum"); - #[cfg(target_os = "linux")] - if bundle_target == BundleTarget::Deb { - c.arg("/usr/bin/app-updater"); - } else { - c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); - } - c - } else { - #[cfg(target_os = "linux")] - { - let mut c = Command::new("sudo"); - if bundle_target == BundleTarget::Deb { - c.arg("/usr/bin/app-updater"); - } else { - c.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1); - } - c - } - #[cfg(not(target_os = "linux"))] - { - Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1) - } - }; - - binary_cmd.env("TARGET", bundle_target.name()); - - let status = binary_cmd.status().expect("failed to run app"); - let code = status.code().unwrap_or(-1); - - if code != expected_exit_code { - panic!( - "failed to run app, expected exit code {expected_exit_code}, got {code}" - ); - } - #[cfg(windows)] - if code == UPDATED_EXIT_CODE { - // wait for the update to finish - std::thread::sleep(std::time::Duration::from_secs(5)); - } - } + let status = binary_cmd.status().expect("failed to run app"); + let code = status.code().unwrap_or(-1); - // graceful shutdown - server.unblock(); + if code != expected_exit_code { + panic!( + "failed to run app, expected exit code {expected_exit_code}, got {code}" + ); + } + #[cfg(windows)] + if code == UPDATED_EXIT_CODE { + // wait for the update to finish + std::thread::sleep(std::time::Duration::from_secs(5)); } } - } - }); - // Always run cleanup - cleanup(); - - // Re-panic if there was an error - if let Err(e) = test_result { - std::panic::resume_unwind(e); + // graceful shutdown + server.unblock(); + } } -} +} \ No newline at end of file From cd37fc129bd4814999897b01bce32a5596324e4c Mon Sep 17 00:00:00 2001 From: jLynx Date: Mon, 25 Nov 2024 10:04:36 +1300 Subject: [PATCH 34/37] Reverted tests --- .github/workflows/integration-tests.yml | 102 ++++++++++++------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index cfdb91c2e..2b103452b 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1,52 +1,52 @@ -# Copyright 2019-2023 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: integration tests - -on: - push: - branches: - - v1 - - v2 - paths: - - '.github/workflows/integration-tests.yml' - - 'plugins/updater/src/**' - pull_request: - branches: - - v1 - - v2 - paths: - - '.github/workflows/integration-tests.yml' - - 'plugins/updater/src/**' - -jobs: - run-integration-tests: - runs-on: ${{ matrix.platform }} - - strategy: - fail-fast: false - matrix: - platform: [ubuntu-22.04, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: install stable - uses: dtolnay/rust-toolchain@stable - - - name: install Linux dependencies - if: matrix.platform == 'ubuntu-22.04' - run: | - sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2 - - - uses: Swatinem/rust-cache@v2 - - - name: install Tauri CLI - run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev - - - name: run integration tests +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: integration tests + +on: + push: + branches: + - v1 + - v2 + paths: + - '.github/workflows/integration-tests.yml' + - 'plugins/updater/src/**' + pull_request: + branches: + - v1 + - v2 + paths: + - '.github/workflows/integration-tests.yml' + - 'plugins/updater/src/**' + +jobs: + run-integration-tests: + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-22.04, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: install stable + uses: dtolnay/rust-toolchain@stable + + - name: install Linux dependencies + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.0 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2 + + - uses: Swatinem/rust-cache@v2 + + - name: install Tauri CLI + run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev + + - name: run integration tests run: cargo test --test '*' -- --ignored \ No newline at end of file From a6b9da74552711ca62ec1bf30b4a693ce94b6e6b Mon Sep 17 00:00:00 2001 From: jLynx Date: Mon, 25 Nov 2024 10:05:09 +1300 Subject: [PATCH 35/37] Reverted tests --- .github/workflows/integration-tests.yml | 102 ++++++++++++------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 2b103452b..cfdb91c2e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1,52 +1,52 @@ -# Copyright 2019-2023 Tauri Programme within The Commons Conservancy -# SPDX-License-Identifier: Apache-2.0 -# SPDX-License-Identifier: MIT - -name: integration tests - -on: - push: - branches: - - v1 - - v2 - paths: - - '.github/workflows/integration-tests.yml' - - 'plugins/updater/src/**' - pull_request: - branches: - - v1 - - v2 - paths: - - '.github/workflows/integration-tests.yml' - - 'plugins/updater/src/**' - -jobs: - run-integration-tests: - runs-on: ${{ matrix.platform }} - - strategy: - fail-fast: false - matrix: - platform: [ubuntu-22.04, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: install stable - uses: dtolnay/rust-toolchain@stable - - - name: install Linux dependencies - if: matrix.platform == 'ubuntu-22.04' - run: | - sudo apt-get update - sudo apt-get install -y webkit2gtk-4.0 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2 - - - uses: Swatinem/rust-cache@v2 - - - name: install Tauri CLI - run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev - - - name: run integration tests +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: integration tests + +on: + push: + branches: + - v1 + - v2 + paths: + - '.github/workflows/integration-tests.yml' + - 'plugins/updater/src/**' + pull_request: + branches: + - v1 + - v2 + paths: + - '.github/workflows/integration-tests.yml' + - 'plugins/updater/src/**' + +jobs: + run-integration-tests: + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-22.04, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: install stable + uses: dtolnay/rust-toolchain@stable + + - name: install Linux dependencies + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.0 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2 + + - uses: Swatinem/rust-cache@v2 + + - name: install Tauri CLI + run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev + + - name: run integration tests run: cargo test --test '*' -- --ignored \ No newline at end of file From 338aedbdd134f2d03f9086efec1c49f8ea388360 Mon Sep 17 00:00:00 2001 From: jLynx Date: Mon, 25 Nov 2024 12:41:37 +1300 Subject: [PATCH 36/37] Fixed formatting --- plugins/updater/tests/app-updater/tests/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index 812a317f1..230ab3763 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -343,4 +343,4 @@ fn update_app() { server.unblock(); } } -} \ No newline at end of file +} From 88b3995cfe6c58aa60bc2bcf87ebdd74b65f8df9 Mon Sep 17 00:00:00 2001 From: jLynx Date: Tue, 26 Nov 2024 08:35:48 +1300 Subject: [PATCH 37/37] Fixed formatting --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index cfdb91c2e..fbbca96ab 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -49,4 +49,4 @@ jobs: run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev - name: run integration tests - run: cargo test --test '*' -- --ignored \ No newline at end of file + run: cargo test --test '*' -- --ignored