diff --git a/src/api2/admin/datastore/backup.rs b/src/api2/admin/datastore/backup.rs index 2495fd06..4e5e701a 100644 --- a/src/api2/admin/datastore/backup.rs +++ b/src/api2/admin/datastore/backup.rs @@ -150,6 +150,10 @@ fn upgrade_to_backup_protocol( fn backup_api() -> Router { let router = Router::new() + .subdir( + "config", Router::new() + .upload(api_method_upload_config()) + ) .subdir( "dynamic_chunk", Router::new() .upload(api_method_upload_dynamic_chunk()) diff --git a/src/api2/admin/datastore/backup/upload_chunk.rs b/src/api2/admin/datastore/backup/upload_chunk.rs index ae0d3db2..1cc674b3 100644 --- a/src/api2/admin/datastore/backup/upload_chunk.rs +++ b/src/api2/admin/datastore/backup/upload_chunk.rs @@ -185,3 +185,65 @@ fn upload_speedtest( Ok(Box::new(resp)) } + +pub fn api_method_upload_config() -> ApiAsyncMethod { + ApiAsyncMethod::new( + upload_config, + ObjectSchema::new("Upload configuration file.") + .required("file-name", crate::api2::types::BACKUP_ARCHIVE_NAME_SCHEMA.clone()) + .required("size", IntegerSchema::new("File size.") + .minimum(1) + .maximum(1024*1024*16) + ) + ) +} + +fn upload_config( + _parts: Parts, + req_body: Body, + param: Value, + _info: &ApiAsyncMethod, + rpcenv: Box, +) -> Result { + + let mut file_name = tools::required_string_param(¶m, "file-name")?.to_owned(); + let size = tools::required_integer_param(¶m, "size")? as usize; + + if !file_name.ends_with(".conf") { + bail!("wrong config file extension: '{}'", file_name); + } else { + file_name.push_str(".zstd"); + } + + let env: &BackupEnvironment = rpcenv.as_ref(); + + let mut path = env.datastore.base_path(); + path.push(env.backup_dir.relative_path()); + path.push(&file_name); + + let env2 = env.clone(); + let env3 = env.clone(); + + let resp = req_body + .map_err(Error::from) + .concat2() + .and_then(move |data| { + if size != data.len() { + bail!("got configuration file with unexpected length ({} != {})", size, data.len()); + } + + let data = zstd::block::compress(&data, 0)?; + + tools::file_set_contents(&path, &data, None)?; + + env2.debug(format!("upload config {:?} ({} bytes, comp: {})", path, size, data.len())); + + Ok(()) + }) + .and_then(move |_| { + Ok(env3.format_response(Ok(Value::Null))) + }) + ; + + Ok(Box::new(resp)) +} diff --git a/src/client/http_client.rs b/src/client/http_client.rs index 8927ffb8..1f33bc03 100644 --- a/src/client/http_client.rs +++ b/src/client/http_client.rs @@ -431,6 +431,31 @@ impl BackupClient { self.h2.clone().post("finish", None).map(|_| ()) } + pub fn upload_config( + &self, + file_name: &str, + src_path: std::path::PathBuf, + ) -> impl Future { + + let h2 = self.h2.clone(); + let file_name = file_name.to_owned(); + + let task = tokio::fs::File::open(src_path) + .map_err(Error::from) + .and_then(|file| { + let contents = vec![]; + tokio::io::read_to_end(file, contents) + .map_err(Error::from) + .and_then(move |(_, contents)| { + let param = json!({"size": contents.len(), "file-name": file_name }); + h2.upload("config", Some(param), contents) + .map(|_| {}) + }) + }); + + task + } + pub fn upload_stream( &self, archive_name: &str,