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