nabla_cli/
config.rs

1use anyhow::Result;
2use serde::Deserialize;
3
4#[derive(Debug, Clone, Deserialize, PartialEq)]
5pub enum DeploymentType {
6    OSS,
7    Private,
8}
9
10impl std::str::FromStr for DeploymentType {
11    type Err = anyhow::Error;
12
13    fn from_str(s: &str) -> Result<Self> {
14        match s.to_lowercase().as_str() {
15            "oss" => Ok(DeploymentType::OSS),
16            "private" => Ok(DeploymentType::Private),
17            _ => Err(anyhow::anyhow!("Invalid deployment type: {}", s)),
18        }
19    }
20}
21
22#[derive(Debug, Clone, Deserialize)]
23pub struct Config {
24    pub port: u16,
25    pub base_url: String,
26    pub fips_mode: bool,
27    pub fips_validation: bool,
28    pub deployment_type: DeploymentType,
29    pub license_signing_key: String,
30}
31
32impl Default for Config {
33    fn default() -> Self {
34        Self {
35            port: 8080,
36            base_url: "http://localhost:8080".to_string(),
37            fips_mode: false,
38            fips_validation: false,
39            deployment_type: DeploymentType::OSS,
40            license_signing_key: "t6eLp6y0Ly8BZJIVv_wK71WyBtJ1zY2Pxz2M_0z5t8Q".to_string(),
41        }
42    }
43}
44
45impl Config {
46    pub fn from_env() -> Result<Self> {
47        dotenvy::dotenv().ok();
48
49        let deployment_type = std::env::var("NABLA_DEPLOYMENT")
50            .unwrap_or_else(|_| "oss".to_string())
51            .parse()?;
52
53        let license_signing_key = Self::get_license_signing_key(&deployment_type)?;
54        
55        let config = Config {
56            port: std::env::var("PORT")
57                .unwrap_or_else(|_| "8080".to_string())
58                .parse()?,
59            base_url: std::env::var("BASE_URL")
60                .unwrap_or_else(|_| "http://localhost:8080".to_string()),
61            fips_mode: std::env::var("FIPS_MODE")
62                .unwrap_or_else(|_| "false".to_string())
63                .parse()
64                .unwrap_or(false),
65            fips_validation: std::env::var("FIPS_VALIDATION")
66                .unwrap_or_else(|_| "false".to_string())
67                .parse()
68                .unwrap_or(false),
69            deployment_type,
70            license_signing_key,
71        };
72
73        Ok(config)
74    }
75
76    fn get_license_signing_key(deployment_type: &DeploymentType) -> Result<String> {
77        // Try environment variable first (fastest)
78        if let Ok(key) = std::env::var("LICENSE_SIGNING_KEY") {
79            return Ok(key);
80        }
81        
82        // Try Doppler API via HTTP for both OSS and Private deployments
83        if let (Ok(project), Ok(config_name)) = (
84            std::env::var("DOPPLER_PROJECT"),
85            std::env::var("DOPPLER_CONFIG")
86        ) {
87            // Try deployment-specific token first, then fall back to general token
88            let doppler_token = if config_name.contains("prd") {
89                std::env::var("DOPPLER_TOKEN_PRD")
90                    .or_else(|_| std::env::var("DOPPLER_TOKEN"))
91            } else if config_name.contains("oss") {
92                std::env::var("DOPPLER_TOKEN_OSS")
93                    .or_else(|_| std::env::var("DOPPLER_TOKEN"))
94            } else {
95                std::env::var("DOPPLER_TOKEN")
96            };
97            
98            if let Ok(token) = doppler_token {
99                
100                // Use ureq for sync HTTP requests (no runtime conflicts)
101                let url = format!("https://api.doppler.com/v3/configs/config/secret?project={}&config={}&name=LICENSE_SIGNING_KEY", 
102                    project, config_name);
103                
104                match ureq::get(&url)
105                    .set("Authorization", &format!("Bearer {}", token))
106                    .call() 
107            {
108                Ok(response) => {
109                    match response.into_json::<serde_json::Value>() {
110                        Ok(json) => {
111                            if let Some(value) = json.get("value")
112                                .and_then(|v| v.get("computed"))
113                                .and_then(|c| c.as_str()) 
114                            {
115                                return Ok(value.to_string());
116                            }
117                        }
118                        Err(_) => {}
119                    }
120                }
121                Err(_) => {}
122            }
123            }
124        }
125        
126        // Final fallback based on deployment type
127        match deployment_type {
128            DeploymentType::OSS => {
129                // Hardcoded public key for OSS deployments as last resort
130                Ok("t6eLp6y0Ly8BZJIVv_wK71WyBtJ1zY2Pxz2M_0z5t8Q".to_string())
131            }
132            DeploymentType::Private => {
133                Err(anyhow::anyhow!("LICENSE_SIGNING_KEY required for private deployment (try Doppler or env var)"))
134            }
135        }
136    }
137}