Skip to main content

cdk_mintd/env_vars/
mod.rs

1#![allow(missing_docs)]
2//! Environment variables module
3//!
4//! This module contains all environment variable definitions and parsing logic
5//! organized by component.
6
7mod common;
8mod database;
9mod info;
10mod limits;
11mod mint_info;
12mod onchain;
13mod payment_backend;
14mod signatory;
15
16mod auth;
17#[cfg(feature = "bdk")]
18mod bdk;
19#[cfg(feature = "cln")]
20mod cln;
21#[cfg(feature = "fakewallet")]
22mod fake_wallet;
23#[cfg(feature = "grpc-processor")]
24mod grpc_processor;
25#[cfg(feature = "ldk-node")]
26mod ldk_node;
27#[cfg(feature = "lnd")]
28mod lnd;
29#[cfg(feature = "management-rpc")]
30mod management_rpc;
31#[cfg(feature = "prometheus")]
32mod prometheus;
33
34use std::env;
35use std::str::FromStr;
36
37use anyhow::{anyhow, bail, Result};
38pub use auth::*;
39#[cfg(feature = "bdk")]
40pub use bdk::*;
41#[cfg(feature = "cln")]
42pub use cln::*;
43pub use common::*;
44pub use database::*;
45#[cfg(feature = "fakewallet")]
46pub use fake_wallet::*;
47#[cfg(feature = "grpc-processor")]
48pub use grpc_processor::*;
49#[cfg(feature = "ldk-node")]
50pub use ldk_node::*;
51pub use limits::*;
52#[cfg(feature = "lnd")]
53pub use lnd::*;
54#[cfg(feature = "management-rpc")]
55pub use management_rpc::*;
56pub use mint_info::*;
57pub use onchain::*;
58pub use payment_backend::*;
59#[cfg(feature = "prometheus")]
60pub use prometheus::*;
61
62use crate::config::{DatabaseEngine, OnchainBackend, PaymentBackend, PaymentBackendType, Settings};
63
64// Security-sensitive opt-ins must not retain a configured `true` when an
65// override is present but malformed, including non-Unicode environment values.
66fn bool_override(name: &str) -> Option<bool> {
67    env::var_os(name).map(
68        |value| match value.to_str().and_then(|value| value.parse().ok()) {
69            Some(value) => value,
70            None => {
71                tracing::warn!("Invalid boolean environment override {name}; using false");
72                false
73            }
74        },
75    )
76}
77
78impl Settings {
79    pub fn from_env(&mut self) -> Result<Self> {
80        if let Ok(database) = env::var(DATABASE_ENV_VAR) {
81            let engine = DatabaseEngine::from_str(&database).map_err(|err| anyhow!(err))?;
82            self.database.engine = engine;
83        }
84
85        // Parse PostgreSQL-specific configuration from environment variables
86        if self.database.engine == DatabaseEngine::Postgres {
87            self.database.postgres = Some(
88                self.database
89                    .postgres
90                    .clone()
91                    .unwrap_or_default()
92                    .from_env(),
93            );
94        }
95
96        // Parse auth database configuration from environment variables
97        self.auth_database = Some(crate::config::AuthDatabase {
98            postgres: Some(
99                self.auth_database
100                    .clone()
101                    .unwrap_or_default()
102                    .postgres
103                    .unwrap_or_default()
104                    .from_env(),
105            ),
106        });
107
108        self.info = self.info.clone().from_env();
109        self.signatory = Some(self.signatory.clone().unwrap_or_default().from_env());
110
111        self.mint_info = self.mint_info.clone().from_env();
112        // CDK_MINTD_PAYMENT_BACKEND_* env vars only apply when there is exactly
113        // one configured payment backend entry. Multi-backend setups must
114        // choose units and backends in the config file so env overrides do not
115        // collapse them.
116        match self.payment_backend.len() {
117            0 => {
118                let backend = PaymentBackend::default().from_env();
119                if backend.backend != PaymentBackendType::None {
120                    self.payment_backend.push(backend);
121                }
122            }
123            1 => {
124                self.payment_backend[0] = self.payment_backend[0].clone().from_env();
125            }
126            _ => {
127                tracing::warn!(
128                    "CDK_MINTD_PAYMENT_BACKEND_* environment variables ignored: multiple [[payment_backend]] entries configured"
129                );
130            }
131        }
132        self.onchain = Some(self.onchain.clone().unwrap_or_default().from_env());
133        self.limits = self.limits.clone().from_env();
134
135        {
136            // Check env vars for auth config even if None
137            let auth = self.auth.clone().unwrap_or_default().from_env();
138
139            // Only set auth if auth_enabled flag is true
140            if auth.auth_enabled {
141                self.auth = Some(auth);
142            } else {
143                self.auth = None;
144            }
145        }
146
147        #[cfg(feature = "management-rpc")]
148        {
149            self.mint_management_rpc = Some(
150                self.mint_management_rpc
151                    .clone()
152                    .unwrap_or_default()
153                    .from_env(),
154            );
155        }
156
157        #[cfg(feature = "prometheus")]
158        {
159            self.prometheus = Some(self.prometheus.clone().unwrap_or_default().from_env());
160        }
161
162        #[cfg(feature = "cln")]
163        {
164            let cln = self.cln.clone().unwrap_or_default().from_env();
165            if cln.rpc_path.as_os_str().is_empty() {
166                self.cln = None;
167            } else {
168                self.cln = Some(cln);
169            }
170        }
171
172        #[cfg(feature = "fakewallet")]
173        {
174            // Fake wallet has defaults so it is always Some if feature enabled
175            let fake_wallet_supported_units_from_env =
176                env::var(ENV_FAKE_WALLET_SUPPORTED_UNITS).is_ok();
177            let fake_wallet = self.fake_wallet.clone().unwrap_or_default().from_env();
178            let supported_units_configured =
179                fake_wallet.supported_units != vec![cdk::nuts::CurrencyUnit::Sat];
180
181            if fake_wallet_supported_units_from_env || supported_units_configured {
182                self.expand_single_fake_wallet_backend_entry(&fake_wallet);
183            }
184
185            self.fake_wallet = Some(fake_wallet);
186        }
187
188        #[cfg(feature = "lnd")]
189        {
190            let lnd = self.lnd.clone().unwrap_or_default().from_env()?;
191            if lnd.address.is_empty() {
192                self.lnd = None;
193            } else {
194                self.lnd = Some(lnd);
195            }
196        }
197
198        #[cfg(feature = "ldk-node")]
199        {
200            let ldk_node = self.ldk_node.clone().unwrap_or_default().from_env();
201            if ldk_node.bitcoin_network.is_none()
202                && ldk_node.esplora_url.is_none()
203                && ldk_node.electrum_url.is_none()
204            {
205                self.ldk_node = None;
206            } else {
207                self.ldk_node = Some(ldk_node);
208            }
209        }
210
211        #[cfg(feature = "grpc-processor")]
212        {
213            let grpc_processor = self.grpc_processor.clone().unwrap_or_default().from_env();
214            let grpc_processor_configured = self
215                .payment_backend
216                .iter()
217                .any(|backend| backend.backend == PaymentBackendType::GrpcProcessor);
218            if grpc_processor.supported_units.is_empty() && !grpc_processor_configured {
219                self.grpc_processor = None;
220            } else {
221                self.grpc_processor = Some(grpc_processor);
222            }
223        }
224
225        #[cfg(feature = "bdk")]
226        {
227            let bdk = self.bdk.clone().unwrap_or_default().from_env();
228            if bdk.network.is_none() && bdk.mnemonic.is_none() {
229                self.bdk = None;
230            } else {
231                self.bdk = Some(bdk);
232            }
233        }
234
235        for payment_backend in &self.payment_backend {
236            match payment_backend.backend {
237                #[cfg(feature = "cln")]
238                PaymentBackendType::Cln => {}
239                #[cfg(feature = "fakewallet")]
240                PaymentBackendType::FakeWallet => {}
241                #[cfg(feature = "lnd")]
242                PaymentBackendType::Lnd => {}
243                #[cfg(feature = "ldk-node")]
244                PaymentBackendType::LdkNode => {}
245                #[cfg(feature = "grpc-processor")]
246                PaymentBackendType::GrpcProcessor => {}
247                PaymentBackendType::None => {}
248                #[allow(unreachable_patterns)]
249                _ => bail!("Selected payment backend is not enabled in this build"),
250            }
251        }
252
253        let has_payment_backend = self
254            .payment_backend
255            .iter()
256            .any(|backend| backend.backend != PaymentBackendType::None);
257        let has_onchain_backend = self
258            .onchain
259            .as_ref()
260            .map(|onchain| onchain.onchain_backend != OnchainBackend::None)
261            .unwrap_or(false);
262        if !has_payment_backend && !has_onchain_backend {
263            bail!("At least one payment backend must be set");
264        }
265
266        self.validate_backend_pairing()
267            .map_err(|err| anyhow!(err))?;
268
269        Ok(self.clone())
270    }
271
272    #[cfg(feature = "fakewallet")]
273    fn expand_single_fake_wallet_backend_entry(&mut self, fake_wallet: &crate::config::FakeWallet) {
274        let fake_wallet_backend_index = self
275            .payment_backend
276            .iter()
277            .enumerate()
278            .filter_map(|(index, backend)| {
279                (backend.backend == PaymentBackendType::FakeWallet).then_some(index)
280            })
281            .collect::<Vec<_>>();
282
283        if fake_wallet_backend_index.len() != 1 {
284            return;
285        }
286
287        let mut units = Vec::new();
288        for unit in &fake_wallet.supported_units {
289            if !units.contains(unit) {
290                units.push(unit.clone());
291            }
292        }
293
294        if units.is_empty() {
295            return;
296        }
297
298        let index = fake_wallet_backend_index[0];
299        let base_backend = self.payment_backend[index].clone();
300        let expanded_backends = units.into_iter().map(|unit| PaymentBackend {
301            unit,
302            ..base_backend.clone()
303        });
304
305        self.payment_backend
306            .splice(index..=index, expanded_backends);
307    }
308}