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