Skip to main content

zvault_server/
config.rs

1//! Server configuration for `ZVault`.
2//!
3//! Loads configuration from environment variables with sensible defaults.
4//! All settings can be overridden via `ZVAULT_*` environment variables.
5
6use std::net::SocketAddr;
7
8/// Server configuration.
9#[derive(Debug, Clone)]
10pub struct ServerConfig {
11    /// Address to bind the HTTP listener to.
12    pub bind_addr: SocketAddr,
13    /// Storage backend type.
14    pub storage_backend: StorageBackendType,
15    /// Log level filter (e.g., `info`, `debug`, `warn`).
16    pub log_level: String,
17    /// Path to the audit log file (if file audit is enabled).
18    pub audit_file_path: Option<String>,
19    /// Whether to enable the default transit engine mount.
20    pub enable_transit: bool,
21    /// Lease expiry scan interval in seconds.
22    pub lease_scan_interval_secs: u64,
23    /// Whether to skip `mlock` (for development without root/`CAP_IPC_LOCK`).
24    pub disable_mlock: bool,
25    /// Spring OAuth configuration (optional — enables "Sign in with Spring").
26    pub spring_oauth: Option<SpringOAuthConfig>,
27}
28
29/// Configuration for Spring OAuth 2.0 / OIDC integration.
30#[derive(Debug, Clone)]
31pub struct SpringOAuthConfig {
32    /// Base URL of the Spring auth server (e.g., `https://auth.puddlesearch.in`).
33    pub auth_url: String,
34    /// OAuth client ID registered in Spring.
35    pub client_id: String,
36    /// OAuth client secret.
37    pub client_secret: String,
38    /// Redirect URI for the callback (auto-derived if not set).
39    pub redirect_uri: Option<String>,
40    /// Default vault policy to assign to Spring-authenticated users.
41    pub default_policy: String,
42    /// Vault policy to assign to Spring admin users.
43    pub admin_policy: String,
44}
45
46/// Supported storage backend types.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum StorageBackendType {
49    /// In-memory (development only, data lost on restart).
50    Memory,
51    /// `RocksDB` persistent storage.
52    RocksDb { path: String },
53    /// Redb persistent storage.
54    Redb { path: String },
55    /// PostgreSQL persistent storage (recommended for Railway / cloud).
56    Postgres { url: String },
57}
58
59impl ServerConfig {
60    /// Load configuration from environment variables.
61    ///
62    /// Environment variables:
63    /// - `PORT` — port to bind on (Railway convention, binds to `0.0.0.0`)
64    /// - `ZVAULT_BIND_ADDR` — full bind address (overrides `PORT`, default: `127.0.0.1:8200`)
65    /// - `ZVAULT_STORAGE` — `memory`, `rocksdb`, `redb`, or `postgres` (default: `memory`)
66    /// - `ZVAULT_STORAGE_PATH` — path for persistent backends (default: `./data`)
67    /// - `DATABASE_URL` — PostgreSQL connection string (required when `ZVAULT_STORAGE=postgres`)
68    /// - `ZVAULT_STORAGE_PATH` — path for persistent backends (default: `./data`)
69    /// - `ZVAULT_LOG_LEVEL` — log filter (default: `info`)
70    /// - `ZVAULT_AUDIT_FILE` — path to audit log file (optional)
71    /// - `ZVAULT_ENABLE_TRANSIT` — enable transit engine (default: `true`)
72    /// - `ZVAULT_LEASE_SCAN_INTERVAL` — seconds between lease scans (default: `60`)
73    /// - `ZVAULT_DISABLE_MLOCK` — skip `mlockall` for dev environments (default: `false`)
74    #[must_use]
75    pub fn from_env() -> Self {
76        // Priority: ZVAULT_BIND_ADDR > PORT (Railway) > default 127.0.0.1:8200
77        let bind_addr = if let Ok(addr) = std::env::var("ZVAULT_BIND_ADDR") {
78            addr.parse()
79                .unwrap_or_else(|_| SocketAddr::from(([127, 0, 0, 1], 8200)))
80        } else if let Ok(port_str) = std::env::var("PORT") {
81            let port: u16 = port_str.parse().unwrap_or(8200);
82            SocketAddr::from(([0, 0, 0, 0], port))
83        } else {
84            SocketAddr::from(([127, 0, 0, 1], 8200))
85        };
86
87        let storage_path = std::env::var("ZVAULT_STORAGE_PATH")
88            .unwrap_or_else(|_| "./data".to_owned());
89
90        let storage_backend = match std::env::var("ZVAULT_STORAGE")
91            .unwrap_or_else(|_| "memory".to_owned())
92            .to_lowercase()
93            .as_str()
94        {
95            "rocksdb" => StorageBackendType::RocksDb { path: storage_path },
96            "redb" => StorageBackendType::Redb { path: storage_path },
97            "postgres" | "postgresql" => {
98                let url = std::env::var("DATABASE_URL")
99                    .unwrap_or_else(|_| "postgres://localhost/zvault".to_owned());
100                StorageBackendType::Postgres { url }
101            }
102            _ => StorageBackendType::Memory,
103        };
104
105        let log_level = std::env::var("ZVAULT_LOG_LEVEL")
106            .unwrap_or_else(|_| "info".to_owned());
107
108        let audit_file_path = std::env::var("ZVAULT_AUDIT_FILE").ok();
109
110        let enable_transit = std::env::var("ZVAULT_ENABLE_TRANSIT")
111            .map(|v| v != "false" && v != "0")
112            .unwrap_or(true);
113
114        let lease_scan_interval_secs = std::env::var("ZVAULT_LEASE_SCAN_INTERVAL")
115            .ok()
116            .and_then(|v| v.parse().ok())
117            .unwrap_or(60);
118
119        let disable_mlock = std::env::var("ZVAULT_DISABLE_MLOCK")
120            .map(|v| v == "true" || v == "1")
121            .unwrap_or(false);
122
123        // Spring OAuth — enabled when SPRING_AUTH_URL is set.
124        let spring_oauth = std::env::var("SPRING_AUTH_URL").ok().map(|auth_url| {
125            SpringOAuthConfig {
126                auth_url,
127                client_id: std::env::var("SPRING_CLIENT_ID")
128                    .unwrap_or_else(|_| "zvault-dashboard".to_owned()),
129                client_secret: std::env::var("SPRING_CLIENT_SECRET")
130                    .unwrap_or_default(),
131                redirect_uri: std::env::var("SPRING_REDIRECT_URI").ok(),
132                default_policy: std::env::var("SPRING_DEFAULT_POLICY")
133                    .unwrap_or_else(|_| "default".to_owned()),
134                admin_policy: std::env::var("SPRING_ADMIN_POLICY")
135                    .unwrap_or_else(|_| "root".to_owned()),
136            }
137        });
138
139        Self {
140            bind_addr,
141            storage_backend,
142            log_level,
143            audit_file_path,
144            enable_transit,
145            lease_scan_interval_secs,
146            disable_mlock,
147            spring_oauth,
148        }
149    }
150}