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