pull/1991/head
jLynx 9 months ago
parent 72a4670ac5
commit c890ec639e

1
Cargo.lock generated

@ -7026,6 +7026,7 @@ dependencies = [
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"percent-encoding",
"reqwest",

@ -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 }

@ -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")]

@ -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<dyn FnOnce() -> Option<PathBuf>>,
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<String> {
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<bool> {
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())
}
}

Loading…
Cancel
Save