mirror of
https://github.com/zyllian/pupdate.git
synced 2025-05-10 10:36:42 -07:00
Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
b8d82cad6b | |||
f4cc78031b | |||
caa0cf6799 | |||
405cf3aba8 | |||
c1da19e352 | |||
58aab0eb08 |
5 changed files with 134 additions and 33 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -424,7 +424,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pupdate"
|
name = "pupdate"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"directories",
|
"directories",
|
||||||
|
|
|
@ -6,7 +6,7 @@ license = "WTFPL"
|
||||||
name = "pupdate"
|
name = "pupdate"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/zyllian/pupdate"
|
repository = "https://github.com/zyllian/pupdate"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = {version = "4", features = ["derive"]}
|
clap = {version = "4", features = ["derive"]}
|
||||||
|
|
|
@ -4,6 +4,8 @@ simple helper utility to update remote systems alongside the local system easily
|
||||||
|
|
||||||
## usage
|
## 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).
|
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
|
## config
|
||||||
|
|
159
src/main.rs
159
src/main.rs
|
@ -40,6 +40,41 @@ struct Config {
|
||||||
/// the directory to log to, no logs if missing
|
/// the directory to log to, no logs if missing
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
log_dir: Option<PathBuf>,
|
log_dir: Option<PathBuf>,
|
||||||
|
/// the package managers to pupdate the local system with
|
||||||
|
#[serde(default)]
|
||||||
|
package_managers: Vec<PackageManager>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<PathBuf>) -> eyre::Result<bool> {
|
||||||
|
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
|
/// pupdates a remote target through ssh
|
||||||
|
@ -77,40 +112,95 @@ async fn pupdate_remote(
|
||||||
Ok((remote, success))
|
Ok((remote, success))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// pupdates the local system using apt-get
|
/// helper for writing log files from multiple processes
|
||||||
async fn pupdate_apt(log_dir: Option<PathBuf>) -> eyre::Result<bool> {
|
async fn log_helper(
|
||||||
async fn log(outputs: &[std::process::Output], log_dir: Option<PathBuf>) -> eyre::Result<bool> {
|
outputs: &[std::process::Output],
|
||||||
if let Some(log_dir) = log_dir {
|
log_dir: Option<PathBuf>,
|
||||||
let mut stdout = File::create(log_dir.join("local.stdout.log")).await?;
|
) -> eyre::Result<bool> {
|
||||||
let mut stderr = File::create(log_dir.join("local.stderr.log")).await?;
|
if let Some(log_dir) = log_dir {
|
||||||
for output in outputs {
|
let mut stdout = File::create(log_dir.join("local.stdout.log")).await?;
|
||||||
stdout.write_all(&output.stdout).await?;
|
let mut stderr = File::create(log_dir.join("local.stderr.log")).await?;
|
||||||
stderr.write_all(&output.stderr).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for output in outputs {
|
for output in outputs {
|
||||||
if !output.status.success() {
|
stdout.write_all(&output.stdout).await?;
|
||||||
return Ok(false);
|
stderr.write_all(&output.stderr).await?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(true)
|
|
||||||
}
|
}
|
||||||
|
for output in outputs {
|
||||||
|
if !output.status.success() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
let update_output = Command::new("sudo")
|
/// helper to prefix a command with sudo if requested
|
||||||
.arg("apt-get")
|
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<PathBuf>,
|
||||||
|
command: &str,
|
||||||
|
with_sudo: bool,
|
||||||
|
yes: bool,
|
||||||
|
) -> eyre::Result<bool> {
|
||||||
|
let update_output = command_maybe_with_sudo(command, with_sudo)
|
||||||
.arg("update")
|
.arg("update")
|
||||||
.output()
|
.output()
|
||||||
.await?;
|
.await?;
|
||||||
if !update_output.status.success() {
|
if !update_output.status.success() {
|
||||||
return log(&[update_output], log_dir).await;
|
return log_helper(&[update_output], log_dir).await;
|
||||||
}
|
}
|
||||||
let upgrade_output = Command::new("sudo")
|
let mut upgrade_command = command_maybe_with_sudo(command, with_sudo);
|
||||||
.arg("apt-get")
|
upgrade_command.arg("upgrade");
|
||||||
.arg("upgrade")
|
if yes {
|
||||||
.arg("-y")
|
upgrade_command.arg("-y");
|
||||||
.output()
|
}
|
||||||
.await?;
|
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
|
||||||
|
async fn pupdate_apt(log_dir: Option<PathBuf>) -> eyre::Result<bool> {
|
||||||
|
update_and_upgrade(log_dir, "apt-get", true, true).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pupdates the local system using brew, untested
|
||||||
|
async fn pupdate_homebrew(log_dir: Option<PathBuf>) -> eyre::Result<bool> {
|
||||||
|
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<PathBuf>,
|
||||||
|
command: &str,
|
||||||
|
with_sudo: bool,
|
||||||
|
yes: bool,
|
||||||
|
) -> eyre::Result<bool> {
|
||||||
|
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<PathBuf>) -> eyre::Result<bool> {
|
||||||
|
syu(log_dir, "pacman", true, true).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pupdates the local system using yay
|
||||||
|
async fn pupdate_yay(log_dir: Option<PathBuf>) -> eyre::Result<bool> {
|
||||||
|
syu(log_dir, "yay", false, true).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -123,7 +213,11 @@ async fn main() -> eyre::Result<()> {
|
||||||
};
|
};
|
||||||
let config_path = args.config.or(base_config_path);
|
let config_path = args.config.or(base_config_path);
|
||||||
let config: Config = if let Some(config) = 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 {
|
} else {
|
||||||
Config::default()
|
Config::default()
|
||||||
};
|
};
|
||||||
|
@ -199,8 +293,15 @@ async fn main() -> eyre::Result<()> {
|
||||||
|
|
||||||
if !args.skip_local {
|
if !args.skip_local {
|
||||||
println!("running local pupdates, you may be pawmpted for your password");
|
println!("running local pupdates, you may be pawmpted for your password");
|
||||||
let start = OffsetDateTime::now_utc();
|
|
||||||
if pupdate_apt(log_dir).await? {
|
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 end = OffsetDateTime::now_utc();
|
||||||
let duration = end - start;
|
let duration = end - start;
|
||||||
|
|
||||||
|
@ -208,8 +309,6 @@ async fn main() -> eyre::Result<()> {
|
||||||
"successfully pupdated the local system in {} seconds",
|
"successfully pupdated the local system in {} seconds",
|
||||||
duration.whole_seconds()
|
duration.whole_seconds()
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
println!("failed to pupdate the local system");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
todo.md
2
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 daemon to remotely pupdate without using ssh (use ssh keys for authentication though maybe?)
|
||||||
- pupdate docker compose containers (interactively?)
|
- pupdate docker compose containers (interactively?)
|
||||||
- support other pupdate methods than just apt
|
- support other pupdate methods than just apt and homebrew
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue