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