|
|
|
@ -748,7 +748,7 @@ impl Update {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Linux (AppImage)
|
|
|
|
|
/// Linux (AppImage and Deb)
|
|
|
|
|
#[cfg(any(
|
|
|
|
|
target_os = "linux",
|
|
|
|
|
target_os = "dragonfly",
|
|
|
|
@ -760,12 +760,19 @@ 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 # 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<()> {
|
|
|
|
|
if self.is_deb_package() {
|
|
|
|
|
self.install_deb(bytes)
|
|
|
|
|
} else {
|
|
|
|
|
// Handle AppImage or other formats
|
|
|
|
|
self.install_appimage(bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn install_appimage(&self, bytes: &[u8]) -> Result<()> {
|
|
|
|
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
|
|
|
|
let extract_path_metadata = self.extract_path.metadata()?;
|
|
|
|
|
|
|
|
|
@ -835,6 +842,162 @@ impl Update {
|
|
|
|
|
|
|
|
|
|
Err(Error::TempDirNotOnSameMountPoint)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_deb_package(&self) -> bool {
|
|
|
|
|
// First check if we're in a typical Debian installation path
|
|
|
|
|
let in_system_path = self
|
|
|
|
|
.extract_path
|
|
|
|
|
.to_str()
|
|
|
|
|
.map(|p| p.starts_with("/usr"))
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
|
|
|
|
if !in_system_path {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then verify it's actually a Debian-based system by checking for dpkg
|
|
|
|
|
let dpkg_exists = std::path::Path::new("/var/lib/dpkg").exists();
|
|
|
|
|
let apt_exists = std::path::Path::new("/etc/apt").exists();
|
|
|
|
|
|
|
|
|
|
// Additional check for the package in dpkg database
|
|
|
|
|
let package_in_dpkg = if let Ok(output) = std::process::Command::new("dpkg")
|
|
|
|
|
.args(["-S", &self.extract_path.to_string_lossy()])
|
|
|
|
|
.output()
|
|
|
|
|
{
|
|
|
|
|
output.status.success()
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Consider it a deb package only if:
|
|
|
|
|
// 1. We're in a system path AND
|
|
|
|
|
// 2. We have Debian package management tools AND
|
|
|
|
|
// 3. The binary is tracked by dpkg
|
|
|
|
|
dpkg_exists && apt_exists && package_in_dpkg
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn install_deb(&self, bytes: &[u8]) -> Result<()> {
|
|
|
|
|
// First verify the bytes are actually a .deb package
|
|
|
|
|
if !infer::archive::is_deb(bytes) {
|
|
|
|
|
return Err(Error::InvalidUpdaterFormat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try different temp directories
|
|
|
|
|
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())),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we get here, all temp locations failed
|
|
|
|
|
Err(Error::TempDirNotFound)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
{
|
|
|
|
|
if status.success() {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Final fallback: terminal sudo
|
|
|
|
|
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> {
|
|
|
|
|
// 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();
|
|
|
|
|
|
|
|
|
|
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<bool> {
|
|
|
|
|
use std::io::Write;
|
|
|
|
|
use std::process::{Command, Stdio};
|
|
|
|
|
|
|
|
|
|
let mut child = Command::new("sudo")
|
|
|
|
|
.arg("-S") // read password from stdin
|
|
|
|
|
.arg("dpkg")
|
|
|
|
|
.arg("-i")
|
|
|
|
|
.arg(deb_path)
|
|
|
|
|
.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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// MacOS
|
|
|
|
|