//! Configuration file and settings management use config::{Config, ConfigError, File}; use serde::{Deserialize, Serialize}; use std::time::Duration; use tracing::warn; #[derive(Debug, Serialize, Deserialize, Clone)] #[allow(unused)] pub struct Info { pub relay_url: Option, pub name: Option, pub description: Option, pub pubkey: Option, pub contact: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Database { pub data_directory: String, pub engine: String, pub in_memory: bool, pub min_conn: u32, pub max_conn: u32, } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Network { pub port: u16, pub address: String, pub remote_ip_header: Option, // retrieve client IP from this HTTP header if present pub ping_interval_seconds: u32, } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Options { pub reject_future_seconds: Option, // if defined, reject any events with a timestamp more than X seconds in the future } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Retention { // TODO: implement pub max_events: Option, // max events pub max_bytes: Option, // max size pub persist_days: Option, // oldest message pub whitelist_addresses: Option>, // whitelisted addresses (never delete) } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Limits { pub messages_per_sec: Option, // Artificially slow down event writing to limit disk consumption (averaged over 1 minute) pub subscriptions_per_min: Option, // Artificially slow down request (db query) creation to prevent abuse (averaged over 1 minute) pub db_conns_per_client: Option, // How many concurrent database queries (not subscriptions) may a client have? pub max_blocking_threads: usize, pub max_event_bytes: Option, // Maximum size of an EVENT message pub max_ws_message_bytes: Option, pub max_ws_frame_bytes: Option, pub broadcast_buffer: usize, // events to buffer for subscribers (prevents slow readers from consuming memory) pub event_persist_buffer: usize, // events to buffer for database commits (block senders if database writes are too slow) pub event_kind_blacklist: Option> } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Authorization { pub pubkey_whitelist: Option>, // If present, only allow these pubkeys to publish events } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Diagnostics { pub tracing: bool, // enables tokio console-subscriber } #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy)] #[serde(rename_all = "lowercase")] pub enum VerifiedUsersMode { Enabled, Passive, Disabled, } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct VerifiedUsers { pub mode: VerifiedUsersMode, // Mode of operation: "enabled" (enforce) or "passive" (check only). If none, this is simply disabled. pub domain_whitelist: Option>, // If present, only allow verified users from these domains can publish events pub domain_blacklist: Option>, // If present, allow all verified users from any domain except these pub verify_expiration: Option, // how long a verification is cached for before no longer being used pub verify_update_frequency: Option, // how often to attempt to update verification pub verify_expiration_duration: Option, // internal result of parsing verify_expiration pub verify_update_frequency_duration: Option, // internal result of parsing verify_update_frequency pub max_consecutive_failures: usize, // maximum number of verification failures in a row, before ceasing future checks } impl VerifiedUsers { pub fn init(&mut self) { self.verify_expiration_duration = self.verify_expiration_duration(); self.verify_update_frequency_duration = self.verify_update_duration(); } #[must_use] pub fn is_enabled(&self) -> bool { self.mode == VerifiedUsersMode::Enabled } #[must_use] pub fn is_active(&self) -> bool { self.mode == VerifiedUsersMode::Enabled || self.mode == VerifiedUsersMode::Passive } #[must_use] pub fn is_passive(&self) -> bool { self.mode == VerifiedUsersMode::Passive } #[must_use] pub fn verify_expiration_duration(&self) -> Option { self.verify_expiration .as_ref() .and_then(|x| parse_duration::parse(x).ok()) } #[must_use] pub fn verify_update_duration(&self) -> Option { self.verify_update_frequency .as_ref() .and_then(|x| parse_duration::parse(x).ok()) } #[must_use] pub fn is_valid(&self) -> bool { self.verify_expiration_duration().is_some() && self.verify_update_duration().is_some() } } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Settings { pub info: Info, pub diagnostics: Diagnostics, pub database: Database, pub network: Network, pub limits: Limits, pub authorization: Authorization, pub verified_users: VerifiedUsers, pub retention: Retention, pub options: Options, } impl Settings { #[must_use] pub fn new() -> Self { let default_settings = Self::default(); // attempt to construct settings with file let from_file = Self::new_from_default(&default_settings); match from_file { Ok(f) => f, Err(e) => { warn!("Error reading config file ({:?})", e); default_settings } } } fn new_from_default(default: &Settings) -> Result { let builder = Config::builder(); let config: Config = builder // use defaults .add_source(Config::try_from(default)?) // override with file contents .add_source(File::with_name("config.toml")) .build()?; let mut settings: Settings = config.try_deserialize()?; // ensure connection pool size is logical assert!( settings.database.min_conn <= settings.database.max_conn, "Database min_conn setting ({}) cannot exceed max_conn ({})", settings.database.min_conn, settings.database.max_conn ); // ensure durations parse assert!( settings.verified_users.is_valid(), "VerifiedUsers time settings could not be parsed" ); // initialize durations for verified users settings.verified_users.init(); Ok(settings) } } impl Default for Settings { fn default() -> Self { Settings { info: Info { relay_url: None, name: Some("Unnamed nostr-rs-relay".to_owned()), description: None, pubkey: None, contact: None, }, diagnostics: Diagnostics { tracing: false }, database: Database { data_directory: ".".to_owned(), engine: "sqlite".to_owned(), in_memory: false, min_conn: 4, max_conn: 8, }, network: Network { port: 8080, ping_interval_seconds: 300, address: "0.0.0.0".to_owned(), remote_ip_header: None, }, limits: Limits { messages_per_sec: None, subscriptions_per_min: None, db_conns_per_client: None, max_blocking_threads: 16, max_event_bytes: Some(2 << 17), // 128K max_ws_message_bytes: Some(2 << 17), // 128K max_ws_frame_bytes: Some(2 << 17), // 128K broadcast_buffer: 16384, event_persist_buffer: 4096, event_kind_blacklist: None, }, authorization: Authorization { pubkey_whitelist: None, // Allow any address to publish }, verified_users: VerifiedUsers { mode: VerifiedUsersMode::Disabled, domain_whitelist: None, domain_blacklist: None, verify_expiration: Some("1 week".to_owned()), verify_update_frequency: Some("1 day".to_owned()), verify_expiration_duration: None, verify_update_frequency_duration: None, max_consecutive_failures: 20, }, retention: Retention { max_events: None, // max events max_bytes: None, // max size persist_days: None, // oldest message whitelist_addresses: None, // whitelisted addresses (never delete) }, options: Options { reject_future_seconds: None, // Reject events in the future if defined }, } } }