Compare commits
74 Commits
0aeb059c10
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
17183aaabb
|
|||
|
840e222e36
|
|||
|
83b2687388
|
|||
|
ef08df173a
|
|||
|
5a2c42544b
|
|||
|
62eaab264f
|
|||
|
5333dcb91a
|
|||
|
27e107770e
|
|||
| 78dc5a228b | |||
| 9bd386a3c7 | |||
| 5ecf3335b4 | |||
| a611649834 | |||
| f8c9462539 | |||
| e9ab336f07 | |||
| 427e6addd1 | |||
| cfa670c0e8 | |||
| 13524bad10 | |||
| beed2f16ab | |||
| 30f8202981 | |||
| 784c15c05c | |||
| de492e1fbd | |||
| 00fcac0e52 | |||
| 484e7de833 | |||
| ab1be53102 | |||
| 136aafe2db | |||
| d89a426032 | |||
| 589b585f38 | |||
| 32aa27ab1a | |||
| e16648a218 | |||
| f8b5ebe60a | |||
| 8ecceb5e11 | |||
| 54a15f24f7 | |||
| f308c0dae9 | |||
| c4e8a5a699 | |||
| 2062140200 | |||
| 9945bfee8a | |||
| 537628221b | |||
| 83cfb5489c | |||
| 2e70fc3d73 | |||
| 63919d5532 | |||
| dbb731e3b3 | |||
| b7c747c565 | |||
| f1ede0bca7 | |||
| 4b97814932 | |||
| b1a9e6f8f2 | |||
| 6e116f7003 | |||
| aca4f43577 | |||
| 05d4907fbd | |||
| ce5e884841 | |||
| 926d557ab0 | |||
| 18bf157d73 | |||
| 6f5bc06217 | |||
| a8bb406fde | |||
| ee2156a2ca | |||
| 831d66fddb | |||
| 13fff9569b | |||
| ab3e69997b | |||
| 2f7ff048a7 | |||
| d1fda09f2e | |||
| 53a1b2db29 | |||
| 8af2c0549b | |||
| 5cde620dd9 | |||
| 4a24773a8c | |||
| d27b09cc1a | |||
| 75f56cb5e2 | |||
| 8578ea2c84 | |||
| a6ff4a315c | |||
| 5f9e71cd7b | |||
| 86eb80dd73 | |||
| 9af3cae5a0 | |||
| 27205f9922 | |||
| 7da74889d3 | |||
| 209aa3ba6b | |||
| 9dddaef777 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
.idea/
|
||||
sample.gpg
|
||||
sample-backupd-config.json
|
||||
# ---> macOS
|
||||
# General
|
||||
.DS_Store
|
||||
|
||||
4357
Cargo.lock
generated
4357
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
39
Cargo.toml
39
Cargo.toml
@@ -1,24 +1,31 @@
|
||||
[package]
|
||||
name = "oss-backupd"
|
||||
version = "0.1.0"
|
||||
version = "1.2.2"
|
||||
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
# cargo b --features use_sequoia_openpgp
|
||||
default = [] # "use_sequoia_openpgp"
|
||||
use_zip = []
|
||||
use_sequoia_openpgp = ["sequoia-openpgp"]
|
||||
|
||||
[dependencies]
|
||||
dirs = "2.0.1"
|
||||
argparse = "0.2.2"
|
||||
json = "0.11.14"
|
||||
rust-crypto = "0.2.36"
|
||||
indicatif = "0.13.0"
|
||||
urlencoding = "1.0.0"
|
||||
base64 = "0.11.0"
|
||||
reqwest = "0.9.22"
|
||||
sequoia-openpgp = "0.12.0"
|
||||
oss-rust-sdk = "0.1.12"
|
||||
chrono = "0.4.10"
|
||||
zip = "0.5.3"
|
||||
rust_util = { git = "https://github.com/jht5945/rust_util" }
|
||||
|
||||
|
||||
dirs = "6.0"
|
||||
argparse = "0.2"
|
||||
json = "0.12"
|
||||
hmac = "0.12"
|
||||
sha-1 = "0.10"
|
||||
indicatif = "0.17"
|
||||
urlencoding = "2.1"
|
||||
base64 = "0.22"
|
||||
reqwest = { version = "0.12", features = ["blocking", "native-tls-vendored"] }
|
||||
sequoia-openpgp = { version = "1.22", optional = true }
|
||||
chrono = "0.4"
|
||||
zip = "2.2"
|
||||
tar = "0.4"
|
||||
flate2 = "1.0"
|
||||
rust_util = "0.6"
|
||||
tiny-encrypt = { version = "1.8", default-features = false }
|
||||
|
||||
30
README.md
30
README.md
@@ -9,6 +9,36 @@ Config read order:
|
||||
1. from `~/.oss-backupd-config.json`
|
||||
1. from `/etc/oss-backupd/config.json`
|
||||
|
||||
Usage:
|
||||
|
||||
Help
|
||||
```shell
|
||||
$ oss-backupd -h
|
||||
Usage:
|
||||
./target/debug/oss-backupd [OPTIONS]
|
||||
|
||||
oss_backupd - command line OSS backup tool.
|
||||
|
||||
Optional arguments:
|
||||
-h,--help Show this help message and exit
|
||||
-V,--version Print version
|
||||
-v,--verbose Verbose
|
||||
-c,--config CONFIG Config file
|
||||
```
|
||||
|
||||
Run with config file
|
||||
```shell
|
||||
$ oss-backupd -c sample-backupd-config.json
|
||||
Compressing opt.rs, Finished: 1.16KB, Speed: -
|
||||
Compressing oss_util.rs, Finished: 5.28KB, Speed: -
|
||||
Compressing pgp_util.rs, Finished: 3.00KB, Speed: -
|
||||
Compressing config_util.rs, Finished: 10.84KB, Speed: -
|
||||
Compressing main.rs, Finished: 9.25KB, Speed: -
|
||||
Compressing zip_util.rs, Finished: 2.57KB, Speed: -
|
||||
[OK ] Success, at item index: 0
|
||||
[OK ] Backup all file(s) finished!
|
||||
```
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
5
justfile
Normal file
5
justfile
Normal file
@@ -0,0 +1,5 @@
|
||||
_:
|
||||
@just --list
|
||||
|
||||
build-linux-musl-with-zig:
|
||||
cargo zigbuild --release --target x86_64-unknown-linux-musl --features use_zip
|
||||
@@ -12,6 +12,10 @@
|
||||
{
|
||||
"target": "",
|
||||
"file_name": "sample_file"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"file_name": "sample_file_2"
|
||||
}
|
||||
]
|
||||
}
|
||||
74
sample.gpg
74
sample.gpg
@@ -1,74 +0,0 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: GPGTools - https://gpgtools.org
|
||||
|
||||
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
|
||||
=HzsD
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -1,19 +1,17 @@
|
||||
use chrono::Utc;
|
||||
use json::JsonValue;
|
||||
use rust_util::{new_box_ioerror, XResult};
|
||||
use std::{
|
||||
fs,
|
||||
path::Path,
|
||||
};
|
||||
use rust_util::{
|
||||
XResult,
|
||||
new_box_ioerror,
|
||||
util_msg::*,
|
||||
};
|
||||
use chrono::{
|
||||
Utc,
|
||||
};
|
||||
|
||||
pub const ETC_OSS_BACKUPD_CONFIG: &str = "/etc/oss-backupd/config.json";
|
||||
pub const OSS_BACKUPD_CONFIG: &str = "oss-backupd-config.json";
|
||||
pub const DOT_OSS_BACKUPD_CONFIG: &str = ".oss-backupd-config.json";
|
||||
pub const NAME: &str = env!("CARGO_PKG_NAME");
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DOT_OSS_BACKUPD_CONFIG: &str = ".oss-backupd-config.json";
|
||||
const OSS_BACKUPD_CONFIG: &str = "oss-backupd-config.json";
|
||||
const ETC_OSS_BACKUPD_CONFIG: &str = "/etc/oss-backupd/config.json";
|
||||
|
||||
/*
|
||||
{
|
||||
@@ -26,12 +24,14 @@ pub const DOT_OSS_BACKUPD_CONFIG: &str = ".oss-backupd-config.json";
|
||||
},
|
||||
"host": "",
|
||||
"encrypt_pubkey_file": "",
|
||||
"backup_count": 10,
|
||||
"items": [
|
||||
{
|
||||
"oss_config": null,
|
||||
"oss_config": null, // OPT @oss_config
|
||||
"target": "",
|
||||
"file_name": "",
|
||||
"encrypt_pubkey_file": null
|
||||
"encrypt_pubkey_file": null, // OPT @encrypt_pubkey_file
|
||||
"backup_count": 8
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -52,6 +52,14 @@ pub struct OSSBackupdConfigItem {
|
||||
pub file_name: Option<String>,
|
||||
pub oss_config: Option<OSSConfig>,
|
||||
pub encrypt_pubkey_file: Option<String>,
|
||||
pub tiny_encrypt: Option<TinyEncryptConfig>,
|
||||
pub backup_count: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TinyEncryptConfig {
|
||||
pub config: String,
|
||||
pub profile: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -60,128 +68,184 @@ pub struct OSSBackupdConfig {
|
||||
pub prefix: Option<String>,
|
||||
pub host: Option<String>,
|
||||
pub items: Vec<OSSBackupdConfigItem>,
|
||||
pub backup_count: Option<u32>,
|
||||
}
|
||||
|
||||
impl OSSBackupdConfigItem {
|
||||
impl OSSBackupdConfig {
|
||||
pub fn get_host(&self) -> String {
|
||||
match &self.host {
|
||||
Some(h) => remove_start_end_slash(h),
|
||||
None => "default_host".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_oss_key(&self, oss_backupd_config: &OSSBackupdConfig, suffix: &str) -> String {
|
||||
real_make_oss_key(oss_backupd_config, &self, suffix)
|
||||
pub fn get_prefix(&self) -> String {
|
||||
match &self.prefix {
|
||||
Some(p) => remove_start_end_slash(p),
|
||||
None => "default_oss_backupd".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn real_make_oss_key(oss_backupd_config: &OSSBackupdConfig, oss_backupd_config_item: &OSSBackupdConfigItem, suffix: &str) -> String {
|
||||
let mut key = String::with_capacity(1024);
|
||||
key.push_str(&(if oss_backupd_config.prefix.is_some() {
|
||||
remove_start_end_slash(&oss_backupd_config.prefix.as_ref().unwrap().as_str())
|
||||
} else {
|
||||
"default_oss_backupd".to_string()
|
||||
}));
|
||||
key.push_str("/");
|
||||
key.push_str(&(if oss_backupd_config.host.is_some() {
|
||||
remove_start_end_slash(&oss_backupd_config.host.as_ref().unwrap().as_str())
|
||||
} else {
|
||||
"default_host".to_string()
|
||||
}));
|
||||
key.push_str("/");
|
||||
key.push_str(if oss_backupd_config_item.file_name.is_some() {
|
||||
oss_backupd_config_item.file_name.as_ref().unwrap().as_str()
|
||||
} else {
|
||||
"default_file_name"
|
||||
});
|
||||
key.push_str("_");
|
||||
let ymdhms = Utc::now().format("%Y%m%d_%H%M%S").to_string();
|
||||
key.push_str(&ymdhms);
|
||||
impl OSSBackupdConfigItem {
|
||||
pub fn make_oss_key(&self, oss_backupd_config: &OSSBackupdConfig, suffix: &str) -> String {
|
||||
real_make_oss_key(oss_backupd_config, self, suffix)
|
||||
}
|
||||
|
||||
if suffix != "" {
|
||||
pub fn get_file_name(&self) -> String {
|
||||
match &self.file_name {
|
||||
Some(f) => f.clone(),
|
||||
None => "default_file_name".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_safe_backup_count(&self) -> usize {
|
||||
(self.backup_count.unwrap_or(10u32) as usize).clamp(1_usize, 1000_usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_config(config_json: &json::JsonValue) -> OSSBackupdConfig {
|
||||
let root_oss_config_object = parse_sub_oss_config(config_json);
|
||||
let encrypt_pubkey_file = get_string_value(config_json, "encrypt_pubkey_file");
|
||||
let tiny_encrypt_config = get_tiny_encrypt_value(config_json);
|
||||
let prefix = get_string_value(config_json, "prefix");
|
||||
let host = get_string_value(config_json, "host");
|
||||
let backup_count = get_u32_value(config_json, "backup_count");
|
||||
let items = &config_json["items"];
|
||||
let mut items_objects: Vec<OSSBackupdConfigItem> = vec![];
|
||||
if items.is_array() {
|
||||
for i in 0..items.len() {
|
||||
items_objects.push(parse_oss_backupd_config_item(
|
||||
&items[i],
|
||||
&root_oss_config_object,
|
||||
&encrypt_pubkey_file,
|
||||
&tiny_encrypt_config,
|
||||
backup_count,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
OSSBackupdConfig {
|
||||
oss_config: root_oss_config_object,
|
||||
prefix,
|
||||
host,
|
||||
backup_count,
|
||||
items: items_objects,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_json(
|
||||
custom_oss_backupd_config: Option<&str>,
|
||||
verbose: bool,
|
||||
) -> Option<json::JsonValue> {
|
||||
let config_content = get_config_content(custom_oss_backupd_config, verbose)?;
|
||||
match json::parse(&config_content) {
|
||||
Ok(o) => Some(o),
|
||||
Err(e) => {
|
||||
failure!("Parse config json failed: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_start_end_slash(s: &str) -> String {
|
||||
let mut ss = s;
|
||||
while ss.starts_with('/') {
|
||||
ss = &ss[1..]
|
||||
}
|
||||
while ss.ends_with('/') {
|
||||
ss = &ss[0..(ss.len() - 1)];
|
||||
}
|
||||
ss.to_owned()
|
||||
}
|
||||
|
||||
fn parse_oss_backupd_config_item(
|
||||
item: &json::JsonValue,
|
||||
root_oss_config_object: &Option<OSSConfig>,
|
||||
root_encrypt_pubkey_file: &Option<String>,
|
||||
root_tiny_encrypt_config: &Option<TinyEncryptConfig>,
|
||||
root_backup_count: Option<u32>,
|
||||
) -> OSSBackupdConfigItem {
|
||||
let target = get_string_value(item, "target");
|
||||
let file_name = get_string_value(item, "file_name");
|
||||
let mut backup_count = get_u32_value(item, "backup_count");
|
||||
let mut encrypt_pubkey_file = get_string_value(item, "encrypt_pubkey_file");
|
||||
let mut tiny_encrypt = get_tiny_encrypt_value(item);
|
||||
let mut oss_config = parse_sub_oss_config(item);
|
||||
if let Some(ref root_oc) = root_oss_config_object {
|
||||
match oss_config {
|
||||
Some(mut oc) => {
|
||||
if oc.endpoint.is_none() && root_oc.endpoint.is_some() {
|
||||
oc.endpoint = root_oc.endpoint.clone()
|
||||
}
|
||||
if oc.access_key_id.is_none() && root_oc.access_key_id.is_some() {
|
||||
oc.access_key_id = root_oc.access_key_id.clone()
|
||||
}
|
||||
if oc.access_key_secret.is_none() && root_oc.access_key_secret.is_some() {
|
||||
oc.access_key_secret = root_oc.access_key_secret.clone();
|
||||
}
|
||||
if oc.bucket.is_none() && root_oc.bucket.is_some() {
|
||||
oc.bucket = root_oc.bucket.clone();
|
||||
}
|
||||
if oc.path.is_none() && root_oc.path.is_some() {
|
||||
oc.path = root_oc.path.clone();
|
||||
}
|
||||
oss_config = Some(oc);
|
||||
}
|
||||
None => oss_config = root_oss_config_object.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
if encrypt_pubkey_file.is_none() && root_encrypt_pubkey_file.is_some() {
|
||||
encrypt_pubkey_file = root_encrypt_pubkey_file.clone();
|
||||
}
|
||||
if tiny_encrypt.is_none() && root_tiny_encrypt_config.is_some() {
|
||||
tiny_encrypt = root_tiny_encrypt_config.clone();
|
||||
}
|
||||
if backup_count.is_none() && root_backup_count.is_some() {
|
||||
backup_count = root_backup_count;
|
||||
}
|
||||
|
||||
OSSBackupdConfigItem {
|
||||
target,
|
||||
file_name,
|
||||
oss_config,
|
||||
backup_count,
|
||||
encrypt_pubkey_file,
|
||||
tiny_encrypt,
|
||||
}
|
||||
}
|
||||
|
||||
fn real_make_oss_key(
|
||||
oss_backupd_config: &OSSBackupdConfig,
|
||||
oss_backupd_config_item: &OSSBackupdConfigItem,
|
||||
suffix: &str,
|
||||
) -> String {
|
||||
let mut key = String::with_capacity(1024);
|
||||
key.push_str(&oss_backupd_config.get_prefix());
|
||||
key.push('/');
|
||||
key.push_str(&oss_backupd_config.get_host());
|
||||
key.push('/');
|
||||
key.push_str(&format!(
|
||||
"{}_{}",
|
||||
&oss_backupd_config_item.get_file_name(),
|
||||
&get_now_ymdhms()
|
||||
));
|
||||
|
||||
if !suffix.is_empty() {
|
||||
key.push_str(&format!(".{}", suffix));
|
||||
}
|
||||
|
||||
key
|
||||
}
|
||||
|
||||
pub fn remove_start_end_slash(s: &str) -> String {
|
||||
let mut ss = s;
|
||||
while ss.starts_with("/") {
|
||||
ss = &ss[1..]
|
||||
}
|
||||
while ss.ends_with("/") {
|
||||
ss = &ss[0..(ss.len() - 1)];
|
||||
}
|
||||
ss.to_string()
|
||||
}
|
||||
|
||||
pub fn parse_config(config_json: &json::JsonValue) -> OSSBackupdConfig {
|
||||
let root_oss_config_object = parse_sub_oss_config(config_json);
|
||||
let encrypt_pubkey_file = get_string_value(config_json, "encrypt_pubkey_file");
|
||||
let prefix = get_string_value(config_json, "prefix");
|
||||
let host = get_string_value(config_json, "host");
|
||||
let items = &config_json["items"];
|
||||
let mut items_objects: Vec<OSSBackupdConfigItem> = vec![];
|
||||
if items.is_array() {
|
||||
for i in 0..items.len() {
|
||||
items_objects.push(parse_oss_backupd_config_item(&items[i], &root_oss_config_object, &encrypt_pubkey_file));
|
||||
}
|
||||
}
|
||||
|
||||
OSSBackupdConfig {
|
||||
oss_config: root_oss_config_object,
|
||||
prefix: prefix,
|
||||
host: host,
|
||||
items: items_objects,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_oss_backupd_config_item(item: &json::JsonValue, root_oss_config_object: &Option<OSSConfig>, root_encrypt_pubkey_file: &Option<String>) -> OSSBackupdConfigItem {
|
||||
let target = get_string_value(item, "target");
|
||||
let file_name = get_string_value(item, "file_name");
|
||||
let mut encrypt_pubkey_file = get_string_value(item, "encrypt_pubkey_file");
|
||||
let mut oss_config = parse_sub_oss_config(item);
|
||||
if root_oss_config_object.is_some() {
|
||||
if oss_config.is_some() {
|
||||
let mut oc = oss_config.unwrap();
|
||||
let root_oc = root_oss_config_object.as_ref().unwrap();
|
||||
|
||||
if oc.endpoint.is_none() && root_oc.endpoint.is_some() {
|
||||
oc.endpoint = root_oc.endpoint.clone()
|
||||
}
|
||||
if oc.access_key_id.is_none() && root_oc.access_key_id.is_some() {
|
||||
oc.access_key_id = root_oc.access_key_id.clone()
|
||||
}
|
||||
if oc.access_key_secret.is_none() && root_oc.access_key_secret.is_some() {
|
||||
oc.access_key_secret = root_oc.access_key_secret.clone();
|
||||
}
|
||||
if oc.bucket.is_none() && root_oc.bucket.is_some() {
|
||||
oc.bucket = root_oc.bucket.clone();
|
||||
}
|
||||
if oc.path.is_none() && root_oc.path.is_some() {
|
||||
oc.path = root_oc.path.clone();
|
||||
}
|
||||
oss_config = Some(oc);
|
||||
} else {
|
||||
oss_config = root_oss_config_object.clone();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if encrypt_pubkey_file.is_none() && root_encrypt_pubkey_file.is_some() {
|
||||
encrypt_pubkey_file = root_encrypt_pubkey_file.clone();
|
||||
}
|
||||
|
||||
OSSBackupdConfigItem {
|
||||
target: target,
|
||||
file_name: file_name,
|
||||
oss_config: oss_config,
|
||||
encrypt_pubkey_file: encrypt_pubkey_file,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sub_oss_config(json: &json::JsonValue) -> Option<OSSConfig> {
|
||||
let root_oss_config = &json["oss_config"];
|
||||
let root_oss_config_object: Option<OSSConfig> = match root_oss_config.is_null() {
|
||||
true => None,
|
||||
false => Some(parse_oss_config(root_oss_config)),
|
||||
};
|
||||
root_oss_config_object
|
||||
iff!(
|
||||
root_oss_config.is_null(),
|
||||
None,
|
||||
Some(parse_oss_config(root_oss_config))
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_oss_config(oss_config: &json::JsonValue) -> OSSConfig {
|
||||
@@ -194,42 +258,55 @@ fn parse_oss_config(oss_config: &json::JsonValue) -> OSSConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_string_value(json: &json::JsonValue, key: &str) -> Option<String> {
|
||||
fn get_string_value(json: &JsonValue, key: &str) -> Option<String> {
|
||||
let value = &json[key];
|
||||
match value.is_string() {
|
||||
true => Some(value.as_str().unwrap().to_string()),
|
||||
false => None,
|
||||
value.as_str().map(|s| s.to_owned())
|
||||
}
|
||||
|
||||
fn get_tiny_encrypt_value(json: &JsonValue) -> Option<TinyEncryptConfig> {
|
||||
let tiny_encrypt = &json["tiny_encrypt"];
|
||||
let config = get_string_value(tiny_encrypt, "config");
|
||||
let profile = get_string_value(tiny_encrypt, "profile");
|
||||
|
||||
if let (Some(config), profile) = (config, profile) {
|
||||
Some(TinyEncryptConfig { config, profile })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_json(custom_oss_backupd_config: Option<&str>, verbose: bool) -> Option<json::JsonValue> {
|
||||
let config_content = get_config_content(custom_oss_backupd_config, verbose)?;
|
||||
match json::parse(&config_content) {
|
||||
Err(e) => {
|
||||
print_message(MessageType::ERROR, &format!("Parse config json failed: {}", e));
|
||||
None
|
||||
},
|
||||
Ok(o) => Some(o),
|
||||
fn get_u32_value(json: &JsonValue, key: &str) -> Option<u32> {
|
||||
let value = &json[key];
|
||||
match value {
|
||||
JsonValue::String(v) => v.parse().ok(),
|
||||
JsonValue::Number(v) => Some(f64::from(*v) as u32),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config_content(custom_oss_backupd_config: Option<&str>, verbose: bool) -> Option<String> {
|
||||
if custom_oss_backupd_config.is_some() {
|
||||
let custom_oss_backupd_config_unrwaped = custom_oss_backupd_config.unwrap();
|
||||
if verbose {
|
||||
print_message(MessageType::DEBUG, &format!("Read config from: {}", custom_oss_backupd_config_unrwaped));
|
||||
if let Some(custom_oss_backupd_config_val) = custom_oss_backupd_config {
|
||||
if verbose {
|
||||
debugging!("Read config from: {}", custom_oss_backupd_config_val);
|
||||
}
|
||||
let custom_oss_backupd_config_path = Path::new(custom_oss_backupd_config_unrwaped);
|
||||
let custom_oss_backupd_config_path = Path::new(custom_oss_backupd_config_val);
|
||||
if custom_oss_backupd_config_path.exists() {
|
||||
match fs::read_to_string(custom_oss_backupd_config_path) {
|
||||
return match fs::read_to_string(custom_oss_backupd_config_path) {
|
||||
Ok(o) => Some(o),
|
||||
Err(e) => {
|
||||
print_message(MessageType::ERROR, &format!("Read config file {} error: {}", custom_oss_backupd_config_unrwaped, e));
|
||||
return None;
|
||||
},
|
||||
Ok(o) => return Some(o),
|
||||
failure!(
|
||||
"Read config file {} error: {}",
|
||||
custom_oss_backupd_config_val,
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
} else {
|
||||
print_message(MessageType::ERROR, &format!("Custom config file not found: {}", custom_oss_backupd_config_unrwaped));
|
||||
failure!(
|
||||
"Custom config file not found: {}",
|
||||
custom_oss_backupd_config_val
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -237,66 +314,71 @@ fn get_config_content(custom_oss_backupd_config: Option<&str>, verbose: bool) ->
|
||||
let oss_backupd_config_path = Path::new(OSS_BACKUPD_CONFIG);
|
||||
if oss_backupd_config_path.exists() {
|
||||
if verbose {
|
||||
print_message(MessageType::DEBUG, &format!("Read config from: {}", OSS_BACKUPD_CONFIG));
|
||||
debugging!("Read config from: {}", OSS_BACKUPD_CONFIG);
|
||||
}
|
||||
match fs::read_to_string(oss_backupd_config_path) {
|
||||
return match fs::read_to_string(oss_backupd_config_path) {
|
||||
Ok(o) => Some(o),
|
||||
Err(e) => {
|
||||
print_message(MessageType::ERROR, &format!("Read config file {} error: {}", OSS_BACKUPD_CONFIG, e));
|
||||
return None;
|
||||
},
|
||||
Ok(o) => return Some(o),
|
||||
failure!("Read config file {} error: {}", OSS_BACKUPD_CONFIG, e);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
let home_dot_oss_backupd_config = & match get_user_home_dir(DOT_OSS_BACKUPD_CONFIG) {
|
||||
Err(e) => {
|
||||
print_message(MessageType::WARN, &format!("Get user home error: {}", e));
|
||||
String::new()
|
||||
},
|
||||
Ok(o) => o,
|
||||
};
|
||||
if home_dot_oss_backupd_config != "" {
|
||||
let home_dot_oss_backupd_config = &get_user_home_dir(DOT_OSS_BACKUPD_CONFIG).unwrap_or_else(|e| {
|
||||
warning!("Get user home error: {}", e);
|
||||
String::new()
|
||||
});
|
||||
if !home_dot_oss_backupd_config.is_empty() {
|
||||
let home_dot_oss_backupd_config_path = Path::new(home_dot_oss_backupd_config);
|
||||
if home_dot_oss_backupd_config_path.exists() {
|
||||
if verbose {
|
||||
print_message(MessageType::DEBUG, &format!("Read config from: {}", home_dot_oss_backupd_config));
|
||||
debugging!("Read config from: {}", home_dot_oss_backupd_config);
|
||||
}
|
||||
match fs::read_to_string(home_dot_oss_backupd_config_path) {
|
||||
return match fs::read_to_string(home_dot_oss_backupd_config_path) {
|
||||
Ok(o) => Some(o),
|
||||
Err(e) => {
|
||||
print_message(MessageType::ERROR, &format!("Read config file {} error: {}", home_dot_oss_backupd_config, e));
|
||||
return None;
|
||||
},
|
||||
Ok(o) => return Some(o),
|
||||
failure!(
|
||||
"Read config file {} error: {}",
|
||||
home_dot_oss_backupd_config,
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
let etc_oss_backupd_config_path = Path::new(ETC_OSS_BACKUPD_CONFIG);
|
||||
if etc_oss_backupd_config_path.exists() {
|
||||
if verbose {
|
||||
print_message(MessageType::DEBUG, &format!("Read config from: {}", ETC_OSS_BACKUPD_CONFIG));
|
||||
debugging!("Read config from: {}", ETC_OSS_BACKUPD_CONFIG);
|
||||
}
|
||||
match fs::read_to_string(etc_oss_backupd_config_path) {
|
||||
return match fs::read_to_string(etc_oss_backupd_config_path) {
|
||||
Ok(o) => Some(o),
|
||||
Err(e) => {
|
||||
print_message(MessageType::ERROR, &format!("Read config file {} error: {}", ETC_OSS_BACKUPD_CONFIG, e));
|
||||
return None;
|
||||
},
|
||||
Ok(o) => return Some(o),
|
||||
failure!("Read config file {} error: {}", ETC_OSS_BACKUPD_CONFIG, e);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
print_message(MessageType::ERROR, "Cannot find config file");
|
||||
failure!("Cannot find config file");
|
||||
None
|
||||
}
|
||||
|
||||
fn get_user_home() -> XResult<String> {
|
||||
match dirs::home_dir() {
|
||||
None => Err(new_box_ioerror("Home dir not found!")),
|
||||
Some(home_dir_o) => match home_dir_o.to_str() {
|
||||
None => Err(new_box_ioerror("Home dir not found!")),
|
||||
Some(home_dir_str) => Ok(home_dir_str.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user_home_dir(dir: &str) -> XResult<String> {
|
||||
Ok(format!("{}/{}", get_user_home()?, dir))
|
||||
}
|
||||
None => Err(new_box_ioerror("Home dir not found!")),
|
||||
Some(home_dir) => home_dir
|
||||
.to_str()
|
||||
.map(|h| h.to_owned())
|
||||
.ok_or_else(|| new_box_ioerror("Home dir not found!")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user_home_dir(dir: &str) -> XResult<String> {
|
||||
Ok(format!("{}/{}", get_user_home()?, dir))
|
||||
}
|
||||
|
||||
fn get_now_ymdhms() -> String {
|
||||
Utc::now().format("%Y%m%d_%H%M%S").to_string()
|
||||
}
|
||||
|
||||
346
src/main.rs
346
src/main.rs
@@ -1,92 +1,297 @@
|
||||
#[macro_use]
|
||||
extern crate rust_util;
|
||||
#[cfg(feature = "use_sequoia_openpgp")]
|
||||
extern crate sequoia_openpgp as openpgp;
|
||||
pub mod oss_util;
|
||||
pub mod pgp_util;
|
||||
pub mod config_util;
|
||||
pub mod zip_util;
|
||||
pub mod opt;
|
||||
|
||||
mod oss_util;
|
||||
#[cfg(feature = "use_sequoia_openpgp")]
|
||||
mod pgp_util;
|
||||
mod config_util;
|
||||
#[cfg(not(feature = "use_zip"))]
|
||||
mod zip_util;
|
||||
mod opt;
|
||||
|
||||
use std::{
|
||||
time::SystemTime,
|
||||
};
|
||||
use rust_util::{
|
||||
XResult,
|
||||
util_msg::*,
|
||||
path::Path,
|
||||
fs::{ self, File },
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use rust_util::{XResult, util_time::*, util_msg::*};
|
||||
use tiny_encrypt::CmdEncrypt;
|
||||
use oss_util::*;
|
||||
use config_util::*;
|
||||
// use pgp_util::OpenPGPTool;
|
||||
use opt::{
|
||||
Options,
|
||||
};
|
||||
#[cfg(feature = "use_sequoia_openpgp")]
|
||||
use pgp_util::OpenPGPTool;
|
||||
use opt::Options;
|
||||
|
||||
// https://docs.sequoia-pgp.org/sequoia_openpgp/serialize/stream/struct.Encryptor.html
|
||||
// https://gitlab.com/sequoia-pgp/sequoia/blob/master/openpgp/examples/generate-encrypt-decrypt.rs
|
||||
fn main() -> XResult<()> {
|
||||
let options = Options::new_and_parse_args()?;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut features: Vec<&str> = vec![];
|
||||
#[cfg(feature = "use_zip")]
|
||||
features.push("use_zip");
|
||||
#[cfg(feature = "use_sequoia_openpgp")]
|
||||
features.push("use_sequoia_openpgp");
|
||||
if options.version {
|
||||
// TODO ...
|
||||
print_message(MessageType::INFO, "VERSION!!!");
|
||||
information!("{NAME} v{VERSION}, features: [{}]", features.join(", "));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if options.verbose {
|
||||
print_message(MessageType::DEBUG, &format!("Config is: {}", &options.config));
|
||||
debugging!("Config is: {}", &options.config);
|
||||
}
|
||||
|
||||
println!("Hello, world!");
|
||||
println!("{}", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs());
|
||||
|
||||
// let oss_client = OSSClient::new("oss-cn-shanghai.aliyuncs.com", "", "");
|
||||
// let c = oss_client.get_file_content("hatterbucket", "Check.java")?;
|
||||
// println!("XXXXX: {:?}", c);
|
||||
// println!("XXXXX: {}", );
|
||||
|
||||
// zip_util::zip_file("hello.txt", "aa.zip")?;
|
||||
|
||||
// let openpgp_client = OpenPGPTool::from_file("sample.gpg")?;
|
||||
// openpgp_client.encrypt_file("a", "b.asc", true)?;
|
||||
|
||||
let config_json = match get_config_json(if options.config == "" { None } else { Some(&options.config) }, options.verbose) {
|
||||
None => return Ok(()),
|
||||
Some(c) => c,
|
||||
let config_json = match get_config_json(iff!(options.config.is_empty(), None, Some(&options.config)), options.verbose) {
|
||||
Some(c) => c, None => return Ok(()),
|
||||
};
|
||||
|
||||
let oss_backupd_config = parse_config(&config_json);
|
||||
|
||||
println!("{:?}", &oss_backupd_config);
|
||||
|
||||
// TODO ...
|
||||
|
||||
println!("{:?}", oss_backupd_config);
|
||||
println!("");
|
||||
for i in &oss_backupd_config.items {
|
||||
println!("{:?}", i);
|
||||
println!("{}", i.make_oss_key(&oss_backupd_config, "gpg"));
|
||||
println!("{}", i.make_oss_key(&oss_backupd_config, "asc"));
|
||||
if options.verbose {
|
||||
debugging!("OSS backup config: {:?}", &oss_backupd_config);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO call it!
|
||||
pub fn process_oss_files(oss_client: &OSSClient, bucket_name: &str, path: &str, meta_file_name: &str, new_file: &str, limit: usize) -> XResult<()> {
|
||||
let meta_file_key = &format!("{}/{}", path, meta_file_name);
|
||||
let meta_file_content = match oss_client.get_file_content(bucket_name, meta_file_key)? {
|
||||
None => "[]".to_string(),
|
||||
Some(c) => c,
|
||||
};
|
||||
let (removed_file, new_meta_file_content) = process_new_backup_file(&meta_file_content, new_file, limit)?;
|
||||
oss_client.put_file_content(bucket_name, meta_file_key, &new_meta_file_content)?;
|
||||
if !removed_file.is_empty() {
|
||||
for rm_file in removed_file {
|
||||
let rm_file_key = &format!("{}/{}", path, rm_file);
|
||||
oss_client.delete_file(bucket_name, rm_file_key)?;
|
||||
for (item_index, config_item) in oss_backupd_config.items.iter().enumerate() {
|
||||
if let Err(e) = process_config_item(&options, config_item, &oss_backupd_config, item_index) {
|
||||
failure!("Config {} not found, at item index: {}", e, item_index);
|
||||
}
|
||||
}
|
||||
|
||||
success!("Backup all file(s) finished!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_new_backup_file(backup_content_json: &str, new_item: &str, limit: usize) -> XResult<(Vec<String>, String)> {
|
||||
fn process_config_item(options: &Options, config_item: &OSSBackupdConfigItem,
|
||||
oss_backupd_config :&OSSBackupdConfig, item_index: usize) -> Result<(), String> {
|
||||
if options.verbose {
|
||||
debugging!("Process config item index: {}, config: {:?}", item_index, config_item);
|
||||
}
|
||||
|
||||
let encrypt_pubkey_file = config_item.encrypt_pubkey_file.as_ref();
|
||||
if options.verbose {
|
||||
debugging!("Encrypt pubkey file: {:?}", encrypt_pubkey_file);
|
||||
}
|
||||
let tiny_encrypt_config = config_item.tiny_encrypt.as_ref();
|
||||
if options.verbose {
|
||||
debugging!("Encrypt tiny-encrypt config: {:?}", tiny_encrypt_config);
|
||||
}
|
||||
if encrypt_pubkey_file.is_none() && tiny_encrypt_config.is_none() {
|
||||
return Err("Config encrypt_pubkey_file or tiny_encrypt MUST config one".to_string());
|
||||
}
|
||||
|
||||
let target = config_item.target.as_ref().ok_or_else(|| "target".to_owned())?;
|
||||
if options.verbose {
|
||||
debugging!("Target file: {}", iff!(target.is_empty(), "<empty>", target));
|
||||
}
|
||||
|
||||
let oss_config = config_item.oss_config.as_ref().ok_or_else(|| "oss_config".to_owned())?;
|
||||
|
||||
let endpoint = oss_config.endpoint.as_ref().ok_or_else(|| "oss_config#endpoint".to_owned())?;
|
||||
if options.verbose {
|
||||
debugging!("Endpoint: {}", endpoint);
|
||||
}
|
||||
let access_key_id = oss_config.access_key_id.as_ref().ok_or_else(|| "oss_config#access_key_id".to_owned())?;
|
||||
if options.verbose {
|
||||
debugging!("Access key id: {}", access_key_id);
|
||||
}
|
||||
let access_key_secret = oss_config.access_key_secret.as_ref().ok_or_else(|| "oss_config#access_key_secret".to_owned())?;
|
||||
|
||||
let bucket = &oss_config.bucket.as_ref().ok_or_else(|| "oss_config#bucket".to_owned())?;
|
||||
if options.verbose {
|
||||
debugging!("Bucket: {}", bucket);
|
||||
}
|
||||
|
||||
let path = &match &oss_config.path {
|
||||
Some(path) => path.to_owned(), None => format!("default_path_at_{}", item_index),
|
||||
};
|
||||
if options.verbose {
|
||||
debugging!("Path: {}", path);
|
||||
}
|
||||
|
||||
let oss_client = OSSClient::new(endpoint, access_key_id, access_key_secret);
|
||||
let backup_count = config_item.get_safe_backup_count();
|
||||
let meta_file_name = &format!("{}/ossbackupd_meta_{}_{}.json", &oss_backupd_config.get_prefix(), &oss_backupd_config.get_host(), &config_item.get_file_name());
|
||||
|
||||
if options.verbose {
|
||||
debugging!("Backup count: {}", backup_count);
|
||||
debugging!("Meta file name: {}", meta_file_name);
|
||||
}
|
||||
|
||||
let secs = get_current_secs();
|
||||
let temp_zip_file = &format!("temp_file_{}.zip", secs);
|
||||
let temp_pgp_file = &format!("temp_file_{}.gpg", secs);
|
||||
let temp_tiny_encrypt_file = &format!("temp_file_{}.tinyenc", secs);
|
||||
|
||||
let remove_temp_files = || {
|
||||
for f in &[temp_zip_file, temp_pgp_file, temp_tiny_encrypt_file] {
|
||||
if Path::new(f).is_file() {
|
||||
if options.verbose {
|
||||
debugging!("Remove file: {}", f);
|
||||
}
|
||||
fs::remove_file(f).ok();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if options.verbose {
|
||||
debugging!("Compress file: {} -> {}", target, temp_zip_file);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "use_zip"))]
|
||||
let zip_file = || {
|
||||
if let Err(e) = zip_util::compress_file(target, temp_zip_file) {
|
||||
failure!("Error in zip file: {}, at item index: {}", e, item_index);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "use_zip")]
|
||||
let zip_file = || {
|
||||
let mut cmd = std::process::Command::new("zip");
|
||||
cmd.args(&["-r", temp_zip_file, target]);
|
||||
if let Err(e) = rust_util::util_cmd::run_command_and_wait(&mut cmd) {
|
||||
print_message(MessageType::ERROR, &format!("Error in zip file: {}, at item index: {}", e, item_index));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if !zip_file() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let temp_encrypted_file;
|
||||
let temp_new_file;
|
||||
if let Some(encrypt_pubkey_file) = encrypt_pubkey_file {
|
||||
#[cfg(feature = "use_sequoia_openpgp")]
|
||||
let enc_file_by_pgp = || {
|
||||
let open_pgp_tool = match OpenPGPTool::from_file(encrypt_pubkey_file) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
print_message(MessageType::ERROR, &format!("Error in load pgp file: {}, at item index: {}", e, item_index));
|
||||
return false;
|
||||
},
|
||||
};
|
||||
if options.verbose {
|
||||
print_message(MessageType::DEBUG, &format!("Encrypt file: {} -> {}", temp_zip_file, temp_pgp_file));
|
||||
}
|
||||
if let Err(e) = open_pgp_tool.encrypt_file(temp_zip_file, temp_pgp_file, false) {
|
||||
print_message(MessageType::ERROR, &format!("Error in encrypt file: {}, at item index: {}", e, item_index));
|
||||
remove_temp_files();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
#[cfg(not(feature = "use_sequoia_openpgp"))]
|
||||
let enc_file_by_pgp = || {
|
||||
let mut cmd = std::process::Command::new("gpg");
|
||||
cmd.args(["-e", "-r", encrypt_pubkey_file, "-o", temp_pgp_file, temp_zip_file]);
|
||||
if let Err(e) = rust_util::util_cmd::run_command_and_wait(&mut cmd) {
|
||||
failure!("Error in encrypt file: {}, at item index: {}", e, item_index);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if !enc_file_by_pgp() {
|
||||
return Ok(());
|
||||
}
|
||||
temp_encrypted_file = temp_pgp_file;
|
||||
temp_new_file = format!("{}/{}", path, config_item.make_oss_key(oss_backupd_config, "zip.gpg"));
|
||||
} else if let Some(tiny_encrypt_config) = tiny_encrypt_config {
|
||||
let config = match tiny_encrypt::TinyEncryptConfig::load(&tiny_encrypt_config.config) {
|
||||
Ok(config) => config,
|
||||
Err(e) => return Err(format!("Load tiny-encrypt config failed: {}", e)),
|
||||
};
|
||||
let envelops = match config.find_envelops(&tiny_encrypt_config.profile, &None) {
|
||||
Ok(envelops) => envelops,
|
||||
Err(e) => return Err(format!("Load tiny-encrypt config and filter failed: {}", e)),
|
||||
};
|
||||
let cmd_encrypt = CmdEncrypt{
|
||||
comment: None,
|
||||
encrypted_comment: None,
|
||||
profile: None,
|
||||
key_filter: None,
|
||||
compress: false,
|
||||
compress_level: None,
|
||||
remove_file: false,
|
||||
create: false,
|
||||
disable_compress_meta: false,
|
||||
pem_output: false,
|
||||
encryption_algorithm: None,
|
||||
paths: vec![],
|
||||
};
|
||||
let path_in = PathBuf::from(temp_zip_file);
|
||||
if let Err(e) = tiny_encrypt::encrypt_single_file_out(&path_in, temp_tiny_encrypt_file, &envelops, &cmd_encrypt) {
|
||||
return Err(format!("Encrypt with tiny-encrypt failed: {}", e));
|
||||
}
|
||||
temp_encrypted_file = temp_tiny_encrypt_file;
|
||||
temp_new_file = format!("{}/{}", path, config_item.make_oss_key(oss_backupd_config, "zip.tinyenc"));
|
||||
} else {
|
||||
return Err("Config encrypt_pubkey_file or tiny_encrypt MUST config one".to_string());
|
||||
}
|
||||
if options.verbose {
|
||||
debugging!("New backup file: {}", temp_new_file);
|
||||
}
|
||||
|
||||
let file_temp_encrypted_file = match File::open(temp_encrypted_file) {
|
||||
Ok(f) => f, Err(e) => {
|
||||
failure!("Error in open file: {}, at item index: {}", e, item_index);
|
||||
remove_temp_files();
|
||||
return Ok(());
|
||||
},
|
||||
};
|
||||
|
||||
if options.verbose {
|
||||
debugging!("Upload file: {}", temp_encrypted_file);
|
||||
}
|
||||
if let Err(e) = oss_client.put_file(bucket, &temp_new_file, DEFAULT_URL_VALID_IN_SECS, file_temp_encrypted_file) {
|
||||
failure!("Error in encrypt file: {}, at item index: {}", e, item_index);
|
||||
remove_temp_files();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if options.verbose {
|
||||
debugging!("Processing meta file: {}", meta_file_name);
|
||||
}
|
||||
match process_oss_files(options, &oss_client, bucket, path, meta_file_name, &temp_new_file, backup_count) {
|
||||
Err(e) => failure!("Error: {}, at item index: {}", e, item_index),
|
||||
Ok(_) => success!("Success, at item index: {}", item_index),
|
||||
};
|
||||
remove_temp_files();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_oss_files(options: &Options, oss_client: &OSSClient, bucket_name: &str, path: &str, meta_file_name: &str, new_file: &str, limit: usize) -> XResult<()> {
|
||||
let meta_file_key = &format!("{}/{}", path, meta_file_name);
|
||||
if options.verbose {
|
||||
debugging!("Read meta file: {}", meta_file_key);
|
||||
}
|
||||
let meta_file_content = oss_client.get_file_content(bucket_name, meta_file_key)?.unwrap_or_else(|| "[]".to_owned());
|
||||
if options.verbose {
|
||||
debugging!("Read meta file content: {}", &meta_file_content);
|
||||
}
|
||||
let (removed_files, new_meta_file_content) = process_new_backup_file(&meta_file_content, new_file, limit)?;
|
||||
if options.verbose {
|
||||
debugging!("Processed meta file content: {}", &new_meta_file_content);
|
||||
}
|
||||
oss_client.put_file_content(bucket_name, meta_file_key, &new_meta_file_content)?;
|
||||
for rm_file in removed_files {
|
||||
information!("Remove OSS key: {}", &rm_file);
|
||||
oss_client.delete_file(bucket_name, &rm_file)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_new_backup_file(backup_content_json: &str, new_item: &str, limit: usize) -> XResult<(Vec<String>, String)> {
|
||||
let mut removed_vec: Vec<String> = vec![];
|
||||
let mut parsed_vec = parse_json_array(backup_content_json)?;
|
||||
while parsed_vec.len() + 1 > limit {
|
||||
@@ -94,12 +299,11 @@ pub fn process_new_backup_file(backup_content_json: &str, new_item: &str, limit:
|
||||
}
|
||||
parsed_vec.push(new_item.to_string());
|
||||
|
||||
let stringifyed_json = stringity_json_array(&parsed_vec)?;
|
||||
|
||||
Ok((removed_vec, stringifyed_json.to_string()))
|
||||
Ok((removed_vec, stringify_json_array(&parsed_vec)?))
|
||||
}
|
||||
|
||||
pub fn stringity_json_array(vec: &Vec<String>) -> XResult<String> {
|
||||
// stringify JSON array
|
||||
fn stringify_json_array(vec: &[String]) -> XResult<String> {
|
||||
let mut json_arr = json::JsonValue::new_array();
|
||||
for v in vec {
|
||||
json_arr.push(json::JsonValue::from(v.as_str()))?;
|
||||
@@ -107,16 +311,16 @@ pub fn stringity_json_array(vec: &Vec<String>) -> XResult<String> {
|
||||
Ok(json::stringify_pretty(json_arr, 4))
|
||||
}
|
||||
|
||||
pub fn parse_json_array(arr: &str) -> XResult<Vec<String>> {
|
||||
// parse JSON array
|
||||
fn parse_json_array(arr: &str) -> XResult<Vec<String>> {
|
||||
let mut vec: Vec<String> = vec![];
|
||||
if arr != "" {
|
||||
let json_arr = &json::parse(&arr)?;
|
||||
if !arr.is_empty() {
|
||||
let json_arr = &json::parse(arr)?;
|
||||
if json_arr.is_array() {
|
||||
for a in json_arr.members() {
|
||||
match a.as_str() {
|
||||
None => (),
|
||||
Some(s) => vec.push(s.to_string()),
|
||||
};
|
||||
if let Some(s) = a.as_str() {
|
||||
vec.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use rust_util::XResult;
|
||||
use argparse::{ArgumentParser, StoreTrue, Store};
|
||||
use argparse::{ ArgumentParser, Store, StoreTrue };
|
||||
|
||||
const EMPTY: &str = "";
|
||||
|
||||
pub struct Options {
|
||||
pub version: bool,
|
||||
@@ -12,7 +14,7 @@ impl Options {
|
||||
Options {
|
||||
version: false,
|
||||
verbose: false,
|
||||
config: "".to_string(),
|
||||
config: EMPTY.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
138
src/oss_util.rs
138
src/oss_util.rs
@@ -1,54 +1,53 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
time::SystemTime,
|
||||
};
|
||||
use crypto::{
|
||||
mac::{
|
||||
Mac,
|
||||
MacResult,
|
||||
},
|
||||
hmac::Hmac,
|
||||
sha1::Sha1,
|
||||
};
|
||||
use reqwest::{
|
||||
Response,
|
||||
};
|
||||
use rust_util::*;
|
||||
use std::fs::File;
|
||||
use base64::Engine;
|
||||
use sha1::Sha1;
|
||||
use hmac::{ Hmac, Mac };
|
||||
use reqwest::blocking::{Client, Response};
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use rust_util::{ XResult, new_box_ioerror, util_time::get_current_secs };
|
||||
|
||||
pub const OSS_VERB_GET: &str = "GET";
|
||||
pub const OSS_VERB_PUT: &str = "PUT";
|
||||
pub const DEFAULT_URL_VALID_IN_SECS: u64 = 1000;
|
||||
|
||||
pub const OSS_VERB_GET: &str = "GET";
|
||||
pub const OSS_VERB_PUT: &str = "PUT";
|
||||
pub const OSS_VERB_DELETE: &str = "DELETE";
|
||||
|
||||
const HTTP_SS: &str = "http://";
|
||||
const HTTPS_SS: &str = "https://";
|
||||
|
||||
const INTERNAL_DEFAULT_VALID_IN_SECS: u64 = 30;
|
||||
|
||||
// https://help.aliyun.com/document_detail/31952.html
|
||||
pub struct OSSClient<'a> {
|
||||
pub endpoint: &'a str,
|
||||
pub access_key_id: &'a str,
|
||||
pub access_key_secret: &'a str,
|
||||
pub struct OSSClient {
|
||||
pub endpoint: String,
|
||||
pub access_key_id: String,
|
||||
pub access_key_secret: String,
|
||||
}
|
||||
|
||||
impl<'a> OSSClient<'a> {
|
||||
pub fn new(endpoint: &'a str, access_key_id: &'a str, access_key_secret: &'a str) -> OSSClient<'a> {
|
||||
impl OSSClient {
|
||||
pub fn new(endpoint: &str, access_key_id: &str, access_key_secret: &str) -> OSSClient {
|
||||
OSSClient {
|
||||
endpoint: endpoint,
|
||||
access_key_id: access_key_id,
|
||||
access_key_secret: access_key_secret,
|
||||
endpoint: endpoint.into(),
|
||||
access_key_id: access_key_id.into(),
|
||||
access_key_secret: access_key_secret.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_file(&self, bucket_name: &str, key: &str, expire_in_seconds: u64, file: File) -> XResult<Response> {
|
||||
let client = reqwest::Client::new();
|
||||
Ok(client.put(&self.generate_signed_put_url(bucket_name, key, expire_in_seconds)).body(file).send()?)
|
||||
let client = Client::new();
|
||||
Ok(client.put(self.generate_signed_put_url(bucket_name, key, expire_in_seconds)).body(file).send()?)
|
||||
}
|
||||
|
||||
pub fn delete_file(&self, bucket_name: &str, key: &str) -> XResult<Response> {
|
||||
let delete_url = self.generate_signed_delete_url(bucket_name, key, 30_u64);
|
||||
let client = reqwest::Client::new();
|
||||
let delete_url = self.generate_signed_delete_url(bucket_name, key, INTERNAL_DEFAULT_VALID_IN_SECS);
|
||||
let client = Client::new();
|
||||
Ok(client.delete(&delete_url).send()?)
|
||||
}
|
||||
|
||||
pub fn get_file_content(&self, bucket_name: &str, key: &str) -> XResult<Option<String>> {
|
||||
let get_url = self.generate_signed_get_url(bucket_name, key, 30_u64);
|
||||
let mut response = reqwest::get(&get_url)?;
|
||||
let get_url = self.generate_signed_get_url(bucket_name, key, INTERNAL_DEFAULT_VALID_IN_SECS);
|
||||
let client = Client::new();
|
||||
let response = client.get(&get_url).send()?;
|
||||
match response.status().as_u16() {
|
||||
404_u16 => Ok(None),
|
||||
200_u16 => Ok(Some(response.text()?)),
|
||||
@@ -57,8 +56,8 @@ impl<'a> OSSClient<'a> {
|
||||
}
|
||||
|
||||
pub fn put_file_content(&self, bucket_name: &str, key: &str, content: &str) -> XResult<Response> {
|
||||
let put_url = self.generate_signed_put_url(bucket_name, key, 30_u64);
|
||||
let client = reqwest::Client::new();
|
||||
let put_url = self.generate_signed_put_url(bucket_name, key, INTERNAL_DEFAULT_VALID_IN_SECS);
|
||||
let client = Client::new();
|
||||
Ok(client.put(&put_url).body(content.as_bytes().to_vec()).send()?)
|
||||
}
|
||||
|
||||
@@ -76,54 +75,49 @@ impl<'a> OSSClient<'a> {
|
||||
|
||||
pub fn generate_signed_url(&self, verb: &str, bucket_name: &str, key: &str, expire_in_seconds: u64, is_https: bool) -> String {
|
||||
let mut signed_url = String::with_capacity(1024);
|
||||
signed_url.push_str(if is_https { "https://" } else { "http://" });
|
||||
signed_url.push_str(&format!("{}.{}/{}", bucket_name, self.endpoint, key));
|
||||
|
||||
signed_url.push_str(iff!(is_https, HTTPS_SS, HTTP_SS));
|
||||
|
||||
let endpoint = &remove_endpoint_http_or_s(&self.endpoint);
|
||||
signed_url.push_str(&format!("{}.{}/{}", bucket_name, endpoint, key));
|
||||
|
||||
let current_secs = get_current_secs();
|
||||
let expire_secs = current_secs + expire_in_seconds;
|
||||
|
||||
signed_url.push_str("?Expires=");
|
||||
signed_url.push_str(expire_secs.to_string().as_str());
|
||||
signed_url.push_str("&OSSAccessKeyId=");
|
||||
signed_url.push_str(&urlencoding::encode(self.access_key_id));
|
||||
signed_url.push_str("&Signature=");
|
||||
|
||||
|
||||
signed_url.push_str(&format!("?Expires={}", &expire_secs.to_string()));
|
||||
signed_url.push_str(&format!("&OSSAccessKeyId={}", &urlencoding::encode(&self.access_key_id)));
|
||||
|
||||
let to_be_signed = get_to_be_signed(verb, expire_secs, bucket_name, key);
|
||||
let signature = to_base64(calc_hmac_sha1(self.access_key_secret.as_bytes(), to_be_signed.as_bytes()));
|
||||
signed_url.push_str(&urlencoding::encode(signature.as_str()));
|
||||
|
||||
let signature = calc_hmac_sha1_as_base64(self.access_key_secret.as_bytes(), to_be_signed.as_bytes());
|
||||
signed_url.push_str(&format!("&Signature={}", &urlencoding::encode(&signature)));
|
||||
|
||||
signed_url
|
||||
}
|
||||
}
|
||||
|
||||
// https://endpoint, or http://endpoint -> endpoint
|
||||
fn remove_endpoint_http_or_s(endpoint: &str) -> String {
|
||||
let mut endpoint = endpoint.to_owned();
|
||||
for prefix in &[HTTP_SS, HTTPS_SS] {
|
||||
if endpoint.starts_with(prefix) {
|
||||
endpoint = endpoint.chars().skip(prefix.chars().count()).collect::<String>()
|
||||
}
|
||||
}
|
||||
endpoint
|
||||
}
|
||||
|
||||
fn get_to_be_signed(verb: &str, expire_secs: u64, bucket_name: &str, key: &str) -> String {
|
||||
let mut to_be_signed = String::with_capacity(512);
|
||||
to_be_signed.push_str(verb);
|
||||
to_be_signed.push_str("\n");
|
||||
to_be_signed.push_str("\n");
|
||||
to_be_signed.push_str("\n");
|
||||
to_be_signed.push_str(expire_secs.to_string().as_str());
|
||||
to_be_signed.push_str("\n");
|
||||
to_be_signed.push_str("/");
|
||||
to_be_signed.push_str(bucket_name);
|
||||
to_be_signed.push_str("/");
|
||||
to_be_signed.push_str(key);
|
||||
to_be_signed.push_str("\n\n\n");
|
||||
to_be_signed.push_str(&expire_secs.to_string());
|
||||
to_be_signed.push('\n');
|
||||
to_be_signed.push_str(&format!("/{}/{}", bucket_name, key));
|
||||
to_be_signed
|
||||
}
|
||||
|
||||
fn to_base64(mac_result: MacResult) -> String {
|
||||
base64::encode(mac_result.code())
|
||||
fn calc_hmac_sha1_as_base64(key: &[u8], message: &[u8]) -> String {
|
||||
Hmac::<Sha1>::new_from_slice(key).map(|mut mac| {
|
||||
mac.update(message);
|
||||
STANDARD.encode(mac.finalize().into_bytes().as_slice())
|
||||
}).unwrap_or_else(|e| format!("[ERROR]Hmac error: {}", e))
|
||||
}
|
||||
|
||||
fn calc_hmac_sha1(key: &[u8], message: &[u8]) -> MacResult {
|
||||
let mut hmac = Hmac::new(Sha1::new(), key);
|
||||
hmac.input(message);
|
||||
hmac.result()
|
||||
}
|
||||
|
||||
fn get_current_secs() -> u64 {
|
||||
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()
|
||||
}
|
||||
|
||||
// use sync meta.txt file ?
|
||||
// record valid files ...
|
||||
|
||||
@@ -3,51 +3,41 @@ use crate::openpgp::armor;
|
||||
use std::{
|
||||
fs::File,
|
||||
path::Path,
|
||||
io::{
|
||||
ErrorKind,
|
||||
Read,
|
||||
Write,
|
||||
BufWriter,
|
||||
},
|
||||
};
|
||||
use rust_util::{
|
||||
XResult,
|
||||
new_box_error,
|
||||
io::{ ErrorKind, Read, Write, BufWriter },
|
||||
};
|
||||
use rust_util::{ XResult, new_box_error };
|
||||
use openpgp::{
|
||||
types::KeyFlags,
|
||||
TPK,
|
||||
Cert,
|
||||
parse::Parse,
|
||||
types::KeyFlags,
|
||||
serialize::stream::{
|
||||
Recipient,
|
||||
Message,
|
||||
Encryptor,
|
||||
Encryptor2,
|
||||
LiteralWriter,
|
||||
},
|
||||
policy::StandardPolicy as P,
|
||||
};
|
||||
use indicatif::{
|
||||
ProgressBar,
|
||||
ProgressStyle
|
||||
};
|
||||
use indicatif::{ ProgressBar, ProgressStyle };
|
||||
|
||||
const BUFF_SIZE: usize = 512 * 1024;
|
||||
const PB_PROGRESS: &str = "#-";
|
||||
const PB_TEMPLATE: &str = "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})";
|
||||
|
||||
pub struct OpenPGPTool {
|
||||
pub tpk: TPK,
|
||||
pub cert: Cert,
|
||||
}
|
||||
|
||||
impl OpenPGPTool {
|
||||
pub fn from_file(file: &str) -> XResult<OpenPGPTool> {
|
||||
Ok(OpenPGPTool{
|
||||
tpk: TPK::from_file(std::path::Path::new(file))?,
|
||||
cert: Cert::from_file(Path::new(file))?,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_bytes(bs: &[u8]) -> XResult<OpenPGPTool> {
|
||||
Ok(OpenPGPTool{
|
||||
tpk: TPK::from_bytes(&bs)?,
|
||||
cert: Cert::from_bytes(&bs)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,20 +49,23 @@ impl OpenPGPTool {
|
||||
return Err(new_box_error(&format!("To file exists: {}", to_file)));
|
||||
}
|
||||
|
||||
let recipient: Recipient = match self.tpk.keys_valid()
|
||||
.key_flags(KeyFlags::default().set_encrypt_at_rest(true).set_encrypt_for_transport(true))
|
||||
.map(|(_, _, key)| key.into())
|
||||
.nth(0) {
|
||||
None => return Err(new_box_error("Encryption key not found in TPK")),
|
||||
Some(r) => r,
|
||||
};
|
||||
// https://gitlab.com/sequoia-pgp/sequoia/-/blob/master/openpgp/examples/encrypt-for.rs
|
||||
let p = &P::new();
|
||||
let mode = KeyFlags::empty().set_storage_encryption();
|
||||
let recipients = self.cert.keys()
|
||||
.with_policy(p, None).alive().revoked(false).key_flags(&mode)
|
||||
.map(|ka| ka.key())
|
||||
.collect::<Vec<_>>();
|
||||
if recipients.is_empty() {
|
||||
return Err(new_box_error("Cannot find any encrypt key in pgp key file."));
|
||||
}
|
||||
let bw = BufWriter::new(File::create(to_file)?);
|
||||
let message = if armor {
|
||||
Message::new(armor::Writer::new(bw, armor::Kind::Message, &[])?)
|
||||
Message::new(armor::Writer::new(bw, armor::Kind::Message)?)
|
||||
} else {
|
||||
Message::new(bw)
|
||||
};
|
||||
let encryptor = Encryptor::for_recipient(message, recipient).build()?;
|
||||
let encryptor = Encryptor2::for_recipients(message, recipients).build()?;
|
||||
let mut pgp_encrypt_writer = LiteralWriter::new(encryptor).build()?;
|
||||
let mut from = File::open(from_file)?;
|
||||
encrypt_read_write(&mut from, &mut pgp_encrypt_writer)?;
|
||||
@@ -88,7 +81,7 @@ fn encrypt_read_write(file: &mut File, write: &mut dyn Write) -> XResult<()> {
|
||||
let mut read = 0_u64;
|
||||
|
||||
let pb = ProgressBar::new(file_len);
|
||||
pb.set_style(ProgressStyle::default_bar().template(PB_TEMPLATE).progress_chars(PB_PROGRESS));
|
||||
pb.set_style(ProgressStyle::default_bar().template(PB_TEMPLATE)?.progress_chars(PB_PROGRESS));
|
||||
|
||||
loop {
|
||||
let len = match file.read(&mut buf) {
|
||||
@@ -97,7 +90,7 @@ fn encrypt_read_write(file: &mut File, write: &mut dyn Write) -> XResult<()> {
|
||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||
Err(e) => return Err(Box::new(e)),
|
||||
};
|
||||
write.write(&buf[..len])?;
|
||||
write.write_all(&buf[..len])?;
|
||||
read += len as u64;
|
||||
pb.set_position(read);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fs::File,
|
||||
path::Path,
|
||||
io::{
|
||||
BufWriter,
|
||||
},
|
||||
io::BufWriter,
|
||||
};
|
||||
use zip::{
|
||||
CompressionMethod,
|
||||
write::{
|
||||
ZipWriter,
|
||||
FileOptions,
|
||||
},
|
||||
write::{ SimpleFileOptions, ZipWriter },
|
||||
};
|
||||
use flate2::{ Compression, write::GzEncoder };
|
||||
use rust_util::{
|
||||
XResult,
|
||||
new_box_ioerror,
|
||||
util_msg::*,
|
||||
util_io::*,
|
||||
util_file::*,
|
||||
};
|
||||
|
||||
pub fn compress_file(target: &str, compress_file: &str) -> XResult<()> {
|
||||
match target {
|
||||
target_zip if target_zip.ends_with(".zip") => zip_file(target, compress_file),
|
||||
target_tar if target_tar.ends_with(".tar.gz") => tar_file(target, compress_file),
|
||||
target => Err(new_box_ioerror(&format!("Unknown target type: {}", target))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tar_file(target: &str, tar_file: &str) -> XResult<()> {
|
||||
let target_tar_gz = File::create(target)?;
|
||||
let enc = GzEncoder::new(target_tar_gz, Compression::default());
|
||||
let mut tar = tar::Builder::new(enc);
|
||||
tar.append_dir_all("", tar_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// http://mvdnes.github.io/rust-docs/zip-rs/zip/index.html
|
||||
pub fn zip_file(target: &str, zip_file: &str) -> XResult<()> {
|
||||
if Path::new(zip_file).exists() {
|
||||
@@ -39,26 +49,36 @@ pub fn zip_file(target: &str, zip_file: &str) -> XResult<()> {
|
||||
let bw = BufWriter::new(File::create(zip_file)?);
|
||||
let mut zip = ZipWriter::new(bw);
|
||||
if target_path.is_file() {
|
||||
let options = FileOptions::default().compression_method(CompressionMethod::Stored);
|
||||
let options = SimpleFileOptions::default().compression_method(CompressionMethod::Stored);
|
||||
let zip_fn = get_file_name(target_path);
|
||||
zip.start_file(zip_fn, options)?;
|
||||
copy_io_with_head(&mut File::open(target_path)?, &mut zip, -1, "Compressing")?;
|
||||
let mut print_status_context = PrintStatusContext::default();
|
||||
copy_io_with_head(&mut File::open(target_path)?, &mut zip, -1, "Compressing", &mut print_status_context)?;
|
||||
|
||||
zip.finish()?;
|
||||
} else {
|
||||
let mut_zip = RefCell::new(zip);
|
||||
walk_dir(&target_path, &|p, e| {
|
||||
print_message(MessageType::WARN, &format!("Compress {} failed: {}", &p.display(), &e));
|
||||
walk_dir(target_path, &|p, e| {
|
||||
warning!("Compress {} failed: {}", &p.display(), &e);
|
||||
}, &|f| {
|
||||
let options = FileOptions::default().compression_method(CompressionMethod::Stored);
|
||||
let options = SimpleFileOptions::default().compression_method(CompressionMethod::Stored);
|
||||
let mut m_zip = mut_zip.borrow_mut();
|
||||
match m_zip.start_file("", options) { // TODO filename
|
||||
Ok(_) => (),
|
||||
Err(e) => print_message(MessageType::WARN, &format!("Compress {} failed: {}", &f.display(), &e)),
|
||||
let file_name = get_file_name(f); // TODO file name! add path
|
||||
match m_zip.start_file(&file_name, options) {
|
||||
Ok(_) => match File::open(f) {
|
||||
Err(e) => warning!("Compress {} failed: {}", &f.display(), e),
|
||||
Ok(mut ff) => {
|
||||
let mut print_status_context = PrintStatusContext::default();
|
||||
if let Err(e) = copy_io_with_head(&mut ff, &mut *m_zip, -1, &format!("Compressing {}", &file_name), &mut print_status_context) {
|
||||
warning!("Compress {} failed: {}", &f.display(), e)
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => warning!("Compress {} failed: {}", &f.display(), e),
|
||||
};
|
||||
}, &|_| { true })?;
|
||||
|
||||
mut_zip.borrow_mut().finish()?;
|
||||
mut_zip.into_inner().finish()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -66,10 +86,7 @@ pub fn zip_file(target: &str, zip_file: &str) -> XResult<()> {
|
||||
|
||||
pub fn get_file_name(path: &Path) -> String {
|
||||
match path.file_name() {
|
||||
None => "no_file_name".to_string(),
|
||||
Some(f) => match f.to_os_string().into_string().ok() {
|
||||
None => "unknown_file_name".to_string(),
|
||||
Some(f) => f,
|
||||
},
|
||||
None => "no_file_name".to_owned(),
|
||||
Some(f) => f.to_os_string().into_string().unwrap_or_else(|_| "unknown_file_name".to_owned()),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user