use serde::Deserialize; /// Gateway configuration, loaded from environment variables with BASCULE_ prefix. #[derive(Debug, Deserialize)] pub struct BasculeConfig { /// gRPC listen address (default: 0.0.0.0:50052) #[serde(default = "default_listen_addr")] pub listen_addr: String, /// OIDC issuer URL for token validation #[serde(default = "default_oidc_issuer")] pub oidc_issuer: String, /// Expected OIDC audience (client_id) #[serde(default = "default_oidc_audience")] pub oidc_audience: String, /// Default session lifetime in seconds for self-grant ceremonies #[serde(default = "default_session_lifetime")] pub session_lifetime_secs: u64, // --- Database (QM-provisioned bascule_svc credentials) --- #[serde(default = "default_db_host")] pub db_host: String, #[serde(default = "default_db_port")] pub db_port: u16, #[serde(default = "default_db_name")] pub db_name: String, #[serde(default = "default_db_user")] pub db_user: String, #[serde(default)] pub db_password: String, // --- OPA sidecar --- #[serde(default = "default_opa_url")] pub opa_url: String, // --- Quartermaster endpoint --- #[serde(default = "default_qm_endpoint")] pub qm_endpoint: String, // --- Accord --- #[serde(default = "default_accord_path")] pub accord_path: String, // --- Audit pipeline --- #[serde(default = "default_audit_batch_size")] pub audit_batch_size: usize, #[serde(default = "default_audit_flush_interval")] pub audit_flush_interval_secs: u64, } fn default_listen_addr() -> String { "0.0.0.0:50052".to_string() } fn default_oidc_issuer() -> String { "http://localhost:8080/realms/guildhouse".to_string() } fn default_oidc_audience() -> String { "bascule-gateway".to_string() } fn default_session_lifetime() -> u64 { 28800 // 8 hours } fn default_db_host() -> String { "localhost".to_string() } fn default_db_port() -> u16 { 5432 } fn default_db_name() -> String { "telemetry".to_string() } fn default_db_user() -> String { "bascule_svc".to_string() } fn default_opa_url() -> String { "http://localhost:8181".to_string() } fn default_qm_endpoint() -> String { "http://quartermaster.quartermaster.svc.cluster.local:50051".to_string() } fn default_accord_path() -> String { "/accord/accord.yaml".to_string() } fn default_audit_batch_size() -> usize { 50 } fn default_audit_flush_interval() -> u64 { 10 } impl BasculeConfig { pub fn from_env() -> anyhow::Result { let config = config::Config::builder() .add_source( config::Environment::with_prefix("BASCULE") .separator("__") .try_parsing(true), ) .set_default("listen_addr", default_listen_addr())? .set_default("oidc_issuer", default_oidc_issuer())? .set_default("oidc_audience", default_oidc_audience())? .set_default("session_lifetime_secs", default_session_lifetime() as i64)? .set_default("db_host", default_db_host())? .set_default("db_port", default_db_port() as i64)? .set_default("db_name", default_db_name())? .set_default("db_user", default_db_user())? .set_default("db_password", "")? .set_default("opa_url", default_opa_url())? .set_default("qm_endpoint", default_qm_endpoint())? .set_default("accord_path", default_accord_path())? .set_default("audit_batch_size", default_audit_batch_size() as i64)? .set_default("audit_flush_interval_secs", default_audit_flush_interval() as i64)? .build()?; Ok(config.try_deserialize()?) } /// HTTP listen address for ceremony approval endpoints. pub fn http_listen_addr(&self) -> String { std::env::var("BASCULE__HTTP_LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:8443".to_string()) } /// Build a PostgreSQL connection URL from the individual config fields. pub fn database_url(&self) -> String { format!( "postgresql://{}:{}@{}:{}/{}", self.db_user, self.db_password, self.db_host, self.db_port, self.db_name ) } }