diff --git a/bffhd/config/dhall.rs b/bffhd/config/dhall.rs index 169e39f..b933a0b 100644 --- a/bffhd/config/dhall.rs +++ b/bffhd/config/dhall.rs @@ -1,6 +1,169 @@ -use crate::Config; +use std::collections::HashMap; +use std::default::Default; +use std::error::Error; +use std::fmt::{Debug, Display}; +use std::marker::PhantomData; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +use crate::authorization::permissions::PrivilegesBuf; +use crate::authorization::roles::Role; +use crate::capnp::{Listen, TlsListen}; +use crate::logging::LogConfig; + +use miette::IntoDiagnostic; use std::path::Path; +#[derive(Debug)] +struct DhallConfig<'a> { + path: &'a Path, +} + pub fn read_config_file(path: impl AsRef) -> Result { serde_dhall::from_file(path).parse().map_err(Into::into) } + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +/// A description of a machine +/// +/// This is the struct that a machine is serialized to/from. +/// Combining this with the actual state of the system will return a machine +pub struct MachineDescription { + /// The name of the machine. Doesn't need to be unique but is what humans will be presented. + pub name: String, + + /// An optional description of the Machine. + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deser_option" + )] + pub description: Option, + + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deser_option" + )] + pub wiki: Option, + + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deser_option" + )] + pub category: Option, + + /// The permission required + #[serde(flatten)] + pub privs: PrivilegesBuf, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + /// A list of address/port pairs to listen on. + pub listens: Vec, + + /// Machine descriptions to load + pub machines: HashMap, + + /// Actors to load and their configuration options + pub actors: HashMap, + + /// Initiators to load and their configuration options + pub initiators: HashMap, + + pub mqtt_url: String, + + pub actor_connections: Vec<(String, String)>, + pub init_connections: Vec<(String, String)>, + + pub db_path: PathBuf, + pub auditlog_path: PathBuf, + + pub roles: HashMap, + + #[serde(flatten)] + pub tlsconfig: TlsListen, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tlskeylog: Option, + + #[serde(default, skip)] + pub verbosity: isize, + + #[serde(default, skip)] + pub logging: LogConfig, +} + +impl Config { + pub fn is_quiet(&self) -> bool { + self.verbosity < 0 + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ModuleConfig { + pub module: String, + pub params: HashMap, +} + +pub(crate) fn deser_option<'de, D, T>(d: D) -> std::result::Result, D::Error> +where + D: serde::Deserializer<'de>, + T: serde::Deserialize<'de>, +{ + Ok(T::deserialize(d).ok()) +} + +impl Default for Config { + fn default() -> Self { + let mut actors: HashMap = HashMap::new(); + let mut initiators: HashMap = HashMap::new(); + let machines = HashMap::new(); + + actors.insert( + "Actor".to_string(), + ModuleConfig { + module: "Shelly".to_string(), + params: HashMap::new(), + }, + ); + initiators.insert( + "Initiator".to_string(), + ModuleConfig { + module: "TCP-Listen".to_string(), + params: HashMap::new(), + }, + ); + + Config { + listens: vec![Listen { + address: "127.0.0.1".to_string(), + port: None, + }], + actors, + initiators, + machines, + mqtt_url: "tcp://localhost:1883".to_string(), + actor_connections: vec![("Testmachine".to_string(), "Actor".to_string())], + init_connections: vec![("Initiator".to_string(), "Testmachine".to_string())], + + db_path: PathBuf::from("/run/bffh/database"), + auditlog_path: PathBuf::from("/var/log/bffh/audit.log"), + roles: HashMap::new(), + + tlsconfig: TlsListen { + certfile: PathBuf::from("./bffh.crt"), + keyfile: PathBuf::from("./bffh.key"), + ..Default::default() + }, + + tlskeylog: None, + verbosity: 0, + logging: LogConfig::default(), + } + } +} diff --git a/bffhd/config/mod.rs b/bffhd/config/mod.rs index fe02283..dfce376 100644 --- a/bffhd/config/mod.rs +++ b/bffhd/config/mod.rs @@ -1,157 +1,50 @@ -use std::collections::HashMap; -use std::default::Default; -use std::path::PathBuf; +use std::path::Path; -use serde::{Deserialize, Serialize}; +use miette::Diagnostic; +use thiserror::Error; +pub(crate) use dhall::deser_option; +pub use dhall::{Config, MachineDescription, ModuleConfig}; mod dhall; -pub use dhall::read_config_file as read; -use crate::authorization::permissions::PrivilegesBuf; -use crate::authorization::roles::Role; -use crate::capnp::{Listen, TlsListen}; -use crate::logging::LogConfig; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -/// A description of a machine -/// -/// This is the struct that a machine is serialized to/from. -/// Combining this with the actual state of the system will return a machine -pub struct MachineDescription { - /// The name of the machine. Doesn't need to be unique but is what humans will be presented. - pub name: String, - - /// An optional description of the Machine. - #[serde( - default, - skip_serializing_if = "Option::is_none", - deserialize_with = "deser_option" +#[derive(Debug, Error, Diagnostic)] +pub enum ConfigError { + #[error("The config file '{0}' does not exist or is not readable")] + #[diagnostic( + code(config::notfound), + help("Make sure the config file and the directory it's in are readable by the user running bffh") )] - pub description: Option, - - #[serde( - default, - skip_serializing_if = "Option::is_none", - deserialize_with = "deser_option" + NotFound(String), + #[error("The path '{0}' does not point to a file")] + #[diagnostic( + code(config::notafile), + help("The config must be a file in the dhall format") )] - pub wiki: Option, - - #[serde( - default, - skip_serializing_if = "Option::is_none", - deserialize_with = "deser_option" - )] - pub category: Option, - - /// The permission required - #[serde(flatten)] - pub privs: PrivilegesBuf, + NotAFile(String), + #[error("failed to parse config: {0}")] + #[diagnostic(code(config::parse))] + Parse( + #[from] + #[source] + serde_dhall::Error, + ), } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - /// A list of address/port pairs to listen on. - pub listens: Vec, - - /// Machine descriptions to load - pub machines: HashMap, - - /// Actors to load and their configuration options - pub actors: HashMap, - - /// Initiators to load and their configuration options - pub initiators: HashMap, - - pub mqtt_url: String, - - pub actor_connections: Vec<(String, String)>, - pub init_connections: Vec<(String, String)>, - - pub db_path: PathBuf, - pub auditlog_path: PathBuf, - - pub roles: HashMap, - - #[serde(flatten)] - pub tlsconfig: TlsListen, - - #[serde(default, skip_serializing_if = "Option::is_none")] - pub tlskeylog: Option, - - #[serde(default, skip)] - pub verbosity: isize, - - #[serde(default, skip)] - pub logging: LogConfig, -} - -impl Config { - pub fn is_quiet(&self) -> bool { - self.verbosity < 0 +pub fn read(file: impl AsRef) -> Result { + let path = file.as_ref(); + if !path.exists() { + return Err(ConfigError::NotFound(path.to_string_lossy().to_string())); } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ModuleConfig { - pub module: String, - pub params: HashMap, -} - -pub(crate) fn deser_option<'de, D, T>(d: D) -> std::result::Result, D::Error> -where - D: serde::Deserializer<'de>, - T: serde::Deserialize<'de>, -{ - Ok(T::deserialize(d).ok()) -} - -impl Default for Config { - fn default() -> Self { - let mut actors: HashMap = HashMap::new(); - let mut initiators: HashMap = HashMap::new(); - let machines = HashMap::new(); - - actors.insert( - "Actor".to_string(), - ModuleConfig { - module: "Shelly".to_string(), - params: HashMap::new(), - }, - ); - initiators.insert( - "Initiator".to_string(), - ModuleConfig { - module: "TCP-Listen".to_string(), - params: HashMap::new(), - }, - ); - - Config { - listens: vec![Listen { - address: "127.0.0.1".to_string(), - port: None, - }], - actors, - initiators, - machines, - mqtt_url: "tcp://localhost:1883".to_string(), - actor_connections: vec![("Testmachine".to_string(), "Actor".to_string())], - init_connections: vec![("Initiator".to_string(), "Testmachine".to_string())], - - db_path: PathBuf::from("/run/bffh/database"), - auditlog_path: PathBuf::from("/var/log/bffh/audit.log"), - roles: HashMap::new(), - - tlsconfig: TlsListen { - certfile: PathBuf::from("./bffh.crt"), - keyfile: PathBuf::from("./bffh.key"), - ..Default::default() - }, - - tlskeylog: None, - verbosity: 0, - logging: LogConfig::default(), + if !path.is_file() { + return Err(ConfigError::NotAFile(path.to_string_lossy().to_string())); + } + let mut config = dhall::read_config_file(file)?; + for (envvar, value) in std::env::vars() { + match envvar.as_str() { + // Do things like this? + // "BFFH_LOG" => config.logging.filter = Some(value), + _ => {} } } + Ok(config) } diff --git a/bin/bffhd/main.rs b/bin/bffhd/main.rs index d52cbca..fff25b7 100644 --- a/bin/bffhd/main.rs +++ b/bin/bffhd/main.rs @@ -135,7 +135,7 @@ fn main() -> miette::Result<()> { } } - let mut config = config::read(&PathBuf::from_str(configpath).unwrap()).unwrap(); + let mut config = config::read(&PathBuf::from_str(configpath).unwrap())?; if matches.is_present("dump") { return Err(miette::miette!("DB Dumping is currently not implemented, except for the users db, using `--dump-users`"));