Files
topgrade/src/main.rs

570 lines
15 KiB
Rust
Raw Normal View History

#![allow(clippy::cognitive_complexity)]
2018-06-28 12:16:54 +03:00
mod config;
mod ctrlc;
2018-12-11 16:43:26 +02:00
mod error;
2018-08-26 16:12:59 +03:00
mod executor;
2018-06-03 18:04:58 +03:00
mod report;
2018-11-26 14:27:19 +02:00
#[cfg(feature = "self-update")]
mod self_update;
2018-12-15 21:52:21 +02:00
mod steps;
2018-05-31 16:00:01 +03:00
mod terminal;
2018-06-17 11:43:25 +03:00
mod utils;
2018-05-30 07:53:19 +03:00
use self::config::{Config, Step};
2018-12-11 16:43:26 +02:00
use self::error::{Error, ErrorKind};
2018-08-24 21:52:17 +03:00
use self::report::Report;
2018-12-15 21:52:21 +02:00
use self::steps::*;
2018-12-09 10:30:41 +02:00
use self::terminal::*;
2018-12-11 16:43:26 +02:00
use failure::{Fail, ResultExt};
2019-08-04 09:33:01 +03:00
use log::{debug, LevelFilter};
#[cfg(feature = "self-update")]
use openssl_probe;
2019-08-04 09:33:01 +03:00
use pretty_env_logger::formatted_timed_builder;
2018-08-25 22:19:38 +03:00
use std::borrow::Cow;
2018-06-20 21:05:49 +03:00
use std::env;
2019-01-13 23:20:32 +02:00
use std::fmt::Debug;
2018-12-11 16:43:26 +02:00
use std::io;
2018-06-17 11:43:25 +03:00
use std::process::exit;
2018-05-29 23:48:30 +03:00
2019-01-13 23:20:32 +02:00
fn execute<'a, F, M>(report: &mut Report<'a>, key: M, func: F, no_retry: bool) -> Result<(), Error>
where
F: Fn() -> Result<(), Error>,
M: Into<Cow<'a, str>> + Debug,
{
2019-08-15 09:23:22 +03:00
debug!("Step {:?}", key);
2019-01-13 23:20:32 +02:00
loop {
match func() {
Ok(()) => {
report.push_result(Some((key, true)));
break;
}
Err(ref e) if e.kind() == ErrorKind::SkipStep => {
break;
}
Err(_) => {
let interrupted = ctrlc::interrupted();
if interrupted {
ctrlc::unset_interrupted();
}
let should_ask = interrupted || !no_retry;
let should_retry = should_ask && should_retry(interrupted).context(ErrorKind::Retry)?;
if !should_retry {
report.push_result(Some((key, false)));
break;
}
}
}
}
Ok(())
}
fn run() -> Result<(), Error> {
ctrlc::set_handler();
let base_dirs = directories::BaseDirs::new().ok_or(ErrorKind::NoBaseDirectories)?;
let config = Config::load(&base_dirs)?;
2018-09-06 14:42:56 +03:00
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
2018-06-27 23:04:39 +03:00
#[cfg(unix)]
{
2018-09-04 11:05:54 +03:00
tmux::run_in_tmux();
2018-06-20 21:05:49 +03:00
}
}
2019-08-04 09:33:01 +03:00
let mut builder = formatted_timed_builder();
if config.verbose() {
2019-08-14 12:38:22 +03:00
builder.filter(Some("topgrade"), LevelFilter::Trace);
}
2019-08-04 09:33:01 +03:00
builder.init();
2018-12-15 21:52:21 +02:00
let git = git::Git::new();
let mut git_repos = git::Repositories::new(&git);
2018-11-07 14:31:44 +02:00
2018-08-24 21:52:17 +03:00
let mut report = Report::new();
2018-05-30 07:53:19 +03:00
2019-06-25 22:47:36 -07:00
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))]
let sudo = utils::sudo();
let run_type = executor::RunType::new(config.dry_run());
#[cfg(feature = "self-update")]
{
openssl_probe::init_ssl_cert_env_vars();
2018-12-31 14:05:15 +02:00
if !run_type.dry() && env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() {
let result = self_update::self_update();
#[cfg(windows)]
{
let upgraded = match &result {
Ok(()) => false,
Err(e) => e.upgraded(),
};
if upgraded {
return result;
}
}
if let Err(e) = result {
2018-12-05 11:34:08 +02:00
print_warning(format!("Self update error: {}", e));
2018-12-23 13:17:53 +02:00
if let Some(cause) = e.cause() {
print_warning(format!("Caused by: {}", cause));
}
}
}
}
2018-11-07 14:31:44 +02:00
2018-06-20 20:26:08 +03:00
if let Some(commands) = config.pre_commands() {
for (name, command) in commands {
2018-12-31 14:05:15 +02:00
generic::run_custom_command(&name, &command, run_type).context(ErrorKind::PreCommand)?;
2018-06-20 20:26:08 +03:00
}
}
2019-08-14 21:36:57 +03:00
let powershell = powershell::Powershell::new();
2019-02-27 09:47:20 +02:00
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
#[cfg(windows)]
execute(&mut report, "WSL", || windows::run_wsl_topgrade(run_type), true)?;
2019-06-05 14:15:45 +03:00
if let Some(topgrades) = config.remote_topgrades() {
2019-07-01 08:53:53 +03:00
if config.should_run(Step::Remotes) {
for remote_topgrade in topgrades {
execute(
&mut report,
remote_topgrade,
|| generic::run_remote_topgrade(run_type, remote_topgrade, config.run_in_tmux()),
config.no_retry(),
)?;
}
2019-06-05 14:15:45 +03:00
}
}
#[cfg(target_os = "linux")]
let distribution = linux::Distribution::detect();
2018-08-22 22:18:48 +03:00
#[cfg(target_os = "linux")]
{
if config.should_run(Step::System) {
match &distribution {
Ok(distribution) => {
2019-02-11 14:10:06 +02:00
execute(
&mut report,
"System update",
|| distribution.upgrade(&sudo, config.cleanup(), run_type),
config.no_retry(),
2019-02-11 14:10:06 +02:00
)?;
}
Err(e) => {
println!("Error detecting current distribution: {}", e);
}
}
2019-02-25 15:10:28 +02:00
execute(
&mut report,
"etc-update",
|| linux::run_etc_update(sudo.as_ref(), run_type),
2019-01-13 23:20:32 +02:00
config.no_retry(),
2019-02-25 15:10:28 +02:00
)?;
2018-08-22 22:18:48 +03:00
}
2018-06-28 07:47:51 +03:00
}
2018-08-22 22:18:48 +03:00
#[cfg(windows)]
2019-02-11 20:38:51 +02:00
execute(
&mut report,
"Chocolatey",
|| windows::run_chocolatey(run_type),
config.no_retry(),
)?;
2018-08-22 22:18:48 +03:00
2018-10-17 13:57:30 +03:00
#[cfg(windows)]
2019-02-11 20:38:51 +02:00
execute(&mut report, "Scoop", || windows::run_scoop(run_type), config.no_retry())?;
2018-10-17 13:57:30 +03:00
2018-08-19 14:45:23 +03:00
#[cfg(unix)]
2019-02-27 10:31:30 +02:00
execute(
&mut report,
"brew",
|| unix::run_homebrew(config.cleanup(), run_type),
config.no_retry(),
2019-02-27 10:31:30 +02:00
)?;
2019-06-25 22:47:36 -07:00
#[cfg(target_os = "dragonfly")]
execute(
&mut report,
"DragonFly BSD Packages",
|| dragonfly::upgrade_packages(sudo.as_ref(), run_type),
config.no_retry(),
)?;
2018-11-12 11:13:43 +02:00
#[cfg(target_os = "freebsd")]
2019-03-10 22:01:01 +02:00
execute(
&mut report,
"FreeBSD Packages",
2019-03-13 11:46:32 +02:00
|| freebsd::upgrade_packages(sudo.as_ref(), run_type),
config.no_retry(),
2019-03-10 22:01:01 +02:00
)?;
2018-10-21 13:05:49 +03:00
#[cfg(unix)]
2019-02-27 10:31:30 +02:00
execute(&mut report, "nix", || unix::run_nix(run_type), config.no_retry())?;
2018-08-19 14:45:23 +03:00
let emacs = emacs::Emacs::new(&base_dirs);
if config.should_run(Step::Emacs) {
if let Some(directory) = emacs.directory() {
git_repos.insert(directory);
}
2018-09-05 11:17:15 +03:00
}
if config.should_run(Step::Vim) {
git_repos.insert(base_dirs.home_dir().join(".vim"));
git_repos.insert(base_dirs.home_dir().join(".config/nvim"));
}
2018-05-30 07:53:19 +03:00
2018-06-27 23:04:39 +03:00
#[cfg(unix)]
{
2018-07-07 02:18:19 +03:00
git_repos.insert(base_dirs.home_dir().join(".zshrc"));
git_repos.insert(base_dirs.home_dir().join(".oh-my-zsh"));
git_repos.insert(base_dirs.home_dir().join(".tmux"));
git_repos.insert(base_dirs.home_dir().join(".config/fish"));
2018-08-27 15:22:44 +03:00
git_repos.insert(base_dirs.config_dir().join("openbox"));
2019-02-26 16:05:10 +02:00
git_repos.insert(base_dirs.config_dir().join("bspwm"));
2019-03-14 21:38:24 +02:00
git_repos.insert(base_dirs.config_dir().join("i3"));
}
2018-05-30 07:53:19 +03:00
2019-08-14 21:36:57 +03:00
if let Some(profile) = powershell.profile() {
git_repos.insert(profile);
2018-08-23 22:08:04 +03:00
}
if config.should_run(Step::GitRepos) {
if let Some(custom_git_repos) = config.git_repos() {
for git_repo in custom_git_repos {
git_repos.glob_insert(git_repo);
}
2018-05-30 07:53:19 +03:00
}
execute(
&mut report,
"Git repositories",
|| git.multi_pull(&git_repos, run_type),
config.no_retry(),
)?;
2018-05-30 07:53:19 +03:00
}
2019-08-14 21:36:57 +03:00
if should_run_powershell {
execute(
&mut report,
"Powershell Modules Update",
|| powershell.update_modules(run_type),
config.no_retry(),
)?;
}
2018-06-27 23:04:39 +03:00
#[cfg(unix)]
{
2019-02-27 10:31:30 +02:00
execute(
&mut report,
"zplug",
2019-01-13 23:20:32 +02:00
|| unix::run_zplug(&base_dirs, run_type),
config.no_retry(),
2019-02-27 10:31:30 +02:00
)?;
execute(
&mut report,
"fisher",
2019-01-13 23:20:32 +02:00
|| unix::run_fisher(&base_dirs, run_type),
config.no_retry(),
2019-02-27 10:31:30 +02:00
)?;
2019-03-10 21:48:49 +02:00
execute(
&mut report,
"tmux",
2019-01-13 23:20:32 +02:00
|| tmux::run_tpm(&base_dirs, run_type),
config.no_retry(),
2019-03-10 21:48:49 +02:00
)?;
2018-05-31 16:17:22 +03:00
}
if config.should_run(Step::Rustup) {
execute(
&mut report,
"rustup",
|| generic::run_rustup(&base_dirs, run_type),
config.no_retry(),
)?;
}
if config.should_run(Step::Cargo) {
execute(
&mut report,
"cargo",
|| generic::run_cargo_update(run_type),
config.no_retry(),
)?;
}
2018-09-05 11:17:15 +03:00
if config.should_run(Step::Emacs) {
execute(&mut report, "Emacs", || emacs.upgrade(run_type), config.no_retry())?;
2018-09-05 11:17:15 +03:00
}
2019-01-13 23:20:32 +02:00
execute(
&mut report,
"opam",
|| generic::run_opam_update(run_type),
config.no_retry(),
)?;
execute(
&mut report,
"vcpkg",
|| generic::run_vcpkg_update(run_type),
config.no_retry(),
)?;
execute(
&mut report,
"pipx",
|| generic::run_pipx_update(run_type),
config.no_retry(),
)?;
execute(
&mut report,
"myrepos",
|| generic::run_myrepos_update(&base_dirs, run_type),
config.no_retry(),
)?;
2019-02-19 08:47:01 +02:00
#[cfg(unix)]
execute(&mut report, "pearl", || unix::run_pearl(run_type), config.no_retry())?;
2019-01-13 23:20:32 +02:00
execute(
&mut report,
"jetpak",
|| generic::run_jetpack(run_type),
config.no_retry(),
)?;
if config.should_run(Step::Vim) {
2019-03-10 21:48:49 +02:00
execute(
&mut report,
"vim",
2019-01-13 23:20:32 +02:00
|| vim::upgrade_vim(&base_dirs, run_type),
config.no_retry(),
2019-03-10 21:48:49 +02:00
)?;
execute(
&mut report,
"Neovim",
|| vim::upgrade_neovim(&base_dirs, run_type),
config.no_retry(),
2019-03-10 21:48:49 +02:00
)?;
}
2019-03-10 21:48:49 +02:00
execute(
&mut report,
"NPM",
|| node::run_npm_upgrade(&base_dirs, run_type),
config.no_retry(),
2019-03-10 21:48:49 +02:00
)?;
2019-01-13 23:20:32 +02:00
execute(
&mut report,
"composer",
2018-12-31 14:05:15 +02:00
|| generic::run_composer_update(&base_dirs, run_type),
config.no_retry(),
2019-01-13 23:20:32 +02:00
)?;
2019-03-10 21:48:49 +02:00
execute(
&mut report,
"yarn",
2019-01-13 23:20:32 +02:00
|| node::yarn_global_update(run_type),
config.no_retry(),
2019-03-10 21:48:49 +02:00
)?;
2018-10-04 11:26:51 +03:00
2018-10-29 14:32:33 +02:00
#[cfg(not(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
2019-01-13 23:20:32 +02:00
execute(&mut report, "apm", || generic::run_apm(run_type), config.no_retry())?;
2019-01-13 14:10:23 +00:00
if config.should_run(Step::Gem) {
2019-01-13 23:20:32 +02:00
execute(
&mut report,
"gem",
|| generic::run_gem(&base_dirs, run_type),
config.no_retry(),
)?;
2019-01-13 14:10:23 +00:00
}
2018-06-27 23:04:39 +03:00
#[cfg(target_os = "linux")]
{
2019-02-25 15:10:28 +02:00
execute(
&mut report,
"Flatpak",
|| linux::flatpak_update(run_type),
config.no_retry(),
)?;
execute(
&mut report,
"snap",
|| linux::run_snap(sudo.as_ref(), run_type),
config.no_retry(),
)?;
2018-06-14 13:24:52 +03:00
}
if let Some(commands) = config.commands() {
for (name, command) in commands {
2019-03-10 22:01:01 +02:00
execute(
&mut report,
name,
|| generic::run_custom_command(&name, &command, run_type),
config.no_retry(),
2019-03-10 22:01:01 +02:00
)?;
}
}
2018-06-27 23:04:39 +03:00
#[cfg(target_os = "linux")]
{
execute(
&mut report,
"pihole",
|| linux::run_pihole_update(sudo.as_ref(), run_type),
config.no_retry(),
)?;
execute(
&mut report,
"rpi-update",
|| linux::run_rpi_update(sudo.as_ref(), run_type),
config.no_retry(),
)?;
2019-02-25 15:10:28 +02:00
execute(
&mut report,
"Firmware upgrades",
|| linux::run_fwupdmgr(run_type),
config.no_retry(),
)?;
2019-02-11 14:10:06 +02:00
execute(
&mut report,
"Restarts",
|| linux::run_needrestart(sudo.as_ref(), run_type),
2019-01-13 23:20:32 +02:00
config.no_retry(),
2019-02-11 14:10:06 +02:00
)?;
2018-05-31 09:19:27 +03:00
}
2018-06-27 23:04:39 +03:00
#[cfg(target_os = "macos")]
{
if config.should_run(Step::System) {
2019-03-10 21:48:49 +02:00
execute(
&mut report,
"App Store",
|| macos::upgrade_macos(run_type),
config.no_retry(),
)?;
}
2018-06-03 18:04:58 +03:00
}
2018-11-12 11:13:43 +02:00
#[cfg(target_os = "freebsd")]
{
if config.should_run(Step::System) {
2019-03-10 22:01:01 +02:00
execute(
&mut report,
"FreeBSD Upgrade",
2019-03-13 11:46:32 +02:00
|| freebsd::upgrade_freebsd(sudo.as_ref(), run_type),
config.no_retry(),
2019-03-10 22:01:01 +02:00
)?;
2018-11-12 11:13:43 +02:00
}
}
2018-08-22 22:18:48 +03:00
#[cfg(windows)]
{
if config.should_run(Step::System) {
2019-02-11 20:38:51 +02:00
execute(
&mut report,
"Windows update",
2019-01-13 23:20:32 +02:00
|| powershell.windows_update(run_type),
config.no_retry(),
2019-02-11 20:38:51 +02:00
)?;
2018-08-22 22:18:48 +03:00
}
}
#[cfg(unix)]
{
if config.should_run(Step::Sdkman) {
execute(
&mut report,
"SDKMAN!",
|| unix::run_sdkman(&base_dirs, config.cleanup(), run_type),
config.no_retry(),
)?;
}
}
2018-08-24 21:52:17 +03:00
if !report.data().is_empty() {
2018-12-05 11:34:08 +02:00
print_separator("Summary");
2018-06-03 18:04:58 +03:00
2018-08-24 21:52:17 +03:00
for (key, succeeded) in report.data() {
2018-12-05 11:34:08 +02:00
print_result(key, *succeeded);
2018-06-03 18:04:58 +03:00
}
#[cfg(target_os = "linux")]
{
if let Ok(distribution) = &distribution {
distribution.show_summary();
}
}
2018-11-15 11:37:08 +02:00
#[cfg(target_os = "freebsd")]
2018-11-15 15:54:24 +02:00
freebsd::audit_packages(&sudo).ok();
2019-06-25 22:47:36 -07:00
#[cfg(target_os = "dragonfly")]
dragonfly::audit_packages(&sudo).ok();
2018-05-29 23:48:30 +03:00
}
2019-06-16 09:09:05 +03:00
if config.keep_at_end() {
2019-08-04 09:25:35 +03:00
print_info("\n(R)eboot\n(S)hell\n(Q)uit");
loop {
match get_char() {
's' | 'S' => {
run_shell();
}
'r' | 'R' => {
reboot();
}
'q' | 'Q' => (),
_ => {
continue;
}
2019-06-13 22:05:18 +03:00
}
2019-08-04 09:25:35 +03:00
break;
2019-06-13 22:05:18 +03:00
}
}
2018-08-24 21:52:17 +03:00
if report.data().iter().all(|(_, succeeded)| *succeeded) {
Ok(())
} else {
2018-12-11 16:43:26 +02:00
Err(ErrorKind::StepFailed)?
}
}
fn main() {
match run() {
Ok(()) => {
exit(0);
}
Err(error) => {
#[cfg(all(windows, feature = "self-update"))]
{
if let ErrorKind::Upgraded(status) = error.kind() {
exit(status.code().unwrap());
}
}
2018-12-11 16:43:26 +02:00
let should_print = match error.kind() {
ErrorKind::StepFailed => false,
ErrorKind::Retry => error
.cause()
.and_then(|cause| cause.downcast_ref::<io::Error>())
.filter(|io_error| io_error.kind() == io::ErrorKind::Interrupted)
.is_none(),
_ => true,
};
if should_print {
println!("Error: {}", error);
if let Some(cause) = error.cause() {
println!("Caused by: {}", cause);
}
}
exit(1);
}
}
2018-05-29 23:48:30 +03:00
}