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