init
This commit is contained in:
commit
b416507e4a
8 changed files with 326 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
153
Cargo.lock
generated
Normal file
153
Cargo.lock
generated
Normal file
|
@ -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"
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
@ -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"
|
2
README.md
Normal file
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# 🍔 BK
|
||||
`bk` is a simple backup utility providing a simple backup workflow.
|
26
config.toml
Normal file
26
config.toml
Normal file
|
@ -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" ]
|
66
src/backup.rs
Normal file
66
src/backup.rs
Normal file
|
@ -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::<Vec<_>>();
|
||||
|
||||
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()]);
|
||||
}
|
||||
}
|
29
src/config.rs
Normal file
29
src/config.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Config {
|
||||
/// Run a script before backup
|
||||
pub start_script: Option<String>,
|
||||
|
||||
// Run a script after backup
|
||||
pub end_script: Option<String>,
|
||||
|
||||
pub rsync: Option<Vec<RsyncConfig>>,
|
||||
pub borg: Option<Vec<BorgConfig>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RsyncConfig {
|
||||
pub src: String,
|
||||
pub dest: String,
|
||||
pub exclude: Option<Vec<String>>,
|
||||
pub delete: Option<bool>,
|
||||
pub ensure_exists: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct BorgConfig {
|
||||
pub repo: String,
|
||||
pub passphrase: Option<String>,
|
||||
pub src: Vec<String>,
|
||||
}
|
40
src/main.rs
Normal file
40
src/main.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use backup::run_backup;
|
||||
|
||||
mod backup;
|
||||
mod config;
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
|
||||
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 <config>");
|
||||
}
|
||||
}
|
||||
|
||||
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::<Vec<_>>());
|
||||
|
||||
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)
|
||||
}
|
Loading…
Reference in a new issue