247 lines
9.3 KiB
Rust
247 lines
9.3 KiB
Rust
// #[macro_use]
|
|
// extern crate lazy_static;
|
|
extern crate sequoia_openpgp as openpgp;
|
|
mod oss_util;
|
|
mod pgp_util;
|
|
mod config_util;
|
|
mod zip_util;
|
|
mod opt;
|
|
|
|
use std::{
|
|
time::SystemTime,
|
|
path::Path,
|
|
fs::{
|
|
self,
|
|
File,
|
|
},
|
|
};
|
|
use rust_util::{
|
|
iff,
|
|
XResult,
|
|
util_msg::*,
|
|
};
|
|
use oss_util::*;
|
|
use config_util::*;
|
|
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()?;
|
|
if options.version {
|
|
print_message(MessageType::INFO, "ossbackupd v0.1.0");
|
|
return Ok(());
|
|
}
|
|
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Config is: {}", &options.config));
|
|
}
|
|
|
|
let config_json = match get_config_json(iff!(options.config.is_empty(), None, Some(&options.config)), options.verbose) {
|
|
None => return Ok(()),
|
|
Some(c) => c,
|
|
};
|
|
|
|
let oss_backupd_config = parse_config(&config_json);
|
|
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("OSS backup config: {:?}", &oss_backupd_config));
|
|
}
|
|
|
|
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) {
|
|
print_message(MessageType::ERROR, &format!("Config {} not found, at item index: {}", e, item_index));
|
|
}
|
|
}
|
|
|
|
print_message(MessageType::OK, "Backup all file(s) finished!");
|
|
Ok(())
|
|
}
|
|
|
|
fn process_config_item(options: &Options, config_item: &OSSBackupdConfigItem,
|
|
oss_backupd_config :&OSSBackupdConfig, item_index: usize) -> Result<(), String> {
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Process config item index: {}, config: {:?}", item_index, config_item));
|
|
}
|
|
|
|
let encrypt_pubkey_file = config_item.encrypt_pubkey_file.as_ref().ok_or("encrypt_pubkey_file".to_owned())?;
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Encrypt pubkey file: {}", encrypt_pubkey_file));
|
|
}
|
|
|
|
let target = config_item.target.as_ref().ok_or("target".to_owned())?;
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Target file: {}", iff!(target.is_empty(), "<empty>", target)));
|
|
}
|
|
|
|
let oss_config = config_item.oss_config.as_ref().ok_or("oss_config".to_owned())?;
|
|
|
|
let endpoint = oss_config.endpoint.as_ref().ok_or("oss_config#endpoint".to_owned())?;
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Endpoint: {}", endpoint));
|
|
}
|
|
let access_key_id = oss_config.access_key_id.as_ref().ok_or("oss_config#access_key_id".to_owned())?;
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Access key id: {}", access_key_id));
|
|
}
|
|
let access_key_secret = oss_config.access_key_secret.as_ref().ok_or("oss_config#access_key_secret".to_owned())?;
|
|
|
|
let bucket = &oss_config.bucket.as_ref().ok_or("oss_config#bucket".to_owned())?;
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Bucket: {}", bucket));
|
|
}
|
|
|
|
let path = &match &oss_config.path {
|
|
None => format!("default_path_at_{}", item_index),
|
|
Some(path) => path.to_owned(),
|
|
};
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("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());
|
|
let new_file = format!("{}/{}", path, config_item.make_oss_key(&oss_backupd_config, "zip.gpg"));
|
|
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Backup count: {}", backup_count));
|
|
print_message(MessageType::DEBUG, &format!("Meta file name: {}", meta_file_name));
|
|
print_message(MessageType::DEBUG, &format!("New backup file: {}", new_file));
|
|
}
|
|
|
|
let open_pgp_tool = match OpenPGPTool::from_file(encrypt_pubkey_file) {
|
|
Err(e) => {
|
|
print_message(MessageType::ERROR, &format!("Error in load pgp file: {}, at item index: {}", e, item_index));
|
|
return Ok(());
|
|
},
|
|
Ok(open_pgp_tool) => open_pgp_tool,
|
|
};
|
|
|
|
let secs = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
|
|
let temp_zip_file = &format!("temp_file_{}.zip", secs);
|
|
let temp_pgp_file = &format!("temp_file_{}.gpg", secs);
|
|
|
|
let remove_temp_files = || {
|
|
for f in vec![temp_zip_file, temp_pgp_file] {
|
|
if Path::new(f).is_file() {
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Remove file: {}", f));
|
|
}
|
|
fs::remove_file(f).ok();
|
|
}
|
|
}
|
|
};
|
|
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Compress file: {} -> {}", target, temp_zip_file));
|
|
}
|
|
if let Err(e) = zip_util::zip_file(target, temp_zip_file) {
|
|
print_message(MessageType::ERROR, &format!("Error in zip file: {}, at item index: {}", e, item_index));
|
|
return Ok(());
|
|
};
|
|
|
|
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();
|
|
return Ok(());
|
|
};
|
|
|
|
let file_temp_pgp_file = match File::open(temp_pgp_file) {
|
|
Err(e) => {
|
|
print_message(MessageType::ERROR, &format!("Error in open file: {}, at item index: {}", e, item_index));
|
|
remove_temp_files();
|
|
return Ok(());
|
|
},
|
|
Ok(file_temp_pgp_file) => file_temp_pgp_file,
|
|
};
|
|
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Upload file: {}", temp_pgp_file));
|
|
}
|
|
if let Err(e) = oss_client.put_file(bucket, &new_file, oss_util::DEFAULT_URL_VALID_IN_SECS, file_temp_pgp_file) {
|
|
print_message(MessageType::ERROR, &format!("Error in encrypt file: {}, at item index: {}", e, item_index));
|
|
remove_temp_files();
|
|
return Ok(());
|
|
}
|
|
|
|
if options.verbose {
|
|
print_message(MessageType::DEBUG, &format!("Processing meta file: {}", meta_file_name));
|
|
}
|
|
match process_oss_files(&options, &oss_client, bucket, path, meta_file_name, &new_file, backup_count) {
|
|
Err(e) => print_message(MessageType::ERROR, &format!("Error: {}, at item index: {}", e, item_index)),
|
|
Ok(_) => print_message(MessageType::OK, &format!("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 {
|
|
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_owned(),
|
|
Some(c) => c,
|
|
};
|
|
if options.verbose {
|
|
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 options.verbose {
|
|
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 {
|
|
print_message(MessageType::INFO, &format!("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 {
|
|
removed_vec.push(parsed_vec.remove(0));
|
|
}
|
|
parsed_vec.push(new_item.to_string());
|
|
|
|
let stringifyed_json = stringity_json_array(&parsed_vec)?;
|
|
|
|
Ok((removed_vec, stringifyed_json.to_string()))
|
|
}
|
|
|
|
// stringify JSON array
|
|
fn stringity_json_array(vec: &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()))?;
|
|
}
|
|
Ok(json::stringify_pretty(json_arr, 4))
|
|
}
|
|
|
|
// parse JSON array
|
|
fn parse_json_array(arr: &str) -> XResult<Vec<String>> {
|
|
let mut vec: Vec<String> = vec![];
|
|
if !arr.is_empty() {
|
|
let json_arr = &json::parse(&arr)?;
|
|
if json_arr.is_array() {
|
|
for a in json_arr.members() {
|
|
if let Some(s) = a.as_str() {
|
|
vec.push(s.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(vec)
|
|
}
|