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