Skip to content

Commit f72d0e1

Browse files
jLynxlucasfernog
andauthored
fix(macOS): Tauri Update Permission Denied Error (#10427)
* Fix mac admin update * Fixed linting * add change file --------- Co-authored-by: Lucas Nogueira <[email protected]>
1 parent a9d0e42 commit f72d0e1

File tree

2 files changed

+62
-18
lines changed

2 files changed

+62
-18
lines changed
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch:bug
3+
---
4+
5+
Fix update installation on macOS when using an user without admin privileges.

core/tauri/src/updater/core.rs

+57-18
Original file line numberDiff line numberDiff line change
@@ -1113,46 +1113,85 @@ fn copy_files_and_run(bytes: &[u8], extract_path: &Path) -> Result {
11131113
let mut extractor = Extract::from_cursor(archive, ArchiveFormat::Tar(Some(Compression::Gz)));
11141114
// the first file in the tar.gz will always be
11151115
// <app_name>/Contents
1116-
let tmp_dir = tempfile::Builder::new()
1117-
.prefix("tauri_current_app")
1118-
.tempdir()?;
11191116

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

11231123
// extract all the files
11241124
extractor.with_files(|entry| {
11251125
let path = entry.path()?;
11261126
// skip the first folder (should be the app name)
11271127
let collected_path: PathBuf = path.iter().skip(1).collect();
1128-
let extraction_path = extract_path.join(collected_path);
1128+
let extraction_path = tmp_extract_dir.join(collected_path);
11291129

1130-
// if something went wrong during the extraction, we should restore previous app
11311130
if let Err(err) = entry.extract(&extraction_path) {
1132-
for file in &extracted_files {
1133-
// delete all the files we extracted
1134-
if file.is_dir() {
1135-
std::fs::remove_dir(file)?;
1136-
} else {
1137-
std::fs::remove_file(file)?;
1138-
}
1139-
}
1140-
Move::from_source(tmp_dir.path()).to_dest(extract_path)?;
1131+
std::fs::remove_dir_all(&tmp_extract_dir).ok();
11411132
return Err(crate::api::Error::Extract(err.to_string()));
11421133
}
11431134

1144-
extracted_files.push(extraction_path);
1145-
11461135
Ok(false)
11471136
})?;
11481137

1138+
let tmp_backup_dir = tempfile::Builder::new()
1139+
.prefix("tauri_current_app")
1140+
.tempdir()?;
1141+
1142+
// create backup of our current app
1143+
let move_result = Move::from_source(extract_path).to_dest(tmp_backup_dir.path());
1144+
let need_authorization = if let Err(err) = move_result {
1145+
if is_permission_error(&err) {
1146+
true
1147+
} else {
1148+
std::fs::remove_dir_all(&tmp_extract_dir).ok();
1149+
return Err(err.into());
1150+
}
1151+
} else {
1152+
false
1153+
};
1154+
1155+
if need_authorization {
1156+
// Ask for permission using AppleScript - run the two moves with admin privileges
1157+
let script = format!(
1158+
"do shell script \"mv -f '{extract_path}' '{tmp_backup_dir}' && mv -f '{tmp_extract_dir}' '{extract_path}'\" with administrator privileges",
1159+
tmp_extract_dir = tmp_extract_dir.display(),
1160+
extract_path = extract_path.display(),
1161+
tmp_backup_dir = tmp_backup_dir.path().display()
1162+
);
1163+
let mut osascript = std::process::Command::new("osascript");
1164+
osascript.arg("-e").arg(script);
1165+
let status = osascript.status()?;
1166+
if !status.success() {
1167+
std::fs::remove_dir_all(&tmp_extract_dir).ok();
1168+
return Err(Error::Io(std::io::Error::new(
1169+
std::io::ErrorKind::PermissionDenied,
1170+
"Failed to move the new app into place",
1171+
)));
1172+
}
1173+
} else {
1174+
// move the new app to the target path
1175+
Move::from_source(&tmp_extract_dir).to_dest(extract_path)?;
1176+
}
1177+
11491178
let _ = std::process::Command::new("touch")
11501179
.arg(extract_path)
11511180
.status();
11521181

11531182
Ok(())
11541183
}
11551184

1185+
#[cfg(target_os = "macos")]
1186+
fn is_permission_error(err: &crate::api::Error) -> bool {
1187+
if let crate::api::Error::Io(io_err) = err {
1188+
if io_err.kind() == std::io::ErrorKind::PermissionDenied {
1189+
return true;
1190+
}
1191+
}
1192+
false
1193+
}
1194+
11561195
pub(crate) fn get_updater_target() -> Option<&'static str> {
11571196
if cfg!(target_os = "linux") {
11581197
Some("linux")

0 commit comments

Comments
 (0)