use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr}; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default)] pub struct Config { pub server: ServerConfig, pub network: NetworkConfig, pub paths: Paths, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct ServerConfig { /// Address the web/API server binds to. pub http_bind: IpAddr, /// Port for the web/API + ISO/iPXE HTTP server (single port, multiplexed by path). pub http_port: u16, /// Address the TFTP server binds to. pub tftp_bind: IpAddr, /// Port for TFTP (RFC 1350 default is 69). pub tftp_port: u16, /// External hostname/IP clients should use to reach this server. If /// `None`, auto-detect from the interface that received the DHCP request /// (via IP_PKTINFO). This is what ends up in DHCP option 54 / siaddr, /// option 66 (TFTP server), and the base of generated iPXE URLs. pub public_ip: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct NetworkConfig { pub dhcp_mode: DhcpMode, /// Address the DHCP proxy/server binds to. For proxy mode, usually 0.0.0.0. pub dhcp_bind: IpAddr, /// UDP port for DHCP server-side receive. Standard is 67. pub dhcp_port: u16, /// UDP port for PXE Boot Server discovery. Standard is 4011. pub pxe_port: u16, /// Optional allowlist of client MAC prefixes (OUI). Empty = serve everyone. pub mac_allowlist: Vec, /// Optional allowlist of subnets (CIDR). Empty = serve everyone. pub subnet_allowlist: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum DhcpMode { /// Run as DHCP proxy (RFC 4578): reply with boot info only, don't lease /// IPs. Coexists with an existing DHCP server on the network. Default /// because it's the only mode that works in most real deployments without /// taking over address assignment. #[default] Proxy, /// Disabled — rely on an external DHCP server that has been manually /// configured with `next-server` / `filename`. OpenPXE only serves TFTP /// + HTTP in this mode. Useful for home routers that can be pre-set. Disabled, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct Paths { /// Directory holding uploaded ISO files. pub iso_dir: PathBuf, /// Directory for extracted kernel/initrd and other per-ISO derived assets. pub work_dir: PathBuf, /// Directory containing bundled iPXE binaries (undionly.kpxe, snponly.efi, ...). pub ipxe_dir: PathBuf, /// Path to the wimboot binary for Windows ISOs (optional — feature-controlled). pub wimboot_path: Option, /// Directory under which Windows ISOs are extracted and served via SMB. /// Only used when `settings.windows_enabled = true`. Defaults to /// `/var/lib/openpxe/smb` in the container image. pub smb_dir: PathBuf, } impl Default for ServerConfig { fn default() -> Self { Self { http_bind: IpAddr::V4(Ipv4Addr::UNSPECIFIED), http_port: 80, tftp_bind: IpAddr::V4(Ipv4Addr::UNSPECIFIED), tftp_port: 69, public_ip: None, } } } impl Default for NetworkConfig { fn default() -> Self { Self { dhcp_mode: DhcpMode::Proxy, dhcp_bind: IpAddr::V4(Ipv4Addr::UNSPECIFIED), dhcp_port: 67, pxe_port: 4011, mac_allowlist: Vec::new(), subnet_allowlist: Vec::new(), } } } impl Default for Paths { fn default() -> Self { Self { iso_dir: PathBuf::from("/var/lib/openpxe/isos"), work_dir: PathBuf::from("/var/lib/openpxe/work"), ipxe_dir: PathBuf::from("/usr/share/openpxe/ipxe"), wimboot_path: None, smb_dir: PathBuf::from("/var/lib/openpxe/smb"), } } } // `Config` derives `Default` because each component supplies its own // non-trivial defaults via `impl Default` blocks above; deriving keeps // this in sync if a new section is added. impl Config { pub fn from_toml_file(path: &Path) -> crate::Result { let text = std::fs::read_to_string(path)?; toml::from_str(&text).map_err(|e| crate::Error::Config(e.to_string())) } /// Apply environment variable overrides. Env var names follow the pattern /// `OPENPXE_
_`, uppercase. Unknown vars are ignored. /// Call this after loading the TOML file so env takes precedence. pub fn apply_env(&mut self) { if let Ok(v) = std::env::var("OPENPXE_HTTP_PORT") { if let Ok(p) = v.parse() { self.server.http_port = p; } } if let Ok(v) = std::env::var("OPENPXE_TFTP_PORT") { if let Ok(p) = v.parse() { self.server.tftp_port = p; } } if let Ok(v) = std::env::var("OPENPXE_DHCP_PORT") { if let Ok(p) = v.parse() { self.network.dhcp_port = p; } } if let Ok(v) = std::env::var("OPENPXE_PUBLIC_IP") { if let Ok(ip) = v.parse() { self.server.public_ip = Some(ip); } } if let Ok(v) = std::env::var("OPENPXE_DHCP_MODE") { self.network.dhcp_mode = match v.to_ascii_lowercase().as_str() { "proxy" => DhcpMode::Proxy, "disabled" | "off" | "none" => DhcpMode::Disabled, _ => self.network.dhcp_mode, }; } if let Ok(v) = std::env::var("OPENPXE_ISO_DIR") { self.paths.iso_dir = PathBuf::from(v); } if let Ok(v) = std::env::var("OPENPXE_WORK_DIR") { self.paths.work_dir = PathBuf::from(v); } if let Ok(v) = std::env::var("OPENPXE_IPXE_DIR") { self.paths.ipxe_dir = PathBuf::from(v); } if let Ok(v) = std::env::var("OPENPXE_SMB_DIR") { self.paths.smb_dir = PathBuf::from(v); } } }