diff --git a/src/main.rs b/src/main.rs index 9728533..1948ce4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,41 @@ struct Config { /// the directory to log to, no logs if missing #[serde(default)] log_dir: Option, + /// the package managers to pupdate the local system with + #[serde(default)] + package_managers: Vec, +} + +/// enum for supported package managers +#[derive(Debug, Deserialize)] +#[serde(rename_all = "snake_case")] +enum PackageManager { + AptGet, + Homebrew, + Pacman, + Yay, +} + +impl PackageManager { + /// gets the packaage manager's name + pub fn get_name(&self) -> &'static str { + match self { + Self::AptGet => "apt-get", + Self::Homebrew => "homebrew", + Self::Pacman => "pacman", + Self::Yay => "yay", + } + } + + /// pupdates the local system + pub async fn pupdate(&self, log_dir: Option) -> eyre::Result { + match self { + Self::AptGet => pupdate_apt(log_dir).await, + Self::Homebrew => pupdate_homebrew(log_dir).await, + Self::Pacman => pupdate_pacman(log_dir).await, + Self::Yay => pupdate_yay(log_dir).await, + } + } } /// pupdates a remote target through ssh @@ -77,6 +112,38 @@ async fn pupdate_remote( Ok((remote, success)) } +/// helper for writing log files from multiple processes +async fn log_helper( + outputs: &[std::process::Output], + log_dir: Option, +) -> eyre::Result { + if let Some(log_dir) = log_dir { + let mut stdout = File::create(log_dir.join("local.stdout.log")).await?; + let mut stderr = File::create(log_dir.join("local.stderr.log")).await?; + for output in outputs { + stdout.write_all(&output.stdout).await?; + stderr.write_all(&output.stderr).await?; + } + } + for output in outputs { + if !output.status.success() { + return Ok(false); + } + } + Ok(true) +} + +/// helper to prefix a command with sudo if requested +fn command_maybe_with_sudo(command: &str, with_sudo: bool) -> Command { + if with_sudo { + let mut cmd = Command::new("sudo"); + cmd.arg(command); + cmd + } else { + Command::new(command) + } +} + /// helper for pupdates which both follow the "X update && X upgrade" set of commands (e.g. apt-get and brew) async fn update_and_upgrade( log_dir: Option, @@ -84,47 +151,20 @@ async fn update_and_upgrade( with_sudo: bool, yes: bool, ) -> eyre::Result { - async fn log(outputs: &[std::process::Output], log_dir: Option) -> eyre::Result { - if let Some(log_dir) = log_dir { - let mut stdout = File::create(log_dir.join("local.stdout.log")).await?; - let mut stderr = File::create(log_dir.join("local.stderr.log")).await?; - for output in outputs { - stdout.write_all(&output.stdout).await?; - stderr.write_all(&output.stderr).await?; - } - } - for output in outputs { - if !output.status.success() { - return Ok(false); - } - } - Ok(true) - } - - fn command_with_sudo(command: &str, with_sudo: bool) -> Command { - if with_sudo { - let mut cmd = Command::new("sudo"); - cmd.arg(command); - cmd - } else { - Command::new(command) - } - } - - let update_output = command_with_sudo(command, with_sudo) + let update_output = command_maybe_with_sudo(command, with_sudo) .arg("update") .output() .await?; if !update_output.status.success() { - return log(&[update_output], log_dir).await; + return log_helper(&[update_output], log_dir).await; } - let mut upgrade_command = command_with_sudo(command, with_sudo); + let mut upgrade_command = command_maybe_with_sudo(command, with_sudo); upgrade_command.arg("upgrade"); if yes { upgrade_command.arg("-y"); } let upgrade_output = upgrade_command.output().await?; - log(&[update_output, upgrade_output], log_dir).await + log_helper(&[update_output, upgrade_output], log_dir).await } /// pupdates the local system using apt-get @@ -137,6 +177,32 @@ async fn pupdate_homebrew(log_dir: Option) -> eyre::Result { update_and_upgrade(log_dir, "brew", false, false).await } +/// helper for pupdates which follow the pacman-style command format +async fn syu( + log_dir: Option, + command: &str, + with_sudo: bool, + yes: bool, +) -> eyre::Result { + let mut command = command_maybe_with_sudo(command, with_sudo); + command.arg("-Syu"); + if yes { + command.arg("--noconfirm"); + } + let output = command.output().await?; + log_helper(&[output], log_dir).await +} + +/// pupdates the local system using pacman +async fn pupdate_pacman(log_dir: Option) -> eyre::Result { + syu(log_dir, "pacman", true, true).await +} + +/// pupdates the local system using yay +async fn pupdate_yay(log_dir: Option) -> eyre::Result { + syu(log_dir, "yay", false, true).await +} + #[tokio::main] async fn main() -> eyre::Result<()> { let args = Args::parse(); @@ -147,7 +213,11 @@ async fn main() -> eyre::Result<()> { }; let config_path = args.config.or(base_config_path); let config: Config = if let Some(config) = config_path { - serde_json::from_str(&std::fs::read_to_string(config)?)? + if config.exists() { + serde_json::from_str(&std::fs::read_to_string(config)?)? + } else { + Config::default() + } } else { Config::default() }; @@ -223,19 +293,15 @@ async fn main() -> eyre::Result<()> { if !args.skip_local { println!("running local pupdates, you may be pawmpted for your password"); - let start = OffsetDateTime::now_utc(); - let result = if cfg!(target_os = "linux") { - println!("pupdating with apt-get..."); - Some(pupdate_apt(log_dir).await) - } else if cfg!(target_os = "macos") { - println!("pupdating with brew..."); - Some(pupdate_homebrew(log_dir).await) - } else { - eprintln!("unsupported operating system for local pupdates, skipping"); - None - }; - if let Some(result) = result { - result?; + + if config.package_managers.is_empty() { + eprintln!("no package managers defined in config, skipping local pupdates"); + } + + for package_manager in config.package_managers { + println!("pupdating with {}", package_manager.get_name()); + let start = OffsetDateTime::now_utc(); + package_manager.pupdate(log_dir.clone()).await?; let end = OffsetDateTime::now_utc(); let duration = end - start; @@ -243,8 +309,6 @@ async fn main() -> eyre::Result<()> { "successfully pupdated the local system in {} seconds", duration.whole_seconds() ); - } else { - println!("failed to pupdate the local system"); } }