We're too far down the rabbit hole. Need different apply implementations for each OS

This commit is contained in:
caesay
2024-01-11 22:48:21 +00:00
parent fa50a19b71
commit 1c942f9a87
8 changed files with 358 additions and 278 deletions

80
src/Rust/Cargo.lock generated
View File

@@ -118,9 +118,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.21.5"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "bindet"
@@ -211,9 +211,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.12"
version = "4.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
checksum = "c12ed66a79a555082f595f7eb980d08669de95009dd4b3d61168c573ebe38fc9"
dependencies = [
"clap_builder",
"clap_derive",
@@ -221,9 +221,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.12"
version = "4.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
checksum = "0f4645eab3431e5a8403a96bea02506a8b35d28cd0f0330977dd5d22f9c84f43"
dependencies = [
"anstream",
"anstyle",
@@ -240,7 +240,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.46",
"syn 2.0.48",
]
[[package]]
@@ -344,9 +344,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
@@ -362,12 +362,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.18"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
dependencies = [
"cfg-if 1.0.0",
]
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "cvt"
@@ -554,7 +551,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.46",
"syn 2.0.48",
]
[[package]]
@@ -610,9 +607,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if 1.0.0",
"libc",
@@ -772,9 +769,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.151"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "libredox"
@@ -1046,7 +1043,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.46",
"syn 2.0.48",
]
[[package]]
@@ -1134,9 +1131,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.74"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
@@ -1261,18 +1258,6 @@ dependencies = [
"winapi 0.2.8",
]
[[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"
@@ -1341,22 +1326,22 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.194"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.194"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.46",
"syn 2.0.48",
]
[[package]]
@@ -1448,7 +1433,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.46",
"syn 2.0.48",
]
[[package]]
@@ -1464,9 +1449,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.46"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@@ -1512,7 +1497,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.46",
"syn 2.0.48",
]
[[package]]
@@ -1691,7 +1676,6 @@ dependencies = [
"rand",
"regex",
"remove_dir_all",
"runas",
"semver",
"sha1_smol",
"simple-stopwatch",
@@ -1778,7 +1762,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.46",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@@ -1800,7 +1784,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.46",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2101,9 +2085,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.32"
version = "0.5.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6"
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
dependencies = [
"memchr",
]

View File

@@ -74,7 +74,6 @@ remove_dir_all = { git = "https://github.com/caesay/remove_dir_all.git", feature
] }
zstd = "0.13"
sha1_smol = "1.0"
runas = "1.1"
[target.'cfg(unix)'.dependencies]
native-dialog = "0.7"

View File

@@ -1,9 +1,5 @@
use crate::shared::{
self,
bundle::{self, Manifest},
dialogs,
};
use anyhow::{anyhow, bail, Result};
use crate::shared::{self, bundle::Manifest};
use anyhow::Result;
use std::path::PathBuf;
pub fn apply<'a>(
@@ -16,6 +12,15 @@ pub fn apply<'a>(
noelevate: bool,
runhooks: bool,
) -> Result<()> {
#[cfg(target_os = "windows")]
use super::apply_windows_impl::apply_package_impl;
#[cfg(target_os = "macos")]
use super::apply_osx_impl::apply_package_impl;
#[cfg(target_os = "linux")]
use super::apply_linux_impl::apply_package_impl;
if wait_for_parent {
if let Err(e) = shared::wait_for_parent_to_exit(60_000) {
warn!("Failed to wait for parent process to exit ({}).", e);
@@ -24,9 +29,10 @@ pub fn apply<'a>(
if let Err(e) = apply_package_impl(&root_path, &app, package, exe_args.clone(), noelevate, runhooks) {
error!("Error applying package: {}", e);
if !restart {
return Err(e);
if restart {
shared::start_package(&app, &root_path, exe_args, Some("VELOPACK_RESTART"))?;
}
return Err(e);
}
// TODO: if the package fails to start, or fails hooks, we could roll back the install
@@ -36,224 +42,3 @@ pub fn apply<'a>(
Ok(())
}
#[cfg(not(target_os = "linux"))]
fn apply_package_impl<'a>(
root_path: &PathBuf,
app: &Manifest,
package: Option<&PathBuf>,
exe_args: Option<Vec<&str>>,
noelevate: bool,
runhooks: bool,
) -> Result<()> {
let mut package_manifest: Option<Manifest> = None;
let mut package_bundle: Option<bundle::BundleInfo<'a>> = None;
if let Some(pkg) = package {
info!("Loading package from argument '{}'.", pkg.to_string_lossy());
let bun = bundle::load_bundle_from_file(&pkg)?;
package_manifest = Some(bun.read_manifest()?);
package_bundle = Some(bun);
} else {
info!("No package specified, searching for latest.");
let packages_dir = app.get_packages_path(&root_path);
if let Ok(paths) = glob::glob(format!("{}/*.nupkg", packages_dir).as_str()) {
for path in paths {
if let Ok(path) = path {
trace!("Checking package: '{}'", path.to_string_lossy());
if let Ok(bun) = bundle::load_bundle_from_file(&path) {
if let Ok(mani) = bun.read_manifest() {
if package_manifest.is_none() || mani.version > package_manifest.clone().unwrap().version {
info!("Found {}: '{}'", mani.version, path.to_string_lossy());
package_manifest = Some(mani);
package_bundle = Some(bun);
}
}
}
}
}
}
}
if package_manifest.is_none() || package_bundle.is_none() {
bail!("Unable to find/load suitable package.");
}
let package_manifest = package_manifest.unwrap();
let found_version = (&package_manifest.version).to_owned();
if found_version <= app.version {
if package.is_none() {
bail!("Latest package found is {}, which is not newer than current version {}.", found_version, app.version);
} else {
warn!("Provided package is {}, which is not newer than current version {}.", found_version, app.version);
}
}
info!("Applying package to current: {}", found_version);
#[cfg(target_os = "windows")]
{
if !crate::windows::prerequisite::prompt_and_install_all_missing(&package_manifest, Some(&app.version))? {
bail!("Stopping apply. Pre-requisites are missing and user cancelled.");
}
if runhooks {
crate::windows::run_hook(&app, &root_path, "--veloapp-obsolete", 15);
} else {
info!("Skipping --veloapp-obsolete hook.");
}
}
let current_dir = app.get_current_path(&root_path);
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);
info!("Will try to elevate permissions and try again...");
ask_user_to_elevate(&package_manifest, noelevate, package, exe_args)?;
}
#[cfg(target_os = "windows")]
{
if let Err(e) = package_manifest.write_uninstall_entry(root_path) {
warn!("Failed to write uninstall entry ({}).", e);
}
if runhooks {
crate::windows::run_hook(&package_manifest, &root_path, "--veloapp-updated", 15);
} else {
info!("Skipping --veloapp-updated hook.");
}
}
info!("Package applied successfully.");
Ok(())
}
#[cfg(target_os = "linux")]
fn apply_package_impl<'a>(
root_path: &PathBuf,
app: &Manifest,
package: Option<&PathBuf>,
exe_args: Option<Vec<&str>>,
noelevate: bool,
_runhooks: bool,
) -> Result<()> {
// on linux, the current "dir" is actually an AppImage file which we need to replace.
let pkg = package.ok_or(anyhow!("Package is required"))?;
info!("Loading bundle from {}", pkg.to_string_lossy());
let bundle = bundle::load_bundle_from_file(pkg)?;
let mut tmp_path = root_path.to_string_lossy().to_string();
tmp_path = tmp_path + "_" + shared::random_string(8).as_ref();
info!("Extracting AppImage to temp file");
let result: Result<()> = (|| {
bundle.extract_zip_predicate_to_path(|z| z.ends_with(".AppImage"), &tmp_path)?;
std::fs::set_permissions(&tmp_path, <std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o755))?;
std::fs::rename(&tmp_path, &root_path)?;
Ok(())
})();
match result {
Ok(()) => {
info!("AppImage extracted successfully to {}", &root_path.to_string_lossy());
}
Err(e) => {
let _ = std::fs::remove_file(&tmp_path);
if shared::is_error_permission_denied(&e) {
error!("An error occurred {}, will attempt to elevate permissions and try again...", e);
ask_user_to_elevate(&app, noelevate, package, exe_args)?;
} else {
bail!("Unable to extract AppImage ({})", e);
}
}
}
Ok(())
}
fn ask_user_to_elevate(app_to: &Manifest, noelevate: bool, package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Result<()> {
if noelevate {
bail!("Not allowed to ask for elevated permissions because --noelevate flag is set.");
}
if dialogs::get_silent() {
bail!("Not allowed to ask for elevated permissions because --silent flag is set.");
}
let title = format!("{} Update", app_to.title);
let body =
format!("{} would like to update to version {}, but requested elevated permissions to do so. Would you like to proceed?", app_to.title, app_to.version);
info!("Showing user elevation prompt?");
if dialogs::show_ok_cancel(title.as_str(), None, body.as_str(), Some("Install Update")) {
info!("User answered yes, starting elevation...");
run_apply_elevated(package, exe_args)?;
} else {
bail!("User cancelled elevation prompt.");
}
Ok(())
}
#[cfg(not(target_os = "linux"))]
fn run_apply_elevated(package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Result<()> {
use runas::Command as RunAsCommand;
let exe = std::env::current_exe()?;
let args = get_run_elevated_args(package, exe_args);
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);
let status = cmd.status().map_err(|z| anyhow!("Failed to restart elevated ({}).", z))?;
info!("elevated proess exited with status: {}", status);
Ok(())
}
#[cfg(target_os = "linux")]
fn run_apply_elevated(package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Result<()> {
// in linux, as soon as the main AppImage process exits, the fs is unmounted
// so we need to write self to a temporary file before we can use pkexec
let temp_path = format!("/tmp/{}_update", shared::random_string(8));
shared::copy_own_fd_to_file(&temp_path)?;
std::fs::set_permissions(&temp_path, <std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o755))?;
let path = std::env::var("APPIMAGE")?;
let mut args = get_run_elevated_args(package, exe_args);
args.insert(0, "env".to_string());
args.insert(1, format!("APPIMAGE={}", path));
args.insert(2, temp_path.to_owned());
info!("Attempting to elevate: pkexec {:?}", args);
let status = std::process::Command::new("pkexec").args(args).status();
let _ = std::fs::remove_file(&temp_path);
info!("pkexec exited with status: {}", status?);
Ok(())
}
fn get_run_elevated_args(package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Vec<String> {
let mut args: Vec<String> = Vec::new();
args.push("apply".to_string());
args.push("--noelevate".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()));
}
args
}

View File

@@ -0,0 +1,77 @@
fn apply_package_impl<'a>(
root_path: &PathBuf,
app: &Manifest,
package: Option<&PathBuf>,
exe_args: Option<Vec<&str>>,
noelevate: bool,
_runhooks: bool,
) -> Result<()> {
// on linux, the current "dir" is actually an AppImage file which we need to replace.
let pkg = package.ok_or(anyhow!("Package is required"))?;
info!("Loading bundle from {}", pkg.to_string_lossy());
let bundle = bundle::load_bundle_from_file(pkg)?;
let mut tmp_path = root_path.to_string_lossy().to_string();
tmp_path = tmp_path + "_" + shared::random_string(8).as_ref();
info!("Extracting AppImage to temp file");
let result: Result<()> = (|| {
bundle.extract_zip_predicate_to_path(|z| z.ends_with(".AppImage"), &tmp_path)?;
std::fs::set_permissions(&tmp_path, <std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o755))?;
std::fs::rename(&tmp_path, &root_path)?;
Ok(())
})();
match result {
Ok(()) => {
info!("AppImage extracted successfully to {}", &root_path.to_string_lossy());
}
Err(e) => {
let _ = std::fs::remove_file(&tmp_path);
if shared::is_error_permission_denied(&e) {
error!("An error occurred {}, will attempt to elevate permissions and try again...", e);
ask_user_to_elevate(&app, noelevate, package, exe_args)?;
} else {
bail!("Unable to extract AppImage ({})", e);
}
}
}
Ok(())
}
fn run_apply_elevated(package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Result<()> {
// in linux, as soon as the main AppImage process exits, the fs is unmounted
// so we need to write self to a temporary file before we can use pkexec
let temp_path = format!("/tmp/{}_update", shared::random_string(8));
shared::copy_own_fd_to_file(&temp_path)?;
std::fs::set_permissions(&temp_path, <std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o755))?;
let path = std::env::var("APPIMAGE")?;
let mut args: Vec<String> = Vec::new();
args.push("env".to_string());
args.push(format!("APPIMAGE={}", path));
args.push(temp_path.to_owned());
args.push("apply".to_string());
args.push("--noelevate".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: pkexec {:?}", args);
let status = std::process::Command::new("pkexec").args(args).status();
let _ = std::fs::remove_file(&temp_path);
info!("pkexec exited with status: {}", status?);
Ok(())
}

View File

@@ -0,0 +1,71 @@
use crate::shared::{
self,
bundle::{self, Manifest},
dialogs,
};
use anyhow::{anyhow, bail, Result};
use std::{fs, path::PathBuf, process::Command};
pub fn apply_package_impl<'a>(
root_path: &PathBuf,
app: &Manifest,
package: Option<&PathBuf>,
_exe_args: Option<Vec<&str>>,
_noelevate: bool,
_runhooks: bool,
) -> Result<()> {
let pkg = package.ok_or(anyhow!("Package is required on OSX"))?;
let tmp_path_new = format!("/tmp/velopack/{}/{}", app.id, shared::random_string(8));
let tmp_path_old = format!("/tmp/velopack/{}/{}", app.id, shared::random_string(8));
let action: Result<()> = (|| {
// 1. extract the bundle to a temp dir
let bundle = bundle::load_bundle_from_file(pkg)?;
let manifest = bundle.read_manifest()?;
fs::create_dir_all(&tmp_path_new)?;
info!("Extracting bundle to {}", &tmp_path_new);
bundle.extract_lib_contents_to_path(&tmp_path_new, |_| {})?;
// 2. attempt to replace the current bundle with the new one
let result: Result<()> = (|| {
info!("Replacing bundle at {}", &root_path.to_string_lossy());
fs::rename(&root_path, &tmp_path_old)?;
fs::rename(&tmp_path_new, &root_path)?;
Ok(())
})();
match result {
Ok(()) => {
info!("Bundle extracted successfully to {}", &root_path.to_string_lossy());
Ok(())
}
Err(e) => {
// 3. if fails for permission error, try again escallated via osascript
if shared::is_error_permission_denied(&e) {
error!("A permissions error occurred ({}), will attempt to elevate permissions and try again...", e);
dialogs::ask_user_to_elevate(&manifest, false)?;
let script = format!(
"do shell script \"mv -f '{}' '{}' && mv -f '{}' '{}'\" with administrator privileges",
&root_path.to_string_lossy(),
&tmp_path_old,
&tmp_path_new,
&root_path.to_string_lossy()
);
info!("Running elevated process via osascript: {}", script);
let output = Command::new("osascript").arg("-e").arg(&script).status()?;
if output.success() {
info!("Bundle applied successfully via osascript.");
Ok(())
} else {
bail!("elevated process failed: exited with code: {}", output);
}
} else {
bail!("Failed to extract bundle ({})", e);
}
}
}
})();
let _ = fs::remove_dir_all(&tmp_path_new);
let _ = fs::remove_dir_all(&tmp_path_old);
action
}

View File

@@ -0,0 +1,134 @@
#[cfg(not(target_os = "linux"))]
fn apply_package_impl<'a>(
root_path: &PathBuf,
app: &Manifest,
package: Option<&PathBuf>,
exe_args: Option<Vec<&str>>,
noelevate: bool,
runhooks: bool,
) -> Result<()> {
let mut package_manifest: Option<Manifest> = None;
let mut package_bundle: Option<bundle::BundleInfo<'a>> = None;
if let Some(pkg) = package {
info!("Loading package from argument '{}'.", pkg.to_string_lossy());
let bun = bundle::load_bundle_from_file(&pkg)?;
package_manifest = Some(bun.read_manifest()?);
package_bundle = Some(bun);
} else {
info!("No package specified, searching for latest.");
let packages_dir = app.get_packages_path(&root_path);
if let Ok(paths) = glob::glob(format!("{}/*.nupkg", packages_dir).as_str()) {
for path in paths {
if let Ok(path) = path {
trace!("Checking package: '{}'", path.to_string_lossy());
if let Ok(bun) = bundle::load_bundle_from_file(&path) {
if let Ok(mani) = bun.read_manifest() {
if package_manifest.is_none() || mani.version > package_manifest.clone().unwrap().version {
info!("Found {}: '{}'", mani.version, path.to_string_lossy());
package_manifest = Some(mani);
package_bundle = Some(bun);
}
}
}
}
}
}
}
if package_manifest.is_none() || package_bundle.is_none() {
bail!("Unable to find/load suitable package.");
}
let package_manifest = package_manifest.unwrap();
let found_version = (&package_manifest.version).to_owned();
if found_version <= app.version {
if package.is_none() {
bail!("Latest package found is {}, which is not newer than current version {}.", found_version, app.version);
} else {
warn!("Provided package is {}, which is not newer than current version {}.", found_version, app.version);
}
}
info!("Applying package to current: {}", found_version);
#[cfg(target_os = "windows")]
{
if !crate::windows::prerequisite::prompt_and_install_all_missing(&package_manifest, Some(&app.version))? {
bail!("Stopping apply. Pre-requisites are missing and user cancelled.");
}
if runhooks {
crate::windows::run_hook(&app, &root_path, "--veloapp-obsolete", 15);
} else {
info!("Skipping --veloapp-obsolete hook.");
}
}
let current_dir = app.get_current_path(&root_path);
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);
info!("Will try to elevate permissions and try again...");
ask_user_to_elevate(&package_manifest, noelevate, package, exe_args)?;
}
#[cfg(target_os = "windows")]
{
if let Err(e) = package_manifest.write_uninstall_entry(root_path) {
warn!("Failed to write uninstall entry ({}).", e);
}
if runhooks {
crate::windows::run_hook(&package_manifest, &root_path, "--veloapp-updated", 15);
} else {
info!("Skipping --veloapp-updated hook.");
}
}
info!("Package applied successfully.");
Ok(())
}
// #[cfg(not(target_os = "linux"))]
// fn run_apply_elevated(package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Result<()> {
// use runas::Command as RunAsCommand;
// let exe = std::env::current_exe()?;
// let args = get_run_elevated_args(package, exe_args);
// 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);
// let status = cmd.status().map_err(|z| anyhow!("Failed to restart elevated ({}).", z))?;
// info!("elevated proess exited with status: {}", status);
// Ok(())
// }
// fn get_run_elevated_args(package: Option<&PathBuf>, exe_args: Option<Vec<&str>>) -> Vec<String> {
// let mut args: Vec<String> = Vec::new();
// args.push("apply".to_string());
// args.push("--noelevate".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()));
// }
// args
// }

View File

@@ -4,6 +4,13 @@ pub use apply::*;
mod patch;
pub use patch::*;
#[cfg(target_os = "windows")]
mod apply_windows_impl;
#[cfg(target_os = "macos")]
mod apply_osx_impl;
#[cfg(target_os = "linux")]
mod apply_linux_impl;
#[cfg(target_os = "windows")]
mod start;
#[cfg(target_os = "windows")]

View File

@@ -1,6 +1,7 @@
use super::dialogs::{generate_alert, generate_confirm};
use super::dialogs_const::*;
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::{Result, bail};
static SILENT: AtomicBool = AtomicBool::new(false);
@@ -42,6 +43,28 @@ pub fn show_ok_cancel(title: &str, header: Option<&str>, body: &str, ok_text: Op
generate_confirm(title, header, body, ok_text, btns, DialogIcon::Warning).map(|dlg_id| dlg_id == DialogResult::Ok).unwrap_or(false)
}
pub fn ask_user_to_elevate(app_to: &crate::bundle::Manifest, noelevate: bool) -> Result<()> {
if noelevate {
bail!("Not allowed to ask for elevated permissions because --noelevate flag is set.");
}
if get_silent() {
bail!("Not allowed to ask for elevated permissions because --silent flag is set.");
}
let title = format!("{} Update", app_to.title);
let body =
format!("{} would like to update to version {}, but requested elevated permissions to do so. Would you like to proceed?", app_to.title, app_to.version);
info!("Showing user elevation prompt?");
if show_ok_cancel(title.as_str(), None, body.as_str(), Some("Install Update")) {
info!("User answered yes to elevation...");
Ok(())
} else {
bail!("User cancelled elevation prompt.");
}
}
#[test]
#[ntest::timeout(2000)]
fn test_no_dialogs_show_if_silent() {