Skip to main content

systemprompt_models/profile/
from_env.rs

1use super::{
2    ContentNegotiationConfig, DatabaseConfig, ExtensionsConfig, PathsConfig, Profile, ProfileError,
3    ProfileResult, ProfileType, RateLimitsConfig, RuntimeConfig, SecurityConfig,
4    SecurityHeadersConfig, ServerConfig, SiteConfig, TierMultipliers, default_agent_registry,
5    default_agents, default_artifacts, default_burst, default_content, default_contexts,
6    default_mcp, default_mcp_registry, default_oauth_auth, default_oauth_public, default_stream,
7    default_tasks,
8};
9
10impl Profile {
11    pub fn from_env(profile_name: &str, display_name: &str) -> ProfileResult<Self> {
12        let db_type = require_env("DATABASE_TYPE")?;
13
14        Ok(Self {
15            name: profile_name.to_string(),
16            display_name: display_name.to_string(),
17            target: ProfileType::Cloud,
18            site: site_config_from_env()?,
19            database: DatabaseConfig {
20                db_type,
21                external_db_access: false,
22            },
23            server: server_config_from_env()?,
24            paths: paths_config_from_env()?,
25            security: security_config_from_env()?,
26            rate_limits: rate_limits_from_env(),
27            runtime: runtime_config_from_env()?,
28            cloud: None,
29            secrets: None,
30            extensions: ExtensionsConfig::default(),
31            gateway: None,
32            governance: None,
33        })
34    }
35}
36
37fn get_env(key: &str) -> Option<String> {
38    std::env::var(key).ok()
39}
40
41fn require_env(name: &'static str) -> ProfileResult<String> {
42    std::env::var(name).map_err(|_| ProfileError::MissingEnvVar { name })
43}
44
45fn site_config_from_env() -> ProfileResult<SiteConfig> {
46    Ok(SiteConfig {
47        name: require_env("SITENAME")?,
48        github_link: get_env("GITHUB_LINK"),
49    })
50}
51
52fn server_config_from_env() -> ProfileResult<ServerConfig> {
53    let port = require_env("PORT")?
54        .parse()
55        .map_err(|e: std::num::ParseIntError| ProfileError::InvalidEnvVar {
56            name: "PORT",
57            message: e.to_string(),
58        })?;
59
60    Ok(ServerConfig {
61        host: require_env("HOST")?,
62        port,
63        api_server_url: require_env("API_SERVER_URL")?,
64        api_internal_url: require_env("API_INTERNAL_URL")?,
65        api_external_url: require_env("API_EXTERNAL_URL")?,
66        use_https: get_env("USE_HTTPS").is_some_and(|v| v.to_lowercase() == "true"),
67        cors_allowed_origins: get_env("CORS_ALLOWED_ORIGINS").map_or_else(Vec::new, |s| {
68            s.split(',').map(|s| s.trim().to_string()).collect()
69        }),
70        content_negotiation: ContentNegotiationConfig {
71            enabled: get_env("CONTENT_NEGOTIATION_ENABLED")
72                .is_some_and(|v| v.to_lowercase() == "true"),
73            ..Default::default()
74        },
75        security_headers: SecurityHeadersConfig::default(),
76    })
77}
78
79fn paths_config_from_env() -> ProfileResult<PathsConfig> {
80    Ok(PathsConfig {
81        system: require_env("SYSTEM_PATH")?,
82        services: require_env("SYSTEMPROMPT_SERVICES_PATH")?,
83        bin: require_env("BIN_PATH")?,
84        storage: get_env("STORAGE_PATH"),
85        geoip_database: get_env("GEOIP_DATABASE_PATH"),
86        web_path: get_env("SYSTEMPROMPT_WEB_PATH"),
87    })
88}
89
90fn security_config_from_env() -> ProfileResult<SecurityConfig> {
91    use crate::auth::JwtAudience;
92
93    let issuer = require_env("JWT_ISSUER")?;
94
95    let access_token_expiration = require_env("JWT_ACCESS_TOKEN_EXPIRATION")?
96        .parse()
97        .map_err(|e: std::num::ParseIntError| ProfileError::InvalidEnvVar {
98            name: "JWT_ACCESS_TOKEN_EXPIRATION",
99            message: e.to_string(),
100        })?;
101
102    let refresh_token_expiration = require_env("JWT_REFRESH_TOKEN_EXPIRATION")?
103        .parse()
104        .map_err(|e: std::num::ParseIntError| ProfileError::InvalidEnvVar {
105            name: "JWT_REFRESH_TOKEN_EXPIRATION",
106            message: e.to_string(),
107        })?;
108
109    let audiences_raw = require_env("JWT_AUDIENCES")?;
110    let audiences = audiences_raw
111        .split(',')
112        .map(|s| {
113            s.trim()
114                .parse::<JwtAudience>()
115                .map_err(|e| ProfileError::InvalidEnvVar {
116                    name: "JWT_AUDIENCES",
117                    message: e.to_string(),
118                })
119        })
120        .collect::<ProfileResult<Vec<_>>>()?;
121
122    let allow_registration =
123        get_env("ALLOW_REGISTRATION").is_none_or(|s| s.eq_ignore_ascii_case("true"));
124
125    Ok(SecurityConfig {
126        issuer,
127        access_token_expiration,
128        refresh_token_expiration,
129        audiences,
130        allow_registration,
131    })
132}
133
134fn rate_limits_from_env() -> RateLimitsConfig {
135    let parse_rate = |key: &str, default: fn() -> u64| -> u64 {
136        get_env(key)
137            .and_then(|s| {
138                s.parse()
139                    .map_err(|e| {
140                        tracing::warn!(key = %key, value = %s, error = %e, "Failed to parse rate limit value");
141                        e
142                    })
143                    .ok()
144            })
145            .unwrap_or_else(default)
146    };
147
148    RateLimitsConfig {
149        disabled: get_env("RATE_LIMIT_DISABLED").is_some_and(|v| v.to_lowercase() == "true"),
150        oauth_public_per_second: parse_rate(
151            "RATE_LIMIT_OAUTH_PUBLIC_PER_SECOND",
152            default_oauth_public,
153        ),
154        oauth_auth_per_second: parse_rate("RATE_LIMIT_OAUTH_AUTH_PER_SECOND", default_oauth_auth),
155        contexts_per_second: parse_rate("RATE_LIMIT_CONTEXTS_PER_SECOND", default_contexts),
156        tasks_per_second: parse_rate("RATE_LIMIT_TASKS_PER_SECOND", default_tasks),
157        artifacts_per_second: parse_rate("RATE_LIMIT_ARTIFACTS_PER_SECOND", default_artifacts),
158        agent_registry_per_second: parse_rate(
159            "RATE_LIMIT_AGENT_REGISTRY_PER_SECOND",
160            default_agent_registry,
161        ),
162        agents_per_second: parse_rate("RATE_LIMIT_AGENTS_PER_SECOND", default_agents),
163        mcp_registry_per_second: parse_rate(
164            "RATE_LIMIT_MCP_REGISTRY_PER_SECOND",
165            default_mcp_registry,
166        ),
167        mcp_per_second: parse_rate("RATE_LIMIT_MCP_PER_SECOND", default_mcp),
168        stream_per_second: parse_rate("RATE_LIMIT_STREAM_PER_SECOND", default_stream),
169        content_per_second: parse_rate("RATE_LIMIT_CONTENT_PER_SECOND", default_content),
170        burst_multiplier: parse_rate("RATE_LIMIT_BURST_MULTIPLIER", default_burst),
171        tier_multipliers: TierMultipliers::default(),
172    }
173}
174
175fn runtime_config_from_env() -> ProfileResult<RuntimeConfig> {
176    let environment = get_env("SYSTEMPROMPT_ENV")
177        .unwrap_or_else(|| "development".to_string())
178        .parse()
179        .map_err(|e: String| ProfileError::InvalidEnvVar {
180            name: "SYSTEMPROMPT_ENV",
181            message: e,
182        })?;
183
184    let log_level = get_env("SYSTEMPROMPT_LOG_LEVEL")
185        .unwrap_or_else(|| "normal".to_string())
186        .parse()
187        .map_err(|e: String| ProfileError::InvalidEnvVar {
188            name: "SYSTEMPROMPT_LOG_LEVEL",
189            message: e,
190        })?;
191
192    let output_format = get_env("SYSTEMPROMPT_OUTPUT_FORMAT")
193        .unwrap_or_else(|| "text".to_string())
194        .parse()
195        .map_err(|e: String| ProfileError::InvalidEnvVar {
196            name: "SYSTEMPROMPT_OUTPUT_FORMAT",
197            message: e,
198        })?;
199
200    Ok(RuntimeConfig {
201        environment,
202        log_level,
203        output_format,
204        no_color: get_env("NO_COLOR").is_some(),
205        non_interactive: get_env("CI").is_some(),
206    })
207}