Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(macOS): Tauri V2 Update Permission Denied Error #2067

Open
wants to merge 6 commits into
base: v2
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 60 additions & 23 deletions plugins/updater/src/updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1015,42 +1015,79 @@ impl Update {
let cursor = Cursor::new(bytes);
let mut extracted_files: Vec<PathBuf> = Vec::new();

// the first file in the tar.gz will always be
// <app_name>/Contents
let tmp_dir = tempfile::Builder::new()
// Create temp directories for backup and extraction
let tmp_backup_dir = tempfile::Builder::new()
.prefix("tauri_current_app")
.tempdir()?;

// create backup of our current app
std::fs::rename(&self.extract_path, tmp_dir.path())?;
let tmp_extract_dir = tempfile::Builder::new()
.prefix("tauri_updated_app")
.tempdir()?;

let decoder = GzDecoder::new(cursor);
let mut archive = tar::Archive::new(decoder);

std::fs::create_dir(&self.extract_path)?;

// Extract files to temporary directory
for entry in archive.entries()? {
let mut entry = entry?;

// skip the first folder (should be the app name)
let collected_path: PathBuf = entry.path()?.iter().skip(1).collect();
let extraction_path = &self.extract_path.join(collected_path);

// if something went wrong during the extraction, we should restore previous app
if let Err(err) = entry.unpack(extraction_path) {
for file in extracted_files.iter().rev() {
// delete all the files we extracted
if file.is_dir() {
std::fs::remove_dir(file)?;
} else {
std::fs::remove_file(file)?;
}
}
std::fs::rename(tmp_dir.path(), &self.extract_path)?;
let extraction_path = tmp_extract_dir.path().join(&collected_path);

// Ensure parent directories exist
if let Some(parent) = extraction_path.parent() {
std::fs::create_dir_all(parent)?;
}

if let Err(err) = entry.unpack(&extraction_path) {
// Cleanup on error
std::fs::remove_dir_all(tmp_extract_dir.path()).ok();
return Err(err.into());
}
extracted_files.push(extraction_path);
}

// Try to move the current app to backup
let move_result = std::fs::rename(
&self.extract_path,
tmp_backup_dir.path().join("current_app"),
);
let need_authorization = if let Err(err) = move_result {
if err.kind() == std::io::ErrorKind::PermissionDenied {
true
} else {
std::fs::remove_dir_all(tmp_extract_dir.path()).ok();
return Err(err.into());
}
} else {
false
};

extracted_files.push(extraction_path.to_path_buf());
if need_authorization {
// Use AppleScript to perform moves with admin privileges
let script = format!(
"do shell script \"rm -rf '{src}' && mv -f '{new}' '{src}'\" with administrator privileges",
src = self.extract_path.display(),
new = tmp_extract_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.path()).ok();
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"Failed to move the new app into place",
)));
}
} else {
// Remove existing directory if it exists
if self.extract_path.exists() {
std::fs::remove_dir_all(&self.extract_path)?;
}
// Move the new app to the target path
std::fs::rename(tmp_extract_dir.path(), &self.extract_path)?;
}

let _ = std::process::Command::new("touch")
Expand Down