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