An ATProtocol powered blogging engine.
at main 5.6 kB view raw
1use serde::{Deserialize, Serialize}; 2use std::time::Duration; 3 4use crate::errors::{BlahgError, Result}; 5 6/// Application configuration loaded from environment variables. 7#[derive(Debug, Clone, Serialize, Deserialize)] 8pub struct Config { 9 /// HTTP server configuration 10 pub http: HttpConfig, 11 /// Base URL for external links and content 12 pub external_base: String, 13 /// Certificate bundles for TLS verification 14 pub certificate_bundles: Vec<String>, 15 /// User agent string for HTTP requests 16 pub user_agent: String, 17 /// Hostname for PLC directory lookups 18 pub plc_hostname: String, 19 /// DNS nameservers for resolution 20 pub dns_nameservers: Vec<std::net::IpAddr>, 21 /// HTTP client timeout duration 22 pub http_client_timeout: Duration, 23 /// Author name for the blog 24 pub author: String, 25 /// Storage path for attachments 26 pub attachment_storage: String, 27 /// Database connection URL 28 pub database_url: String, 29 /// Path to jetstream cursor file 30 pub jetstream_cursor_path: Option<String>, 31 /// Whether to enable jetstream event consumption 32 pub enable_jetstream: bool, 33 /// Syntect theme for syntax highlighting 34 pub syntect_theme: String, 35} 36 37/// HTTP server configuration options. 38#[derive(Debug, Clone, Serialize, Deserialize)] 39pub struct HttpConfig { 40 /// Port to bind the HTTP server to 41 pub port: u16, 42 /// Path to static assets 43 pub static_path: String, 44 /// Path to template files 45 pub templates_path: String, 46} 47 48impl Default for Config { 49 fn default() -> Self { 50 Self { 51 http: HttpConfig::default(), 52 external_base: "http://localhost:8080".to_string(), 53 certificate_bundles: Vec::new(), 54 user_agent: format!( 55 "Blahg/{} (+https://tangled.sh/@smokesignal.events/blahg)", 56 env!("CARGO_PKG_VERSION") 57 ), 58 plc_hostname: "plc.directory".to_string(), 59 dns_nameservers: Vec::new(), 60 http_client_timeout: Duration::from_secs(10), 61 author: String::new(), 62 attachment_storage: "./attachments".to_string(), 63 database_url: "sqlite://blahg.db".to_string(), 64 jetstream_cursor_path: None, 65 enable_jetstream: true, 66 syntect_theme: std::env::var("SYNTECT_THEME").unwrap_or_else(|_| "base16-ocean.dark".to_string()), 67 } 68 } 69} 70 71impl Default for HttpConfig { 72 fn default() -> Self { 73 Self { 74 port: 8080, 75 static_path: format!("{}/static", env!("CARGO_MANIFEST_DIR")), 76 templates_path: format!("{}/templates", env!("CARGO_MANIFEST_DIR")), 77 } 78 } 79} 80 81impl Config { 82 /// Create configuration from environment variables. 83 pub fn from_env() -> Result<Self> { 84 let mut config = Self::default(); 85 86 if let Ok(port) = std::env::var("HTTP_PORT") { 87 config.http.port = port 88 .parse() 89 .map_err(|_| BlahgError::ConfigHttpPortInvalid { port: port.clone() })?; 90 } 91 92 if let Ok(static_path) = std::env::var("HTTP_STATIC_PATH") { 93 config.http.static_path = static_path; 94 } 95 96 if let Ok(templates_path) = std::env::var("HTTP_TEMPLATES_PATH") { 97 config.http.templates_path = templates_path; 98 } 99 100 if let Ok(external_base) = std::env::var("EXTERNAL_BASE") { 101 config.external_base = external_base; 102 } 103 104 if let Ok(cert_bundles) = std::env::var("CERTIFICATE_BUNDLES") { 105 config.certificate_bundles = cert_bundles 106 .split(';') 107 .map(|s| s.trim().to_string()) 108 .filter(|s| !s.is_empty()) 109 .collect(); 110 } 111 112 if let Ok(user_agent) = std::env::var("USER_AGENT") { 113 config.user_agent = user_agent; 114 } 115 116 if let Ok(plc_hostname) = std::env::var("PLC_HOSTNAME") { 117 config.plc_hostname = plc_hostname; 118 } 119 120 if let Ok(dns_nameservers) = std::env::var("DNS_NAMESERVERS") { 121 config.dns_nameservers = dns_nameservers 122 .split(',') 123 .map(|s| s.trim()) 124 .filter(|s| !s.is_empty()) 125 .map(|s| { 126 s.parse::<std::net::IpAddr>() 127 .map_err(|e| BlahgError::NameserverParsingFailed(s.to_string(), e)) 128 }) 129 .collect::<Result<Vec<std::net::IpAddr>>>()?; 130 } 131 132 if let Ok(timeout) = std::env::var("HTTP_CLIENT_TIMEOUT") { 133 config.http_client_timeout = duration_str::parse(&timeout).map_err(|e| { 134 BlahgError::ConfigHttpTimeoutInvalid { 135 details: e.to_string(), 136 } 137 })?; 138 } 139 140 if let Ok(author) = std::env::var("AUTHOR") { 141 config.author = author; 142 } 143 144 if let Ok(attachment_storage) = std::env::var("ATTACHMENT_STORAGE") { 145 config.attachment_storage = attachment_storage; 146 } 147 148 if let Ok(database_url) = std::env::var("DATABASE_URL") { 149 config.database_url = database_url; 150 } 151 152 if let Ok(jetstream_cursor_path) = std::env::var("JETSTREAM_CURSOR_PATH") { 153 config.jetstream_cursor_path = Some(jetstream_cursor_path); 154 } 155 156 if let Ok(enable_jetstream) = std::env::var("ENABLE_JETSTREAM") { 157 config.enable_jetstream = enable_jetstream.parse().unwrap_or(true); 158 } 159 160 if let Ok(syntect_theme) = std::env::var("SYNTECT_THEME") { 161 config.syntect_theme = syntect_theme; 162 } 163 164 Ok(config) 165 } 166}