2018-08-03 11:52:31 +00:00
|
|
|
//! main.rs
|
|
|
|
//!
|
|
|
|
//! The main entrypoint for the application. Orchestrates the building of the installation
|
|
|
|
//! framework, and opens necessary HTTP servers/frontends.
|
|
|
|
|
2018-08-03 15:12:03 +00:00
|
|
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
2018-08-04 07:15:27 +00:00
|
|
|
#![deny(unsafe_code)]
|
2018-08-04 08:35:00 +00:00
|
|
|
#![deny(missing_docs)]
|
2018-01-26 12:29:28 +00:00
|
|
|
|
2018-08-03 11:49:38 +00:00
|
|
|
#[cfg(windows)]
|
|
|
|
extern crate nfd;
|
|
|
|
|
2018-01-26 12:29:28 +00:00
|
|
|
extern crate web_view;
|
2018-01-27 11:56:36 +00:00
|
|
|
|
2018-07-28 05:33:06 +00:00
|
|
|
extern crate futures;
|
2018-08-03 07:50:17 +00:00
|
|
|
extern crate hyper;
|
2018-08-03 11:52:31 +00:00
|
|
|
extern crate url;
|
2018-01-29 10:27:54 +00:00
|
|
|
|
2018-01-30 04:53:28 +00:00
|
|
|
extern crate number_prefix;
|
2018-01-30 04:54:44 +00:00
|
|
|
extern crate reqwest;
|
2018-01-30 04:53:28 +00:00
|
|
|
|
2018-01-27 03:27:41 +00:00
|
|
|
extern crate serde;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate serde_derive;
|
|
|
|
extern crate serde_json;
|
|
|
|
extern crate toml;
|
|
|
|
|
2018-01-29 12:28:14 +00:00
|
|
|
extern crate regex;
|
2018-01-29 12:37:17 +00:00
|
|
|
extern crate semver;
|
2018-01-29 07:21:37 +00:00
|
|
|
|
2018-08-04 08:35:00 +00:00
|
|
|
extern crate dirs;
|
2018-08-08 02:47:32 +00:00
|
|
|
extern crate tar;
|
2018-08-08 06:42:09 +00:00
|
|
|
extern crate xz_decom;
|
2018-01-30 07:29:34 +00:00
|
|
|
extern crate zip;
|
|
|
|
|
2018-08-04 06:28:13 +00:00
|
|
|
extern crate fern;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate log;
|
|
|
|
|
|
|
|
extern crate chrono;
|
|
|
|
|
2018-08-04 13:35:56 +00:00
|
|
|
extern crate clap;
|
|
|
|
|
2018-08-08 02:47:32 +00:00
|
|
|
mod archives;
|
2018-01-26 12:29:28 +00:00
|
|
|
mod assets;
|
2018-01-27 03:27:41 +00:00
|
|
|
mod config;
|
2018-05-03 11:50:44 +00:00
|
|
|
mod http;
|
2018-01-27 03:27:41 +00:00
|
|
|
mod installer;
|
2018-08-04 07:03:32 +00:00
|
|
|
mod logging;
|
2018-08-06 10:51:59 +00:00
|
|
|
mod native;
|
2018-05-03 11:50:44 +00:00
|
|
|
mod rest;
|
2018-01-29 07:21:37 +00:00
|
|
|
mod sources;
|
2018-08-03 07:50:17 +00:00
|
|
|
mod tasks;
|
2018-01-26 12:29:28 +00:00
|
|
|
|
|
|
|
use web_view::*;
|
|
|
|
|
2018-01-27 03:27:41 +00:00
|
|
|
use installer::InstallerFramework;
|
2018-01-26 12:29:28 +00:00
|
|
|
|
2018-08-03 12:04:58 +00:00
|
|
|
#[cfg(windows)]
|
2018-08-03 11:49:38 +00:00
|
|
|
use nfd::Response;
|
2018-08-03 12:04:58 +00:00
|
|
|
|
2018-01-27 03:27:41 +00:00
|
|
|
use rest::WebServer;
|
|
|
|
|
2018-08-09 05:21:50 +00:00
|
|
|
use std::net::TcpListener;
|
2018-08-03 14:54:03 +00:00
|
|
|
use std::net::ToSocketAddrs;
|
|
|
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
use std::sync::RwLock;
|
|
|
|
|
2018-08-09 05:21:50 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
use std::process::exit;
|
|
|
|
use std::process::Command;
|
|
|
|
use std::{thread, time};
|
|
|
|
|
|
|
|
use std::fs::remove_file;
|
|
|
|
use std::fs::File;
|
|
|
|
|
2018-08-04 07:03:32 +00:00
|
|
|
use logging::LoggingErrors;
|
|
|
|
|
2018-08-04 13:35:56 +00:00
|
|
|
use clap::App;
|
|
|
|
use clap::Arg;
|
|
|
|
use log::Level;
|
|
|
|
|
2018-08-07 05:34:57 +00:00
|
|
|
use config::BaseAttributes;
|
|
|
|
|
2018-09-20 03:37:44 +00:00
|
|
|
static RAW_CONFIG: &'static str = include_str!(concat!(env!("OUT_DIR"), "/bootstrap.toml"));
|
2018-01-26 12:29:28 +00:00
|
|
|
|
2018-08-03 11:49:38 +00:00
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
enum CallbackType {
|
|
|
|
SelectInstallDir { callback_name: String },
|
2018-08-04 13:35:56 +00:00
|
|
|
Log { msg: String, kind: String },
|
2018-08-03 11:49:38 +00:00
|
|
|
}
|
|
|
|
|
2018-01-26 12:29:28 +00:00
|
|
|
fn main() {
|
2018-10-01 03:17:59 +00:00
|
|
|
let config = BaseAttributes::from_toml_str(RAW_CONFIG).expect("Config file could not be read");
|
2018-10-01 01:27:31 +00:00
|
|
|
|
2018-10-01 03:17:59 +00:00
|
|
|
logging::setup_logger(format!("{}_installer.log", config.name))
|
|
|
|
.expect("Unable to setup logging!");
|
2018-01-27 03:27:41 +00:00
|
|
|
|
2018-08-07 05:34:57 +00:00
|
|
|
let app_name = config.name.clone();
|
2018-01-27 04:33:15 +00:00
|
|
|
|
2018-08-09 05:21:50 +00:00
|
|
|
let app_about = format!("An interactive installer for {}", app_name);
|
|
|
|
let app = App::new(format!("{} installer", app_name))
|
2018-08-04 13:35:56 +00:00
|
|
|
.version(env!("CARGO_PKG_VERSION"))
|
2018-08-09 05:21:50 +00:00
|
|
|
.about(app_about.as_ref())
|
2018-08-04 13:35:56 +00:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("launcher")
|
|
|
|
.long("launcher")
|
|
|
|
.value_name("TARGET")
|
|
|
|
.help("Launches the specified executable after checking for updates")
|
|
|
|
.takes_value(true),
|
2018-08-09 05:21:50 +00:00
|
|
|
).arg(
|
|
|
|
Arg::with_name("swap")
|
|
|
|
.long("swap")
|
|
|
|
.value_name("TARGET")
|
|
|
|
.help("Internal usage - swaps around a new installer executable")
|
|
|
|
.takes_value(true),
|
|
|
|
);
|
|
|
|
|
|
|
|
let reinterpret_app = app.clone(); // In case a reparse is needed
|
|
|
|
let mut matches = app.get_matches();
|
2018-08-04 13:35:56 +00:00
|
|
|
|
2018-08-04 06:28:13 +00:00
|
|
|
info!("{} installer", app_name);
|
2018-08-03 10:59:43 +00:00
|
|
|
|
2018-08-04 07:03:32 +00:00
|
|
|
let current_exe = std::env::current_exe().log_expect("Current executable could not be found");
|
|
|
|
let current_path = current_exe
|
|
|
|
.parent()
|
|
|
|
.log_expect("Parent directory of executable could not be found");
|
2018-08-09 05:21:50 +00:00
|
|
|
|
|
|
|
// Check to see if we are currently in a self-update
|
|
|
|
if let Some(to_path) = matches.value_of("swap") {
|
|
|
|
let to_path = PathBuf::from(to_path);
|
|
|
|
|
|
|
|
// Sleep a little bit to allow Windows to close the previous file handle
|
|
|
|
thread::sleep(time::Duration::from_millis(3000));
|
|
|
|
|
|
|
|
info!(
|
|
|
|
"Swapping installer from {} to {}",
|
|
|
|
current_exe.display(),
|
|
|
|
to_path.display()
|
|
|
|
);
|
|
|
|
|
2018-10-04 04:07:49 +00:00
|
|
|
// Attempt it a few times because Windows can hold a lock
|
|
|
|
for i in 1..=5 {
|
|
|
|
let swap_result = if cfg!(windows) {
|
|
|
|
use std::fs::copy;
|
|
|
|
|
|
|
|
copy(¤t_exe, &to_path).map(|_x| ())
|
|
|
|
} else {
|
|
|
|
use std::fs::rename;
|
|
|
|
|
|
|
|
rename(¤t_exe, &to_path)
|
|
|
|
};
|
|
|
|
|
|
|
|
match swap_result {
|
|
|
|
Ok(_) => break,
|
|
|
|
Err(e) => {
|
|
|
|
if i < 5 {
|
|
|
|
info!("Copy attempt failed: {:?}, retrying in 3 seconds.", e);
|
|
|
|
thread::sleep(time::Duration::from_millis(3000));
|
|
|
|
} else {
|
|
|
|
let _: () = Err(e).log_expect("Copying new binary failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-09 05:21:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Command::new(to_path)
|
|
|
|
.spawn()
|
|
|
|
.log_expect("Unable to start child process");
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2018-10-04 04:07:49 +00:00
|
|
|
// If we just finished a update, we need to inject our previous command line arguments
|
2018-08-09 05:21:50 +00:00
|
|
|
let args_file = current_path.join("args.json");
|
|
|
|
|
|
|
|
if args_file.exists() {
|
|
|
|
let database: Vec<String> = {
|
|
|
|
let metadata_file =
|
|
|
|
File::open(&args_file).log_expect("Unable to open args file handle");
|
|
|
|
|
|
|
|
serde_json::from_reader(metadata_file).log_expect("Unable to read metadata file")
|
|
|
|
};
|
|
|
|
|
|
|
|
matches = reinterpret_app.get_matches_from(database);
|
|
|
|
|
2018-10-04 04:07:49 +00:00
|
|
|
info!("Parsed command line arguments from original instance");
|
2018-08-09 05:21:50 +00:00
|
|
|
remove_file(args_file).log_expect("Unable to clean up args file");
|
2018-10-04 04:07:49 +00:00
|
|
|
}
|
2018-08-09 05:21:50 +00:00
|
|
|
|
2018-10-04 04:07:49 +00:00
|
|
|
// Cleanup any remaining new maintenance tool instances if they exist
|
|
|
|
if cfg!(windows) {
|
|
|
|
let updater_executable = current_path.join("maintenancetool_new.exe");
|
2018-08-09 05:21:50 +00:00
|
|
|
|
2018-10-04 04:07:49 +00:00
|
|
|
if updater_executable.exists() {
|
2018-08-09 05:21:50 +00:00
|
|
|
// Sleep a little bit to allow Windows to close the previous file handle
|
|
|
|
thread::sleep(time::Duration::from_millis(3000));
|
|
|
|
|
2018-10-04 04:07:49 +00:00
|
|
|
// Attempt it a few times because Windows can hold a lock
|
|
|
|
for i in 1..=5 {
|
|
|
|
let swap_result = remove_file(&updater_executable);
|
|
|
|
match swap_result {
|
|
|
|
Ok(_) => break,
|
|
|
|
Err(e) => {
|
|
|
|
if i < 5 {
|
|
|
|
info!("Cleanup attempt failed: {:?}, retrying in 3 seconds.", e);
|
|
|
|
thread::sleep(time::Duration::from_millis(3000));
|
|
|
|
} else {
|
|
|
|
warn!("Deleting temp binary failed after 5 attempts: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-09 05:21:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load in metadata as to learn about the environment
|
2018-05-03 04:14:44 +00:00
|
|
|
let metadata_file = current_path.join("metadata.json");
|
2018-08-04 13:35:56 +00:00
|
|
|
let mut framework = if metadata_file.exists() {
|
2018-08-04 06:28:13 +00:00
|
|
|
info!("Using pre-existing metadata file: {:?}", metadata_file);
|
2018-08-04 07:03:32 +00:00
|
|
|
InstallerFramework::new_with_db(config, current_path).log_expect("Unable to parse metadata")
|
2018-05-03 03:30:58 +00:00
|
|
|
} else {
|
2018-08-04 06:28:13 +00:00
|
|
|
info!("Starting fresh install");
|
2018-05-03 03:30:58 +00:00
|
|
|
InstallerFramework::new(config)
|
|
|
|
};
|
2018-01-27 03:27:41 +00:00
|
|
|
|
2018-08-04 13:35:56 +00:00
|
|
|
let is_launcher = if let Some(string) = matches.value_of("launcher") {
|
|
|
|
framework.is_launcher = true;
|
|
|
|
framework.launcher_path = Some(string.to_string());
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
};
|
|
|
|
|
2018-08-03 15:01:20 +00:00
|
|
|
// Firstly, allocate us an epidermal port
|
|
|
|
let target_port = {
|
2018-08-04 07:03:32 +00:00
|
|
|
let listener = TcpListener::bind("127.0.0.1:0")
|
|
|
|
.log_expect("At least one local address should be free");
|
2018-08-03 15:01:20 +00:00
|
|
|
listener
|
|
|
|
.local_addr()
|
2018-08-04 07:03:32 +00:00
|
|
|
.log_expect("Should be able to pull address from listener")
|
2018-08-03 15:01:20 +00:00
|
|
|
.port()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Now, iterate over all ports
|
2018-08-03 14:54:03 +00:00
|
|
|
let addresses = "localhost:0"
|
|
|
|
.to_socket_addrs()
|
2018-08-04 07:03:32 +00:00
|
|
|
.log_expect("No localhost address found");
|
2018-08-03 14:54:03 +00:00
|
|
|
|
|
|
|
let mut servers = Vec::new();
|
|
|
|
let mut http_address = None;
|
|
|
|
|
|
|
|
let framework = Arc::new(RwLock::new(framework));
|
2018-01-27 03:27:41 +00:00
|
|
|
|
2018-01-26 12:29:28 +00:00
|
|
|
// Startup HTTP server for handling the web view
|
2018-08-03 15:01:20 +00:00
|
|
|
for mut address in addresses {
|
|
|
|
address.set_port(target_port);
|
|
|
|
|
2018-08-04 08:35:00 +00:00
|
|
|
let server = WebServer::with_addr(framework.clone(), address)
|
2018-08-04 07:03:32 +00:00
|
|
|
.log_expect("Failed to bind to address");
|
2018-08-03 14:54:03 +00:00
|
|
|
|
2018-08-07 10:23:28 +00:00
|
|
|
info!("Server: {:?}", address);
|
2018-08-03 14:54:03 +00:00
|
|
|
|
2018-08-04 07:03:32 +00:00
|
|
|
http_address = Some(address);
|
2018-08-03 14:54:03 +00:00
|
|
|
|
|
|
|
servers.push(server);
|
|
|
|
}
|
|
|
|
|
2018-08-04 07:05:46 +00:00
|
|
|
let http_address = http_address.log_expect("No HTTP address found");
|
2018-08-03 14:54:03 +00:00
|
|
|
|
|
|
|
let http_address = format!("http://localhost:{}", http_address.port());
|
2018-01-27 03:27:41 +00:00
|
|
|
|
2018-01-26 12:29:28 +00:00
|
|
|
// Init the web view
|
2018-08-04 13:35:56 +00:00
|
|
|
let size = if is_launcher { (600, 300) } else { (1024, 500) };
|
|
|
|
|
2018-01-26 12:29:28 +00:00
|
|
|
let resizable = false;
|
|
|
|
let debug = true;
|
|
|
|
|
|
|
|
run(
|
2018-01-27 04:33:15 +00:00
|
|
|
&format!("{} Installer", app_name),
|
2018-05-03 13:08:26 +00:00
|
|
|
Content::Url(http_address),
|
2018-01-26 12:29:28 +00:00
|
|
|
Some(size),
|
|
|
|
resizable,
|
|
|
|
debug,
|
2018-01-29 07:21:37 +00:00
|
|
|
|_| {},
|
2018-07-28 05:33:06 +00:00
|
|
|
|wv, msg, _| {
|
2018-08-03 11:49:38 +00:00
|
|
|
let command: CallbackType =
|
2018-08-04 07:03:32 +00:00
|
|
|
serde_json::from_str(msg).log_expect(&format!("Unable to parse string: {:?}", msg));
|
2018-08-03 11:49:38 +00:00
|
|
|
|
2018-08-04 06:28:13 +00:00
|
|
|
debug!("Incoming payload: {:?}", command);
|
2018-08-03 11:49:38 +00:00
|
|
|
|
|
|
|
match command {
|
|
|
|
CallbackType::SelectInstallDir { callback_name } => {
|
|
|
|
#[cfg(windows)]
|
2018-08-04 07:03:32 +00:00
|
|
|
let result = match nfd::open_pick_folder(None)
|
|
|
|
.log_expect("Unable to open folder dialog")
|
|
|
|
{
|
|
|
|
Response::Okay(v) => v,
|
|
|
|
_ => return,
|
|
|
|
};
|
2018-08-03 11:49:38 +00:00
|
|
|
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
let result =
|
|
|
|
wv.dialog(Dialog::ChooseDirectory, "Select a install directory...", "");
|
|
|
|
|
2018-08-04 08:35:00 +00:00
|
|
|
if !result.is_empty() {
|
2018-08-04 07:03:32 +00:00
|
|
|
let result = serde_json::to_string(&result)
|
|
|
|
.log_expect("Unable to serialize response");
|
2018-08-03 11:49:38 +00:00
|
|
|
let command = format!("{}({});", callback_name, result);
|
2018-08-04 06:28:13 +00:00
|
|
|
debug!("Injecting response: {}", command);
|
2018-08-03 11:49:38 +00:00
|
|
|
wv.eval(&command);
|
|
|
|
}
|
|
|
|
}
|
2018-08-04 13:35:56 +00:00
|
|
|
CallbackType::Log { msg, kind } => {
|
|
|
|
let kind = match kind.as_ref() {
|
|
|
|
"info" | "log" => Level::Info,
|
|
|
|
"warn" => Level::Warn,
|
|
|
|
"error" => Level::Error,
|
|
|
|
_ => Level::Error,
|
|
|
|
};
|
|
|
|
|
|
|
|
log!(target: "liftinstall::frontend-js", kind, "{}", msg);
|
|
|
|
}
|
2018-07-28 05:33:06 +00:00
|
|
|
}
|
|
|
|
},
|
2018-01-29 07:21:37 +00:00
|
|
|
(),
|
2018-01-26 12:29:28 +00:00
|
|
|
);
|
|
|
|
}
|