peach-workspace/peach-lib/src/config_manager.rs

294 lines
10 KiB
Rust
Raw Normal View History

2021-08-06 17:58:40 +00:00
//! Interfaces for writing and reading PeachCloud configurations, stored in yaml.
//!
//! Different PeachCloud microservices import peach-lib, so that they can share
//! this interface.
2021-08-06 17:58:40 +00:00
//!
2022-05-09 13:53:03 +00:00
//! 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
//!
2021-08-06 17:58:40 +00:00
//! The configuration file is located at: "/var/lib/peachcloud/config.yml"
2022-05-09 13:53:03 +00:00
//! unless its path is configured by setting PEACH_CONFIG_PATH env variable.
2021-08-06 17:58:40 +00:00
2022-05-10 10:59:36 +00:00
use std::collections::{BTreeMap, HashMap};
2022-05-03 13:50:26 +00:00
use std::{env, fs};
2021-08-06 17:58:40 +00:00
use fslock::LockFile;
2022-05-09 13:53:03 +00:00
use lazy_static::lazy_static;
2022-05-10 10:59:36 +00:00
use log::debug;
2021-08-06 17:58:40 +00:00
use crate::error::PeachError;
2022-05-03 13:50:26 +00:00
// 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()
}
};
2022-05-09 13:53:03 +00:00
// lock file (used to avoid race conditions during config reading & writing)
2022-05-03 13:50:26 +00:00
// the lock file path is the config file path + ".lock"
static ref LOCK_FILE_PATH: String = format!("{}.lock", *CONFIG_PATH);
}
2021-08-06 17:58:40 +00:00
2022-05-09 13:53:03 +00:00
// 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<String, String> {
2022-05-11 09:24:33 +00:00
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", ""),
2022-05-10 10:59:36 +00:00
(
2022-05-11 09:24:33 +00:00
"DYN_DNS_SERVER_ADDRESS",
"http://dynserver.dyn.peachcloud.org",
2022-05-10 10:59:36 +00:00
),
2022-05-11 09:24:33 +00:00
("DYN_USE_CUSTOM_SERVER", "true"),
("DYN_TSIG_KEY_PATH", ""),
("DYN_NAMESERVER", "ns.peachcloud.org"),
("DYN_ENABLED", "false"),
("SSB_ADMIN_IDS", ""),
2022-05-12 09:26:23 +00:00
("ADMIN_PASSWORD_HASH", "47"),
2022-05-11 09:24:33 +00:00
("TEMPORARY_PASSWORD_HASH", ""),
("GO_SBOT_DATADIR", "/home/peach/.ssb-go"),
2022-05-11 09:24:33 +00:00
("PEACH_CONFIGDIR", "/var/lib/peachcloud"),
2022-05-12 10:53:06 +00:00
("PEACH_HOMEDIR", "/home/peach"),
("PEACH_WEBDIR", "/usr/share/peach-web"),
2022-05-09 13:53:03 +00:00
]);
2022-05-11 09:24:33 +00:00
// convert HashMap<&str, &str> to HashMap<String, String> and return
let pc_defaults: HashMap<String, String> = peach_config_defaults
.iter()
.map(|(key, val)| (key.to_string(), val.to_string()))
.collect();
pc_defaults
2022-05-09 13:53:03 +00:00
}
2022-05-10 10:59:36 +00:00
// 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<String, PeachError> {
// 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(),
}),
}
}
}
}
}
2022-05-09 13:53:03 +00:00
2022-05-10 10:59:36 +00:00
// helper function to load PeachCloud configuration file saved to disc
2022-05-09 13:53:03 +00:00
pub fn load_peach_config_from_disc() -> Result<HashMap<String, String>, PeachError> {
2022-05-10 10:59:36 +00:00
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<String, String> = 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<String, String> = serde_yaml::from_str(&contents)?;
Ok(peach_config)
}
2022-05-09 13:53:03 +00:00
}
2022-05-10 10:59:36 +00:00
// helper function to save PeachCloud configuration file to disc
// takes in a Hashmap<String, String> 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<String, String>,
) -> Result<HashMap<String, String>, PeachError> {
2021-08-06 17:58:40 +00:00
// use a file lock to avoid race conditions while saving config
2022-05-03 13:50:26 +00:00
let mut lock = LockFile::open(&*LOCK_FILE_PATH)?;
2021-08-06 17:58:40 +00:00
lock.lock()?;
2022-05-10 10:59:36 +00:00
// 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)?;
2021-08-06 17:58:40 +00:00
2022-05-09 13:53:03 +00:00
// write yaml to file
fs::write(CONFIG_PATH.as_str(), yaml_str).map_err(|source| PeachError::Write {
source,
2022-05-09 13:53:03 +00:00
path: CONFIG_PATH.to_string(),
2021-08-06 17:58:40 +00:00
})?;
// unlock file lock
lock.unlock()?;
2022-05-09 13:53:03 +00:00
// return modified HashMap
2021-08-06 17:58:40 +00:00
Ok(peach_config)
}
2022-05-10 10:59:36 +00:00
// helper functions for serializing and deserializing PeachConfig values from disc
pub fn save_config_value(key: &str, value: &str) -> Result<HashMap<String, String>, PeachError> {
2022-05-09 13:53:03 +00:00
// 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());
2022-05-09 13:53:03 +00:00
2022-05-11 08:34:53 +00:00
// save the modified hashmap to disc
2022-05-09 13:53:03 +00:00
save_peach_config_to_disc(peach_config)
2021-08-06 17:58:40 +00:00
}
2022-05-09 13:53:03 +00:00
// set all dyn configuration values at once
2021-08-06 17:58:40 +00:00
pub fn set_peach_dyndns_config(
dyn_domain: &str,
dyn_dns_server_address: &str,
dyn_tsig_key_path: &str,
dyn_enabled: bool,
2022-05-09 13:53:03 +00:00
) -> Result<HashMap<String, String>, PeachError> {
let mut peach_config = load_peach_config_from_disc()?;
let dyn_enabled_str = match dyn_enabled {
true => "true",
2022-05-10 10:59:36 +00:00
false => "false",
2022-05-09 13:53:03 +00:00
};
peach_config.insert("DYN_DOMAIN".to_string(), dyn_domain.to_string());
2022-05-10 10:59:36 +00:00
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(),
);
2022-05-09 13:53:03 +00:00
peach_config.insert("DYN_ENABLED".to_string(), dyn_enabled_str.to_string());
save_peach_config_to_disc(peach_config)
2021-08-06 17:58:40 +00:00
}
2022-05-10 10:59:36 +00:00
pub fn set_external_domain(
new_external_domain: &str,
) -> Result<HashMap<String, String>, PeachError> {
save_config_value("EXTERNAL_DOMAIN", new_external_domain)
2021-08-06 17:58:40 +00:00
}
pub fn get_peachcloud_domain() -> Result<Option<String>, PeachError> {
2022-05-09 13:53:03 +00:00
let external_domain = get_config_value("EXTERNAL_DOMAIN")?;
let dyn_domain = get_config_value("DYN_DOMAIN")?;
if !external_domain.is_empty() {
2022-05-10 10:59:36 +00:00
Ok(Some(external_domain))
2022-05-09 13:53:03 +00:00
} else if !dyn_domain.is_empty() {
2022-05-10 10:59:36 +00:00
Ok(Some(dyn_domain))
2021-08-06 17:58:40 +00:00
} else {
Ok(None)
}
}
2021-12-18 16:24:43 +00:00
pub fn get_dyndns_server_address() -> Result<String, PeachError> {
2022-05-09 13:53:03 +00:00
get_config_value("DYN_DNS_SERVER_ADDRESS")
}
2022-05-10 10:59:36 +00:00
pub fn set_dyndns_enabled_value(
enabled_value: bool,
) -> Result<HashMap<String, String>, PeachError> {
2022-05-09 13:53:03 +00:00
match enabled_value {
true => save_config_value("DYN_ENABLED", "true"),
false => save_config_value("DYN_ENABLED", "false"),
2022-01-11 23:03:07 +00:00
}
2021-12-18 16:24:43 +00:00
}
2022-05-09 13:53:03 +00:00
pub fn get_dyndns_enabled_value() -> Result<bool, PeachError> {
let val = get_config_value("DYN_ENABLED")?;
2022-05-10 10:59:36 +00:00
Ok(val == "true")
2021-08-06 17:58:40 +00:00
}
2022-05-10 10:59:36 +00:00
pub fn set_admin_password_hash(
password_hash: String,
) -> Result<HashMap<String, String>, PeachError> {
save_config_value("ADMIN_PASSWORD_HASH", &password_hash)
2022-05-10 10:59:36 +00:00
}
pub fn get_admin_password_hash() -> Result<String, PeachError> {
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<HashMap<String, String>, PeachError> {
save_config_value("TEMPORARY_PASSWORD_HASH", &password_hash)
2022-05-10 10:59:36 +00:00
}
pub fn get_temporary_password_hash() -> Result<String, PeachError> {
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
2022-05-09 13:53:03 +00:00
pub fn add_ssb_admin_id(ssb_id: &str) -> Result<Vec<String>, 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)
2021-08-06 17:58:40 +00:00
}
2022-05-10 10:59:36 +00:00
// 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
2022-05-09 13:53:03 +00:00
pub fn delete_ssb_admin_id(ssb_id: &str) -> Result<Vec<String>, PeachError> {
let mut ssb_admin_ids = get_ssb_admin_ids()?;
2021-08-06 17:58:40 +00:00
let index_result = ssb_admin_ids.iter().position(|x| *x == ssb_id);
match index_result {
Some(index) => {
ssb_admin_ids.remove(index);
2022-05-09 13:53:03 +00:00
save_ssb_admin_ids(ssb_admin_ids)
2021-08-06 17:58:40 +00:00
}
None => Err(PeachError::SsbAdminIdNotFound {
id: ssb_id.to_string(),
}),
}
}
2022-05-10 10:59:36 +00:00
// looks up the String value for SSB_ADMIN_IDS and converts it into a Vec<String>
pub fn get_ssb_admin_ids() -> Result<Vec<String>, PeachError> {
let ssb_admin_ids_str = get_config_value("SSB_ADMIN_IDS")?;
let ssb_admin_ids: Vec<String> = serde_json::from_str(&ssb_admin_ids_str)?;
2022-05-09 13:53:03 +00:00
Ok(ssb_admin_ids)
}
2022-05-10 10:59:36 +00:00
// takes in a Vec<String> and saves SSB_ADMIN_IDS as a json string representation of this vec
pub fn save_ssb_admin_ids(ssb_admin_ids: Vec<String>) -> Result<Vec<String>, 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)?;
2022-05-09 13:53:03 +00:00
Ok(ssb_admin_ids)
}