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