diff --git a/src/config.rs b/src/config.rs index 71052d23..ee89d5fc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,6 +16,7 @@ use proxmox::tools::try_block; use crate::buildcfg; pub mod datastore; +pub mod remotes; /// Check configuration directory permissions /// diff --git a/src/config/remotes.rs b/src/config/remotes.rs new file mode 100644 index 00000000..83bf9b67 --- /dev/null +++ b/src/config/remotes.rs @@ -0,0 +1,127 @@ +use failure::*; +use lazy_static::lazy_static; +use std::collections::HashMap; +use serde::Deserialize; + +use proxmox::api::{api, schema::*}; + +use proxmox::tools::{fs::replace_file, fs::CreateOptions}; + +use crate::section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; + +lazy_static! { + static ref CONFIG: SectionConfig = init(); +} + +// fixme: define better schemas + +const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.") + .min_length(3) + .schema(); + +const COMMENT_SCHEMA: Schema = StringSchema::new("Comment").schema(); +const REMOTE_HOST_SCHEMA: Schema = StringSchema::new("Host IP address or DNS name.").schema(); +const REMOTE_USERID_SCHEMA: Schema = StringSchema::new("User ID").schema(); +const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth token.").schema(); + +#[api( + properties: { + comment: { + optional: true, + schema: COMMENT_SCHEMA, + }, + host: { + schema: REMOTE_HOST_SCHEMA, + }, + userid: { + schema: REMOTE_USERID_SCHEMA, + }, + password: { + schema: REMOTE_PASSWORD_SCHEMA, + }, + } +)] +#[derive(Deserialize)] +/// Remote properties. +pub struct Remote { + pub comment: Option, + pub host: String, + pub userid: String, + pub password: String, +} + +fn init() -> SectionConfig { + let obj_schema = match Remote::API_SCHEMA { + Schema::Object(ref obj_schema) => obj_schema, + _ => unreachable!(), + }; + + let plugin = SectionConfigPlugin::new("remote".to_string(), obj_schema); + let mut config = SectionConfig::new(&REMOTE_ID_SCHEMA); + config.register_plugin(plugin); + + config +} + +const REMOTES_CFG_FILENAME: &str = "/etc/proxmox-backup/remotes.cfg"; + +pub fn config() -> Result { + let content = match std::fs::read_to_string(REMOTES_CFG_FILENAME) { + Ok(c) => c, + Err(err) => { + if err.kind() == std::io::ErrorKind::NotFound { + String::from("") + } else { + bail!("unable to read '{}' - {}", REMOTES_CFG_FILENAME, err); + } + } + }; + + CONFIG.parse(REMOTES_CFG_FILENAME, &content) +} + +pub fn lookup(remote: &str) -> Result { + + let remotes = config()?; + + let config = match remotes.sections.get(remote) { + Some((type_name, config)) => { + if type_name != "remote" { + bail!("got unexpected type '{}' for remote '{}'", type_name, remote); + } + config + } + None => { + bail!("no such remote '{}'", remote); + } + }; + + let remote: Remote = serde_json::from_value(config.clone())?; + + Ok(remote) +} + +pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { + let raw = CONFIG.write(REMOTES_CFG_FILENAME, &config)?; + + let backup_user = crate::backup::backup_user()?; + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); + // set the correct owner/group/permissions while saving file + // owner(rw) = root, group(r)= backup + let options = CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(backup_user.gid); + + replace_file(REMOTES_CFG_FILENAME, raw.as_bytes(), options)?; + + Ok(()) +} + +// shell completion helper +pub fn complete_remote_name(_arg: &str, _param: &HashMap) -> Vec { + match config() { + Ok(data) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), + Err(_) => return vec![], + } +}