Files
OpenPXE/crates/core/src/config.rs
T
2026-05-21 02:13:08 -04:00

171 lines
6.1 KiB
Rust

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<Ipv4Addr>,
}
#[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<String>,
/// Optional allowlist of subnets (CIDR). Empty = serve everyone.
pub subnet_allowlist: Vec<String>,
}
#[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<PathBuf>,
/// 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<Self> {
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_<SECTION>_<FIELD>`, 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);
}
}
}