From 4570fc319bca58a4c7a43a961bf9c4d77975da0f Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Mon, 4 Jan 2021 16:48:40 +0800 Subject: [PATCH] chore: initial code commit --- .gitignore | 3 ++ Cargo.toml | 12 +++++++ LICENSE | 2 +- README.md | 31 ++++++++++++++++- config.yaml.tmpl | 16 +++++++++ src/main.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 Cargo.toml create mode 100644 config.yaml.tmpl create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 088ba6b..29eead4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +# Project specific ignores: +/config*.yaml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0bf2b1d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rrclone" +version = "0.0.1" +authors = ["Vehbi Sinan Tunalioglu "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.8" +chrono = "0.4" diff --git a/LICENSE b/LICENSE index 1e86b3e..463435a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2021, Telostat +Copyright (c) 2021, Telostat Pte Ltd All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 65d5768..a3356aa 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,31 @@ # rrclone -Rclone wrapper to use YAML/JSON configuration for backup tasks + +`rrclone` is an Rclone wrapper to use YAML/JSON configuration for backup tasks. + +> **TODO:** Provide full README. + +> **NOTE:** This is a work-in-progress application with a very limited +> functionality. Expect breaking changes and improved functionality as we move +> on. + +## Installation + +1. Install [rclone](https://rclone.org/install/) +2. Put `rrclone` onto `$PATH` + +## Usage + +1. Configure rclone +2. Prepare configuration file for `rrclone`. +3. Run `rrclone` in dry-run mode: + ```sh + rrclone ./config.yaml --dry-run + ``` +4. Run rrclone: + ```sh + rrclone ./config.yaml + ``` + +## License + +See [LICENSE](./LICENSE) file. diff --git a/config.yaml.tmpl b/config.yaml.tmpl new file mode 100644 index 0000000..b247988 --- /dev/null +++ b/config.yaml.tmpl @@ -0,0 +1,16 @@ +- source_remote: source1 + source_path: / + target_remote: target1 + target_path: /data/backups/source1 + filters: + - "+ /data/**" + - "+ /home/**" + - "- *" +- source_remote: source2 + source_path: / + target_remote: target1 + target_path: /data/backups/source2 + filters: + - "+ /data/**" + - "+ /home/**" + - "- *" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..eb016e2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +use chrono::Utc; +use serde::{Deserialize, Serialize}; +use std::env; +use std::fs; +use std::process::Command; +use std::process::Stdio; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct Task { + source_remote: String, + source_path: String, + target_remote: String, + target_path: String, + filters: Vec, +} + +fn main() -> Result<(), serde_yaml::Error> { + let args: Vec = env::args().collect(); + let dryrun = args.len() == 3 && &args[2] == "--dry-run"; + + let contents = + fs::read_to_string(&args[1]).expect("Something went wrong while reading the YAML file"); + + let result: Result, _> = serde_yaml::from_str(&contents); + + match result { + Ok(tasks) => run_tasks(&tasks, dryrun), + Err(x) => log(format!("Something went wrong while decoding the YAML file {:?}:", x)), + } + + Ok(()) +} + +fn log(msg: String) -> () { + eprintln!( + "RRCLONE>> [{}] {}", + Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), + msg + ); +} + +fn run_tasks(ts: &Vec, dryrun: bool) -> () { + for t in ts { + run_task(t, dryrun) + } +} + +fn run_task(t: &Task, dryrun: bool) -> () { + let tstart = Utc::now(); + let source = format!("{}:{}", t.source_remote, t.source_path); + let target = format!("{}:{}", t.target_remote, t.target_path); + + log(format!("Syncing from \"{}\" to \"{}\"", &source, &target)); + + let mut args = vec![ + "-v", + "--stats-log-level", + "NOTICE", + "--stats", + "10000m", + "sync", + &source, + &target, + ]; + + if dryrun { + args.push("--dry-run"); + } + + for f in &t.filters { + args.push("--filter"); + args.push(f); + } + + let mut child = Command::new("rclone") + .args(&args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to execute process"); + + let output = child.wait().expect("Failed to read stdout from the task"); + + let elapsed = Utc::now() - tstart; + + match output.code() { + Some(0) => log(format!("Tasks finished successfully in {:?} second(s).", elapsed.num_seconds())), + Some(code) => log(format!("Task finished with errors in {:?} second(s). Error code: {}", elapsed.num_seconds(), code)), + None => log(format!("Task terminated by signal in {:?} second(s).", elapsed.num_seconds())), + } +}