//! Interfaces for writing and reading PeachCloud configurations, stored in yaml. //! //! Different PeachCloud microservices import peach-lib, so that they can share //! this interface. //! //! Config values are looked up from three locations in this order by key name: //! 1. from environmental variables //! 2. from a configuration file //! 3. from default values //! //! The configuration file is located at: "/var/lib/peachcloud/config.yml" //! unless its path is configured by setting PEACH_CONFIG_PATH env variable. use std::collections::{BTreeMap, HashMap}; use std::{env, fs}; use fslock::LockFile; use lazy_static::lazy_static; use log::debug; use crate::error::PeachError; // load path to main configuration file // from PEACH_CONFIG_PATH if that environment variable is set // or using the default value if not set pub const DEFAULT_YAML_PATH: &str = "/var/lib/peachcloud/config.yml"; lazy_static! { static ref CONFIG_PATH: String = { if let Ok(val) = env::var("PEACH_CONFIG_PATH") { val } else { DEFAULT_YAML_PATH.to_string() } }; // lock file (used to avoid race conditions during config reading & writing) // the lock file path is the config file path + ".lock" static ref LOCK_FILE_PATH: String = format!("{}.lock", *CONFIG_PATH); } // Default values for PeachCloud configs which are used for any key which is not set // via an environment variable or in a saved configuration file. pub fn get_peach_config_defaults() -> HashMap { let peach_config_defaults: HashMap<&str, &str> = HashMap::from([ ("STANDALONE_MODE", "true"), ("DISABLE_AUTH", "false"), ("ADDR", "127.0.0.1"), ("PORT", "8000"), ("EXTERNAL_DOMAIN", ""), ("DYN_DOMAIN", ""), ( "DYN_DNS_SERVER_ADDRESS", "http://dynserver.dyn.peachcloud.org", ), ("DYN_USE_CUSTOM_SERVER", "true"), ("DYN_TSIG_KEY_PATH", ""), ("DYN_NAMESERVER", "ns.peachcloud.org"), ("DYN_ENABLED", "false"), ("SSB_ADMIN_IDS", ""), ("ADMIN_PASSWORD_HASH", "47"), ("TEMPORARY_PASSWORD_HASH", ""), ("GO_SBOT_DATADIR", "/home/peach/.ssb-go"), ("PEACH_CONFIGDIR", "/var/lib/peachcloud"), ("PEACH_HOMEDIR", "/home/peach"), ("PEACH_WEBDIR", "/usr/share/peach-web"), ]); // convert HashMap<&str, &str> to HashMap and return let pc_defaults: HashMap = peach_config_defaults .iter() .map(|(key, val)| (key.to_string(), val.to_string())) .collect(); pc_defaults } // primary interface for getting config values // Config values are looked up from three locations in this order by key name: // 1. from environmental variables // 2. from a configuration file // 3. from default values pub fn get_config_value(key: &str) -> Result { // first check if there is an environmental variable set if let Ok(val) = env::var(key) { Ok(val) } else { // then check if a value is set in the config file let peach_config_on_disc = load_peach_config_from_disc()?; let val = peach_config_on_disc.get(key); // if no value is found in the config file, then get the default value match val { // return config value Some(v) => Ok(v.to_string()), // get default value None => { match get_peach_config_defaults().get(key) { Some(v) => Ok(v.to_string()), // if this key was not found in the defaults, then it was an invalid key None => Err(PeachError::InvalidKey { key: key.to_string(), }), } } } } } // helper function to load PeachCloud configuration file saved to disc pub fn load_peach_config_from_disc() -> Result, PeachError> { let peach_config_exists = std::path::Path::new(CONFIG_PATH.as_str()).exists(); // if config file does not exist, return an emtpy HashMap if !peach_config_exists { let peach_config: HashMap = HashMap::new(); Ok(peach_config) } // otherwise we load peach config from disk else { debug!("Loading peach config: {} exists", CONFIG_PATH.as_str()); let contents = fs::read_to_string(CONFIG_PATH.as_str()).map_err(|source| PeachError::Read { source, path: CONFIG_PATH.to_string(), })?; let peach_config: HashMap = serde_yaml::from_str(&contents)?; Ok(peach_config) } } // helper function to save PeachCloud configuration file to disc // takes in a Hashmap and saves the whole HashMap as a yaml file // with the keys in alphabetical order pub fn save_peach_config_to_disc( peach_config: HashMap, ) -> Result, PeachError> { // use a file lock to avoid race conditions while saving config let mut lock = LockFile::open(&*LOCK_FILE_PATH)?; lock.lock()?; // first convert Hashmap to BTreeMap (so that keys are saved in deterministic alphabetical order) let ordered: BTreeMap<_, _> = peach_config.iter().collect(); // then serialize BTreeMap as yaml let yaml_str = serde_yaml::to_string(&ordered)?; // write yaml to file fs::write(CONFIG_PATH.as_str(), yaml_str).map_err(|source| PeachError::Write { source, path: CONFIG_PATH.to_string(), })?; // unlock file lock lock.unlock()?; // return modified HashMap Ok(peach_config) } // helper functions for serializing and deserializing PeachConfig values from disc pub fn save_config_value(key: &str, value: &str) -> Result, PeachError> { // get current config from disc let mut peach_config = load_peach_config_from_disc()?; // insert new key/value peach_config.insert(key.to_string(), value.to_string()); // save the modified hashmap to disc save_peach_config_to_disc(peach_config) } // set all dyn configuration values at once pub fn set_peach_dyndns_config( dyn_domain: &str, dyn_dns_server_address: &str, dyn_tsig_key_path: &str, dyn_enabled: bool, ) -> Result, PeachError> { let mut peach_config = load_peach_config_from_disc()?; let dyn_enabled_str = match dyn_enabled { true => "true", false => "false", }; peach_config.insert("DYN_DOMAIN".to_string(), dyn_domain.to_string()); peach_config.insert( "DYN_DNS_SERVER_ADDRESS".to_string(), dyn_dns_server_address.to_string(), ); peach_config.insert( "DYN_TSIG_KEY_PATH".to_string(), dyn_tsig_key_path.to_string(), ); peach_config.insert("DYN_ENABLED".to_string(), dyn_enabled_str.to_string()); save_peach_config_to_disc(peach_config) } pub fn set_external_domain( new_external_domain: &str, ) -> Result, PeachError> { save_config_value("EXTERNAL_DOMAIN", new_external_domain) } pub fn get_peachcloud_domain() -> Result, PeachError> { let external_domain = get_config_value("EXTERNAL_DOMAIN")?; let dyn_domain = get_config_value("DYN_DOMAIN")?; if !external_domain.is_empty() { Ok(Some(external_domain)) } else if !dyn_domain.is_empty() { Ok(Some(dyn_domain)) } else { Ok(None) } } pub fn get_dyndns_server_address() -> Result { get_config_value("DYN_DNS_SERVER_ADDRESS") } pub fn set_dyndns_enabled_value( enabled_value: bool, ) -> Result, PeachError> { match enabled_value { true => save_config_value("DYN_ENABLED", "true"), false => save_config_value("DYN_ENABLED", "false"), } } pub fn get_dyndns_enabled_value() -> Result { let val = get_config_value("DYN_ENABLED")?; Ok(val == "true") } pub fn set_admin_password_hash( password_hash: String, ) -> Result, PeachError> { save_config_value("ADMIN_PASSWORD_HASH", &password_hash) } pub fn get_admin_password_hash() -> Result { let admin_password_hash = get_config_value("ADMIN_PASSWORD_HASH")?; if !admin_password_hash.is_empty() { Ok(admin_password_hash) } else { Err(PeachError::PasswordNotSet) } } pub fn set_temporary_password_hash( password_hash: String, ) -> Result, PeachError> { save_config_value("TEMPORARY_PASSWORD_HASH", &password_hash) } pub fn get_temporary_password_hash() -> Result { let admin_password_hash = get_config_value("TEMPORARY_PASSWORD_HASH")?; if !admin_password_hash.is_empty() { Ok(admin_password_hash) } else { Err(PeachError::PasswordNotSet) } } // add ssb_id to vector of admin ids and save new value for SSB_ADMIN_IDS pub fn add_ssb_admin_id(ssb_id: &str) -> Result, PeachError> { let mut ssb_admin_ids = get_ssb_admin_ids()?; ssb_admin_ids.push(ssb_id.to_string()); save_ssb_admin_ids(ssb_admin_ids) } // remove ssb_id from vector of admin ids if found and save new value for SSB_ADMIN_IDS // if value is not found then return an error pub fn delete_ssb_admin_id(ssb_id: &str) -> Result, PeachError> { let mut ssb_admin_ids = get_ssb_admin_ids()?; let index_result = ssb_admin_ids.iter().position(|x| *x == ssb_id); match index_result { Some(index) => { ssb_admin_ids.remove(index); save_ssb_admin_ids(ssb_admin_ids) } None => Err(PeachError::SsbAdminIdNotFound { id: ssb_id.to_string(), }), } } // looks up the String value for SSB_ADMIN_IDS and converts it into a Vec pub fn get_ssb_admin_ids() -> Result, PeachError> { let ssb_admin_ids_str = get_config_value("SSB_ADMIN_IDS")?; let ssb_admin_ids: Vec = serde_json::from_str(&ssb_admin_ids_str)?; Ok(ssb_admin_ids) } // takes in a Vec and saves SSB_ADMIN_IDS as a json string representation of this vec pub fn save_ssb_admin_ids(ssb_admin_ids: Vec) -> Result, PeachError> { let ssb_admin_ids_as_json_str = serde_json::to_string(&ssb_admin_ids)?; save_config_value("SSB_ADMIN_IDS", &ssb_admin_ids_as_json_str)?; Ok(ssb_admin_ids) }