use serde::{Deserialize, Serialize}; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, path::Path, }; const CONFIG_FILE: &str = "config.toml"; #[derive(Deserialize, Serialize)] pub struct Config { #[serde(default = "Config::default_admin_passwords")] pub admin_passwords: Vec, /// (seconds) #[serde(default = "Config::default_antispam_duration")] pub antispam_duration: u64, #[serde(default = "Config::default_antispam_enable")] pub antispam_enable: bool, /// Maximum number of mutations by IP within antispam_duration #[serde(default = "Config::default_antispam_mutation_limit")] pub antispam_mutation_limit: u32, #[serde(default = "Config::default_antispam_whitelist")] pub antispam_whitelist: Vec, /// New or edited comments need admin's approval before being public #[serde(default = "Config::default_comment_approve")] pub comment_approve: bool, /// Duration for which a comment can be edited #[serde(default = "Config::default_comment_edit_timeout")] pub comment_edit_timeout: u64, #[serde(default = "Config::default_comment_author_max_len")] pub comment_author_max_len: usize, #[serde(default = "Config::default_comment_email_max_len")] pub comment_email_max_len: usize, #[serde(default = "Config::default_comment_text_max_len")] pub comment_text_max_len: usize, #[serde(default = "Config::default_cookies_https_only")] pub cookies_https_only: bool, #[serde(default = "Config::default_cookies_domain")] pub cookies_domain: Option, #[serde(default = "Config::default_cors_allow_origin")] pub cors_allow_origin: String, /// Format: "language_REGION" #[serde(default = "Config::default_default_lang")] pub default_lang: String, #[serde(default = "Config::default_listen")] pub listen: SocketAddr, /// Send a matrix message on new comment #[serde(default = "Config::default_matrix_notify")] pub matrix_notify: bool, #[serde(default = "Config::default_matrix_password")] pub matrix_password: String, /// If connection fails, retry connecting after minimum this duration (seconds) #[serde(default = "Config::default_matrix_retry_timeout")] pub matrix_retry_timeout: u64, #[serde(default = "Config::default_matrix_room")] pub matrix_room: String, #[serde(default = "Config::default_matrix_server")] pub matrix_server: String, #[serde(default = "Config::default_matrix_user")] pub matrix_user: String, /// Our address, with protocol and hostname but without path or trailing `/` /// It is used to make hyperlinks that should be clickable from anywhere. #[serde(default = "Config::default_public_address")] pub public_address: String, /// Are we behind a reverse proxy? /// Determines whether we assume client address is in a Forwarded header or socket address. #[serde(default = "Config::default_reverse_proxy")] pub reverse_proxy: bool, /// Our root url path (without protocol and hostname) /// Useful when used behind a reverse proxy in a pseudo-subdirectory. /// Should start and end with `/`. #[serde(default = "Config::default_root_url")] pub root_url: String, /// Templates directory. May be absolute or relative to config/data directory. #[serde(default = "Config::default_templates_dir")] pub templates_dir: String, } impl Config { fn default_admin_passwords() -> Vec { vec![] } fn default_antispam_duration() -> u64 { 3600 } fn default_antispam_enable() -> bool { true } fn default_antispam_mutation_limit() -> u32 { 10 } fn default_antispam_whitelist() -> Vec { vec![[127u8, 0, 0, 1].into(), [0u8; 4].into(), [0u8; 16].into()] } fn default_comment_approve() -> bool { true } fn default_comment_edit_timeout() -> u64 { 7 * 86400 } fn default_comment_author_max_len() -> usize { 64 } fn default_comment_email_max_len() -> usize { 128 } fn default_comment_text_max_len() -> usize { 128 * 1024 } fn default_cookies_https_only() -> bool { false } fn default_cookies_domain() -> Option { None } fn default_cors_allow_origin() -> String { "*".into() } fn default_default_lang() -> String { "en_US".into() } fn default_listen() -> SocketAddr { SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 31720) } fn default_matrix_notify() -> bool { false } fn default_matrix_password() -> String { "".into() } fn default_matrix_retry_timeout() -> u64 { 3600 } fn default_matrix_room() -> String { "#maintenance:matrix.txmn.tk".into() } fn default_matrix_server() -> String { "https://matrix.txmn.tk".into() } fn default_matrix_user() -> String { "@tuxmain:matrix.txmn.tk".into() } fn default_public_address() -> String { "http://127.0.0.1:31720".into() } fn default_reverse_proxy() -> bool { false } fn default_root_url() -> String { "/".into() } fn default_templates_dir() -> String { "templates".into() } } impl Default for Config { fn default() -> Self { Self { admin_passwords: Self::default_admin_passwords(), antispam_duration: Self::default_antispam_duration(), antispam_enable: Self::default_antispam_enable(), antispam_mutation_limit: Self::default_antispam_mutation_limit(), antispam_whitelist: Self::default_antispam_whitelist(), comment_approve: Self::default_comment_approve(), comment_edit_timeout: Self::default_comment_edit_timeout(), comment_author_max_len: Self::default_comment_author_max_len(), comment_email_max_len: Self::default_comment_email_max_len(), comment_text_max_len: Self::default_comment_text_max_len(), cookies_https_only: Self::default_cookies_https_only(), cookies_domain: Self::default_cookies_domain(), cors_allow_origin: Self::default_cors_allow_origin(), default_lang: Self::default_default_lang(), listen: Self::default_listen(), matrix_notify: Self::default_matrix_notify(), matrix_password: Self::default_matrix_password(), matrix_retry_timeout: Self::default_matrix_retry_timeout(), matrix_room: Self::default_matrix_room(), matrix_server: Self::default_matrix_server(), matrix_user: Self::default_matrix_user(), public_address: Self::default_public_address(), reverse_proxy: Self::default_reverse_proxy(), root_url: Self::default_root_url(), templates_dir: Self::default_templates_dir(), } } } pub fn read_config(dir: &Path) -> Config { let path = dir.join(CONFIG_FILE); if !path.is_file() { let config = Config::default(); std::fs::write(path, toml_edit::easy::to_string_pretty(&config).unwrap()) .expect("Cannot write config file"); config } else { toml_edit::easy::from_str( std::str::from_utf8(&std::fs::read(path).expect("Cannot read config file")) .expect("Bad encoding in config file"), ) .expect("Bad TOML in config file") } } pub fn write_config(dir: &Path, config: &Config) { let path = dir.join(CONFIG_FILE); std::fs::write(path, toml_edit::easy::to_string_pretty(&config).unwrap()) .expect("Cannot write config file"); }