move remote config into pbs-config workspace
This commit is contained in:
		| @ -58,6 +58,9 @@ pub use crypto::{CryptMode, Fingerprint}; | ||||
|  | ||||
| pub mod file_restore; | ||||
|  | ||||
| mod remote; | ||||
| pub use remote::*; | ||||
|  | ||||
| #[rustfmt::skip] | ||||
| #[macro_use] | ||||
| mod local_macros { | ||||
| @ -132,6 +135,16 @@ pub const CIDR_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V4_RE | ||||
| pub const CIDR_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V6_REGEX); | ||||
| pub const CIDR_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_REGEX); | ||||
|  | ||||
| pub const DNS_NAME_FORMAT: ApiStringFormat = | ||||
|     ApiStringFormat::Pattern(&DNS_NAME_REGEX); | ||||
|  | ||||
| pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat = | ||||
|     ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX); | ||||
|  | ||||
| pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.") | ||||
|     .format(&DNS_NAME_OR_IP_FORMAT) | ||||
|     .schema(); | ||||
|  | ||||
| pub const BACKUP_ID_SCHEMA: Schema = StringSchema::new("Backup ID.") | ||||
|     .format(&BACKUP_ID_FORMAT) | ||||
|     .schema(); | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| pub mod domains; | ||||
| pub mod remote; | ||||
|  | ||||
| use anyhow::{format_err, Error}; | ||||
|  | ||||
|  | ||||
							
								
								
									
										64
									
								
								pbs-config/src/remote.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								pbs-config/src/remote.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| use anyhow::{Error}; | ||||
| use lazy_static::lazy_static; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use proxmox::api::{ | ||||
|     schema::*, | ||||
|     section_config::{ | ||||
|         SectionConfig, | ||||
|         SectionConfigData, | ||||
|         SectionConfigPlugin, | ||||
|     } | ||||
| }; | ||||
|  | ||||
| use pbs_api_types::{Remote, REMOTE_ID_SCHEMA}; | ||||
|  | ||||
| use crate::{open_backup_lockfile, BackupLockGuard}; | ||||
|  | ||||
| lazy_static! { | ||||
|     pub static ref CONFIG: SectionConfig = init(); | ||||
| } | ||||
|  | ||||
| fn init() -> SectionConfig { | ||||
|     let obj_schema = match Remote::API_SCHEMA { | ||||
|         Schema::AllOf(ref allof_schema) => allof_schema, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|     let plugin = SectionConfigPlugin::new("remote".to_string(), Some("name".to_string()), obj_schema); | ||||
|     let mut config = SectionConfig::new(&REMOTE_ID_SCHEMA); | ||||
|     config.register_plugin(plugin); | ||||
|  | ||||
|     config | ||||
| } | ||||
|  | ||||
| pub const REMOTE_CFG_FILENAME: &str = "/etc/proxmox-backup/remote.cfg"; | ||||
| pub const REMOTE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.remote.lck"; | ||||
|  | ||||
| /// Get exclusive lock | ||||
| pub fn lock_config() -> Result<BackupLockGuard, Error> { | ||||
|     open_backup_lockfile(REMOTE_CFG_LOCKFILE, None, true) | ||||
| } | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
|     let content = proxmox::tools::fs::file_read_optional_string(REMOTE_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
|     let digest = openssl::sha::sha256(content.as_bytes()); | ||||
|     let data = CONFIG.parse(REMOTE_CFG_FILENAME, &content)?; | ||||
|     Ok((data, digest)) | ||||
| } | ||||
|  | ||||
| pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { | ||||
|     let raw = CONFIG.write(REMOTE_CFG_FILENAME, &config)?; | ||||
|     crate::replace_backup_config(REMOTE_CFG_FILENAME, raw.as_bytes()) | ||||
| } | ||||
|  | ||||
| // shell completion helper | ||||
| pub fn complete_remote_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     match config() { | ||||
|         Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), | ||||
|         Err(_) => return vec![], | ||||
|     } | ||||
| } | ||||
| @ -6,12 +6,13 @@ use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; | ||||
| use proxmox::http_err; | ||||
|  | ||||
| use pbs_client::{HttpClient, HttpClientOptions}; | ||||
| use pbs_api_types::{ | ||||
|     REMOTE_ID_SCHEMA, REMOTE_PASSWORD_SCHEMA, Remote, RemoteConfig, RemoteConfigUpdater, | ||||
|     Authid, PROXMOX_CONFIG_DIGEST_SCHEMA, DataStoreListItem, | ||||
| }; | ||||
|  | ||||
| use crate::api2::types::*; | ||||
| use crate::config::cached_user_info::CachedUserInfo; | ||||
| use crate::config::remote; | ||||
| use crate::config::acl::{PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY}; | ||||
| use pbs_config::open_backup_lockfile; | ||||
|  | ||||
| #[api( | ||||
|     input: { | ||||
| @ -20,7 +21,7 @@ use pbs_config::open_backup_lockfile; | ||||
|     returns: { | ||||
|         description: "The list of configured remotes (with config digest).", | ||||
|         type: Array, | ||||
|         items: { type: remote::Remote }, | ||||
|         items: { type: Remote }, | ||||
|     }, | ||||
|     access: { | ||||
|         description: "List configured remotes filtered by Remote.Audit privileges", | ||||
| @ -32,13 +33,13 @@ pub fn list_remotes( | ||||
|     _param: Value, | ||||
|     _info: &ApiMethod, | ||||
|     mut rpcenv: &mut dyn RpcEnvironment, | ||||
| ) -> Result<Vec<remote::Remote>, Error> { | ||||
| ) -> Result<Vec<Remote>, Error> { | ||||
|     let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | ||||
|     let user_info = CachedUserInfo::new()?; | ||||
|  | ||||
|     let (config, digest) = remote::config()?; | ||||
|     let (config, digest) = pbs_config::remote::config()?; | ||||
|  | ||||
|     let mut list: Vec<remote::Remote> = config.convert_to_typed_array("remote")?; | ||||
|     let mut list: Vec<Remote> = config.convert_to_typed_array("remote")?; | ||||
|     // don't return password in api | ||||
|     for remote in &mut list { | ||||
|         remote.password = "".to_string(); | ||||
| @ -64,12 +65,12 @@ pub fn list_remotes( | ||||
|                 schema: REMOTE_ID_SCHEMA, | ||||
|             }, | ||||
|             config: { | ||||
|                 type: remote::RemoteConfig, | ||||
|                 type: RemoteConfig, | ||||
|                 flatten: true, | ||||
|             }, | ||||
|             password: { | ||||
|                 // We expect the plain password here (not base64 encoded) | ||||
|                 schema: remote::REMOTE_PASSWORD_SCHEMA, | ||||
|                 schema: REMOTE_PASSWORD_SCHEMA, | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
| @ -80,23 +81,23 @@ pub fn list_remotes( | ||||
| /// Create new remote. | ||||
| pub fn create_remote( | ||||
|     name: String, | ||||
|     config: remote::RemoteConfig, | ||||
|     config: RemoteConfig, | ||||
|     password: String, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; | ||||
|     let _lock = pbs_config::remote::config()?; | ||||
|  | ||||
|     let (mut section_config, _digest) = remote::config()?; | ||||
|     let (mut section_config, _digest) = pbs_config::remote::config()?; | ||||
|  | ||||
|     if section_config.sections.get(&name).is_some() { | ||||
|         bail!("remote '{}' already exists.", name); | ||||
|     } | ||||
|  | ||||
|     let remote = remote::Remote { name: name.clone(), config, password }; | ||||
|     let remote = Remote { name: name.clone(), config, password }; | ||||
|  | ||||
|     section_config.set_data(&name, "remote", &remote)?; | ||||
|  | ||||
|     remote::save_config(§ion_config)?; | ||||
|     pbs_config::remote::save_config(§ion_config)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
| @ -109,7 +110,7 @@ pub fn create_remote( | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
|     returns: { type: remote::Remote }, | ||||
|     returns: { type: Remote }, | ||||
|     access: { | ||||
|         permission: &Permission::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT, false), | ||||
|     } | ||||
| @ -119,9 +120,9 @@ pub fn read_remote( | ||||
|     name: String, | ||||
|     _info: &ApiMethod, | ||||
|     mut rpcenv: &mut dyn RpcEnvironment, | ||||
| ) -> Result<remote::Remote, Error> { | ||||
|     let (config, digest) = remote::config()?; | ||||
|     let mut data: remote::Remote = config.lookup("remote", &name)?; | ||||
| ) -> Result<Remote, Error> { | ||||
|     let (config, digest) = pbs_config::remote::config()?; | ||||
|     let mut data: Remote = config.lookup("remote", &name)?; | ||||
|     data.password = "".to_string(); // do not return password in api | ||||
|     rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | ||||
|     Ok(data) | ||||
| @ -148,13 +149,13 @@ pub enum DeletableProperty { | ||||
|                 schema: REMOTE_ID_SCHEMA, | ||||
|             }, | ||||
|             update: { | ||||
|                 type: remote::RemoteConfigUpdater, | ||||
|                 type: RemoteConfigUpdater, | ||||
|                 flatten: true, | ||||
|             }, | ||||
|             password: { | ||||
|                 // We expect the plain password here (not base64 encoded) | ||||
|                 optional: true, | ||||
|                 schema: remote::REMOTE_PASSWORD_SCHEMA, | ||||
|                 schema: REMOTE_PASSWORD_SCHEMA, | ||||
|             }, | ||||
|             delete: { | ||||
|                 description: "List of properties to delete.", | ||||
| @ -177,22 +178,22 @@ pub enum DeletableProperty { | ||||
| /// Update remote configuration. | ||||
| pub fn update_remote( | ||||
|     name: String, | ||||
|     update: remote::RemoteConfigUpdater, | ||||
|     update: RemoteConfigUpdater, | ||||
|     password: Option<String>, | ||||
|     delete: Option<Vec<DeletableProperty>>, | ||||
|     digest: Option<String>, | ||||
| ) -> Result<(), Error> { | ||||
|  | ||||
|     let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; | ||||
|     let _lock = pbs_config::remote::config()?; | ||||
|  | ||||
|     let (mut config, expected_digest) = remote::config()?; | ||||
|     let (mut config, expected_digest) = pbs_config::remote::config()?; | ||||
|  | ||||
|     if let Some(ref digest) = digest { | ||||
|         let digest = proxmox::tools::hex_to_digest(digest)?; | ||||
|         crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | ||||
|     } | ||||
|  | ||||
|     let mut data: remote::Remote = config.lookup("remote", &name)?; | ||||
|     let mut data: Remote = config.lookup("remote", &name)?; | ||||
|  | ||||
|     if let Some(delete) = delete { | ||||
|         for delete_prop in delete { | ||||
| @ -221,7 +222,7 @@ pub fn update_remote( | ||||
|  | ||||
|     config.set_data(&name, "remote", &data)?; | ||||
|  | ||||
|     remote::save_config(&config)?; | ||||
|     pbs_config::remote::save_config(&config)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
| @ -257,9 +258,9 @@ pub fn delete_remote(name: String, digest: Option<String>) -> Result<(), Error> | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; | ||||
|     let _lock = pbs_config::remote::config()?; | ||||
|  | ||||
|     let (mut config, expected_digest) = remote::config()?; | ||||
|     let (mut config, expected_digest) = pbs_config::remote::config()?; | ||||
|  | ||||
|     if let Some(ref digest) = digest { | ||||
|         let digest = proxmox::tools::hex_to_digest(digest)?; | ||||
| @ -271,13 +272,13 @@ pub fn delete_remote(name: String, digest: Option<String>) -> Result<(), Error> | ||||
|         None => bail!("remote '{}' does not exist.", name), | ||||
|     } | ||||
|  | ||||
|     remote::save_config(&config)?; | ||||
|     pbs_config::remote::save_config(&config)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Helper to get client for remote.cfg entry | ||||
| pub async fn remote_client(remote: remote::Remote) -> Result<HttpClient, Error> { | ||||
| pub async fn remote_client(remote: Remote) -> Result<HttpClient, Error> { | ||||
|     let options = HttpClientOptions::new_non_interactive(remote.password.clone(), remote.config.fingerprint.clone()); | ||||
|  | ||||
|     let client = HttpClient::new( | ||||
| @ -312,8 +313,8 @@ pub async fn remote_client(remote: remote::Remote) -> Result<HttpClient, Error> | ||||
| )] | ||||
| /// List datastores of a remote.cfg entry | ||||
| pub async fn scan_remote_datastores(name: String) -> Result<Vec<DataStoreListItem>, Error> { | ||||
|     let (remote_config, _digest) = remote::config()?; | ||||
|     let remote: remote::Remote = remote_config.lookup("remote", &name)?; | ||||
|     let (remote_config, _digest) = pbs_config::remote::config()?; | ||||
|     let remote: Remote = remote_config.lookup("remote", &name)?; | ||||
|  | ||||
|     let map_remote_err = |api_err| { | ||||
|         http_err!(INTERNAL_SERVER_ERROR, | ||||
|  | ||||
| @ -8,20 +8,20 @@ use proxmox::api::api; | ||||
| use proxmox::api::{ApiMethod, Router, RpcEnvironment, Permission}; | ||||
|  | ||||
| use pbs_client::{HttpClient, BackupRepository}; | ||||
| use pbs_api_types::{ | ||||
|     Remote, DATASTORE_SCHEMA, REMOTE_ID_SCHEMA, Authid, | ||||
| }; | ||||
|  | ||||
| use crate::server::{WorkerTask, jobstate::Job, pull::pull_store}; | ||||
| use crate::backup::DataStore; | ||||
| use crate::api2::types::{ | ||||
|     DATASTORE_SCHEMA, REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA, Authid, | ||||
| }; | ||||
| use crate::api2::types::REMOVE_VANISHED_BACKUPS_SCHEMA; | ||||
|  | ||||
| use crate::config::{ | ||||
|     remote, | ||||
|     sync::SyncJobConfig, | ||||
|     acl::{PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_PRUNE, PRIV_REMOTE_READ}, | ||||
|     cached_user_info::CachedUserInfo, | ||||
| }; | ||||
|  | ||||
|  | ||||
| pub fn check_pull_privs( | ||||
|     auth_id: &Authid, | ||||
|     store: &str, | ||||
| @ -50,8 +50,8 @@ pub async fn get_pull_parameters( | ||||
|  | ||||
|     let tgt_store = DataStore::lookup_datastore(store)?; | ||||
|  | ||||
|     let (remote_config, _digest) = remote::config()?; | ||||
|     let remote: remote::Remote = remote_config.lookup("remote", remote)?; | ||||
|     let (remote_config, _digest) = pbs_config::remote::config()?; | ||||
|     let remote: Remote = remote_config.lookup("remote", remote)?; | ||||
|  | ||||
|     let src_repo = BackupRepository::new( | ||||
|         Some(remote.config.auth_id.clone()), | ||||
|  | ||||
| @ -52,15 +52,9 @@ pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat = | ||||
| pub const HOSTNAME_FORMAT: ApiStringFormat = | ||||
|     ApiStringFormat::Pattern(&HOSTNAME_REGEX); | ||||
|  | ||||
| pub const DNS_NAME_FORMAT: ApiStringFormat = | ||||
|     ApiStringFormat::Pattern(&DNS_NAME_REGEX); | ||||
|  | ||||
| pub const DNS_ALIAS_FORMAT: ApiStringFormat = | ||||
|     ApiStringFormat::Pattern(&DNS_ALIAS_REGEX); | ||||
|  | ||||
| pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat = | ||||
|     ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX); | ||||
|  | ||||
| pub const ACL_PATH_FORMAT: ApiStringFormat = | ||||
|     ApiStringFormat::Pattern(&ACL_PATH_REGEX); | ||||
|  | ||||
| @ -284,12 +278,6 @@ pub const VERIFICATION_SCHEDULE_SCHEMA: Schema = StringSchema::new( | ||||
|     .type_text("<calendar-event>") | ||||
|     .schema(); | ||||
|  | ||||
| pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.") | ||||
|     .format(&PROXMOX_SAFE_ID_FORMAT) | ||||
|     .min_length(3) | ||||
|     .max_length(32) | ||||
|     .schema(); | ||||
|  | ||||
| pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.") | ||||
|     .format(&PROXMOX_SAFE_ID_FORMAT) | ||||
|     .min_length(3) | ||||
| @ -315,10 +303,6 @@ pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in R | ||||
|     .format(&HOSTNAME_FORMAT) | ||||
|     .schema(); | ||||
|  | ||||
| pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.") | ||||
|     .format(&DNS_NAME_OR_IP_FORMAT) | ||||
|     .schema(); | ||||
|  | ||||
| pub const SUBSCRIPTION_KEY_SCHEMA: Schema = StringSchema::new("Proxmox Backup Server subscription key.") | ||||
|     .format(&SUBSCRIPTION_KEY_FORMAT) | ||||
|     .min_length(15) | ||||
|  | ||||
| @ -58,7 +58,7 @@ fn main() -> Result<(), Error> { | ||||
|             "tape.cfg" => dump_section_config(&config::drive::CONFIG), | ||||
|             "tape-job.cfg" => dump_section_config(&config::tape_job::CONFIG), | ||||
|             "user.cfg" => dump_section_config(&config::user::CONFIG), | ||||
|             "remote.cfg" => dump_section_config(&config::remote::CONFIG), | ||||
|             "remote.cfg" => dump_section_config(&pbs_config::remote::CONFIG), | ||||
|             "sync.cfg" => dump_section_config(&config::sync::CONFIG), | ||||
|             "verification.cfg" => dump_section_config(&config::verify::CONFIG), | ||||
|             "media-pool.cfg" => dump_section_config(&config::media_pool::CONFIG), | ||||
|  | ||||
| @ -380,7 +380,7 @@ fn main() { | ||||
|             CliCommand::new(&API_METHOD_PULL_DATASTORE) | ||||
|                 .arg_param(&["remote", "remote-store", "local-store"]) | ||||
|                 .completion_cb("local-store", config::datastore::complete_datastore_name) | ||||
|                 .completion_cb("remote", config::remote::complete_remote_name) | ||||
|                 .completion_cb("remote", pbs_config::remote::complete_remote_name) | ||||
|                 .completion_cb("remote-store", complete_remote_datastore_name) | ||||
|         ) | ||||
|         .insert( | ||||
|  | ||||
| @ -3,8 +3,10 @@ use serde_json::Value; | ||||
|  | ||||
| use proxmox::api::{api, cli::*, RpcEnvironment, ApiHandler}; | ||||
|  | ||||
| use proxmox_backup::config; | ||||
| use proxmox_backup::api2::{self, types::* }; | ||||
| use pbs_api_types::REMOTE_ID_SCHEMA; | ||||
|  | ||||
| use proxmox_backup::api2; | ||||
|  | ||||
|  | ||||
| #[api( | ||||
|     input: { | ||||
| @ -77,7 +79,7 @@ pub fn remote_commands() -> CommandLineInterface { | ||||
|             "show", | ||||
|             CliCommand::new(&API_METHOD_SHOW_REMOTE) | ||||
|                 .arg_param(&["name"]) | ||||
|                 .completion_cb("name", config::remote::complete_remote_name) | ||||
|                 .completion_cb("name", pbs_config::remote::complete_remote_name) | ||||
|         ) | ||||
|         .insert( | ||||
|             "create", | ||||
| @ -89,13 +91,13 @@ pub fn remote_commands() -> CommandLineInterface { | ||||
|             "update", | ||||
|             CliCommand::new(&api2::config::remote::API_METHOD_UPDATE_REMOTE) | ||||
|                 .arg_param(&["name"]) | ||||
|                 .completion_cb("name", config::remote::complete_remote_name) | ||||
|                 .completion_cb("name", pbs_config::remote::complete_remote_name) | ||||
|         ) | ||||
|         .insert( | ||||
|             "remove", | ||||
|             CliCommand::new(&api2::config::remote::API_METHOD_DELETE_REMOTE) | ||||
|                 .arg_param(&["name"]) | ||||
|                 .completion_cb("name", config::remote::complete_remote_name) | ||||
|                 .completion_cb("name", pbs_config::remote::complete_remote_name) | ||||
|         ); | ||||
|  | ||||
|     cmd_def.into() | ||||
|  | ||||
| @ -85,7 +85,7 @@ pub fn sync_job_commands() -> CommandLineInterface { | ||||
|                 .completion_cb("id", config::sync::complete_sync_job_id) | ||||
|                 .completion_cb("schedule", config::datastore::complete_calendar_event) | ||||
|                 .completion_cb("store", config::datastore::complete_datastore_name) | ||||
|                 .completion_cb("remote", config::remote::complete_remote_name) | ||||
|                 .completion_cb("remote", pbs_config::remote::complete_remote_name) | ||||
|                 .completion_cb("remote-store", crate::complete_remote_datastore_name) | ||||
|         ) | ||||
|         .insert("update", | ||||
|  | ||||
| @ -20,7 +20,6 @@ pub mod cached_user_info; | ||||
| pub mod datastore; | ||||
| pub mod network; | ||||
| pub mod node; | ||||
| pub mod remote; | ||||
| pub mod sync; | ||||
| pub mod tfa; | ||||
| pub mod token_shadow; | ||||
|  | ||||
| @ -1,134 +0,0 @@ | ||||
| use anyhow::{Error}; | ||||
| use lazy_static::lazy_static; | ||||
| use std::collections::HashMap; | ||||
| use serde::{Serialize, Deserialize}; | ||||
|  | ||||
| use proxmox::api::{ | ||||
|     api, | ||||
|     schema::*, | ||||
|     section_config::{ | ||||
|         SectionConfig, | ||||
|         SectionConfigData, | ||||
|         SectionConfigPlugin, | ||||
|     } | ||||
| }; | ||||
|  | ||||
| use crate::api2::types::*; | ||||
|  | ||||
| lazy_static! { | ||||
|     pub static ref CONFIG: SectionConfig = init(); | ||||
| } | ||||
|  | ||||
| pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth token for remote host.") | ||||
|     .format(&PASSWORD_FORMAT) | ||||
|     .min_length(1) | ||||
|     .max_length(1024) | ||||
|     .schema(); | ||||
|  | ||||
| pub const REMOTE_PASSWORD_BASE64_SCHEMA: Schema = StringSchema::new("Password or auth token for remote host (stored as base64 string).") | ||||
|     .format(&PASSWORD_FORMAT) | ||||
|     .min_length(1) | ||||
|     .max_length(1024) | ||||
|     .schema(); | ||||
|  | ||||
| #[api( | ||||
|     properties: { | ||||
|         comment: { | ||||
|             optional: true, | ||||
|             schema: SINGLE_LINE_COMMENT_SCHEMA, | ||||
|         }, | ||||
|         host: { | ||||
|             schema: DNS_NAME_OR_IP_SCHEMA, | ||||
|         }, | ||||
|         port: { | ||||
|             optional: true, | ||||
|             description: "The (optional) port", | ||||
|             type: u16, | ||||
|         }, | ||||
|         "auth-id": { | ||||
|             type: Authid, | ||||
|         }, | ||||
|         fingerprint: { | ||||
|             optional: true, | ||||
|             schema: CERT_FINGERPRINT_SHA256_SCHEMA, | ||||
|         }, | ||||
|     }, | ||||
| )] | ||||
| #[derive(Serialize,Deserialize,Updater)] | ||||
| #[serde(rename_all = "kebab-case")] | ||||
| /// Remote configuration properties. | ||||
| pub struct RemoteConfig { | ||||
|     #[serde(skip_serializing_if="Option::is_none")] | ||||
|     pub comment: Option<String>, | ||||
|     pub host: String, | ||||
|     #[serde(skip_serializing_if="Option::is_none")] | ||||
|     pub port: Option<u16>, | ||||
|     pub auth_id: Authid, | ||||
|     #[serde(skip_serializing_if="Option::is_none")] | ||||
|     pub fingerprint: Option<String>, | ||||
| } | ||||
|  | ||||
| #[api( | ||||
|     properties: { | ||||
|         name: { | ||||
|             schema: REMOTE_ID_SCHEMA, | ||||
|         }, | ||||
|         config: { | ||||
|             type: RemoteConfig, | ||||
|         }, | ||||
|         password: { | ||||
|             schema: REMOTE_PASSWORD_BASE64_SCHEMA, | ||||
|         }, | ||||
|     }, | ||||
| )] | ||||
| #[derive(Serialize,Deserialize)] | ||||
| #[serde(rename_all = "kebab-case")] | ||||
| /// Remote properties. | ||||
| pub struct Remote { | ||||
|     pub name: String, | ||||
|     // Note: The stored password is base64 encoded | ||||
|     #[serde(skip_serializing_if="String::is_empty")] | ||||
|     #[serde(with = "proxmox::tools::serde::string_as_base64")] | ||||
|     pub password: String, | ||||
|     #[serde(flatten)] | ||||
|     pub config: RemoteConfig, | ||||
| } | ||||
|  | ||||
| fn init() -> SectionConfig { | ||||
|     let obj_schema = match Remote::API_SCHEMA { | ||||
|         Schema::AllOf(ref allof_schema) => allof_schema, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|     let plugin = SectionConfigPlugin::new("remote".to_string(), Some("name".to_string()), obj_schema); | ||||
|     let mut config = SectionConfig::new(&REMOTE_ID_SCHEMA); | ||||
|     config.register_plugin(plugin); | ||||
|  | ||||
|     config | ||||
| } | ||||
|  | ||||
| pub const REMOTE_CFG_FILENAME: &str = "/etc/proxmox-backup/remote.cfg"; | ||||
| pub const REMOTE_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.remote.lck"; | ||||
|  | ||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | ||||
|  | ||||
|     let content = proxmox::tools::fs::file_read_optional_string(REMOTE_CFG_FILENAME)? | ||||
|         .unwrap_or_else(|| "".to_string()); | ||||
|  | ||||
|     let digest = openssl::sha::sha256(content.as_bytes()); | ||||
|     let data = CONFIG.parse(REMOTE_CFG_FILENAME, &content)?; | ||||
|     Ok((data, digest)) | ||||
| } | ||||
|  | ||||
| pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { | ||||
|     let raw = CONFIG.write(REMOTE_CFG_FILENAME, &config)?; | ||||
|     pbs_config::replace_backup_config(REMOTE_CFG_FILENAME, raw.as_bytes()) | ||||
| } | ||||
|  | ||||
| // shell completion helper | ||||
| pub fn complete_remote_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||
|     match config() { | ||||
|         Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), | ||||
|         Err(_) => return vec![], | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user