From c890ec639e4e39f19e3bea8e8f4918456123e500 Mon Sep 17 00:00:00 2001 From: jLynx Date: Fri, 1 Nov 2024 12:42:25 +1300 Subject: [PATCH] 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 2044a052..69b6b9a3 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 9a1944e8..29f7c867 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 6f90bcfc..5f6b7d8f 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 1396e5b9..f372cb4e 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()) } }