Skip to content

Commit

Permalink
fix(macOS): Tauri Update Permission Denied Error (#10427)
Browse files Browse the repository at this point in the history
* Fix mac admin update

* Fixed linting

* add change file

---------

Co-authored-by: Lucas Nogueira <[email protected]>
  • Loading branch information
jLynx and lucasfernog authored Feb 2, 2025
1 parent a9d0e42 commit f72d0e1
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changes/fix-macos-privileged-update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:bug
---

Fix update installation on macOS when using an user without admin privileges.
75 changes: 57 additions & 18 deletions core/tauri/src/updater/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,46 +1113,85 @@ fn copy_files_and_run(bytes: &[u8], extract_path: &Path) -> Result {
let mut extractor = Extract::from_cursor(archive, ArchiveFormat::Tar(Some(Compression::Gz)));
// the first file in the tar.gz will always be
// <app_name>/Contents
let tmp_dir = tempfile::Builder::new()
.prefix("tauri_current_app")
.tempdir()?;

// create backup of our current app
Move::from_source(extract_path).to_dest(tmp_dir.path())?;
// We'll extract the files to a temp directory first
let tmp_extract_dir = tempfile::Builder::new()
.prefix("tauri_updated_app")
.tempdir()?
.into_path();

// extract all the files
extractor.with_files(|entry| {
let path = entry.path()?;
// skip the first folder (should be the app name)
let collected_path: PathBuf = path.iter().skip(1).collect();
let extraction_path = extract_path.join(collected_path);
let extraction_path = tmp_extract_dir.join(collected_path);

// if something went wrong during the extraction, we should restore previous app
if let Err(err) = entry.extract(&extraction_path) {
for file in &extracted_files {
// delete all the files we extracted
if file.is_dir() {
std::fs::remove_dir(file)?;
} else {
std::fs::remove_file(file)?;
}
}
Move::from_source(tmp_dir.path()).to_dest(extract_path)?;
std::fs::remove_dir_all(&tmp_extract_dir).ok();
return Err(crate::api::Error::Extract(err.to_string()));
}

extracted_files.push(extraction_path);

Ok(false)
})?;

let tmp_backup_dir = tempfile::Builder::new()
.prefix("tauri_current_app")
.tempdir()?;

// create backup of our current app
let move_result = Move::from_source(extract_path).to_dest(tmp_backup_dir.path());
let need_authorization = if let Err(err) = move_result {
if is_permission_error(&err) {
true
} else {
std::fs::remove_dir_all(&tmp_extract_dir).ok();
return Err(err.into());
}
} else {
false
};

if need_authorization {
// Ask for permission using AppleScript - run the two moves with admin privileges
let script = format!(
"do shell script \"mv -f '{extract_path}' '{tmp_backup_dir}' && mv -f '{tmp_extract_dir}' '{extract_path}'\" with administrator privileges",
tmp_extract_dir = tmp_extract_dir.display(),
extract_path = extract_path.display(),
tmp_backup_dir = tmp_backup_dir.path().display()
);
let mut osascript = std::process::Command::new("osascript");
osascript.arg("-e").arg(script);
let status = osascript.status()?;
if !status.success() {
std::fs::remove_dir_all(&tmp_extract_dir).ok();
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"Failed to move the new app into place",
)));
}
} else {
// move the new app to the target path
Move::from_source(&tmp_extract_dir).to_dest(extract_path)?;
}

let _ = std::process::Command::new("touch")
.arg(extract_path)
.status();

Ok(())
}

#[cfg(target_os = "macos")]
fn is_permission_error(err: &crate::api::Error) -> bool {
if let crate::api::Error::Io(io_err) = err {
if io_err.kind() == std::io::ErrorKind::PermissionDenied {
return true;
}
}
false
}

pub(crate) fn get_updater_target() -> Option<&'static str> {
if cfg!(target_os = "linux") {
Some("linux")
Expand Down

0 comments on commit f72d0e1

Please sign in to comment.