Compare commits

..

74 Commits

Author SHA1 Message Date
17183aaabb feat: --features use_zip 2025-12-23 00:07:51 +08:00
840e222e36 feat: v1.2.2, add features for --version 2025-01-17 00:04:44 +08:00
83b2687388 feat: updates 2025-01-15 00:53:30 +08:00
ef08df173a feat" add jusfile 2025-01-14 23:50:04 +08:00
5a2c42544b feat: v1.2.1, update versions 2025-01-14 23:41:30 +08:00
62eaab264f feat: v1.2.1, update versions 2025-01-14 23:37:52 +08:00
5333dcb91a feat: v1.2.0, add feature encrypt with tiny-encrypt 2024-12-15 17:45:54 +08:00
27e107770e feat: pending add tiny-encrypt encryption method 2024-12-15 16:21:22 +08:00
78dc5a228b style: code style 2020-08-02 23:08:31 +08:00
9bd386a3c7 style: code style 2020-08-02 23:06:21 +08:00
5ecf3335b4 style: code style 2020-08-02 23:04:15 +08:00
a611649834 style: code style 2020-08-02 23:01:53 +08:00
f8c9462539 update cargo lock 2020-05-24 19:35:28 +08:00
e9ab336f07 ref use 2020-05-24 09:43:21 +08:00
427e6addd1 fix crontab task fail 2020-05-10 12:02:59 +08:00
cfa670c0e8 ref true/false 2020-05-02 23:58:24 +08:00
13524bad10 re-format 2020-05-02 23:53:43 +08:00
beed2f16ab use rust_util 0.2.3 2020-05-02 23:49:32 +08:00
30f8202981 ref append_dir_all 2020-04-18 17:17:32 +08:00
784c15c05c add compress_file 2020-04-18 17:14:21 +08:00
de492e1fbd fix spell 2020-04-17 08:05:40 +08:00
00fcac0e52 version 1.0.0 -> 1.1.0 2020-04-13 01:41:43 +08:00
484e7de833 ref return 2020-04-13 00:09:43 +08:00
ab1be53102 add zip -r 2020-04-12 23:59:59 +08:00
136aafe2db add use_zip 2020-04-12 23:55:59 +08:00
d89a426032 add TODO 2020-04-12 23:42:12 +08:00
589b585f38 add swtch for sequoia_openpgp 2020-04-12 23:03:51 +08:00
32aa27ab1a sequoia-openpgp 0.12.0 -> 0.16.0 2020-04-12 22:04:52 +08:00
e16648a218 ref real_make_oss_key 2020-04-12 17:56:00 +08:00
f8b5ebe60a rm space 2020-04-12 17:54:02 +08:00
8ecceb5e11 remove pub for const 2020-04-12 17:53:02 +08:00
54a15f24f7 version 0.1.0 -> 1.0.0 2020-04-12 17:51:38 +08:00
f308c0dae9 update crate version 2020-04-12 15:37:20 +08:00
c4e8a5a699 use calc_hmac_sha1_as_base64 2020-04-12 15:29:13 +08:00
2062140200 ref hmac/signed url 2020-04-12 15:25:58 +08:00
9945bfee8a use ok_or_else 2020-04-12 15:15:12 +08:00
537628221b use get_current_secs 2020-04-12 14:56:23 +08:00
83cfb5489c ref unwrap_or_else 2020-04-12 14:52:13 +08:00
2e70fc3d73 fix clippy 2020-04-12 14:17:42 +08:00
63919d5532 ref 2020-04-12 01:38:15 +08:00
dbb731e3b3 &str 2020-04-12 01:28:02 +08:00
b7c747c565 update use 2020-04-12 01:19:44 +08:00
f1ede0bca7 use num 2020-04-05 20:34:13 +08:00
4b97814932 rm lazy_static 2020-04-04 23:36:54 +08:00
b1a9e6f8f2 use sha-1/hmac 2020-04-04 23:34:42 +08:00
6e116f7003 update README 2020-04-04 22:57:50 +08:00
aca4f43577 update README 2020-04-04 22:56:59 +08:00
05d4907fbd .gpg -> .zip.gpg 2020-04-04 22:53:44 +08:00
ce5e884841 use consts 2020-04-04 22:47:32 +08:00
926d557ab0 update min/max 2020-04-04 22:39:29 +08:00
18bf157d73 opt config_util.rs 2020-04-04 22:30:30 +08:00
6f5bc06217 use match,is_empty 2020-04-04 22:22:20 +08:00
a8bb406fde add backup_count in sample 2020-04-04 21:49:54 +08:00
ee2156a2ca update remove_endpoint_http_or_s 2020-04-04 01:40:49 +08:00
831d66fddb add zip file support 2020-04-04 01:38:54 +08:00
13fff9569b use enumerate 2020-04-03 08:18:16 +08:00
ab3e69997b remove unnessary pub 2020-04-03 08:12:54 +08:00
2f7ff048a7 add fn get_safe_backup_count 2020-04-03 08:03:19 +08:00
d1fda09f2e del files use for loop 2020-04-03 07:56:52 +08:00
53a1b2db29 add backup count configable 2020-04-02 08:20:26 +08:00
8af2c0549b check file 2020-04-02 01:22:19 +08:00
5cde620dd9 opt rm files 2020-04-02 01:19:11 +08:00
4a24773a8c rm sample.gpg 2020-04-02 00:49:34 +08:00
d27b09cc1a use result 2020-04-01 01:59:15 +08:00
75f56cb5e2 add remove http:// or https:// 2020-03-30 01:28:20 +08:00
8578ea2c84 use if let 2020-03-30 01:17:38 +08:00
a6ff4a315c ref code 2020-03-30 01:01:04 +08:00
5f9e71cd7b ref code 2020-03-30 00:53:04 +08:00
86eb80dd73 now can run 2020-03-30 00:48:57 +08:00
9af3cae5a0 add verbose 2020-03-30 00:09:38 +08:00
27205f9922 add zip, pgp and put file 2020-03-29 01:14:29 +08:00
7da74889d3 update crates 2020-03-29 00:16:11 +08:00
209aa3ba6b Merge branch 'master' of git.hatter.ink:hatter/oss-backupd 2019-12-25 01:02:15 +08:00
9dddaef777 add lay_static, debug 2019-12-25 01:02:06 +08:00
13 changed files with 3657 additions and 1909 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
.idea/
sample.gpg
sample-backupd-config.json
# ---> macOS
# General
.DS_Store

4357
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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 }

View File

@@ -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
View File

@@ -0,0 +1,5 @@
_:
@just --list
build-linux-musl-with-zig:
cargo zigbuild --release --target x86_64-unknown-linux-musl --features use_zip

View File

@@ -12,6 +12,10 @@
{
"target": "",
"file_name": "sample_file"
},
{
"target": "",
"file_name": "sample_file_2"
}
]
}

View File

@@ -1,74 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: GPGTools - https://gpgtools.org
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
=HzsD
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -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()
}

View File

@@ -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());
}
}
}
}

View File

@@ -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(),
}
}

View File

@@ -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 ...

View File

@@ -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);
}

View File

@@ -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()),
}
}