1use std::{
8 env,
9 net::{IpAddr, Ipv4Addr, SocketAddr},
10 path::{Path, PathBuf},
11 time::Duration,
12};
13
14use n0_error::{Result, StdResultExt};
15use serde::{Deserialize, Serialize};
16use tracing::info;
17
18use crate::store::Options;
19pub use crate::{
20 dns::DnsConfig,
21 http::{CertMode, HttpConfig, HttpsConfig, RateLimitConfig},
22};
23
24const DEFAULT_METRICS_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9117);
25
26#[derive(Debug, Serialize, Deserialize)]
31#[non_exhaustive]
32pub struct Config {
33 pub http: Option<HttpConfig>,
37 pub https: Option<HttpsConfig>,
41 pub dns: DnsConfig,
43 pub metrics: Option<MetricsConfig>,
48
49 pub mainline: Option<MainlineConfig>,
54
55 pub zone_store: Option<StoreConfig>,
59
60 #[serde(default)]
62 pub pkarr_put_rate_limit: RateLimitConfig,
63
64 pub data_dir: Option<PathBuf>,
69}
70
71#[derive(Debug, Serialize, Deserialize, Clone)]
73#[non_exhaustive]
74pub struct StoreConfig {
75 pub max_batch_size: usize,
77
78 #[serde(with = "humantime_serde")]
82 pub max_batch_time: Duration,
83
84 #[serde(with = "humantime_serde")]
86 pub eviction: Duration,
87
88 #[serde(with = "humantime_serde")]
90 pub eviction_interval: Duration,
91}
92
93impl Default for StoreConfig {
94 fn default() -> Self {
95 Options::default().into()
96 }
97}
98
99impl From<Options> for StoreConfig {
100 fn from(value: Options) -> Self {
101 Self {
102 max_batch_size: value.max_batch_size,
103 max_batch_time: value.max_batch_time,
104 eviction: value.eviction,
105 eviction_interval: value.eviction_interval,
106 }
107 }
108}
109
110impl From<StoreConfig> for Options {
111 fn from(value: StoreConfig) -> Self {
112 Self {
113 max_batch_size: value.max_batch_size,
114 max_batch_time: value.max_batch_time,
115 eviction: value.eviction,
116 eviction_interval: value.eviction_interval,
117 }
118 }
119}
120
121#[derive(Debug, Serialize, Deserialize)]
130#[non_exhaustive]
131pub struct MetricsConfig {
132 pub disabled: bool,
134 pub bind_addr: Option<SocketAddr>,
138}
139
140impl MetricsConfig {
141 pub fn disabled() -> Self {
143 Self {
144 disabled: true,
145 bind_addr: None,
146 }
147 }
148}
149
150#[derive(Debug, Serialize, Deserialize)]
155#[non_exhaustive]
156pub struct MainlineConfig {
157 pub enabled: bool,
159 pub bootstrap: Option<Vec<String>>,
165}
166
167#[derive(Debug, Serialize, Deserialize, Default)]
169pub(crate) enum BootstrapOption {
170 #[default]
172 Default,
173 Custom(Vec<String>),
175}
176
177#[allow(clippy::derivable_impls)]
178impl Default for MainlineConfig {
179 fn default() -> Self {
180 Self {
181 enabled: false,
182 bootstrap: None,
183 }
184 }
185}
186
187impl Config {
188 pub async fn load(path: impl AsRef<Path>) -> Result<Config> {
190 info!(
191 "loading config file from {}",
192 path.as_ref().to_string_lossy()
193 );
194 let s = tokio::fs::read_to_string(path.as_ref())
195 .await
196 .with_std_context(|_| format!("failed to read {}", path.as_ref().to_string_lossy()))?;
197 let config: Config = toml::from_str(&s).anyerr()?;
198 Ok(config)
199 }
200
201 pub fn data_dir(&self) -> Result<PathBuf> {
209 let dir = if let Some(dir) = &self.data_dir {
210 dir.clone()
211 } else if let Some(val) = env::var_os("IROH_DNS_DATA_DIR") {
212 PathBuf::from(val)
213 } else {
214 let path = dirs_next::data_dir()
215 .std_context("operating environment provides no directory for application data")?;
216
217 path.join("iroh-dns")
218 };
219 Ok(dir)
220 }
221
222 pub fn signed_packet_store_path(&self) -> Result<PathBuf> {
227 Ok(self.data_dir()?.join("signed-packets-1.db"))
228 }
229
230 pub(crate) fn metrics_addr(&self) -> Option<SocketAddr> {
232 match &self.metrics {
233 None => Some(DEFAULT_METRICS_ADDR),
234 Some(conf) => match conf.disabled {
235 true => None,
236 false => Some(conf.bind_addr.unwrap_or(DEFAULT_METRICS_ADDR)),
237 },
238 }
239 }
240
241 pub(crate) fn mainline_enabled(&self) -> Option<BootstrapOption> {
242 match self.mainline.as_ref() {
243 None => None,
244 Some(MainlineConfig { enabled: false, .. }) => None,
245 Some(MainlineConfig {
246 bootstrap: Some(bootstrap),
247 ..
248 }) => Some(BootstrapOption::Custom(bootstrap.clone())),
249 Some(MainlineConfig {
250 bootstrap: None, ..
251 }) => Some(BootstrapOption::Default),
252 }
253 }
254}
255
256impl Default for Config {
257 fn default() -> Self {
258 Self {
259 http: Some(HttpConfig {
260 port: 8080,
261 bind_addr: None,
262 }),
263 https: Some(HttpsConfig {
264 port: 8443,
265 bind_addr: None,
266 domains: vec!["localhost".to_string()],
267 cert_mode: CertMode::SelfSigned,
268 letsencrypt_contact: None,
269 letsencrypt_prod: None,
270 }),
271 dns: DnsConfig {
272 port: 5300,
273 bind_addr: None,
274 origins: vec!["irohdns.example.".to_string(), ".".to_string()],
275
276 default_soa: "irohdns.example hostmaster.irohdns.example 0 10800 3600 604800 3600"
277 .to_string(),
278 default_ttl: 900,
279
280 rr_a: Some(Ipv4Addr::LOCALHOST),
281 rr_aaaa: None,
282 rr_ns: Some("ns1.irohdns.example.".to_string()),
283 },
284 zone_store: None,
285 metrics: None,
286 mainline: None,
287 pkarr_put_rate_limit: RateLimitConfig::default(),
288 data_dir: None,
289 }
290 }
291}