From 12f82e72f033a2d92307ae55c3e3702b6161399b Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 7 Nov 2020 12:04:55 +0100 Subject: [PATCH] Change settings to be done in the config directory Signed-off-by: trivernis --- .gitignore | 4 +- Cargo.lock | 172 ++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 5 +- README.md | 15 +--- src/data/node_data.rs | 12 ++- src/main.rs | 27 ++++--- src/modules/mod.rs | 6 ++ src/server/mod.rs | 6 ++ src/utils/env.rs | 20 ----- src/utils/keys.rs | 10 ++- src/utils/mod.rs | 24 ++++-- src/utils/result.rs | 24 +++++- src/utils/settings.rs | 48 ++++++++++++ 13 files changed, 293 insertions(+), 80 deletions(-) create mode 100644 src/modules/mod.rs delete mode 100644 src/utils/env.rs create mode 100644 src/utils/settings.rs diff --git a/.gitignore b/.gitignore index c391ef9..8d81ed9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target .idea *.env -test \ No newline at end of file +test +config +nodes diff --git a/Cargo.lock b/Cargo.lock index f69f577..8050f67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "atty" version = "0.2.14" @@ -126,7 +132,7 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", + "num-traits 0.2.14", "time", "winapi", ] @@ -175,6 +181,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "config" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +dependencies = [ + "lazy_static", + "nom", + "rust-ini", + "serde 1.0.117", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "const_fn" version = "0.4.3" @@ -235,12 +257,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "env_logger" version = "0.8.1" @@ -287,6 +303,12 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "hashbrown" version = "0.9.1" @@ -349,12 +371,31 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 0.1.10", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.80" @@ -371,6 +412,22 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" +dependencies = [ + "serde 0.8.23", + "serde_test", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + [[package]] name = "lock_api" version = "0.4.1" @@ -423,6 +480,17 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -430,7 +498,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", - "num-traits", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -609,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" dependencies = [ "byteorder", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -620,7 +697,7 @@ checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" dependencies = [ "byteorder", "rmp", - "serde", + "serde 1.0.117", ] [[package]] @@ -638,6 +715,18 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + [[package]] name = "salsa20" version = "0.7.1" @@ -663,6 +752,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.117" @@ -672,6 +767,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "linked-hash-map 0.3.0", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + [[package]] name = "serde_derive" version = "1.0.117" @@ -683,6 +791,26 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +dependencies = [ + "itoa", + "ryu", + "serde 1.0.117", +] + +[[package]] +name = "serde_test" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" +dependencies = [ + "serde 0.8.23", +] + [[package]] name = "sha2" version = "0.9.2" @@ -709,19 +837,26 @@ dependencies = [ "base64", "chrono", "colored", - "dotenv", + "config", "env_logger", + "glob", "hostname", "log", "mac_address", "rand", "rusqlite", - "serde", + "serde 1.0.117", "structopt", "toml", "vented", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.8.0" @@ -825,7 +960,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ - "serde", + "serde 1.0.117", ] [[package]] @@ -890,7 +1025,7 @@ dependencies = [ "rmp", "rmp-serde", "scheduled-thread-pool", - "serde", + "serde 1.0.117", "sha2", "typenum", "x25519-dalek", @@ -970,6 +1105,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "yaml-rust" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +dependencies = [ + "linked-hash-map 0.5.3", +] + [[package]] name = "zeroize" version = "1.1.1" diff --git a/Cargo.toml b/Cargo.toml index 5a8de7a..7a4d578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ rusqlite = "0.24.1" rand = "0.7.3" base64 = "0.13.0" structopt = "0.3.20" -dotenv = "0.15.0" mac_address = "1.1.1" hostname = "0.3.1" log = "0.4.11" @@ -20,4 +19,6 @@ env_logger = "0.8.1" colored = "2.0.0" chrono = "0.4.19" toml = "0.5.7" -serde = { version = "1.0.117", features = ["serde_derive"] } \ No newline at end of file +serde = { version = "1.0.117", features = ["serde_derive"] } +config = "0.10.1" +glob = "0.3.0" \ No newline at end of file diff --git a/README.md b/README.md index 6cb5e86..4caa32c 100644 --- a/README.md +++ b/README.md @@ -23,18 +23,9 @@ When run without a subcommand the server executes normally. ## Configuration -Basic server configuration is done in .env files. - -|Variable | Description | Default Value | -|--------|----------|---------------| -| SNEKCLOUD_NODES_DIR | Directory containing the .toml files for the network nodes| nodes | -| SNEKCLOUD_PRIVATE_KEY | Path of the private key (generated with generate-key) | node_key | -| SNEKCLOUD_LISTEN_ADDRESS | The address the server listens on | 127.0.0.1:22222 | -| SNEKCLOUD_NODE_ID | The NodeID of the instance | None | - -The NodeID is a parameter that can either be set manually or is generated -from the mac-address or hostname depending on what is available. - +The configuration for the server has to be done in the config directory. +This directory will always contain the default configuration `default.toml` and will +load additional files with the same ending. ## License diff --git a/src/data/node_data.rs b/src/data/node_data.rs index 4832417..3cefabd 100644 --- a/src/data/node_data.rs +++ b/src/data/node_data.rs @@ -4,20 +4,21 @@ use crate::utils::keys::{armor_public_key, extract_public_key}; use std::path::PathBuf; use crate::utils::result::SnekcloudResult; use std::fs; +use crate::utils::write_toml_pretty; #[derive(Serialize, Deserialize)] pub struct NodeData { pub id: String, - pub address: Option, + pub addresses: Vec, public_key: String, } impl NodeData { - pub fn with_address(id: String, address: String, public_key: PublicKey) -> Self { + pub fn with_addresses(id: String, addresses: Vec, public_key: PublicKey) -> Self { let public_key = armor_public_key(public_key); Self { id, - address: Some(address), + addresses, public_key } } @@ -32,10 +33,7 @@ impl NodeData { /// Writes the data to the given file pub fn write_to_file(&self, path: PathBuf) -> SnekcloudResult<()> { - let content = toml::to_string(self)?; - fs::write(path, content.as_bytes())?; - - Ok(()) + write_toml_pretty(&path, self) } pub fn public_key(&self) -> PublicKey { diff --git a/src/main.rs b/src/main.rs index ad615d3..03df805 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use crate::server::SnekcloudServer; -use crate::utils::get_node_id; -use crate::utils::env::{get_private_key_path, get_key_file_storage, get_listen_address}; +use crate::utils::settings::{Settings, get_settings}; use crate::utils::keys::{extract_private_key, read_node_keys, generate_private_key, armor_private_key}; use std::path::PathBuf; use crate::utils::result::SnekcloudResult; @@ -13,6 +12,7 @@ use crate::data::node_data::NodeData; pub(crate) mod utils; pub(crate) mod server; pub(crate) mod data; +pub(crate) mod modules; #[derive(StructOpt, Debug)] struct Opt { @@ -47,6 +47,8 @@ struct WriteInfoFileOptions { fn main() -> SnekcloudResult<()>{ init_logger(); let opt: Opt = Opt::from_args(); + let settings = get_settings()?; + if let Some(command) = opt.sub_command { match command { SubCommand::GenerateKey(options) => { @@ -55,27 +57,30 @@ fn main() -> SnekcloudResult<()>{ fs::write(options.output_file, string_content)?; }, SubCommand::WriteInfoFile(options) => { - let key = get_private_key()?; - let data = NodeData::with_address(get_node_id(), get_listen_address(), key.public_key()); + let key = get_private_key(&settings)?; + let data = NodeData::with_addresses(settings.node_id, settings.listen_addresses, key.public_key()); data.write_to_file(options.output_file)?; } } } else { - start_server(opt)?; + start_server(opt, &settings)?; } Ok(()) } -fn start_server(_options: Opt) -> SnekcloudResult<()> { - let keys = read_node_keys(&PathBuf::from(get_key_file_storage()))?; - let mut server = SnekcloudServer::new(get_node_id(), get_private_key()?, keys, 8); - server.add_listen_address(get_listen_address()); +fn start_server(_options: Opt, settings: &Settings) -> SnekcloudResult<()> { + let keys = read_node_keys(&settings.node_data_dir)?; + let mut server = SnekcloudServer::new(settings.node_id.clone(), get_private_key(settings)?, keys, 8); + + for address in &settings.listen_addresses { + server.add_listen_address(address.clone()); + } server.run()?; Ok(()) } -fn get_private_key() -> SnekcloudResult { - extract_private_key(&fs::read_to_string(get_private_key_path())?) +fn get_private_key(settings: &Settings) -> SnekcloudResult { + extract_private_key(&fs::read_to_string(&settings.private_key)?) } \ No newline at end of file diff --git a/src/modules/mod.rs b/src/modules/mod.rs new file mode 100644 index 0000000..2829737 --- /dev/null +++ b/src/modules/mod.rs @@ -0,0 +1,6 @@ +use vented::server::VentedServer; +use crate::utils::result::SnekcloudResult; + +pub trait Module { + fn init(&mut self, server: &VentedServer) -> SnekcloudResult<()>; +} \ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs index 29c18cf..9957d2a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -3,6 +3,7 @@ use crate::utils::result::SnekcloudResult; use vented::crypto::SecretKey; use vented::server::data::Node; use vented::WaitGroup; +use crate::modules::Module; pub struct SnekcloudServer { inner: VentedServer, @@ -33,4 +34,9 @@ impl SnekcloudServer { Ok(()) } + + /// Registers a module on the server + pub fn register_module(&mut self, module: &mut impl Module) -> SnekcloudResult<()> { + module.init(&mut self.inner) + } } \ No newline at end of file diff --git a/src/utils/env.rs b/src/utils/env.rs deleted file mode 100644 index 15183ce..0000000 --- a/src/utils/env.rs +++ /dev/null @@ -1,20 +0,0 @@ -const VAR_KEY_FILE_STORAGE: &str = "SNEKCLOUD_NODES_DIR"; -const VAR_PRIVATE_KEY_PATH: &str = "SNEKCLOUD_PRIVATE_KEY"; -const VAR_LISTEN_ADDRESS: &str = "SNEKCLOUD_LISTEN_ADDRESS"; -const VAR_NODE_ID: &str = "SNEKCLOUD_NODE_ID"; - -pub fn get_key_file_storage() -> String { - dotenv::var(VAR_KEY_FILE_STORAGE).unwrap_or("nodes".to_string()) -} - -pub fn get_private_key_path() -> String { - dotenv::var(VAR_PRIVATE_KEY_PATH).unwrap_or("node_key".to_string()) -} - -pub fn get_env_node_id() -> Option { - dotenv::var(VAR_NODE_ID).ok() -} - -pub fn get_listen_address() -> String { - dotenv::var(VAR_LISTEN_ADDRESS).unwrap_or("127.0.0.1:22222".to_string()) -} \ No newline at end of file diff --git a/src/utils/keys.rs b/src/utils/keys.rs index e43f265..54addf8 100644 --- a/src/utils/keys.rs +++ b/src/utils/keys.rs @@ -1,8 +1,9 @@ -use std::path::PathBuf; +use std::path::{PathBuf, Path}; use vented::crypto::{SecretKey, PublicKey}; use crate::utils::result::{SnekcloudResult, SnekcloudError}; use vented::server::data::Node; use crate::data::node_data::NodeData; +use std::fs::create_dir; const PRIVATE_KEY_HEADER_LINE: &str = "---BEGIN-SNEKCLOUD-PRIVATE-KEY---\n"; const PRIVATE_KEY_FOOTER_LINE: &str = "\n---END-SNEKCLOUD-PRIVATE-KEY---"; @@ -12,6 +13,9 @@ const PUBLIC_KEY_FOOTER_LINE: &str = "\n---END-SNEKCLOUD-PUBLIC-KEY---"; /// Reads a folder of node public keys pub fn read_node_keys(path: &PathBuf) -> SnekcloudResult> { + if !Path::new(path).exists() { + create_dir(path)?; + } let dir_content = path.read_dir()?; let content = dir_content @@ -22,9 +26,9 @@ pub fn read_node_keys(path: &PathBuf) -> SnekcloudResult> { }) .filter(|(meta, _)|meta.is_file()) .filter_map(|(_, entry)|{ - let data = NodeData::from_file(entry.path()).ok()?; + let mut data = NodeData::from_file(entry.path()).ok()?; - Some(Node {public_key: data.public_key(), address: data.address, id: data.id}) + Some(Node {public_key: data.public_key(), address: data.addresses.pop(), id: data.id}) }).collect(); Ok(content) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e7659f2..f81740e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,16 +1,18 @@ -use crate::utils::env::get_env_node_id; use rand::RngCore; +use std::path::PathBuf; +use crate::utils::result::SnekcloudResult; +use serde::Serialize; +use std::fs; pub mod result; pub mod keys; -pub mod env; +pub mod settings; pub mod logging; + + pub fn get_node_id() -> String { - if let Some(id) = get_env_node_id() { - log::trace!("Using env node_id"); - id - } else if let Ok(Some(address)) = mac_address::get_mac_address() { + if let Ok(Some(address)) = mac_address::get_mac_address() { log::trace!("Using mac address as node_id"); base64::encode(address.bytes()) } else if let Ok(hostname) = hostname::get() { @@ -24,4 +26,14 @@ pub fn get_node_id() -> String { base64::encode(id_raw) } +} + +/// Writes a pretty toml file to the given path +pub fn write_toml_pretty(path: &PathBuf, value: &T) -> SnekcloudResult<()> { + let mut buf_str = String::new(); + let mut serializer = toml::Serializer::pretty(&mut buf_str); + value.serialize(&mut serializer)?; + fs::write(path, buf_str.as_bytes())?; + + Ok(()) } \ No newline at end of file diff --git a/src/utils/result.rs b/src/utils/result.rs index 3bcb80e..e9cd8ee 100644 --- a/src/utils/result.rs +++ b/src/utils/result.rs @@ -14,6 +14,8 @@ pub enum SnekcloudError { TomlDeserializeError(toml::de::Error), TomlSerializeError(toml::ser::Error), InvalidKey, + ConfigError(config::ConfigError), + GlobPatternError(glob::PatternError), } impl fmt::Display for SnekcloudError { @@ -25,6 +27,8 @@ impl fmt::Display for SnekcloudError { Self::InvalidKey => write!(f, "Invalid Key!"), Self::TomlDeserializeError(e) => write!(f, "Toml Deserialization Error: {}", e), Self::TomlSerializeError(e) => write!(f, "Toml Serialization Error: {}", e), + Self::ConfigError(e) => write!(f, "Config Error: {}",e), + Self::GlobPatternError(e) => write!(f, "Glob Error {}", e), } } } @@ -50,13 +54,25 @@ impl From for SnekcloudError { } impl From for SnekcloudError { - fn from(other: toml::ser::Error) -> Self { - Self::TomlSerializeError(other) + fn from(error: toml::ser::Error) -> Self { + Self::TomlSerializeError(error) } } impl From for SnekcloudError { - fn from(other: toml::de::Error) -> Self { - Self::TomlDeserializeError(other) + fn from(error: toml::de::Error) -> Self { + Self::TomlDeserializeError(error) + } +} + +impl From for SnekcloudError { + fn from(error: config::ConfigError) -> Self { + Self::ConfigError(error) + } +} + +impl From for SnekcloudError { + fn from(error: glob::PatternError) -> Self { + Self::GlobPatternError(error) } } \ No newline at end of file diff --git a/src/utils/settings.rs b/src/utils/settings.rs new file mode 100644 index 0000000..67ac985 --- /dev/null +++ b/src/utils/settings.rs @@ -0,0 +1,48 @@ +use crate::utils::result::{SnekcloudResult, SnekcloudError}; +use serde::{Serialize, Deserialize}; +use crate::utils::{get_node_id, write_toml_pretty}; +use std::fs; +use std::path::{Path, PathBuf}; +use config::File; + + +const CONFIG_DIR: &str = "config/"; +const DEFAULT_CONFIG: &str = "config/00_default.toml"; +const GLOB_CONFIG: &str = "config/*.toml"; +const ENV_PREFIX : &str = "SNEKCLOUD"; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Settings { + pub listen_addresses: Vec, + pub node_id: String, + pub private_key: PathBuf, + pub node_data_dir: PathBuf, +} + +impl Default for Settings { + fn default() -> Self { + Self { + listen_addresses: vec!["127.0.0.1:22222".to_string()], + node_id: get_node_id(), + private_key: PathBuf::from("node_key"), + node_data_dir: PathBuf::from("nodes"), + } + } +} + +pub fn get_settings() -> SnekcloudResult { + if !Path::new(CONFIG_DIR).exists() { + fs::create_dir(CONFIG_DIR)?; + } + write_toml_pretty(&PathBuf::from(DEFAULT_CONFIG), &Settings::default())?; + + let mut settings = config::Config::default(); + settings + .merge(config::File::with_name(DEFAULT_CONFIG))? + .merge(glob::glob(GLOB_CONFIG)?.map(|path| File::from(path.unwrap())) + .collect::>())? + .merge(config::Environment::with_prefix(ENV_PREFIX))?; + + + settings.try_into().map_err(SnekcloudError::from) +}