use crate::State; use anyhow::Context; use inotify::{EventMask, Inotify, WatchMask}; use log::{error, info}; use serde::{ de::{value, Error, SeqAccess, Visitor}, Deserialize, Deserializer, Serialize, }; use std::{ collections::{HashMap, HashSet}, fmt, fs::read_to_string, marker::PhantomData, net::SocketAddr, path::{Path, PathBuf}, sync::Arc, }; #[derive(Debug, Serialize, Deserialize)] pub struct Config { #[serde(default = "true_default")] pub watch_config: bool, pub http: Option, pub https: Option, #[serde(default)] pub limits: Limits, #[serde(default)] pub hosts: HashMap, } fn true_default() -> bool { true } #[derive(Debug, Serialize, Deserialize)] #[serde(default)] pub struct Limits { pub max_incoming_connections: usize, pub max_outgoing_connections: usize, } #[derive(Debug, Serialize, Deserialize)] pub struct HttpConfig { #[serde(deserialize_with = "string_or_seq")] pub bind: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct HttpsConfig { #[serde(deserialize_with = "string_or_seq")] pub bind: Vec, pub tls_cert: PathBuf, pub tls_key: PathBuf, } #[derive(Debug, Serialize, Deserialize)] pub struct Route(#[serde(deserialize_with = "seq_or_not")] pub Vec); #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RouteFilter { HttpBasicAuth { #[serde(flatten)] config: HttpBasicAuthConfig, }, Proxy { backend: SocketAddr, }, Files { #[serde(flatten)] config: FileserverConfig, }, AccessLog { #[serde(flatten)] config: AccessLogConfig, }, } #[derive(Debug, Serialize, Deserialize)] pub struct AccessLogConfig { pub file: PathBuf, #[serde(default)] pub flush: bool, #[serde(default)] pub reject_on_fail: bool, } #[derive(Debug, Serialize, Deserialize)] pub struct HttpBasicAuthConfig { pub realm: String, pub valid: HashSet, } #[derive(Debug, Serialize, Deserialize)] pub struct FileserverConfig { pub root: PathBuf, #[serde(default)] pub index: bool, } // try deser Vec but fall back to deser T and putting that in Vec fn seq_or_not<'de, D, V: Deserialize<'de>>(des: D) -> Result, D::Error> where D: Deserializer<'de>, { struct SeqOrNot(PhantomData); impl<'de, V: Deserialize<'de>> Visitor<'de> for SeqOrNot { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a sequence or not a sequence") } fn visit_enum(self, data: A) -> Result where A: serde::de::EnumAccess<'de>, { Ok(vec![V::deserialize(value::EnumAccessDeserializer::new( data, ))?]) } fn visit_map(self, map: A) -> Result where A: serde::de::MapAccess<'de>, { Ok(vec![V::deserialize(value::MapAccessDeserializer::new( map, ))?]) } fn visit_str(self, val: &str) -> Result, E> where E: Error, { Ok(vec![V::deserialize(value::StrDeserializer::new(val))?]) } fn visit_seq(self, val: A) -> Result, A::Error> where A: SeqAccess<'de>, { Vec::::deserialize(value::SeqAccessDeserializer::new(val)) } } des.deserialize_any(SeqOrNot::(PhantomData)) } // fall back to expecting a single string and putting that in a 1-length vector fn string_or_seq<'de, D>(des: D) -> Result, D::Error> where D: Deserializer<'de>, { struct StringOrList; impl<'de> Visitor<'de> for StringOrList { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("sequence or list") } fn visit_str(self, val: &str) -> Result, E> where E: Error, { let addr = SocketAddr::deserialize(value::StrDeserializer::new(val))?; Ok(vec![addr]) } fn visit_seq(self, val: A) -> Result, A::Error> where A: SeqAccess<'de>, { Vec::::deserialize(value::SeqAccessDeserializer::new(val)) } } des.deserialize_any(StringOrList) } impl Config { pub fn load(path: &Path) -> anyhow::Result { info!("loading config from {path:?}"); let raw = read_to_string(path).context("reading config file")?; let config: Config = serde_yaml::from_str(&raw).context("parsing config")?; Ok(config) } } impl Default for Limits { fn default() -> Self { Self { max_incoming_connections: 512, max_outgoing_connections: 256, } } } pub fn setup_file_watch(config_path: PathBuf, state: Arc) { std::thread::spawn(move || { let mut inotify = Inotify::init().unwrap(); inotify .watches() .add( config_path.parent().unwrap(), WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE, ) .unwrap(); let mut buffer = [0u8; 4096]; loop { let events = inotify .read_events_blocking(&mut buffer) .expect("Failed to read inotify events"); for event in events { if event.mask.contains(EventMask::MODIFY) { match Config::load(&config_path) { Ok(conf) => { let mut r = state.config.blocking_write(); *r = Arc::new(conf) } Err(e) => error!("config has errors: {e}"), } } } } }); }