mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
We're too far down the rabbit hole. Need different apply implementations for each OS
This commit is contained in:
80
src/Rust/Cargo.lock
generated
80
src/Rust/Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(¤t_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
|
||||
}
|
||||
|
||||
77
src/Rust/src/commands/apply_linux_impl.rs
Normal file
77
src/Rust/src/commands/apply_linux_impl.rs
Normal 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(())
|
||||
}
|
||||
71
src/Rust/src/commands/apply_osx_impl.rs
Normal file
71
src/Rust/src/commands/apply_osx_impl.rs
Normal 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
|
||||
}
|
||||
134
src/Rust/src/commands/apply_windows_impl.rs
Normal file
134
src/Rust/src/commands/apply_windows_impl.rs
Normal 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(¤t_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
|
||||
// }
|
||||
@@ -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")]
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user