#[macro_use] extern crate lazy_static; 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; use std::{ fs::{self, File}, time::SystemTime, }; use rust_util::{ XResult, util_msg::*, iff, }; 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)); } let mut item_index = -1; for config_item in &oss_backupd_config.items { item_index += 1; if options.verbose { print_message(MessageType::DEBUG, &format!("Process config item index: {}, config: {:?}", item_index, config_item)); } let encrypt_pubkey_file = match &config_item.encrypt_pubkey_file { None => { print_message(MessageType::ERROR, &format!("Config encrypt_pubkey_file not found, at item index: {}", item_index)); continue; }, Some(encrypt_pubkey_file) => encrypt_pubkey_file, }; if options.verbose { print_message(MessageType::DEBUG, &format!("Encrypt pubkey file: {}", encrypt_pubkey_file)); } let target = match &config_item.target { None => { print_message(MessageType::ERROR, &format!("Config target not found, at item index: {}", item_index)); continue; }, Some(target) => target, }; if options.verbose { print_message(MessageType::DEBUG, &format!("Target file: {}", iff!(target.is_empty(), "", target))); } let oss_config = match &config_item.oss_config { None => { print_message(MessageType::ERROR, &format!("Config oss_config not found, at item index: {}", item_index)); continue; }, Some(oss_config) => oss_config, }; let endpoint = match &oss_config.endpoint { None => { print_message(MessageType::ERROR, &format!("Config oss_config#endpoint not found, at item index: {}", item_index)); continue; } Some(endpoint) => endpoint, }; if options.verbose { print_message(MessageType::DEBUG, &format!("Endpoint: {}", endpoint)); } let access_key_id = match &oss_config.access_key_id { None => { print_message(MessageType::ERROR, &format!("Config oss_config#access_key_id not found, at item index: {}", item_index)); continue; }, Some(access_key_id) => access_key_id, }; if options.verbose { print_message(MessageType::DEBUG, &format!("Access key id: {}", access_key_id)); } let access_key_secret = match &oss_config.access_key_secret { None => { print_message(MessageType::ERROR, &format!("Config oss_config#access_key_secret not found, at item index: {}", item_index)); continue; }, Some(access_key_secret) => access_key_secret, }; let bucket = match &oss_config.bucket { None => { print_message(MessageType::ERROR, &format!("Config oss_config#bucket not found, at item index: {}", item_index)); continue; }, Some(bucket) => bucket, }; 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 default_limit = 10_usize; // TODO read from config! 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, "gpg")); if options.verbose { print_message(MessageType::DEBUG, &format!("Default limit: {}", default_limit)); 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)); continue; }, Ok(open_pgp_tool) => open_pgp_tool, }; let temp_zip_file = &format!("temp_file_{}.zip", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()); let temp_pgp_file = &format!("temp_file_{}.gpg", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()); 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)); continue; }; 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)); fs::remove_file(temp_zip_file).ok(); continue; }; 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)); fs::remove_file(temp_zip_file).ok(); fs::remove_file(temp_pgp_file).ok(); continue;}, 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, 1000, file_temp_pgp_file) { print_message(MessageType::ERROR, &format!("Error in encrypt file: {}, at item index: {}", e, item_index)); fs::remove_file(temp_zip_file).ok(); fs::remove_file(temp_pgp_file).ok(); continue; } if options.verbose { print_message(MessageType::DEBUG, &format!("Processing meta file: {}", meta_file_name)); } match process_oss_files(&oss_client, bucket, path, meta_file_name, &new_file, default_limit) { 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)); }, }; fs::remove_file(temp_zip_file).ok(); fs::remove_file(temp_pgp_file).ok(); } print_message(MessageType::OK, "Backup all file(s) finished!"); Ok(()) } 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 { print_message(MessageType::INFO, &format!("Remove OSS key: {}", &rm_file)); oss_client.delete_file(bucket_name, &rm_file)?; } } Ok(()) } pub fn process_new_backup_file(backup_content_json: &str, new_item: &str, limit: usize) -> XResult<(Vec, String)> { let mut removed_vec: Vec = 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 pub fn stringity_json_array(vec: &Vec) -> XResult { 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 pub fn parse_json_array(arr: &str) -> XResult> { let mut vec: Vec = vec![]; if arr != "" { 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()), }; } } } Ok(vec) }