systemprompt_models/profile/
from_env.rs1use 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}