Allow apps to be installed in /Applications and update elevated on osx

This commit is contained in:
caesay
2024-01-01 18:34:45 +00:00
parent 9af45015d8
commit 25d3dabf27
6 changed files with 80 additions and 11 deletions

13
src/Rust/Cargo.lock generated
View File

@@ -1203,6 +1203,18 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "runas"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49535b7c73aec5596ae2c44a6d8a7a8f8592e5744564c327fd4846750413d921"
dependencies = [
"libc",
"security-framework-sys",
"which",
"windows-sys 0.48.0",
]
[[package]]
name = "rustix"
version = "0.38.28"
@@ -1619,6 +1631,7 @@ dependencies = [
"rand",
"regex",
"remove_dir_all",
"runas",
"semver",
"sha1_smol",
"simple-stopwatch",

View File

@@ -64,7 +64,7 @@ ureq = { version = "2.9", default-features = false, features = [
native-tls = "0.2"
file-rotate = "0.7"
derivative = "2.2"
simple-stopwatch = "0.1.4"
simple-stopwatch = "0.1"
glob = "0.3"
enum-flags = "0.3"
remove_dir_all = { git = "https://github.com/caesay/remove_dir_all.git", features = [
@@ -72,7 +72,8 @@ remove_dir_all = { git = "https://github.com/caesay/remove_dir_all.git", feature
] }
ntest = "0.9.0"
zstd = "0.13"
sha1_smol = "1.0.0"
sha1_smol = "1.0"
runas = "1.1"
[target.'cfg(target_os = "macos")'.dependencies]
native-dialog = "0.7"

View File

@@ -1,25 +1,28 @@
use crate::shared::{
self,
bundle::{self, BundleInfo, Manifest},
dialogs,
};
use anyhow::{bail, Result};
use anyhow::{anyhow, bail, Result};
use glob::glob;
use runas::Command as RunAsCommand;
use std::path::PathBuf;
pub fn apply<'a>(restart: bool, wait_for_parent: bool, package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Result<()> {
pub fn apply<'a>(restart: bool, wait_for_parent: bool, package: Option<&PathBuf>, exe_args: Option<Vec<&str>>, noelevate: bool) -> Result<()> {
if wait_for_parent {
let _ = shared::wait_for_parent_to_exit(60_000); // 1 minute
}
let (root_path, app) = shared::detect_current_manifest()?;
if let Err(e) = apply_package(package, &app, &root_path) {
if let Err(e) = apply_package(package, &app, &root_path, noelevate, restart, exe_args.clone()) {
error!("Error applying package: {}", e);
if !restart {
return Err(e);
}
}
// TODO: if the package fails to start, or fails hooks, we could roll back the install
if restart {
shared::start_package(&app, &root_path, exe_args, Some("VELOPACK_RESTART"))?;
}
@@ -27,7 +30,7 @@ pub fn apply<'a>(restart: bool, wait_for_parent: bool, package: Option<&PathBuf>
Ok(())
}
fn apply_package<'a>(package: Option<&PathBuf>, app: &Manifest, root_path: &PathBuf) -> Result<()> {
fn apply_package<'a>(package: Option<&PathBuf>, app: &Manifest, root_path: &PathBuf, noelevate: bool, restart: bool, exe_args: Option<Vec<&str>>) -> Result<()> {
let mut package_manifest: Option<Manifest> = None;
let mut package_bundle: Option<BundleInfo<'a>> = None;
@@ -82,13 +85,29 @@ fn apply_package<'a>(package: Option<&PathBuf>, app: &Manifest, root_path: &Path
crate::windows::run_hook(&app, &root_path, "--veloapp-obsolete", 15);
let current_dir = app.get_current_path(&root_path);
shared::replace_dir_with_rollback(current_dir.clone(), || {
if let Err(e) = shared::replace_dir_with_rollback(current_dir.clone(), || {
if let Some(bundle) = package_bundle.take() {
bundle.extract_lib_contents_to_path(&current_dir, |_| {})
} else {
bail!("No bundle could be loaded.");
}
})?;
}) {
// replacing the package failed, we can try to elevate it though.
error!("Failed to apply package: {}", e);
if !dialogs::get_silent() && !noelevate {
info!("Will try to elevate permissions and try again...");
let title = format!("{} Update", app.title);
let body = format!("{} {} is ready to be installed - you have {}, would you like to do this now?", app.title, found_version, app.version);
if dialogs::show_ok_cancel(title.as_str(), None, body.as_str(), Some("Install Update")) {
run_apply_elevated(restart, package, exe_args)?;
} else {
info!("User cancelled elevation prompt.");
return Err(e);
}
} else {
return Err(e);
}
}
#[cfg(target_os = "windows")]
crate::windows::run_hook(&package_manifest, &root_path, "--veloapp-updated", 15);
@@ -96,3 +115,36 @@ fn apply_package<'a>(package: Option<&PathBuf>, app: &Manifest, root_path: &Path
info!("Package applied successfully.");
Ok(())
}
fn run_apply_elevated(restart: bool, package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Result<()> {
let exe = std::env::current_exe()?;
let mut args: Vec<String> = Vec::new();
args.push("apply".to_string());
args.push("--noelevate".to_string());
if restart {
args.push("--restart".to_string());
}
let package = package.map(|p| p.to_string_lossy().to_string());
if let Some(pkg) = package {
args.push("--package".to_string());
args.push(pkg);
}
if let Some(a) = exe_args {
args.push("--".to_string());
a.iter().for_each(|a| args.push(a.to_string()));
}
info!("Attempting to elevate: {} {:?}", exe.to_string_lossy(), args);
let mut cmd = RunAsCommand::new(&exe);
cmd.gui(true);
cmd.force_prompt(false);
cmd.args(&args);
cmd.status().map_err(|z| anyhow!("Failed to restart elevated ({}).", z))?;
std::process::exit(0);
}

View File

@@ -58,7 +58,7 @@ pub fn detect_current_manifest() -> Result<(PathBuf, Manifest)> {
let root_dir = &my_path[..app_idx.unwrap()];
let root_dir = root_dir.to_owned() + ".app";
debug!("Detected Root: {}", root_dir);
debug!("Detected AppId: {}", manifest.id);
Ok((Path::new(&root_dir).to_path_buf(), manifest))

View File

@@ -28,6 +28,7 @@ fn root_command() -> Command {
.arg(arg!(-r --restart "Restart the application after the update"))
.arg(arg!(-w --wait "Wait for the parent process to terminate before applying the update"))
.arg(arg!(-p --package <FILE> "Update package to apply").value_parser(value_parser!(PathBuf)))
.arg(arg!(--noelevate "If the application does not have sufficient privileges, do not elevate to admin"))
.arg(arg!([EXE_ARGS] "Arguments to pass to the started executable. Must be preceeded by '--'.").required(false).last(true).num_args(0..))
)
.subcommand(Command::new("patch")
@@ -139,6 +140,7 @@ fn patch(matches: &ArgMatches) -> Result<()> {
}
fn apply(matches: &ArgMatches) -> Result<()> {
let noelevate = matches.get_flag("noelevate");
let restart = matches.get_flag("restart");
let wait_for_parent = matches.get_flag("wait");
let package = matches.get_one::<PathBuf>("package");
@@ -149,8 +151,9 @@ fn apply(matches: &ArgMatches) -> Result<()> {
info!(" Wait: {:?}", wait_for_parent);
info!(" Package: {:?}", package);
info!(" Exe Args: {:?}", exe_args);
info!(" No Elevate: {}", noelevate);
commands::apply(restart, wait_for_parent, package, exe_args)
commands::apply(restart, wait_for_parent, package, exe_args, noelevate)
}
#[cfg(target_os = "windows")]

View File

@@ -126,7 +126,7 @@ public class HelperExe : HelperFile
distXml.Insert(2, $"<title>{SecurityElement.Escape(appTitle)}</title>");
// disable local system installation (install to home dir)
distXml.Insert(2, "<domains enable_anywhere=\"false\" enable_currentUserHome=\"true\" enable_localSystem=\"false\" />");
distXml.Insert(2, "<domains enable_anywhere=\"false\" enable_currentUserHome=\"true\" enable_localSystem=\"true\" />");
// add extra landing content (eg. license, readme)
foreach (var kvp in extraContent) {