summaryrefslogtreecommitdiff
path: root/src/config.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/config.rs')
-rw-r--r--src/config.rs124
1 files changed, 124 insertions, 0 deletions
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..d6ffbc5
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,124 @@
+use crate::passwords::{passwords_filename, PasswordError, Passwords};
+
+use bytesize::MIB;
+use log::{error, trace};
+use serde::{Deserialize, Serialize};
+use std::path::{Path, PathBuf};
+
+const DEFAULT_CHUNK_SIZE: usize = MIB as usize;
+const DEVNULL: &str = "/dev/null";
+
+#[derive(Debug, Deserialize, Clone)]
+#[serde(deny_unknown_fields)]
+struct TentativeClientConfig {
+ server_url: String,
+ verify_tls_cert: Option<bool>,
+ chunk_size: Option<usize>,
+ roots: Vec<PathBuf>,
+ log: Option<PathBuf>,
+ encrypt: Option<bool>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+pub enum ClientConfig {
+ Plain(ClientConfigWithoutPasswords),
+ WithPasswords(ClientConfigWithoutPasswords, Passwords),
+}
+
+impl ClientConfig {
+ pub fn read_without_passwords(filename: &Path) -> Result<Self, ClientConfigError> {
+ let config = ClientConfigWithoutPasswords::read_config(filename)?;
+ Ok(ClientConfig::Plain(config))
+ }
+
+ pub fn read_with_passwords(filename: &Path) -> Result<Self, ClientConfigError> {
+ let config = ClientConfigWithoutPasswords::read_config(filename)?;
+ if config.encrypt {
+ let passwords = Passwords::load(&passwords_filename(filename))
+ .map_err(ClientConfigError::PasswordsMissing)?;
+ Ok(ClientConfig::WithPasswords(config, passwords))
+ } else {
+ Ok(ClientConfig::Plain(config))
+ }
+ }
+
+ pub fn config(&self) -> &ClientConfigWithoutPasswords {
+ match self {
+ Self::Plain(config) => &config,
+ Self::WithPasswords(config, _) => &config,
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Clone)]
+pub struct ClientConfigWithoutPasswords {
+ pub filename: PathBuf,
+ pub server_url: String,
+ pub verify_tls_cert: bool,
+ pub chunk_size: usize,
+ pub roots: Vec<PathBuf>,
+ pub log: PathBuf,
+ pub encrypt: bool,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ClientConfigError {
+ #[error("server_url is empty")]
+ ServerUrlIsEmpty,
+
+ #[error("No backup roots in config; at least one is needed")]
+ NoBackupRoot,
+
+ #[error("server URL doesn't use https: {0}")]
+ NotHttps(String),
+
+ #[error("No passwords are set: you may need to run 'obnam init': {0}")]
+ PasswordsMissing(PasswordError),
+
+ #[error(transparent)]
+ IoError(#[from] std::io::Error),
+
+ #[error(transparent)]
+ SerdeYamlError(#[from] serde_yaml::Error),
+}
+
+pub type ClientConfigResult<T> = Result<T, ClientConfigError>;
+
+impl ClientConfigWithoutPasswords {
+ pub fn read_config(filename: &Path) -> ClientConfigResult<Self> {
+ trace!("read_config: filename={:?}", filename);
+ let config = std::fs::read_to_string(filename)?;
+ let tentative: TentativeClientConfig = serde_yaml::from_str(&config)?;
+
+ let encrypt = tentative.encrypt.or(Some(false)).unwrap();
+
+ let config = Self {
+ filename: filename.to_path_buf(),
+ server_url: tentative.server_url,
+ roots: tentative.roots,
+ verify_tls_cert: tentative.verify_tls_cert.or(Some(false)).unwrap(),
+ chunk_size: tentative.chunk_size.or(Some(DEFAULT_CHUNK_SIZE)).unwrap(),
+ log: tentative
+ .log
+ .or_else(|| Some(PathBuf::from(DEVNULL)))
+ .unwrap(),
+ encrypt,
+ };
+
+ config.check()?;
+ Ok(config)
+ }
+
+ fn check(&self) -> Result<(), ClientConfigError> {
+ if self.server_url.is_empty() {
+ return Err(ClientConfigError::ServerUrlIsEmpty);
+ }
+ if !self.server_url.starts_with("https://") {
+ return Err(ClientConfigError::NotHttps(self.server_url.to_string()));
+ }
+ if self.roots.is_empty() {
+ return Err(ClientConfigError::NoBackupRoot);
+ }
+ Ok(())
+ }
+}