hashtree_client/
config.rs1use anyhow::{ensure, Result};
2use hashtree_config::Config;
3use std::time::Duration;
4
5#[derive(Clone)]
6pub struct ClientConfig {
7 pub daemon_url: Option<String>,
8 pub local_only: bool,
9 pub relays: Vec<String>,
10 pub read_servers: Vec<String>,
11 pub resolve_window: Duration,
12 pub request_timeout: Duration,
13}
14
15fn flag(name: &str) -> Option<bool> {
16 std::env::var(name).ok().map(|v| {
17 !matches!(
18 v.trim().to_ascii_lowercase().as_str(),
19 "0" | "false" | "no" | "off"
20 )
21 })
22}
23
24impl ClientConfig {
25 pub fn from_env() -> Result<Self> {
27 let path = hashtree_config::get_config_path();
28 let config: Config = match std::fs::read_to_string(path) {
29 Ok(text) => toml::from_str(&text)?,
30 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Config::default(),
31 Err(e) => return Err(e.into()),
32 };
33 let local_only = flag("HTREE_LOCAL_DAEMON_ONLY").unwrap_or(false);
34 let prefer_local = local_only || flag("HTREE_PREFER_LOCAL_DAEMON").unwrap_or(true);
35 let port = config
36 .server
37 .bind_address
38 .rsplit_once(':')
39 .and_then(|(_, p)| p.parse::<u16>().ok())
40 .unwrap_or(8080);
41 let daemon_url = prefer_local.then(|| {
42 std::env::var("HTREE_DAEMON_URL").unwrap_or_else(|_| format!("http://127.0.0.1:{port}"))
43 });
44 if let Some(value) = &daemon_url {
45 let url = reqwest::Url::parse(value)?;
46 ensure!(
47 url.scheme() == "http"
48 && url.username().is_empty()
49 && url.password().is_none()
50 && url.query().is_none()
51 && url.fragment().is_none()
52 && url.path() == "/"
53 && url.host_str().is_some_and(|h| h == "localhost"
54 || h.parse::<std::net::IpAddr>()
55 .is_ok_and(|ip| ip.is_loopback())),
56 "HTREE_DAEMON_URL must be a loopback HTTP origin"
57 );
58 }
59 let relays = std::env::var("NOSTR_RELAYS")
60 .ok()
61 .map(|v| {
62 v.split(',')
63 .map(str::trim)
64 .filter(|v| !v.is_empty())
65 .map(str::to_string)
66 .collect()
67 })
68 .unwrap_or(config.nostr.relays);
69 let mut read_servers = config.blossom.read_servers;
70 read_servers.extend(config.blossom.servers);
71 Ok(Self {
72 daemon_url: daemon_url.map(|s| s.trim_end_matches('/').to_string()),
73 local_only,
74 relays,
75 read_servers,
76 resolve_window: Duration::from_secs(3),
77 request_timeout: Duration::from_secs(30),
78 })
79 }
80}