Skip to main content

systemprompt_models/config/
mod.rs

1//! Global runtime [`Config`] singleton and validation helpers.
2//!
3//! [`Config`] is the resolved, flat configuration installed once at
4//! startup into a process-wide `OnceLock` and read via [`Config::get`].
5//! Submodules cover environment classification, postgres-URL
6//! validation, rate-limit shapes, and verbosity levels.
7//! Accessors return [`crate::errors::ConfigError`] when not initialized.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12use std::path::PathBuf;
13use std::sync::OnceLock;
14use systemprompt_traits::ConfigProvider;
15
16use crate::auth::JwtAudience;
17use crate::profile::{ContentNegotiationConfig, SecurityHeadersConfig, TrustedIssuer};
18
19mod environment;
20mod paths;
21mod rate_limits;
22mod validation;
23mod verbosity;
24
25pub use environment::Environment;
26pub use paths::PathNotConfiguredError;
27pub use rate_limits::RateLimitConfig;
28pub use validation::validate_postgres_url;
29pub use verbosity::VerbosityLevel;
30
31static CONFIG: OnceLock<Config> = OnceLock::new();
32
33pub const DEFAULT_MAX_CONCURRENT_STREAMS: usize = 256;
34
35#[must_use]
36pub fn stable_instance_id() -> Option<String> {
37    std::env::var("HOSTNAME")
38        .ok()
39        .map(|h| h.trim().to_owned())
40        .filter(|h| !h.is_empty())
41}
42
43#[must_use]
44pub fn random_instance_id() -> String {
45    format!("instance-{}", uuid::Uuid::new_v4().simple())
46}
47
48#[must_use]
49pub fn default_instance_id() -> String {
50    stable_instance_id().unwrap_or_else(random_instance_id)
51}
52
53#[derive(Debug, Clone)]
54pub struct Config {
55    pub instance_id: String,
56    pub metrics_port: Option<u16>,
57    pub max_concurrent_streams: usize,
58    pub sitename: String,
59    pub database_type: String,
60    pub database_url: String,
61    pub database_write_url: Option<String>,
62    pub github_link: String,
63    pub github_token: Option<String>,
64    pub system_path: String,
65    pub services_path: String,
66    pub bin_path: String,
67    pub skills_path: String,
68    pub settings_path: String,
69    pub content_config_path: String,
70    pub geoip_database_path: Option<String>,
71    pub web_path: String,
72    pub web_config_path: String,
73    pub web_metadata_path: String,
74    pub host: String,
75    pub port: u16,
76    pub api_server_url: String,
77    pub api_internal_url: String,
78    pub api_external_url: String,
79    pub jwt_issuer: String,
80    pub jwt_access_token_expiration: i64,
81    pub jwt_refresh_token_expiration: i64,
82    pub jwt_audiences: Vec<JwtAudience>,
83    pub allowed_resource_audiences: Vec<String>,
84    pub trusted_issuers: Vec<TrustedIssuer>,
85    pub id_jag_ttl_secs: i64,
86    pub signing_key_path: PathBuf,
87    pub use_https: bool,
88    pub rate_limits: RateLimitConfig,
89    pub cors_allowed_origins: Vec<String>,
90    pub trusted_proxies: Vec<ipnet::IpNet>,
91    pub is_cloud: bool,
92    pub content_negotiation: ContentNegotiationConfig,
93    pub security_headers: SecurityHeadersConfig,
94    pub allow_registration: bool,
95    pub login_page_url: Option<String>,
96    pub system_admin_username: String,
97    pub system_admin_email: Option<systemprompt_identifiers::Email>,
98}
99
100impl Config {
101    pub fn is_initialized() -> bool {
102        CONFIG.get().is_some()
103    }
104
105    pub fn get() -> Result<&'static Self, crate::errors::ConfigError> {
106        CONFIG
107            .get()
108            .ok_or(crate::errors::ConfigError::NotInitialized)
109    }
110
111    pub fn install(config: Self) -> Result<(), Box<Self>> {
112        CONFIG.set(config).map_err(Box::new)
113    }
114
115    pub fn logs_path(&self) -> String {
116        format!("{}/logs", self.system_path)
117    }
118}
119
120impl ConfigProvider for Config {
121    fn get(&self, key: &str) -> Option<String> {
122        match key {
123            "database_type" => Some(self.database_type.clone()),
124            "database_url" => Some(self.database_url.clone()),
125            "database_write_url" => self.database_write_url.clone(),
126            "host" => Some(self.host.clone()),
127            "port" => Some(self.port.to_string()),
128            "system_path" => Some(self.system_path.clone()),
129            "services_path" => Some(self.services_path.clone()),
130            "bin_path" => Some(self.bin_path.clone()),
131            "skills_path" => Some(self.skills_path.clone()),
132            "settings_path" => Some(self.settings_path.clone()),
133            "content_config_path" => Some(self.content_config_path.clone()),
134            "web_path" => Some(self.web_path.clone()),
135            "web_config_path" => Some(self.web_config_path.clone()),
136            "web_metadata_path" => Some(self.web_metadata_path.clone()),
137            "sitename" => Some(self.sitename.clone()),
138            "github_link" => Some(self.github_link.clone()),
139            "github_token" => self.github_token.clone(),
140            "api_server_url" => Some(self.api_server_url.clone()),
141            "api_external_url" => Some(self.api_external_url.clone()),
142            "jwt_issuer" => Some(self.jwt_issuer.clone()),
143            "is_cloud" => Some(self.is_cloud.to_string()),
144            "instance_id" => Some(self.instance_id.clone()),
145            "max_concurrent_streams" => Some(self.max_concurrent_streams.to_string()),
146            _ => None,
147        }
148    }
149
150    fn database_url(&self) -> &str {
151        &self.database_url
152    }
153
154    fn database_write_url(&self) -> Option<&str> {
155        self.database_write_url.as_deref()
156    }
157
158    fn system_path(&self) -> &str {
159        &self.system_path
160    }
161
162    fn api_port(&self) -> u16 {
163        self.port
164    }
165
166    fn as_any(&self) -> &dyn std::any::Any {
167        self
168    }
169}