commit b416507e4a124ea8b735ec1df8525536bef89e57 Author: JMARyA Date: Sun Jan 5 00:37:20 2025 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3be7e83 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,153 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bk" +version = "0.1.0" +dependencies = [ + "serde", + "toml", + "yansi", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "2.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "winnow" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" +dependencies = [ + "memchr", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f8ef011 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bk" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0.217", features = ["derive"] } +toml = "0.8.19" +yansi = "1.0.1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..d73910b --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# 🍔 BK +`bk` is a simple backup utility providing a simple backup workflow. diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..c4c1265 --- /dev/null +++ b/config.toml @@ -0,0 +1,26 @@ +# Run a script before backup +start_script = "before.sh" + +# Run a script after backup +end_script = "after.sh" + +# Rsync Operation +[[rsync]] +# Directories SHOULD have trailing `/` +src = "/home/me/" +dest = "/backup/home/me/" + +# Excludes +exclude = [".cache", ".local"] + +# Delete entries not present in `src` from `destination` +delete = true + +# Ensure this directory exists and it not empty before running rsync +ensure_exists = "/home" + +# Borg Operation +[[borg]] +repo = "/backup/repo.borg" +passphrase = "pass" +src = [ "/home/me/.config" ] diff --git a/src/backup.rs b/src/backup.rs new file mode 100644 index 0000000..2ea1300 --- /dev/null +++ b/src/backup.rs @@ -0,0 +1,66 @@ +use yansi::{Color, Paint}; + +use crate::{ + config::{Config, RsyncConfig}, + run_command, +}; + +pub fn ensure_exists(dir: &str) { + let exists = std::fs::exists(dir).unwrap_or_default(); + let entries = std::fs::read_dir(dir) + .unwrap() + .flatten() + .collect::>(); + + if !exists || entries.len() == 0 { + println!( + "{} {}", + "Error:".paint(Color::Red), + "Directory {dir} does not exists" + ); + std::process::exit(1); + } +} + +pub fn run_backup_rsync(conf: &RsyncConfig) { + println!("Running backup for {} -> {}", conf.src, conf.dest); + + if let Some(dir) = &conf.ensure_exists { + ensure_exists(&dir); + } + + let mut cmd = vec!["rsync", "-avzhruP"]; + + if conf.delete.unwrap_or_default() { + cmd.push("--delete"); + } + + if let Some(exclude) = &conf.exclude { + for e in exclude { + cmd.extend(&["--exclude", e.as_str()]); + } + } + + cmd.push(&conf.src); + cmd.push(&conf.dest); + + run_command(&cmd); +} + +pub fn run_backup(conf: Config) { + if let Some(script) = &conf.start_script { + run_command(&["sh", script.as_str()]); + } + + for rsync in &conf.rsync.unwrap_or_default() { + run_backup_rsync(rsync); + } + + for borg in &conf.borg.unwrap_or_default() { + // TODO : Implement + } + + if let Some(script) = &conf.end_script { + run_command(&["sh", script.as_str()]); + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..386f24d --- /dev/null +++ b/src/config.rs @@ -0,0 +1,29 @@ +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +pub struct Config { + /// Run a script before backup + pub start_script: Option, + + // Run a script after backup + pub end_script: Option, + + pub rsync: Option>, + pub borg: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct RsyncConfig { + pub src: String, + pub dest: String, + pub exclude: Option>, + pub delete: Option, + pub ensure_exists: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct BorgConfig { + pub repo: String, + pub passphrase: Option, + pub src: Vec, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9134acf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,40 @@ +use backup::run_backup; + +mod backup; +mod config; + +fn main() { + let args = std::env::args().collect::>(); + + if let Some(conf) = args.get(1) { + let conf = toml::from_str(&std::fs::read_to_string(conf).unwrap()).unwrap(); + run_backup(conf); + } else { + println!("Usage: bk "); + } +} + +pub fn run_command(cmd: &[&str]) -> (String, String) { + println!("--> {} ", cmd.join(" ")); + + let mut cmd_setup = std::process::Command::new(cmd[0]); + let mut cmd_setup = cmd_setup.args(cmd.iter().skip(1).collect::>()); + + cmd_setup = cmd_setup + .stdout(std::process::Stdio::inherit()) + .stdin(std::process::Stdio::inherit()); + + let child = cmd_setup.spawn().unwrap(); + + let status = child.wait_with_output().unwrap(); + assert!(status.status.success()); + + let output = String::from_utf8(status.stdout).unwrap(); + let stderr = String::from_utf8(status.stderr).unwrap(); + + if !stderr.trim().is_empty() { + eprintln!("{stderr}"); + } + + (output, stderr) +}