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