Skip to main content

pylon_runtime/
config.rs

1//! Unified server configuration loaded once at startup.
2//!
3//! Replaces scattered `std::env::var(...)` reads. All env vars start with
4//! `PYLON_` and are documented in `SECURITY.md` and `README.md`.
5
6use std::time::Duration;
7
8#[derive(Debug, Clone)]
9pub struct ServerConfig {
10    // -----------------------------------------------------------------------
11    // Network
12    // -----------------------------------------------------------------------
13    pub port: u16,
14    pub cors_origin: String,
15
16    // -----------------------------------------------------------------------
17    // Storage
18    // -----------------------------------------------------------------------
19    pub db_path: String,
20    pub manifest_path: String,
21    pub files_dir: String,
22    pub files_url_prefix: String,
23    pub session_db: Option<String>,
24
25    // -----------------------------------------------------------------------
26    // Auth
27    // -----------------------------------------------------------------------
28    pub admin_token: Option<String>,
29
30    // -----------------------------------------------------------------------
31    // Rate limiting
32    // -----------------------------------------------------------------------
33    pub rate_limit_max: u32,
34    pub rate_limit_window: Duration,
35    pub fn_rate_limit_max: u32,
36    pub fn_rate_limit_window: Duration,
37
38    // -----------------------------------------------------------------------
39    // Functions runtime (Bun process)
40    // -----------------------------------------------------------------------
41    pub functions_dir: String,
42    pub functions_runtime: Option<String>,
43
44    // -----------------------------------------------------------------------
45    // Modes
46    // -----------------------------------------------------------------------
47    pub is_dev: bool,
48    pub drain_timeout: Duration,
49
50    // -----------------------------------------------------------------------
51    // AI proxy
52    // -----------------------------------------------------------------------
53    pub ai_provider: String,
54    pub ai_api_key: String,
55    pub ai_model: String,
56    pub ai_base_url: String,
57
58    // -----------------------------------------------------------------------
59    // Workflow runner
60    // -----------------------------------------------------------------------
61    pub workflow_runner_url: String,
62}
63
64impl ServerConfig {
65    /// Load config from environment variables. Falls back to dev-friendly
66    /// defaults when a variable is unset.
67    pub fn from_env(default_port: u16) -> Self {
68        Self {
69            port: env_u16("PYLON_PORT", default_port),
70            cors_origin: env_str("PYLON_CORS_ORIGIN", "*"),
71            db_path: env_str("PYLON_DB_PATH", "pylon.db"),
72            manifest_path: env_str("PYLON_MANIFEST", "pylon.manifest.json"),
73            files_dir: env_str("PYLON_FILES_DIR", "uploads"),
74            files_url_prefix: env_str("PYLON_FILES_URL_PREFIX", "/api/files"),
75            session_db: std::env::var("PYLON_SESSION_DB").ok(),
76            admin_token: std::env::var("PYLON_ADMIN_TOKEN").ok(),
77            rate_limit_max: env_u32("PYLON_RATE_LIMIT_MAX", 100),
78            rate_limit_window: Duration::from_secs(env_u64("PYLON_RATE_LIMIT_WINDOW", 60)),
79            fn_rate_limit_max: env_u32("PYLON_FN_RATE_LIMIT_MAX", 30),
80            fn_rate_limit_window: Duration::from_secs(env_u64("PYLON_FN_RATE_LIMIT_WINDOW", 60)),
81            functions_dir: env_str("PYLON_FUNCTIONS_DIR", "functions"),
82            functions_runtime: std::env::var("PYLON_FUNCTIONS_RUNTIME").ok(),
83            is_dev: env_bool("PYLON_DEV_MODE", true),
84            drain_timeout: Duration::from_secs(env_u64("PYLON_DRAIN_SECS", 10)),
85            ai_provider: env_str("PYLON_AI_PROVIDER", ""),
86            ai_api_key: env_str("PYLON_AI_API_KEY", ""),
87            ai_model: env_str("PYLON_AI_MODEL", ""),
88            ai_base_url: env_str("PYLON_AI_BASE_URL", ""),
89            workflow_runner_url: env_str("PYLON_WORKFLOW_RUNNER_URL", "http://127.0.0.1:9876/run"),
90        }
91    }
92}
93
94fn env_str(key: &str, default: &str) -> String {
95    std::env::var(key).unwrap_or_else(|_| default.to_string())
96}
97
98fn env_u16(key: &str, default: u16) -> u16 {
99    std::env::var(key)
100        .ok()
101        .and_then(|v| v.parse().ok())
102        .unwrap_or(default)
103}
104
105fn env_u32(key: &str, default: u32) -> u32 {
106    std::env::var(key)
107        .ok()
108        .and_then(|v| v.parse().ok())
109        .unwrap_or(default)
110}
111
112fn env_u64(key: &str, default: u64) -> u64 {
113    std::env::var(key)
114        .ok()
115        .and_then(|v| v.parse().ok())
116        .unwrap_or(default)
117}
118
119fn env_bool(key: &str, default: bool) -> bool {
120    std::env::var(key)
121        .ok()
122        .map(|v| matches!(v.as_str(), "1" | "true" | "TRUE" | "yes"))
123        .unwrap_or(default)
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn defaults_are_sane() {
132        let c = ServerConfig::from_env(4321);
133        assert!(c.port > 0);
134        assert!(c.rate_limit_max > 0);
135        assert!(!c.cors_origin.is_empty());
136    }
137
138    #[test]
139    fn env_overrides_default() {
140        std::env::set_var("PYLON_PORT", "9999");
141        let c = ServerConfig::from_env(4321);
142        std::env::remove_var("PYLON_PORT");
143        assert_eq!(c.port, 9999);
144    }
145}