use std::collections::HashMap; use std::env; use std::path::PathBuf; #[derive(Debug, Clone)] pub struct Config { pub gateway_id: String, pub farm_id: String, pub gateway_token: String, pub uplink_url: String, pub go2rtc_bin: String, pub go2rtc_config: String, /// Path to the fleet config file - see `fleet.rs` and /// `printers.example.toml` for the shape. pub printers_path: String, /// Per-printer CA certificate overrides, keyed by printer ID. Most /// Bambu printers chain to the bundled shared CA (`certs/bambu_ca2.pem`) /// and don't need an entry here - but some models (P1P, at least) ship /// their own certificate instead of one signed by that shared CA, with /// no single file covering all of them. Look up a printer's own entry /// (if any) when constructing it; there's no fleet-wide "the" override. pub bambu_ca_cert_overrides: HashMap, /// When `false`, Bambu MQTTS connections skip certificate verification /// entirely instead of checking against a CA. `true` (the default) is /// the secure choice; only flip this fleet-wide policy if you have /// printers you can't get to verify any other way. pub bambu_require_valid_cert: bool, } impl Config { pub fn from_env() -> anyhow::Result { Ok(Self { gateway_id: require("CONTINUUM_GATEWAY_ID")?, farm_id: require("CONTINUUM_FARM_ID")?, gateway_token: require("CONTINUUM_GATEWAY_TOKEN")?, uplink_url: env_or("CONTINUUM_UPLINK_URL", "wss://api.continuum.local/ws/edge/v1"), go2rtc_bin: env_or("CONTINUUM_GO2RTC_BIN", "go2rtc"), go2rtc_config: env_or("CONTINUUM_GO2RTC_CONFIG", "./go2rtc.yaml"), printers_path: env_or("CONTINUUM_PRINTERS_PATH", "printers.toml"), bambu_ca_cert_overrides: parse_cert_overrides(&env_or("CONTINUUM_BAMBU_CA_CERT_OVERRIDES", "")), bambu_require_valid_cert: env_or("CONTINUUM_BAMBU_REQUIRE_VALID_CERT", "true") == "true", }) } } /// Parses `"printer_id=path,printer_id2=path2"` into a lookup map. Empty /// input (the common case - no overrides needed) yields an empty map. fn parse_cert_overrides(raw: &str) -> HashMap { raw.split(',') .filter_map(|entry| entry.split_once('=')) .map(|(id, path)| (id.trim().to_string(), PathBuf::from(path.trim()))) .collect() } fn require(key: &str) -> anyhow::Result { env::var(key).map_err(|_| anyhow::anyhow!("missing required env var {key}")) } fn env_or(key: &str, default: &str) -> String { env::var(key).unwrap_or_else(|_| default.to_string()) }