From 58aab0eb08d25b65a7cd49dae01f0ebb1e586367 Mon Sep 17 00:00:00 2001 From: zyl Date: Tue, 8 Oct 2024 16:51:22 -0700 Subject: [PATCH 1/6] add install instructions --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 19e9120..dd7c7c0 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ simple helper utility to update remote systems alongside the local system easily ## usage +you can install easily using `cargo install pupdate`. + run `pupdate -h` for help with arguments. with no arguments, pupdate will update the local system and any remotes configured in the config file (default ~/.pupdate). ## config From c1da19e35296e16f245b5e5f8392343da3c578df Mon Sep 17 00:00:00 2001 From: zyl Date: Thu, 17 Oct 2024 18:27:33 -0700 Subject: [PATCH 2/6] bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc50438..fad8ce0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,7 +424,7 @@ dependencies = [ [[package]] name = "pupdate" -version = "0.1.0" +version = "0.2.0" dependencies = [ "clap", "directories", diff --git a/Cargo.toml b/Cargo.toml index 2683e10..3df694b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "WTFPL" name = "pupdate" readme = "README.md" repository = "https://github.com/zyllian/pupdate" -version = "0.1.0" +version = "0.2.0" [dependencies] clap = {version = "4", features = ["derive"]} From 405cf3aba8d803d57b910a159cc914d937c17f5a Mon Sep 17 00:00:00 2001 From: zyl Date: Thu, 17 Oct 2024 18:27:41 -0700 Subject: [PATCH 3/6] add untested homebrew support --- src/main.rs | 57 ++++++++++++++++++++++++++++++++++++++++++----------- todo.md | 2 +- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index a36c9bf..9728533 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,8 +77,13 @@ async fn pupdate_remote( Ok((remote, success)) } -/// pupdates the local system using apt-get -async fn pupdate_apt(log_dir: Option) -> eyre::Result { +/// 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, + command: &str, + 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?; @@ -96,23 +101,42 @@ async fn pupdate_apt(log_dir: Option) -> eyre::Result { Ok(true) } - let update_output = Command::new("sudo") - .arg("apt-get") + 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) .arg("update") .output() .await?; if !update_output.status.success() { return log(&[update_output], log_dir).await; } - let upgrade_output = Command::new("sudo") - .arg("apt-get") - .arg("upgrade") - .arg("-y") - .output() - .await?; + let mut upgrade_command = command_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 } +/// pupdates the local system using apt-get +async fn pupdate_apt(log_dir: Option) -> eyre::Result { + update_and_upgrade(log_dir, "apt-get", true, true).await +} + +/// pupdates the local system using brew, untested +async fn pupdate_homebrew(log_dir: Option) -> eyre::Result { + update_and_upgrade(log_dir, "brew", false, false).await +} + #[tokio::main] async fn main() -> eyre::Result<()> { let args = Args::parse(); @@ -200,7 +224,18 @@ 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(); - if pupdate_apt(log_dir).await? { + 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?; let end = OffsetDateTime::now_utc(); let duration = end - start; diff --git a/todo.md b/todo.md index 869cc85..7564e83 100644 --- a/todo.md +++ b/todo.md @@ -4,4 +4,4 @@ some things that may or may not get done at some point: - pupdate daemon to remotely pupdate without using ssh (use ssh keys for authentication though maybe?) - pupdate docker compose containers (interactively?) -- support other pupdate methods than just apt +- support other pupdate methods than just apt and homebrew From caa0cf6799bd45f3642aba3b369b83a2e1de292d Mon Sep 17 00:00:00 2001 From: zyl Date: Mon, 9 Dec 2024 22:44:11 -0800 Subject: [PATCH 4/6] configurable package managers, add pacman/yay support --- src/main.rs | 158 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 111 insertions(+), 47 deletions(-) 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"); } } From f4cc78031b0fa3aed173d8b5202cfc89e51d5d55 Mon Sep 17 00:00:00 2001 From: zyl Date: Mon, 9 Dec 2024 22:44:49 -0800 Subject: [PATCH 5/6] update version number --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3df694b..a3aaf05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "WTFPL" name = "pupdate" readme = "README.md" repository = "https://github.com/zyllian/pupdate" -version = "0.2.0" +version = "0.3.0" [dependencies] clap = {version = "4", features = ["derive"]} From b8d82cad6b332b8e0d8f542b083ec1d043246b73 Mon Sep 17 00:00:00 2001 From: zyl Date: Mon, 9 Dec 2024 22:46:56 -0800 Subject: [PATCH 6/6] undo version change --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a3aaf05..3df694b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "WTFPL" name = "pupdate" readme = "README.md" repository = "https://github.com/zyllian/pupdate" -version = "0.3.0" +version = "0.2.0" [dependencies] clap = {version = "4", features = ["derive"]}