mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-04-25 11:46:06 +02:00
fix(macOS): Tauri V2 Update Permission Denied Error (#2067)
* WIP * Fixed linting * WIP * Fixed linting * use osakit to show actual app name on dialog * sync versions * lint --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"updater": patch
|
||||
"updater-js": patch
|
||||
---
|
||||
|
||||
Fix update installation on macOS when using an user without admin privileges.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"updater": minor
|
||||
"updater-js": minor
|
||||
---
|
||||
|
||||
Remove the `UpdaterBuilder::new` function, use `UpdaterExt::updater_builder` instead.
|
||||
Generated
+72
-16
@@ -685,6 +685,25 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-sys"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7"
|
||||
dependencies = [
|
||||
"objc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e58aa60e59d8dbfcc36138f5f18be5f24394d33b38b24f7fd0b1caa33095f22f"
|
||||
dependencies = [
|
||||
"block-sys",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.5.1"
|
||||
@@ -2851,6 +2870,16 @@ dependencies = [
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icrate"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb69199826926eb864697bddd27f73d9fddcffc004f5733131e15b465e30642"
|
||||
dependencies = [
|
||||
"block2 0.4.0",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
@@ -3941,7 +3970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-data",
|
||||
@@ -3957,7 +3986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
@@ -3969,7 +3998,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
@@ -3981,7 +4010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
@@ -3992,7 +4021,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
@@ -4004,7 +4033,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-contacts",
|
||||
"objc2-foundation",
|
||||
@@ -4023,7 +4052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"dispatch",
|
||||
"libc",
|
||||
"objc2",
|
||||
@@ -4035,7 +4064,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
@@ -4048,11 +4077,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-osa-kit"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6788b04a18ea31e3dc3ab256b8546639e5bbae07c1a0dc4ea8615252bc6aee9a"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.2.2"
|
||||
@@ -4060,7 +4101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
@@ -4083,7 +4124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
@@ -4103,7 +4144,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
@@ -4115,7 +4156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
@@ -4128,7 +4169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
@@ -4276,6 +4317,20 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "osakit"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35366a452fce3f8947eb2f33226a133aaf0cacedef2af67ade348d58be7f85d0"
|
||||
dependencies = [
|
||||
"icrate",
|
||||
"objc2-foundation",
|
||||
"objc2-osa-kit",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -5080,7 +5135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46f6f80a9b882647d9014673ca9925d30ffc9750f2eed2b4490e189eaebd01e8"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
@@ -6894,6 +6949,7 @@ dependencies = [
|
||||
"http",
|
||||
"infer",
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
"semver",
|
||||
@@ -8693,7 +8749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e644bf458e27b11b0ecafc9e5633d1304fdae82baca1d42185669752fe6ca4f"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
|
||||
@@ -62,6 +62,7 @@ flate2 = { version = "1", optional = true }
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
tar = "0.4"
|
||||
flate2 = "1"
|
||||
osakit = { version = "0.3", features = ["full"] }
|
||||
|
||||
[features]
|
||||
default = ["rustls-tls", "zip"]
|
||||
|
||||
@@ -70,7 +70,6 @@ pub trait UpdaterExt<R: Runtime> {
|
||||
impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
|
||||
fn updater_builder(&self) -> UpdaterBuilder {
|
||||
let app = self.app_handle();
|
||||
let package_info = app.package_info();
|
||||
let UpdaterState {
|
||||
config,
|
||||
target,
|
||||
@@ -78,12 +77,7 @@ impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
|
||||
headers,
|
||||
} = self.state::<UpdaterState>().inner();
|
||||
|
||||
let mut builder = UpdaterBuilder::new(
|
||||
package_info.name.clone(),
|
||||
package_info.version.clone(),
|
||||
config.clone(),
|
||||
)
|
||||
.headers(headers.clone());
|
||||
let mut builder = UpdaterBuilder::new(app, config.clone()).headers(headers.clone());
|
||||
|
||||
if let Some(target) = target {
|
||||
builder = builder.target(target);
|
||||
|
||||
@@ -23,7 +23,7 @@ use reqwest::{
|
||||
};
|
||||
use semver::Version;
|
||||
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize};
|
||||
use tauri::{utils::platform::current_exe, Resource};
|
||||
use tauri::{utils::platform::current_exe, AppHandle, Resource, Runtime};
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
@@ -94,8 +94,13 @@ impl RemoteRelease {
|
||||
|
||||
pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>;
|
||||
pub type VersionComparator = Arc<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>;
|
||||
type MainThreadClosure = Box<dyn FnOnce() + Send + Sync + 'static>;
|
||||
type RunOnMainThread =
|
||||
Box<dyn Fn(MainThreadClosure) -> std::result::Result<(), tauri::Error> + Send + Sync + 'static>;
|
||||
|
||||
pub struct UpdaterBuilder {
|
||||
#[allow(dead_code)]
|
||||
run_on_main_thread: RunOnMainThread,
|
||||
app_name: String,
|
||||
current_version: Version,
|
||||
config: Config,
|
||||
@@ -112,18 +117,20 @@ pub struct UpdaterBuilder {
|
||||
}
|
||||
|
||||
impl UpdaterBuilder {
|
||||
/// It's prefered to use [`crate::UpdaterExt::updater_builder`] instead of
|
||||
/// constructing a [`UpdaterBuilder`] with this function yourself
|
||||
pub fn new(app_name: String, current_version: Version, config: crate::Config) -> Self {
|
||||
pub(crate) fn new<R: Runtime>(app: &AppHandle<R>, config: crate::Config) -> Self {
|
||||
let app_ = app.clone();
|
||||
let run_on_main_thread =
|
||||
move |f: Box<dyn FnOnce() + Send + Sync + 'static>| app_.run_on_main_thread(f);
|
||||
Self {
|
||||
run_on_main_thread: Box::new(run_on_main_thread),
|
||||
installer_args: config
|
||||
.windows
|
||||
.as_ref()
|
||||
.map(|w| w.installer_args.clone())
|
||||
.unwrap_or_default(),
|
||||
current_exe_args: Vec::new(),
|
||||
app_name,
|
||||
current_version,
|
||||
app_name: app.package_info().name.clone(),
|
||||
current_version: app.package_info().version.clone(),
|
||||
config,
|
||||
version_comparator: None,
|
||||
executable_path: None,
|
||||
@@ -259,6 +266,7 @@ impl UpdaterBuilder {
|
||||
};
|
||||
|
||||
Ok(Updater {
|
||||
run_on_main_thread: Arc::new(self.run_on_main_thread),
|
||||
config: self.config,
|
||||
app_name: self.app_name,
|
||||
current_version: self.current_version,
|
||||
@@ -291,6 +299,8 @@ impl UpdaterBuilder {
|
||||
}
|
||||
|
||||
pub struct Updater {
|
||||
#[allow(dead_code)]
|
||||
run_on_main_thread: Arc<RunOnMainThread>,
|
||||
config: Config,
|
||||
app_name: String,
|
||||
current_version: Version,
|
||||
@@ -412,6 +422,7 @@ impl Updater {
|
||||
|
||||
let update = if should_update {
|
||||
Some(Update {
|
||||
run_on_main_thread: self.run_on_main_thread.clone(),
|
||||
config: self.config.clone(),
|
||||
on_before_exit: self.on_before_exit.clone(),
|
||||
app_name: self.app_name.clone(),
|
||||
@@ -440,6 +451,8 @@ impl Updater {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
#[allow(dead_code)]
|
||||
run_on_main_thread: Arc<RunOnMainThread>,
|
||||
config: Config,
|
||||
#[allow(unused)]
|
||||
on_before_exit: Option<OnBeforeExit>,
|
||||
@@ -1031,42 +1044,85 @@ 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);
|
||||
let extraction_path = tmp_extract_dir.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)?;
|
||||
return Err(err.into());
|
||||
// Ensure parent directories exist
|
||||
if let Some(parent) = extraction_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
extracted_files.push(extraction_path.to_path_buf());
|
||||
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
|
||||
};
|
||||
|
||||
if need_authorization {
|
||||
// Use AppleScript to perform moves with admin privileges
|
||||
let apple_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 (tx, rx) = std::sync::mpsc::channel();
|
||||
let res = (self.run_on_main_thread)(Box::new(move || {
|
||||
let mut script =
|
||||
osakit::Script::new_from_source(osakit::Language::AppleScript, &apple_script);
|
||||
script.compile().expect("invalid AppleScript");
|
||||
let r = script.execute();
|
||||
tx.send(r).unwrap();
|
||||
}));
|
||||
let result = rx.recv().unwrap();
|
||||
|
||||
if res.is_err() || result.is_err() {
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user