Name update

This commit is contained in:
Miles Ward
2026-05-06 14:13:38 -04:00
parent e1b7154b51
commit 20c585e3ed
56 changed files with 755 additions and 802 deletions
+16 -16
View File
@@ -54,7 +54,7 @@ pub enum DhcpMode {
#[default]
Proxy,
/// Disabled — rely on an external DHCP server that has been manually
/// configured with `next-server` / `filename`. PXEForge only serves TFTP
/// configured with `next-server` / `filename`. OpenPXE only serves TFTP
/// + HTTP in this mode. Useful for home routers that can be pre-set.
Disabled,
}
@@ -72,7 +72,7 @@ pub struct Paths {
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/pxeforge/smb` in the container image.
/// `/var/lib/openpxe/smb` in the container image.
pub smb_dir: PathBuf,
}
@@ -104,11 +104,11 @@ impl Default for NetworkConfig {
impl Default for Paths {
fn default() -> Self {
Self {
iso_dir: PathBuf::from("/var/lib/pxeforge/isos"),
work_dir: PathBuf::from("/var/lib/pxeforge/work"),
ipxe_dir: PathBuf::from("/usr/share/pxeforge/ipxe"),
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/pxeforge/smb"),
smb_dir: PathBuf::from("/var/lib/openpxe/smb"),
}
}
}
@@ -124,38 +124,38 @@ impl Config {
}
/// Apply environment variable overrides. Env var names follow the pattern
/// `PXEFORGE_<SECTION>_<FIELD>`, uppercase. Unknown vars are ignored.
/// `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("PXEFORGE_HTTP_PORT") {
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("PXEFORGE_TFTP_PORT") {
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("PXEFORGE_DHCP_PORT") {
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("PXEFORGE_PUBLIC_IP") {
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("PXEFORGE_DHCP_MODE") {
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("PXEFORGE_ISO_DIR") {
if let Ok(v) = std::env::var("OPENPXE_ISO_DIR") {
self.paths.iso_dir = PathBuf::from(v);
}
if let Ok(v) = std::env::var("PXEFORGE_WORK_DIR") {
if let Ok(v) = std::env::var("OPENPXE_WORK_DIR") {
self.paths.work_dir = PathBuf::from(v);
}
if let Ok(v) = std::env::var("PXEFORGE_IPXE_DIR") {
if let Ok(v) = std::env::var("OPENPXE_IPXE_DIR") {
self.paths.ipxe_dir = PathBuf::from(v);
}
if let Ok(v) = std::env::var("PXEFORGE_SMB_DIR") {
if let Ok(v) = std::env::var("OPENPXE_SMB_DIR") {
self.paths.smb_dir = PathBuf::from(v);
}
}
+4 -4
View File
@@ -73,7 +73,7 @@ impl HostBindings {
}
Err(e) => {
tracing::warn!(
target: "pxeforge::hosts",
target: "openpxe::hosts",
"hosts.json present but unreadable ({e}); starting empty"
);
Inner::default()
@@ -149,7 +149,7 @@ impl HostBindings {
let body = match serde_json::to_vec_pretty(&items) {
Ok(b) => b,
Err(e) => {
tracing::warn!(target: "pxeforge::hosts", "serialize hosts.json: {e}");
tracing::warn!(target: "openpxe::hosts", "serialize hosts.json: {e}");
return;
}
};
@@ -158,11 +158,11 @@ impl HostBindings {
let _ = std::fs::create_dir_all(parent);
}
if let Err(e) = std::fs::write(&tmp, body) {
tracing::warn!(target: "pxeforge::hosts", "write hosts.json tmp: {e}");
tracing::warn!(target: "openpxe::hosts", "write hosts.json tmp: {e}");
return;
}
if let Err(e) = std::fs::rename(&tmp, self.path.as_path()) {
tracing::warn!(target: "pxeforge::hosts", "rename hosts.json: {e}");
tracing::warn!(target: "openpxe::hosts", "rename hosts.json: {e}");
}
}
}
+4 -4
View File
@@ -1,12 +1,12 @@
//! PXEForge shared core: config, arch detection, client state registry,
//! runtime settings, and the Gated Deployment queue.
//! OpenPXE shared core: config, arch detection, client state registry,
//! runtime settings, and the Queued Deployment queue.
#![forbid(unsafe_code)]
pub mod arch;
pub mod client;
pub mod config;
pub mod error;
pub mod gate;
pub mod queue;
pub mod host_bindings;
pub mod log_bus;
pub mod metrics;
@@ -16,7 +16,7 @@ pub use arch::{ClientArch, FirmwareClass};
pub use client::{ClientEvent, ClientRegistry, ClientSnapshot};
pub use config::{Config, DhcpMode, NetworkConfig, Paths, ServerConfig};
pub use error::{Error, Result};
pub use gate::{Gate, GateQueue};
pub use queue::{Gate, DeploymentQueue};
pub use host_bindings::{normalize_mac, HostBinding, HostBindings};
pub use log_bus::{LogBus, LogBusLayer, LogLine};
pub use metrics::{HttpRoute, Metrics};
+1 -1
View File
@@ -38,7 +38,7 @@ impl LogLine {
/// Compact one-line "tail -f"-style render.
#[must_use]
pub fn render(&self) -> String {
// 2026-04-29T12:34:56Z [info] pxeforge::http: HTTP listening on 0.0.0.0:80
// 2026-04-29T12:34:56Z [info] openpxe::http: HTTP listening on 0.0.0.0:80
let ts = self
.timestamp
.format(&time::format_description::well_known::Rfc3339)
+51 -51
View File
@@ -1,21 +1,21 @@
//! Tiny lock-free Prometheus-compatible metrics.
//!
//! We don't pull in `prometheus` or `metrics-rs` for this — they bring
//! their own runtime, registry, and complexity. PXEForge has a fixed,
//! their own runtime, registry, and complexity. OpenPXE has a fixed,
//! tiny set of counters/gauges and the exposition format is plain text.
//! A handful of `AtomicU64`s and a `Display` impl gets us everything
//! Prometheus / Grafana / VictoriaMetrics needs to scrape:
//!
//! pxeforge_dhcp_replies_total counter (per arch label)
//! pxeforge_tftp_transfers_total counter (per status label)
//! pxeforge_tftp_bytes_total counter
//! pxeforge_http_requests_total counter (per route label)
//! pxeforge_iso_count gauge
//! pxeforge_client_count gauge
//! pxeforge_gate_count gauge
//! pxeforge_gate_imaging gauge
//! pxeforge_uptime_seconds gauge
//! pxeforge_build_info{version} gauge (always 1)
//! openpxe_dhcp_replies_total counter (per arch label)
//! openpxe_tftp_transfers_total counter (per status label)
//! openpxe_tftp_bytes_total counter
//! openpxe_http_requests_total counter (per route label)
//! openpxe_iso_count gauge
//! openpxe_client_count gauge
//! openpxe_queue_count gauge
//! openpxe_queue_imaging gauge
//! openpxe_uptime_seconds gauge
//! openpxe_build_info{version} gauge (always 1)
//!
//! Cheap to clone — internal state is a couple of arcs. Counters use
//! `Relaxed` ordering: we don't synchronise across counters, just need
@@ -47,8 +47,8 @@ struct Inner {
// Gauges (set explicitly; not cumulative)
iso_count: AtomicU64,
client_count: AtomicU64,
gate_count: AtomicU64,
gate_imaging: AtomicU64,
queue_count: AtomicU64,
queue_imaging: AtomicU64,
nfs_mounts_active: AtomicU64,
}
@@ -113,9 +113,9 @@ impl Metrics {
self.inner.client_count.store(n, Ordering::Relaxed);
}
pub fn set_gate_counts(&self, total: u64, imaging: u64) {
self.inner.gate_count.store(total, Ordering::Relaxed);
self.inner.gate_imaging.store(imaging, Ordering::Relaxed);
pub fn set_queue_counts(&self, total: u64, imaging: u64) {
self.inner.queue_count.store(total, Ordering::Relaxed);
self.inner.queue_imaging.store(imaging, Ordering::Relaxed);
}
pub fn set_nfs_active(&self, n: u64) {
@@ -151,58 +151,58 @@ impl Metrics {
};
// Counters with one HELP/TYPE per metric name and per-label rows.
let _ = writeln!(out, "# HELP pxeforge_dhcp_replies_total Number of proxyDHCP replies sent, by client architecture.");
let _ = writeln!(out, "# TYPE pxeforge_dhcp_replies_total counter");
let _ = writeln!(out, "# HELP openpxe_dhcp_replies_total Number of proxyDHCP replies sent, by client architecture.");
let _ = writeln!(out, "# TYPE openpxe_dhcp_replies_total counter");
let _ = writeln!(
out,
"pxeforge_dhcp_replies_total{{arch=\"bios\"}} {}",
"openpxe_dhcp_replies_total{{arch=\"bios\"}} {}",
i.dhcp_replies_legacy.load(Ordering::Relaxed)
);
let _ = writeln!(
out,
"pxeforge_dhcp_replies_total{{arch=\"uefi\"}} {}",
"openpxe_dhcp_replies_total{{arch=\"uefi\"}} {}",
i.dhcp_replies_uefi.load(Ordering::Relaxed)
);
let _ = writeln!(
out,
"pxeforge_dhcp_replies_total{{arch=\"arm64\"}} {}",
"openpxe_dhcp_replies_total{{arch=\"arm64\"}} {}",
i.dhcp_replies_arm64.load(Ordering::Relaxed)
);
let _ = writeln!(
out,
"pxeforge_dhcp_replies_total{{arch=\"unknown\"}} {}",
"openpxe_dhcp_replies_total{{arch=\"unknown\"}} {}",
i.dhcp_replies_unknown.load(Ordering::Relaxed)
);
write_counter(
&mut out,
"pxeforge_dhcp_declined_total",
"openpxe_dhcp_declined_total",
"DHCP requests we saw but did not reply to (mac filter, arch unsupported, etc).",
i.dhcp_declined.load(Ordering::Relaxed),
"",
);
let _ = writeln!(out, "# HELP pxeforge_tftp_transfers_total TFTP transfers, by status.");
let _ = writeln!(out, "# TYPE pxeforge_tftp_transfers_total counter");
let _ = writeln!(out, "# HELP openpxe_tftp_transfers_total TFTP transfers, by status.");
let _ = writeln!(out, "# TYPE openpxe_tftp_transfers_total counter");
let _ = writeln!(
out,
"pxeforge_tftp_transfers_total{{status=\"ok\"}} {}",
"openpxe_tftp_transfers_total{{status=\"ok\"}} {}",
i.tftp_transfers_ok.load(Ordering::Relaxed)
);
let _ = writeln!(
out,
"pxeforge_tftp_transfers_total{{status=\"err\"}} {}",
"openpxe_tftp_transfers_total{{status=\"err\"}} {}",
i.tftp_transfers_err.load(Ordering::Relaxed)
);
write_counter(
&mut out,
"pxeforge_tftp_bytes_total",
"openpxe_tftp_bytes_total",
"Total bytes successfully delivered over TFTP.",
i.tftp_bytes.load(Ordering::Relaxed),
"",
);
let _ = writeln!(out, "# HELP pxeforge_http_requests_total HTTP requests served, by route family.");
let _ = writeln!(out, "# TYPE pxeforge_http_requests_total counter");
let _ = writeln!(out, "# HELP openpxe_http_requests_total HTTP requests served, by route family.");
let _ = writeln!(out, "# TYPE openpxe_http_requests_total counter");
for (label, counter) in [
("boot_script", &i.http_boot_script),
("iso_range", &i.http_iso_range),
@@ -212,22 +212,22 @@ impl Metrics {
] {
let _ = writeln!(
out,
"pxeforge_http_requests_total{{route=\"{label}\"}} {}",
"openpxe_http_requests_total{{route=\"{label}\"}} {}",
counter.load(Ordering::Relaxed)
);
}
// Gauges.
write_gauge(&mut out, "pxeforge_iso_count", "ISOs currently registered (local + NFS).", i.iso_count.load(Ordering::Relaxed), "");
write_gauge(&mut out, "pxeforge_client_count", "PXE clients seen this process lifetime.", i.client_count.load(Ordering::Relaxed), "");
write_gauge(&mut out, "pxeforge_gate_count", "Clients currently waiting at the deployment gate.", i.gate_count.load(Ordering::Relaxed), "");
write_gauge(&mut out, "pxeforge_gate_imaging", "Clients currently imaging (gate + assigned target).", i.gate_imaging.load(Ordering::Relaxed), "");
write_gauge(&mut out, "pxeforge_nfs_mounts_active", "NFS shares currently mounted.", i.nfs_mounts_active.load(Ordering::Relaxed), "");
write_gauge(&mut out, "pxeforge_uptime_seconds", "Seconds since this PXEForge instance started.", uptime_secs, "");
write_gauge(&mut out, "openpxe_iso_count", "ISOs currently registered (local + NFS).", i.iso_count.load(Ordering::Relaxed), "");
write_gauge(&mut out, "openpxe_client_count", "PXE clients seen this process lifetime.", i.client_count.load(Ordering::Relaxed), "");
write_gauge(&mut out, "openpxe_queue_count", "Clients currently waiting at the deployment queue.", i.queue_count.load(Ordering::Relaxed), "");
write_gauge(&mut out, "openpxe_queue_imaging", "Clients currently imaging (queue + assigned target).", i.queue_imaging.load(Ordering::Relaxed), "");
write_gauge(&mut out, "openpxe_nfs_mounts_active", "NFS shares currently mounted.", i.nfs_mounts_active.load(Ordering::Relaxed), "");
write_gauge(&mut out, "openpxe_uptime_seconds", "Seconds since this OpenPXE instance started.", uptime_secs, "");
let _ = writeln!(out, "# HELP pxeforge_build_info Build metadata. Always 1; the version is in the label.");
let _ = writeln!(out, "# TYPE pxeforge_build_info gauge");
let _ = writeln!(out, "pxeforge_build_info{{version=\"{version}\"}} 1");
let _ = writeln!(out, "# HELP openpxe_build_info Build metadata. Always 1; the version is in the label.");
let _ = writeln!(out, "# TYPE openpxe_build_info gauge");
let _ = writeln!(out, "openpxe_build_info{{version=\"{version}\"}} 1");
out
}
@@ -259,16 +259,16 @@ mod tests {
m.record_http(HttpRoute::Api);
m.set_iso_count(3);
let out = m.render("0.2.0", 42);
assert_eq!(out.matches("# TYPE pxeforge_dhcp_replies_total counter").count(), 1);
assert_eq!(out.matches("# TYPE pxeforge_iso_count gauge").count(), 1);
assert!(out.contains("pxeforge_dhcp_replies_total{arch=\"uefi\"} 1"));
assert!(out.contains("pxeforge_dhcp_replies_total{arch=\"bios\"} 1"));
assert!(out.contains("pxeforge_tftp_transfers_total{status=\"ok\"} 1"));
assert!(out.contains("pxeforge_tftp_bytes_total 1024"));
assert!(out.contains("pxeforge_http_requests_total{route=\"api\"} 1"));
assert!(out.contains("pxeforge_iso_count 3"));
assert!(out.contains("pxeforge_uptime_seconds 42"));
assert!(out.contains("pxeforge_build_info{version=\"0.2.0\"} 1"));
assert_eq!(out.matches("# TYPE openpxe_dhcp_replies_total counter").count(), 1);
assert_eq!(out.matches("# TYPE openpxe_iso_count gauge").count(), 1);
assert!(out.contains("openpxe_dhcp_replies_total{arch=\"uefi\"} 1"));
assert!(out.contains("openpxe_dhcp_replies_total{arch=\"bios\"} 1"));
assert!(out.contains("openpxe_tftp_transfers_total{status=\"ok\"} 1"));
assert!(out.contains("openpxe_tftp_bytes_total 1024"));
assert!(out.contains("openpxe_http_requests_total{route=\"api\"} 1"));
assert!(out.contains("openpxe_iso_count 3"));
assert!(out.contains("openpxe_uptime_seconds 42"));
assert!(out.contains("openpxe_build_info{version=\"0.2.0\"} 1"));
}
#[test]
@@ -278,6 +278,6 @@ mod tests {
a.record_dhcp_reply("bios");
b.record_dhcp_reply("bios");
let out = a.render("test", 0);
assert!(out.contains("pxeforge_dhcp_replies_total{arch=\"bios\"} 2"));
assert!(out.contains("openpxe_dhcp_replies_total{arch=\"bios\"} 2"));
}
}
@@ -1,14 +1,14 @@
//! Gated Deployment queue.
//! Queued Deployment queue.
//!
//! When a client selects "Gated Deployment" at the PXE menu, iPXE POSTs to
//! `/api/gate/join` and receives a gate position. It then enters a poll
//! loop hitting `/api/gate/poll/<id>`; the server holds the request open
//! When a client selects "Queued Deployment" at the PXE menu, iPXE POSTs to
//! `/api/queue/join` and receives a gate position. It then enters a poll
//! loop hitting `/api/queue/poll/<id>`; the server holds the request open
//! until either (a) the operator assigns an ISO from the WebUI, in which
//! case the poll returns an iPXE `chain` URL, or (b) the poll times out
//! (iPXE's HTTP client has its own timeout), in which case iPXE re-POSTs.
//!
//! The WebUI shows the queue (`GET /api/gate`) and issues
//! `POST /api/gate/assign { iso_id, gate_ids: [...] }` to launch a single
//! `POST /api/queue/assign { iso_id, entry_ids: [...] }` to launch a single
//! ISO across many gated clients at once. This is the "horse-race gate"
//! UX the user asked for — every horse leaves the line simultaneously.
@@ -40,7 +40,7 @@ pub struct Gate {
}
#[derive(Debug)]
struct GateInner {
struct QueueEntryInner {
id: String,
position: u32,
mac: String,
@@ -54,7 +54,7 @@ struct GateInner {
notify: Arc<Notify>,
}
impl GateInner {
impl QueueEntryInner {
fn snapshot(&self) -> Gate {
Gate {
id: self.id.clone(),
@@ -70,11 +70,11 @@ impl GateInner {
}
#[derive(Debug, Default)]
pub struct GateQueue {
inner: RwLock<HashMap<String, GateInner>>,
pub struct DeploymentQueue {
inner: RwLock<HashMap<String, QueueEntryInner>>,
}
impl GateQueue {
impl DeploymentQueue {
#[must_use]
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
@@ -97,7 +97,7 @@ impl GateQueue {
// Race position = max(position) + 1, or 1 if empty.
let next_pos = guard.values().map(|g| g.position).max().unwrap_or(0) + 1;
let id = Uuid::new_v4().to_string();
let inner = GateInner {
let inner = QueueEntryInner {
id: id.clone(),
position: next_pos,
mac: mac.to_string(),
@@ -115,16 +115,16 @@ impl GateQueue {
/// Look up the `Notify` primitive for a given gate id, for long-polling.
#[must_use]
pub fn notifier(&self, gate_id: &str) -> Option<Arc<Notify>> {
self.inner.read().get(gate_id).map(|g| g.notify.clone())
pub fn notifier(&self, entry_id: &str) -> Option<Arc<Notify>> {
self.inner.read().get(entry_id).map(|g| g.notify.clone())
}
/// Update the last-poll timestamp (keeps the gate's "live" indicator
/// fresh in the UI) and return the current snapshot. Returns None if
/// the gate was released/expired between requests.
pub fn touch(&self, gate_id: &str) -> Option<Gate> {
pub fn touch(&self, entry_id: &str) -> Option<Gate> {
let mut guard = self.inner.write();
let g = guard.get_mut(gate_id)?;
let g = guard.get_mut(entry_id)?;
g.last_poll_at = OffsetDateTime::now_utc();
Some(g.snapshot())
}
@@ -132,10 +132,10 @@ impl GateQueue {
/// Operator assigns an ISO entry (boot_entry id) to one or more gates.
/// Returns the number of gates that were updated. Gates not in the
/// queue are silently skipped.
pub fn assign(&self, gate_ids: &[String], target: &str) -> usize {
pub fn assign(&self, entry_ids: &[String], target: &str) -> usize {
let mut guard = self.inner.write();
let mut updated = 0;
for id in gate_ids {
for id in entry_ids {
if let Some(g) = guard.get_mut(id) {
g.assigned_target = Some(target.to_string());
g.notify.notify_waiters();
@@ -147,9 +147,9 @@ impl GateQueue {
/// Remove a gate and return its final snapshot. Called after the client
/// has successfully chained onto its assignment.
pub fn release(&self, gate_id: &str) -> Option<Gate> {
pub fn release(&self, entry_id: &str) -> Option<Gate> {
let mut guard = self.inner.write();
let g = guard.remove(gate_id)?;
let g = guard.remove(entry_id)?;
g.notify.notify_waiters();
// Renumber positions so the display stays contiguous (1..N). This
// is O(N) but the queue is expected to be small (dozens of hosts).
@@ -164,7 +164,7 @@ impl GateQueue {
#[must_use]
pub fn list(&self) -> Vec<Gate> {
let guard = self.inner.read();
let mut v: Vec<_> = guard.values().map(GateInner::snapshot).collect();
let mut v: Vec<_> = guard.values().map(QueueEntryInner::snapshot).collect();
v.sort_by_key(|g| g.position);
v
}
@@ -186,7 +186,7 @@ mod tests {
#[test]
fn join_assigns_sequential_positions() {
let q = GateQueue::new();
let q = DeploymentQueue::new();
let g1 = q.join("aa:bb:cc:00:00:01", None, None);
let g2 = q.join("aa:bb:cc:00:00:02", None, None);
let g3 = q.join("aa:bb:cc:00:00:03", None, None);
@@ -197,7 +197,7 @@ mod tests {
#[test]
fn rejoining_same_mac_is_idempotent() {
let q = GateQueue::new();
let q = DeploymentQueue::new();
let g1 = q.join("aa:bb:cc:00:00:01", None, None);
let g2 = q.join("aa:bb:cc:00:00:01", None, None);
assert_eq!(g1.id, g2.id);
@@ -207,7 +207,7 @@ mod tests {
#[test]
fn assign_broadcasts_target() {
let q = GateQueue::new();
let q = DeploymentQueue::new();
let g1 = q.join("aa:bb:cc:00:00:01", None, None);
let g2 = q.join("aa:bb:cc:00:00:02", None, None);
let n = q.assign(&[g1.id.clone(), g2.id.clone()], "ubuntu-24-04-linux");
@@ -219,7 +219,7 @@ mod tests {
#[test]
fn release_renumbers() {
let q = GateQueue::new();
let q = DeploymentQueue::new();
let a = q.join("aa:00:00:00:00:01", None, None);
let _b = q.join("aa:00:00:00:00:02", None, None);
let c = q.join("aa:00:00:00:00:03", None, None);
@@ -234,7 +234,7 @@ mod tests {
#[tokio::test]
async fn assign_wakes_waiter() {
let q = GateQueue::new();
let q = DeploymentQueue::new();
let g = q.join("aa:00:00:00:00:01", None, None);
let notify = q.notifier(&g.id).unwrap();
+12 -8
View File
@@ -47,13 +47,13 @@ pub struct Settings {
/// `timeout_action = LocalHdd`).
pub default_local_hdd: bool,
/// When a client hits the Gated Deployment item, how long (seconds) to
/// When a client hits the Queued Deployment item, how long (seconds) to
/// hold it at the gate before giving up and falling back to the menu.
/// 0 = forever.
pub gate_wait_max_secs: u32,
/// Optional DNS server advertised on the Network tab. Purely
/// informational today — PXEForge does not run a DNS server, but
/// informational today — OpenPXE does not run a DNS server, but
/// operators expect to be able to record what the upstream DNS is.
/// Empty string = unset (UI shows placeholder).
pub dns_server: String,
@@ -66,16 +66,20 @@ pub enum TimeoutAction {
Stay,
/// Chain the "Boot from Local HDD" entry.
LocalHdd,
/// Put the client into the gate queue, waiting for operator assignment.
/// Put the client into the deployment queue, waiting for operator
/// assignment. The serde alias keeps v0.2.0 settings.json files
/// readable after the v0.3.0 rename — old `"gated_deployment"`
/// values deserialize transparently.
#[default]
GatedDeployment,
#[serde(alias = "gated_deployment")]
QueuedDeployment,
}
impl Default for Settings {
fn default() -> Self {
Self {
boot_menu_timeout_secs: 600,
timeout_action: TimeoutAction::GatedDeployment,
timeout_action: TimeoutAction::QueuedDeployment,
windows_enabled: false,
smb_host_override: String::new(),
extra_kernel_args: String::new(),
@@ -103,7 +107,7 @@ impl SettingsStore {
Ok(s) => s,
Err(e) => {
tracing::warn!(
target: "pxeforge::settings",
target: "openpxe::settings",
"settings.json present but unreadable ({e}); falling back to defaults"
);
Settings::default()
@@ -130,7 +134,7 @@ impl SettingsStore {
}
let snap = self.snapshot();
if let Err(e) = self.persist(&snap) {
tracing::warn!(target: "pxeforge::settings", "failed to persist settings: {e}");
tracing::warn!(target: "openpxe::settings", "failed to persist settings: {e}");
}
}
@@ -157,7 +161,7 @@ mod tests {
let store = SettingsStore::load_or_default(dir.path());
let s = store.snapshot();
assert_eq!(s.boot_menu_timeout_secs, 600);
assert_eq!(s.timeout_action, TimeoutAction::GatedDeployment);
assert_eq!(s.timeout_action, TimeoutAction::QueuedDeployment);
assert!(!s.windows_enabled);
}