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