Compare commits
72 Commits
209aa3ba6b
...
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 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
.idea/
|
||||||
|
sample.gpg
|
||||||
|
sample-backupd-config.json
|
||||||
# ---> macOS
|
# ---> macOS
|
||||||
# General
|
# General
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
4358
Cargo.lock
generated
4358
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
40
Cargo.toml
@@ -1,25 +1,31 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "oss-backupd"
|
name = "oss-backupd"
|
||||||
version = "0.1.0"
|
version = "1.2.2"
|
||||||
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# 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]
|
[dependencies]
|
||||||
dirs = "2.0.1"
|
dirs = "6.0"
|
||||||
argparse = "0.2.2"
|
argparse = "0.2"
|
||||||
json = "0.11.14"
|
json = "0.12"
|
||||||
rust-crypto = "0.2.36"
|
hmac = "0.12"
|
||||||
indicatif = "0.13.0"
|
sha-1 = "0.10"
|
||||||
urlencoding = "1.0.0"
|
indicatif = "0.17"
|
||||||
base64 = "0.11.0"
|
urlencoding = "2.1"
|
||||||
reqwest = "0.9.22"
|
base64 = "0.22"
|
||||||
sequoia-openpgp = "0.12.0"
|
reqwest = { version = "0.12", features = ["blocking", "native-tls-vendored"] }
|
||||||
oss-rust-sdk = "0.1.12"
|
sequoia-openpgp = { version = "1.22", optional = true }
|
||||||
chrono = "0.4.10"
|
chrono = "0.4"
|
||||||
zip = "0.5.3"
|
zip = "2.2"
|
||||||
lazy_static = "1.3.0"
|
tar = "0.4"
|
||||||
rust_util = { git = "https://github.com/jht5945/rust_util" }
|
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 `~/.oss-backupd-config.json`
|
||||||
1. from `/etc/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>
|
<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": "",
|
"target": "",
|
||||||
"file_name": "sample_file"
|
"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::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
path::Path,
|
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 NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
pub const OSS_BACKUPD_CONFIG: &str = "oss-backupd-config.json";
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
pub const DOT_OSS_BACKUPD_CONFIG: &str = ".oss-backupd-config.json";
|
|
||||||
|
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": "",
|
"host": "",
|
||||||
"encrypt_pubkey_file": "",
|
"encrypt_pubkey_file": "",
|
||||||
|
"backup_count": 10,
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"oss_config": null,
|
"oss_config": null, // OPT @oss_config
|
||||||
"target": "",
|
"target": "",
|
||||||
"file_name": "",
|
"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 file_name: Option<String>,
|
||||||
pub oss_config: Option<OSSConfig>,
|
pub oss_config: Option<OSSConfig>,
|
||||||
pub encrypt_pubkey_file: Option<String>,
|
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)]
|
#[derive(Debug)]
|
||||||
@@ -60,128 +68,184 @@ pub struct OSSBackupdConfig {
|
|||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
pub items: Vec<OSSBackupdConfigItem>,
|
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 {
|
pub fn get_prefix(&self) -> String {
|
||||||
real_make_oss_key(oss_backupd_config, &self, suffix)
|
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 {
|
impl OSSBackupdConfigItem {
|
||||||
let mut key = String::with_capacity(1024);
|
pub fn make_oss_key(&self, oss_backupd_config: &OSSBackupdConfig, suffix: &str) -> String {
|
||||||
key.push_str(&(if oss_backupd_config.prefix.is_some() {
|
real_make_oss_key(oss_backupd_config, self, suffix)
|
||||||
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);
|
|
||||||
|
|
||||||
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.push_str(&format!(".{}", suffix));
|
||||||
}
|
}
|
||||||
|
|
||||||
key
|
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> {
|
fn parse_sub_oss_config(json: &json::JsonValue) -> Option<OSSConfig> {
|
||||||
let root_oss_config = &json["oss_config"];
|
let root_oss_config = &json["oss_config"];
|
||||||
let root_oss_config_object: Option<OSSConfig> = match root_oss_config.is_null() {
|
iff!(
|
||||||
true => None,
|
root_oss_config.is_null(),
|
||||||
false => Some(parse_oss_config(root_oss_config)),
|
None,
|
||||||
};
|
Some(parse_oss_config(root_oss_config))
|
||||||
root_oss_config_object
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_oss_config(oss_config: &json::JsonValue) -> OSSConfig {
|
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];
|
let value = &json[key];
|
||||||
match value.is_string() {
|
value.as_str().map(|s| s.to_owned())
|
||||||
true => Some(value.as_str().unwrap().to_string()),
|
}
|
||||||
false => None,
|
|
||||||
|
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> {
|
fn get_u32_value(json: &JsonValue, key: &str) -> Option<u32> {
|
||||||
let config_content = get_config_content(custom_oss_backupd_config, verbose)?;
|
let value = &json[key];
|
||||||
match json::parse(&config_content) {
|
match value {
|
||||||
Err(e) => {
|
JsonValue::String(v) => v.parse().ok(),
|
||||||
print_message(MessageType::ERROR, &format!("Parse config json failed: {}", e));
|
JsonValue::Number(v) => Some(f64::from(*v) as u32),
|
||||||
None
|
_ => None,
|
||||||
},
|
|
||||||
Ok(o) => Some(o),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config_content(custom_oss_backupd_config: Option<&str>, verbose: bool) -> Option<String> {
|
fn get_config_content(custom_oss_backupd_config: Option<&str>, verbose: bool) -> Option<String> {
|
||||||
if custom_oss_backupd_config.is_some() {
|
if let Some(custom_oss_backupd_config_val) = custom_oss_backupd_config {
|
||||||
let custom_oss_backupd_config_unrwaped = custom_oss_backupd_config.unwrap();
|
if verbose {
|
||||||
if verbose {
|
debugging!("Read config from: {}", custom_oss_backupd_config_val);
|
||||||
print_message(MessageType::DEBUG, &format!("Read config from: {}", custom_oss_backupd_config_unrwaped));
|
|
||||||
}
|
}
|
||||||
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() {
|
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) => {
|
Err(e) => {
|
||||||
print_message(MessageType::ERROR, &format!("Read config file {} error: {}", custom_oss_backupd_config_unrwaped, e));
|
failure!(
|
||||||
return None;
|
"Read config file {} error: {}",
|
||||||
},
|
custom_oss_backupd_config_val,
|
||||||
Ok(o) => return Some(o),
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} 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;
|
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);
|
let oss_backupd_config_path = Path::new(OSS_BACKUPD_CONFIG);
|
||||||
if oss_backupd_config_path.exists() {
|
if oss_backupd_config_path.exists() {
|
||||||
if verbose {
|
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) => {
|
Err(e) => {
|
||||||
print_message(MessageType::ERROR, &format!("Read config file {} error: {}", OSS_BACKUPD_CONFIG, e));
|
failure!("Read config file {} error: {}", OSS_BACKUPD_CONFIG, e);
|
||||||
return None;
|
None
|
||||||
},
|
}
|
||||||
Ok(o) => return Some(o),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let home_dot_oss_backupd_config = & match get_user_home_dir(DOT_OSS_BACKUPD_CONFIG) {
|
let home_dot_oss_backupd_config = &get_user_home_dir(DOT_OSS_BACKUPD_CONFIG).unwrap_or_else(|e| {
|
||||||
Err(e) => {
|
warning!("Get user home error: {}", e);
|
||||||
print_message(MessageType::WARN, &format!("Get user home error: {}", e));
|
String::new()
|
||||||
String::new()
|
});
|
||||||
},
|
if !home_dot_oss_backupd_config.is_empty() {
|
||||||
Ok(o) => o,
|
|
||||||
};
|
|
||||||
if home_dot_oss_backupd_config != "" {
|
|
||||||
let home_dot_oss_backupd_config_path = Path::new(home_dot_oss_backupd_config);
|
let home_dot_oss_backupd_config_path = Path::new(home_dot_oss_backupd_config);
|
||||||
if home_dot_oss_backupd_config_path.exists() {
|
if home_dot_oss_backupd_config_path.exists() {
|
||||||
if verbose {
|
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) => {
|
Err(e) => {
|
||||||
print_message(MessageType::ERROR, &format!("Read config file {} error: {}", home_dot_oss_backupd_config, e));
|
failure!(
|
||||||
return None;
|
"Read config file {} error: {}",
|
||||||
},
|
home_dot_oss_backupd_config,
|
||||||
Ok(o) => return Some(o),
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let etc_oss_backupd_config_path = Path::new(ETC_OSS_BACKUPD_CONFIG);
|
let etc_oss_backupd_config_path = Path::new(ETC_OSS_BACKUPD_CONFIG);
|
||||||
if etc_oss_backupd_config_path.exists() {
|
if etc_oss_backupd_config_path.exists() {
|
||||||
if verbose {
|
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) => {
|
Err(e) => {
|
||||||
print_message(MessageType::ERROR, &format!("Read config file {} error: {}", ETC_OSS_BACKUPD_CONFIG, e));
|
failure!("Read config file {} error: {}", ETC_OSS_BACKUPD_CONFIG, e);
|
||||||
return None;
|
None
|
||||||
},
|
}
|
||||||
Ok(o) => return Some(o),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
print_message(MessageType::ERROR, "Cannot find config file");
|
failure!("Cannot find config file");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_user_home() -> XResult<String> {
|
fn get_user_home() -> XResult<String> {
|
||||||
match dirs::home_dir() {
|
match dirs::home_dir() {
|
||||||
None => Err(new_box_ioerror("Home dir not found!")),
|
None => Err(new_box_ioerror("Home dir not found!")),
|
||||||
Some(home_dir_o) => match home_dir_o.to_str() {
|
Some(home_dir) => home_dir
|
||||||
None => Err(new_box_ioerror("Home dir not found!")),
|
.to_str()
|
||||||
Some(home_dir_str) => Ok(home_dir_str.to_string()),
|
.map(|h| h.to_owned())
|
||||||
},
|
.ok_or_else(|| new_box_ioerror("Home dir not found!")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_user_home_dir(dir: &str) -> XResult<String> {
|
fn get_user_home_dir(dir: &str) -> XResult<String> {
|
||||||
Ok(format!("{}/{}", get_user_home()?, dir))
|
Ok(format!("{}/{}", get_user_home()?, dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_now_ymdhms() -> String {
|
||||||
|
Utc::now().format("%Y%m%d_%H%M%S").to_string()
|
||||||
|
}
|
||||||
|
|||||||
356
src/main.rs
356
src/main.rs
@@ -1,104 +1,297 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate rust_util;
|
||||||
|
#[cfg(feature = "use_sequoia_openpgp")]
|
||||||
extern crate sequoia_openpgp as openpgp;
|
extern crate sequoia_openpgp as openpgp;
|
||||||
pub mod oss_util;
|
|
||||||
pub mod pgp_util;
|
mod oss_util;
|
||||||
pub mod config_util;
|
#[cfg(feature = "use_sequoia_openpgp")]
|
||||||
pub mod zip_util;
|
mod pgp_util;
|
||||||
pub mod opt;
|
mod config_util;
|
||||||
|
#[cfg(not(feature = "use_zip"))]
|
||||||
|
mod zip_util;
|
||||||
|
mod opt;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
time::SystemTime,
|
path::Path,
|
||||||
};
|
fs::{ self, File },
|
||||||
use rust_util::{
|
|
||||||
XResult,
|
|
||||||
util_msg::*,
|
|
||||||
};
|
};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use rust_util::{XResult, util_time::*, util_msg::*};
|
||||||
|
use tiny_encrypt::CmdEncrypt;
|
||||||
use oss_util::*;
|
use oss_util::*;
|
||||||
use config_util::*;
|
use config_util::*;
|
||||||
// use pgp_util::OpenPGPTool;
|
#[cfg(feature = "use_sequoia_openpgp")]
|
||||||
use opt::{
|
use pgp_util::OpenPGPTool;
|
||||||
Options,
|
use opt::Options;
|
||||||
};
|
|
||||||
|
|
||||||
// https://docs.sequoia-pgp.org/sequoia_openpgp/serialize/stream/struct.Encryptor.html
|
// 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
|
// https://gitlab.com/sequoia-pgp/sequoia/blob/master/openpgp/examples/generate-encrypt-decrypt.rs
|
||||||
fn main() -> XResult<()> {
|
fn main() -> XResult<()> {
|
||||||
let options = Options::new_and_parse_args()?;
|
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 {
|
if options.version {
|
||||||
// TODO ...
|
information!("{NAME} v{VERSION}, features: [{}]", features.join(", "));
|
||||||
print_message(MessageType::INFO, "VERSION!!!");
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.verbose {
|
if options.verbose {
|
||||||
print_message(MessageType::DEBUG, &format!("Config is: {}", &options.config));
|
debugging!("Config is: {}", &options.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Hello, world!");
|
let config_json = match get_config_json(iff!(options.config.is_empty(), None, Some(&options.config)), options.verbose) {
|
||||||
println!("{}", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs());
|
Some(c) => c, None => return Ok(()),
|
||||||
|
|
||||||
// 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 oss_backupd_config = parse_config(&config_json);
|
let oss_backupd_config = parse_config(&config_json);
|
||||||
|
|
||||||
println!("{:?}", &oss_backupd_config);
|
if options.verbose {
|
||||||
|
debugging!("OSS backup config: {:?}", &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"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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);
|
||||||
// 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);
|
|
||||||
if *opt::IS_DEBUG {
|
|
||||||
print_message(MessageType::DEBUG, &format!("Read meta file: {}", meta_file_key));
|
|
||||||
}
|
|
||||||
let meta_file_content = match oss_client.get_file_content(bucket_name, meta_file_key)? {
|
|
||||||
None => "[]".to_string(),
|
|
||||||
Some(c) => c,
|
|
||||||
};
|
|
||||||
if *opt::IS_DEBUG {
|
|
||||||
print_message(MessageType::DEBUG, &format!("Read meta file content: {}", &meta_file_content));
|
|
||||||
}
|
|
||||||
let (removed_file, new_meta_file_content) = process_new_backup_file(&meta_file_content, new_file, limit)?;
|
|
||||||
if *opt::IS_DEBUG {
|
|
||||||
print_message(MessageType::DEBUG, &format!("Processed meta file content: {}", &new_meta_file_content));
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
print_message(MessageType::INFO, &format!("Remove OSS key: {}", rm_file_key));
|
|
||||||
oss_client.delete_file(bucket_name, rm_file_key)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
success!("Backup all file(s) finished!");
|
||||||
Ok(())
|
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 removed_vec: Vec<String> = vec![];
|
||||||
let mut parsed_vec = parse_json_array(backup_content_json)?;
|
let mut parsed_vec = parse_json_array(backup_content_json)?;
|
||||||
while parsed_vec.len() + 1 > limit {
|
while parsed_vec.len() + 1 > limit {
|
||||||
@@ -106,12 +299,11 @@ pub fn process_new_backup_file(backup_content_json: &str, new_item: &str, limit:
|
|||||||
}
|
}
|
||||||
parsed_vec.push(new_item.to_string());
|
parsed_vec.push(new_item.to_string());
|
||||||
|
|
||||||
let stringifyed_json = stringity_json_array(&parsed_vec)?;
|
Ok((removed_vec, stringify_json_array(&parsed_vec)?))
|
||||||
|
|
||||||
Ok((removed_vec, stringifyed_json.to_string()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
let mut json_arr = json::JsonValue::new_array();
|
||||||
for v in vec {
|
for v in vec {
|
||||||
json_arr.push(json::JsonValue::from(v.as_str()))?;
|
json_arr.push(json::JsonValue::from(v.as_str()))?;
|
||||||
@@ -119,16 +311,16 @@ pub fn stringity_json_array(vec: &Vec<String>) -> XResult<String> {
|
|||||||
Ok(json::stringify_pretty(json_arr, 4))
|
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![];
|
let mut vec: Vec<String> = vec![];
|
||||||
if arr != "" {
|
if !arr.is_empty() {
|
||||||
let json_arr = &json::parse(&arr)?;
|
let json_arr = &json::parse(arr)?;
|
||||||
if json_arr.is_array() {
|
if json_arr.is_array() {
|
||||||
for a in json_arr.members() {
|
for a in json_arr.members() {
|
||||||
match a.as_str() {
|
if let Some(s) = a.as_str() {
|
||||||
None => (),
|
vec.push(s.to_string());
|
||||||
Some(s) => vec.push(s.to_string()),
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/opt.rs
13
src/opt.rs
@@ -1,12 +1,7 @@
|
|||||||
use rust_util::{
|
use rust_util::XResult;
|
||||||
XResult,
|
use argparse::{ ArgumentParser, Store, StoreTrue };
|
||||||
util_env::is_env_on,
|
|
||||||
};
|
|
||||||
use argparse::{ArgumentParser, StoreTrue, Store};
|
|
||||||
|
|
||||||
lazy_static! {
|
const EMPTY: &str = "";
|
||||||
pub static ref IS_DEBUG: bool = is_env_on("DEBUG");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub version: bool,
|
pub version: bool,
|
||||||
@@ -19,7 +14,7 @@ impl Options {
|
|||||||
Options {
|
Options {
|
||||||
version: false,
|
version: false,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
config: "".to_string(),
|
config: EMPTY.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
130
src/oss_util.rs
130
src/oss_util.rs
@@ -1,54 +1,53 @@
|
|||||||
use std::{
|
use std::fs::File;
|
||||||
fs::File,
|
use base64::Engine;
|
||||||
time::SystemTime,
|
use sha1::Sha1;
|
||||||
};
|
use hmac::{ Hmac, Mac };
|
||||||
use crypto::{
|
use reqwest::blocking::{Client, Response};
|
||||||
mac::{
|
use base64::engine::general_purpose::STANDARD;
|
||||||
Mac,
|
use rust_util::{ XResult, new_box_ioerror, util_time::get_current_secs };
|
||||||
MacResult,
|
|
||||||
},
|
|
||||||
hmac::Hmac,
|
|
||||||
sha1::Sha1,
|
|
||||||
};
|
|
||||||
use reqwest::{
|
|
||||||
Response,
|
|
||||||
};
|
|
||||||
use rust_util::*;
|
|
||||||
|
|
||||||
pub const OSS_VERB_GET: &str = "GET";
|
pub const DEFAULT_URL_VALID_IN_SECS: u64 = 1000;
|
||||||
pub const OSS_VERB_PUT: &str = "PUT";
|
|
||||||
|
pub const OSS_VERB_GET: &str = "GET";
|
||||||
|
pub const OSS_VERB_PUT: &str = "PUT";
|
||||||
pub const OSS_VERB_DELETE: &str = "DELETE";
|
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
|
// https://help.aliyun.com/document_detail/31952.html
|
||||||
pub struct OSSClient<'a> {
|
pub struct OSSClient {
|
||||||
pub endpoint: &'a str,
|
pub endpoint: String,
|
||||||
pub access_key_id: &'a str,
|
pub access_key_id: String,
|
||||||
pub access_key_secret: &'a str,
|
pub access_key_secret: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> OSSClient<'a> {
|
impl OSSClient {
|
||||||
pub fn new(endpoint: &'a str, access_key_id: &'a str, access_key_secret: &'a str) -> OSSClient<'a> {
|
pub fn new(endpoint: &str, access_key_id: &str, access_key_secret: &str) -> OSSClient {
|
||||||
OSSClient {
|
OSSClient {
|
||||||
endpoint: endpoint,
|
endpoint: endpoint.into(),
|
||||||
access_key_id: access_key_id,
|
access_key_id: access_key_id.into(),
|
||||||
access_key_secret: access_key_secret,
|
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> {
|
pub fn put_file(&self, bucket_name: &str, key: &str, expire_in_seconds: u64, file: File) -> XResult<Response> {
|
||||||
let client = reqwest::Client::new();
|
let client = Client::new();
|
||||||
Ok(client.put(&self.generate_signed_put_url(bucket_name, key, expire_in_seconds)).body(file).send()?)
|
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> {
|
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 delete_url = self.generate_signed_delete_url(bucket_name, key, INTERNAL_DEFAULT_VALID_IN_SECS);
|
||||||
let client = reqwest::Client::new();
|
let client = Client::new();
|
||||||
Ok(client.delete(&delete_url).send()?)
|
Ok(client.delete(&delete_url).send()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_file_content(&self, bucket_name: &str, key: &str) -> XResult<Option<String>> {
|
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 get_url = self.generate_signed_get_url(bucket_name, key, INTERNAL_DEFAULT_VALID_IN_SECS);
|
||||||
let mut response = reqwest::get(&get_url)?;
|
let client = Client::new();
|
||||||
|
let response = client.get(&get_url).send()?;
|
||||||
match response.status().as_u16() {
|
match response.status().as_u16() {
|
||||||
404_u16 => Ok(None),
|
404_u16 => Ok(None),
|
||||||
200_u16 => Ok(Some(response.text()?)),
|
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> {
|
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 put_url = self.generate_signed_put_url(bucket_name, key, INTERNAL_DEFAULT_VALID_IN_SECS);
|
||||||
let client = reqwest::Client::new();
|
let client = Client::new();
|
||||||
Ok(client.put(&put_url).body(content.as_bytes().to_vec()).send()?)
|
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 {
|
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);
|
let mut signed_url = String::with_capacity(1024);
|
||||||
signed_url.push_str(if is_https { "https://" } else { "http://" });
|
signed_url.push_str(iff!(is_https, HTTPS_SS, HTTP_SS));
|
||||||
signed_url.push_str(&format!("{}.{}/{}", bucket_name, self.endpoint, key));
|
|
||||||
|
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 current_secs = get_current_secs();
|
||||||
let expire_secs = current_secs + expire_in_seconds;
|
let expire_secs = current_secs + expire_in_seconds;
|
||||||
|
|
||||||
signed_url.push_str("?Expires=");
|
signed_url.push_str(&format!("?Expires={}", &expire_secs.to_string()));
|
||||||
signed_url.push_str(expire_secs.to_string().as_str());
|
signed_url.push_str(&format!("&OSSAccessKeyId={}", &urlencoding::encode(&self.access_key_id)));
|
||||||
signed_url.push_str("&OSSAccessKeyId=");
|
|
||||||
signed_url.push_str(&urlencoding::encode(self.access_key_id));
|
|
||||||
signed_url.push_str("&Signature=");
|
|
||||||
|
|
||||||
let to_be_signed = get_to_be_signed(verb, expire_secs, bucket_name, key);
|
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()));
|
let signature = calc_hmac_sha1_as_base64(self.access_key_secret.as_bytes(), to_be_signed.as_bytes());
|
||||||
signed_url.push_str(&urlencoding::encode(signature.as_str()));
|
signed_url.push_str(&format!("&Signature={}", &urlencoding::encode(&signature)));
|
||||||
|
|
||||||
signed_url
|
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 {
|
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);
|
let mut to_be_signed = String::with_capacity(512);
|
||||||
to_be_signed.push_str(verb);
|
to_be_signed.push_str(verb);
|
||||||
to_be_signed.push_str("\n");
|
to_be_signed.push_str("\n\n\n");
|
||||||
to_be_signed.push_str("\n");
|
to_be_signed.push_str(&expire_secs.to_string());
|
||||||
to_be_signed.push_str("\n");
|
to_be_signed.push('\n');
|
||||||
to_be_signed.push_str(expire_secs.to_string().as_str());
|
to_be_signed.push_str(&format!("/{}/{}", bucket_name, key));
|
||||||
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
|
to_be_signed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_base64(mac_result: MacResult) -> String {
|
fn calc_hmac_sha1_as_base64(key: &[u8], message: &[u8]) -> String {
|
||||||
base64::encode(mac_result.code())
|
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::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
path::Path,
|
path::Path,
|
||||||
io::{
|
io::{ ErrorKind, Read, Write, BufWriter },
|
||||||
ErrorKind,
|
|
||||||
Read,
|
|
||||||
Write,
|
|
||||||
BufWriter,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use rust_util::{
|
|
||||||
XResult,
|
|
||||||
new_box_error,
|
|
||||||
};
|
};
|
||||||
|
use rust_util::{ XResult, new_box_error };
|
||||||
use openpgp::{
|
use openpgp::{
|
||||||
types::KeyFlags,
|
Cert,
|
||||||
TPK,
|
|
||||||
parse::Parse,
|
parse::Parse,
|
||||||
|
types::KeyFlags,
|
||||||
serialize::stream::{
|
serialize::stream::{
|
||||||
Recipient,
|
|
||||||
Message,
|
Message,
|
||||||
Encryptor,
|
Encryptor2,
|
||||||
LiteralWriter,
|
LiteralWriter,
|
||||||
},
|
},
|
||||||
|
policy::StandardPolicy as P,
|
||||||
};
|
};
|
||||||
use indicatif::{
|
use indicatif::{ ProgressBar, ProgressStyle };
|
||||||
ProgressBar,
|
|
||||||
ProgressStyle
|
|
||||||
};
|
|
||||||
|
|
||||||
const BUFF_SIZE: usize = 512 * 1024;
|
const BUFF_SIZE: usize = 512 * 1024;
|
||||||
const PB_PROGRESS: &str = "#-";
|
const PB_PROGRESS: &str = "#-";
|
||||||
const PB_TEMPLATE: &str = "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})";
|
const PB_TEMPLATE: &str = "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})";
|
||||||
|
|
||||||
pub struct OpenPGPTool {
|
pub struct OpenPGPTool {
|
||||||
pub tpk: TPK,
|
pub cert: Cert,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenPGPTool {
|
impl OpenPGPTool {
|
||||||
pub fn from_file(file: &str) -> XResult<OpenPGPTool> {
|
pub fn from_file(file: &str) -> XResult<OpenPGPTool> {
|
||||||
Ok(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> {
|
pub fn from_bytes(bs: &[u8]) -> XResult<OpenPGPTool> {
|
||||||
Ok(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)));
|
return Err(new_box_error(&format!("To file exists: {}", to_file)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let recipient: Recipient = match self.tpk.keys_valid()
|
// https://gitlab.com/sequoia-pgp/sequoia/-/blob/master/openpgp/examples/encrypt-for.rs
|
||||||
.key_flags(KeyFlags::default().set_encrypt_at_rest(true).set_encrypt_for_transport(true))
|
let p = &P::new();
|
||||||
.map(|(_, _, key)| key.into())
|
let mode = KeyFlags::empty().set_storage_encryption();
|
||||||
.nth(0) {
|
let recipients = self.cert.keys()
|
||||||
None => return Err(new_box_error("Encryption key not found in TPK")),
|
.with_policy(p, None).alive().revoked(false).key_flags(&mode)
|
||||||
Some(r) => r,
|
.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 bw = BufWriter::new(File::create(to_file)?);
|
||||||
let message = if armor {
|
let message = if armor {
|
||||||
Message::new(armor::Writer::new(bw, armor::Kind::Message, &[])?)
|
Message::new(armor::Writer::new(bw, armor::Kind::Message)?)
|
||||||
} else {
|
} else {
|
||||||
Message::new(bw)
|
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 pgp_encrypt_writer = LiteralWriter::new(encryptor).build()?;
|
||||||
let mut from = File::open(from_file)?;
|
let mut from = File::open(from_file)?;
|
||||||
encrypt_read_write(&mut from, &mut pgp_encrypt_writer)?;
|
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 mut read = 0_u64;
|
||||||
|
|
||||||
let pb = ProgressBar::new(file_len);
|
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 {
|
loop {
|
||||||
let len = match file.read(&mut buf) {
|
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(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||||
Err(e) => return Err(Box::new(e)),
|
Err(e) => return Err(Box::new(e)),
|
||||||
};
|
};
|
||||||
write.write(&buf[..len])?;
|
write.write_all(&buf[..len])?;
|
||||||
read += len as u64;
|
read += len as u64;
|
||||||
pb.set_position(read);
|
pb.set_position(read);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,37 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
fs::File,
|
fs::File,
|
||||||
path::Path,
|
path::Path,
|
||||||
io::{
|
io::BufWriter,
|
||||||
BufWriter,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use zip::{
|
use zip::{
|
||||||
CompressionMethod,
|
CompressionMethod,
|
||||||
write::{
|
write::{ SimpleFileOptions, ZipWriter },
|
||||||
ZipWriter,
|
|
||||||
FileOptions,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
use flate2::{ Compression, write::GzEncoder };
|
||||||
use rust_util::{
|
use rust_util::{
|
||||||
XResult,
|
XResult,
|
||||||
new_box_ioerror,
|
new_box_ioerror,
|
||||||
util_msg::*,
|
|
||||||
util_io::*,
|
util_io::*,
|
||||||
util_file::*,
|
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
|
// http://mvdnes.github.io/rust-docs/zip-rs/zip/index.html
|
||||||
pub fn zip_file(target: &str, zip_file: &str) -> XResult<()> {
|
pub fn zip_file(target: &str, zip_file: &str) -> XResult<()> {
|
||||||
if Path::new(zip_file).exists() {
|
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 bw = BufWriter::new(File::create(zip_file)?);
|
||||||
let mut zip = ZipWriter::new(bw);
|
let mut zip = ZipWriter::new(bw);
|
||||||
if target_path.is_file() {
|
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);
|
let zip_fn = get_file_name(target_path);
|
||||||
zip.start_file(zip_fn, options)?;
|
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()?;
|
zip.finish()?;
|
||||||
} else {
|
} else {
|
||||||
let mut_zip = RefCell::new(zip);
|
let mut_zip = RefCell::new(zip);
|
||||||
walk_dir(&target_path, &|p, e| {
|
walk_dir(target_path, &|p, e| {
|
||||||
print_message(MessageType::WARN, &format!("Compress {} failed: {}", &p.display(), &e));
|
warning!("Compress {} failed: {}", &p.display(), &e);
|
||||||
}, &|f| {
|
}, &|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();
|
let mut m_zip = mut_zip.borrow_mut();
|
||||||
match m_zip.start_file("", options) { // TODO filename
|
let file_name = get_file_name(f); // TODO file name! add path
|
||||||
Ok(_) => (),
|
match m_zip.start_file(&file_name, options) {
|
||||||
Err(e) => print_message(MessageType::WARN, &format!("Compress {} failed: {}", &f.display(), &e)),
|
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 })?;
|
}, &|_| { true })?;
|
||||||
|
|
||||||
mut_zip.borrow_mut().finish()?;
|
mut_zip.into_inner().finish()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -66,10 +86,7 @@ pub fn zip_file(target: &str, zip_file: &str) -> XResult<()> {
|
|||||||
|
|
||||||
pub fn get_file_name(path: &Path) -> String {
|
pub fn get_file_name(path: &Path) -> String {
|
||||||
match path.file_name() {
|
match path.file_name() {
|
||||||
None => "no_file_name".to_string(),
|
None => "no_file_name".to_owned(),
|
||||||
Some(f) => match f.to_os_string().into_string().ok() {
|
Some(f) => f.to_os_string().into_string().unwrap_or_else(|_| "unknown_file_name".to_owned()),
|
||||||
None => "unknown_file_name".to_string(),
|
|
||||||
Some(f) => f,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user