use std::{ fs, path::Path, cmp::{ min, max, }, }; use rust_util::{ iff, XResult, new_box_ioerror, util_msg::*, }; use chrono::Utc; pub const NAME: &str = "oss-backupd"; pub const VERSION: &str = "1.0.0"; 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"; /* { "oss_config": { "endpoint": "", "access_key_id": "", "access_key_secret": "", "bucket": "", "path": "" }, "host": "", "encrypt_pubkey_file": "", "backup_count": 10, "items": [ { "oss_config": null, // OPT @oss_config "target": "", "file_name": "", "encrypt_pubkey_file": null, // OPT @encrypt_pubkey_file "backup_count": 8 } ] } */ #[derive(Debug, Clone)] pub struct OSSConfig { pub endpoint: Option, pub access_key_id: Option, pub access_key_secret: Option, pub bucket: Option, pub path: Option, } #[derive(Debug)] pub struct OSSBackupdConfigItem { pub target: Option, pub file_name: Option, pub oss_config: Option, pub encrypt_pubkey_file: Option, pub backup_count: Option, } #[derive(Debug)] pub struct OSSBackupdConfig { pub oss_config: Option, pub prefix: Option, pub host: Option, pub items: Vec, pub backup_count: Option, } 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 get_prefix(&self) -> String { match &self.prefix { Some(p) => remove_start_end_slash(&p), None => "default_oss_backupd".to_owned(), } } } impl OSSBackupdConfigItem { 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_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 { min( max( self.backup_count.unwrap_or(10u32) as usize, 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 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 = 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, 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 { let config_content = get_config_content(custom_oss_backupd_config, verbose)?; match json::parse(&config_content) { Ok(o) => Some(o), Err(e) => { print_message(MessageType::ERROR, &format!("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, root_encrypt_pubkey_file: &Option, root_backup_count: Option) -> 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 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 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, } } 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_str("/"); key.push_str(&oss_backupd_config.get_host()); key.push_str("/"); key.push_str(&format!("{}_{}", &oss_backupd_config_item.get_file_name(), &get_now_ymdhms())); if !suffix.is_empty() { key.push_str(&format!(".{}", suffix)); } key } fn parse_sub_oss_config(json: &json::JsonValue) -> Option { let root_oss_config = &json["oss_config"]; iff!(root_oss_config.is_null(), None, Some(parse_oss_config(root_oss_config))) } fn parse_oss_config(oss_config: &json::JsonValue) -> OSSConfig { OSSConfig { endpoint: get_string_value(oss_config, "endpoint"), access_key_id: get_string_value(oss_config, "access_key_id"), access_key_secret: get_string_value(oss_config, "access_key_secret"), bucket: get_string_value(oss_config, "bucket"), path: get_string_value(oss_config, "path"), } } fn get_string_value(json: &json::JsonValue, key: &str) -> Option { let value = &json[key]; value.as_str().map(|s| s.to_owned()) } fn get_u32_value(json: &json::JsonValue, key: &str) -> Option { let value = &json[key]; match value { json::JsonValue::String(v) => v.parse().ok(), json::JsonValue::Number(v) => Some(f64::from(*v) as u32), _ => None, } } fn get_config_content(custom_oss_backupd_config: Option<&str>, verbose: bool) -> Option { if let Some(custom_oss_backupd_config_val) = custom_oss_backupd_config { if verbose { print_message(MessageType::DEBUG, &format!("Read config from: {}", custom_oss_backupd_config_val)); } let custom_oss_backupd_config_path = Path::new(custom_oss_backupd_config_val); if custom_oss_backupd_config_path.exists() { 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_val, e)); None }, }; } else { print_message(MessageType::ERROR, &format!("Custom config file not found: {}", custom_oss_backupd_config_val)); return None; } } // is not assigned by -c or --conifg FILE 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)); } 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)); None }, }; } let home_dot_oss_backupd_config = & match get_user_home_dir(DOT_OSS_BACKUPD_CONFIG) { Ok(o) => o, Err(e) => { print_message(MessageType::WARN, &format!("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)); } 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)); 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)); } 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)); None }, }; } print_message(MessageType::ERROR, "Cannot find config file"); None } fn get_user_home() -> XResult { match dirs::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 { Ok(format!("{}/{}", get_user_home()?, dir)) } fn get_now_ymdhms() -> String { Utc::now().format("%Y%m%d_%H%M%S").to_string() }