systemprompt_models/config/
mod.rs1use std::path::PathBuf;
12use std::sync::OnceLock;
13use systemprompt_traits::ConfigProvider;
14
15use crate::auth::JwtAudience;
16use crate::profile::{ContentNegotiationConfig, SecurityHeadersConfig, TrustedIssuer};
17
18mod paths;
19mod rate_limits;
20mod validation;
21
22pub use paths::PathNotConfiguredError;
23pub use rate_limits::RateLimitConfig;
24pub use validation::validate_postgres_url;
25
26static CONFIG: OnceLock<Config> = OnceLock::new();
27
28pub const DEFAULT_MAX_CONCURRENT_STREAMS: usize = 256;
29
30#[must_use]
31pub fn stable_instance_id(lookup: impl Fn(&str) -> Option<String>) -> Option<String> {
32 lookup("HOSTNAME")
33 .map(|h| h.trim().to_owned())
34 .filter(|h| !h.is_empty())
35}
36
37#[must_use]
38pub fn random_instance_id() -> String {
39 format!("instance-{}", uuid::Uuid::new_v4().simple())
40}
41
42#[derive(Clone)]
43pub struct Config {
44 pub instance_id: String,
45 pub metrics_port: Option<u16>,
46 pub max_concurrent_streams: usize,
47 pub sitename: String,
48 pub database_type: String,
49 pub database_url: String,
50 pub database_write_url: Option<String>,
51 pub github_link: String,
52 pub github_token: Option<String>,
53 pub system_path: String,
54 pub services_path: String,
55 pub bin_path: String,
56 pub skills_path: String,
57 pub settings_path: String,
58 pub content_config_path: String,
59 pub geoip_database_path: Option<String>,
60 pub web_path: String,
61 pub web_config_path: String,
62 pub web_metadata_path: String,
63 pub host: String,
64 pub port: u16,
65 pub api_server_url: String,
66 pub api_internal_url: String,
67 pub api_external_url: String,
68 pub jwt_issuer: String,
69 pub jwt_access_token_expiration: i64,
70 pub jwt_refresh_token_expiration: i64,
71 pub jwt_audiences: Vec<JwtAudience>,
72 pub allowed_resource_audiences: Vec<String>,
73 pub trusted_issuers: Vec<TrustedIssuer>,
74 pub id_jag_ttl_secs: i64,
75 pub signing_key_path: PathBuf,
76 pub use_https: bool,
77 pub rate_limits: RateLimitConfig,
78 pub cors_allowed_origins: Vec<String>,
79 pub trusted_proxies: Vec<ipnet::IpNet>,
80 pub is_cloud: bool,
81 pub content_negotiation: ContentNegotiationConfig,
82 pub security_headers: SecurityHeadersConfig,
83 pub allow_registration: bool,
84 pub allow_dynamic_client_registration: bool,
85 pub login_page_url: Option<String>,
86 pub system_admin_username: String,
87 pub system_admin_email: Option<systemprompt_identifiers::Email>,
88}
89
90const REDACTED: &str = "<redacted>";
91
92impl std::fmt::Debug for Config {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.debug_struct("Config")
97 .field("instance_id", &self.instance_id)
98 .field("metrics_port", &self.metrics_port)
99 .field("max_concurrent_streams", &self.max_concurrent_streams)
100 .field("sitename", &self.sitename)
101 .field("database_type", &self.database_type)
102 .field("database_url", &REDACTED)
103 .field("database_write_url", &REDACTED)
104 .field("github_link", &self.github_link)
105 .field("github_token", &REDACTED)
106 .field("system_path", &self.system_path)
107 .field("services_path", &self.services_path)
108 .field("bin_path", &self.bin_path)
109 .field("skills_path", &self.skills_path)
110 .field("settings_path", &self.settings_path)
111 .field("content_config_path", &self.content_config_path)
112 .field("geoip_database_path", &self.geoip_database_path)
113 .field("web_path", &self.web_path)
114 .field("web_config_path", &self.web_config_path)
115 .field("web_metadata_path", &self.web_metadata_path)
116 .field("host", &self.host)
117 .field("port", &self.port)
118 .field("api_server_url", &self.api_server_url)
119 .field("api_internal_url", &self.api_internal_url)
120 .field("api_external_url", &self.api_external_url)
121 .field("jwt_issuer", &self.jwt_issuer)
122 .field(
123 "jwt_access_token_expiration",
124 &self.jwt_access_token_expiration,
125 )
126 .field(
127 "jwt_refresh_token_expiration",
128 &self.jwt_refresh_token_expiration,
129 )
130 .field("jwt_audiences", &self.jwt_audiences)
131 .field(
132 "allowed_resource_audiences",
133 &self.allowed_resource_audiences,
134 )
135 .field("trusted_issuers", &self.trusted_issuers)
136 .field("id_jag_ttl_secs", &self.id_jag_ttl_secs)
137 .field("signing_key_path", &self.signing_key_path)
138 .field("use_https", &self.use_https)
139 .field("rate_limits", &self.rate_limits)
140 .field("cors_allowed_origins", &self.cors_allowed_origins)
141 .field("trusted_proxies", &self.trusted_proxies)
142 .field("is_cloud", &self.is_cloud)
143 .field("content_negotiation", &self.content_negotiation)
144 .field("security_headers", &self.security_headers)
145 .field("allow_registration", &self.allow_registration)
146 .field(
147 "allow_dynamic_client_registration",
148 &self.allow_dynamic_client_registration,
149 )
150 .field("login_page_url", &self.login_page_url)
151 .field("system_admin_username", &self.system_admin_username)
152 .field("system_admin_email", &self.system_admin_email)
153 .finish()
154 }
155}
156
157impl Config {
158 pub fn is_initialized() -> bool {
159 CONFIG.get().is_some()
160 }
161
162 pub fn get() -> Result<&'static Self, crate::errors::ConfigError> {
163 CONFIG
164 .get()
165 .ok_or(crate::errors::ConfigError::NotInitialized)
166 }
167
168 pub fn install(config: Self) -> Result<(), Box<Self>> {
169 CONFIG.set(config).map_err(Box::new)
170 }
171
172 pub fn logs_path(&self) -> String {
173 format!("{}/logs", self.system_path)
174 }
175}
176
177impl ConfigProvider for Config {
178 fn get(&self, key: &str) -> Option<String> {
179 match key {
180 "database_type" => Some(self.database_type.clone()),
181 "database_url" => Some(self.database_url.clone()),
182 "database_write_url" => self.database_write_url.clone(),
183 "host" => Some(self.host.clone()),
184 "port" => Some(self.port.to_string()),
185 "system_path" => Some(self.system_path.clone()),
186 "services_path" => Some(self.services_path.clone()),
187 "bin_path" => Some(self.bin_path.clone()),
188 "skills_path" => Some(self.skills_path.clone()),
189 "settings_path" => Some(self.settings_path.clone()),
190 "content_config_path" => Some(self.content_config_path.clone()),
191 "web_path" => Some(self.web_path.clone()),
192 "web_config_path" => Some(self.web_config_path.clone()),
193 "web_metadata_path" => Some(self.web_metadata_path.clone()),
194 "sitename" => Some(self.sitename.clone()),
195 "github_link" => Some(self.github_link.clone()),
196 "github_token" => self.github_token.clone(),
197 "api_server_url" => Some(self.api_server_url.clone()),
198 "api_external_url" => Some(self.api_external_url.clone()),
199 "jwt_issuer" => Some(self.jwt_issuer.clone()),
200 "is_cloud" => Some(self.is_cloud.to_string()),
201 "instance_id" => Some(self.instance_id.clone()),
202 "max_concurrent_streams" => Some(self.max_concurrent_streams.to_string()),
203 _ => None,
204 }
205 }
206
207 fn database_url(&self) -> &str {
208 &self.database_url
209 }
210
211 fn database_write_url(&self) -> Option<&str> {
212 self.database_write_url.as_deref()
213 }
214
215 fn system_path(&self) -> &str {
216 &self.system_path
217 }
218
219 fn api_port(&self) -> u16 {
220 self.port
221 }
222
223 fn as_any(&self) -> &dyn std::any::Any {
224 self
225 }
226}