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