329 lines
11 KiB
Rust
329 lines
11 KiB
Rust
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<String>,
|
|
pub access_key_id: Option<String>,
|
|
pub access_key_secret: Option<String>,
|
|
pub bucket: Option<String>,
|
|
pub path: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct OSSBackupdConfigItem {
|
|
pub target: Option<String>,
|
|
pub file_name: Option<String>,
|
|
pub oss_config: Option<OSSConfig>,
|
|
pub encrypt_pubkey_file: Option<String>,
|
|
pub backup_count: Option<u32>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct OSSBackupdConfig {
|
|
pub oss_config: Option<OSSConfig>,
|
|
pub prefix: Option<String>,
|
|
pub host: Option<String>,
|
|
pub items: Vec<OSSBackupdConfigItem>,
|
|
pub backup_count: Option<u32>,
|
|
}
|
|
|
|
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<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, 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) => {
|
|
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<OSSConfig>, root_encrypt_pubkey_file: &Option<String>, 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 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<OSSConfig> {
|
|
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<String> {
|
|
let value = &json[key];
|
|
value.as_str().map(|s| s.to_owned())
|
|
}
|
|
|
|
fn get_u32_value(json: &json::JsonValue, key: &str) -> Option<u32> {
|
|
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<String> {
|
|
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<String> {
|
|
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<String> {
|
|
Ok(format!("{}/{}", get_user_home()?, dir))
|
|
}
|
|
|
|
fn get_now_ymdhms() -> String {
|
|
Utc::now().format("%Y%m%d_%H%M%S").to_string()
|
|
}
|