Full rename to match the openpxe.com brand. The product now reads as a
polished open-source project rather than a personal-tool nickname:
the anvil/forge metaphor is gone, replaced with the rainbow-horizon
brand mark from the marketing site.
## Naming changes
**PXEForge → OpenPXE** everywhere it's user-visible or developer-
facing:
- All 8 crate package names (`pxeforge-*` → `openpxe-*`).
- The bin crate dir + binary (`crates/pxeforge` → `crates/openpxe`,
`bin = "openpxe"`).
- Env vars: `PXEFORGE_*` → `OPENPXE_*` (no compat shim — pre-beta).
- Tracing targets: `pxeforge::*` → `openpxe::*`.
- Prometheus metrics: `pxeforge_*` → `openpxe_*` (pre-beta; nobody
has dashboards on these yet).
- Container image: `gitea.milesward.dev/mward4/openpxe:0.3.0`.
- All in-tree paths: `/var/lib/openpxe/{isos,work,smb}`,
`/usr/share/openpxe/ipxe`, `/etc/openpxe/...`.
- Unraid template renamed `pxeforge.xml` → `openpxe.xml`.
- README, NEXT_PHASE.md, architecture.md, comments, and the WebUI
brand string.
**Gated Deployment → Queued Deployment** as the user-facing concept:
- `Settings::TimeoutAction::GatedDeployment` →
`QueuedDeployment` (with `#[serde(alias = "gated_deployment")]`
so v0.2.0 settings.json files keep deserializing).
- Rust types: `Gate` → `QueueEntry`, `GateQueue` → `DeploymentQueue`,
`GateInner` → `QueueEntryInner`.
- File: `crates/core/src/gate.rs` → `crates/core/src/queue.rs`.
- HTTP routes: `/api/gate/*` → `/api/queue/*`. The JSON list key
flipped from `"gates"` to `"entries"` to match.
- iPXE shortcut: `/boot/_gate.ipxe` → `/boot/_queue.ipxe`. The
top-level menu's item id is now `queue` instead of `gate`.
- WebUI sidebar tab: "Forge Gate" → "Queue".
- Field on `AppState`: `gates` → `queue`.
## Brand assets
The anvil + forging-sparks logos are dropped:
- `logo.svg` is now a 24×24 medallion filled with the
`rainbow-horizon` gradient from openpxe.com (sliding hue rotation
via SMIL on the gradient stops, no JS needed).
- `anvil-forge.svg` renamed to `loader.svg` and rebuilt as a 64×64
louder version of the same disc — used for page-load transitions
and the imaging-progress widget. Adds a subtle scale pulse and a
white inner-glow so it has dimensionality on either theme.
## CSS rename
- `.forge-progress` → `.queue-progress`
- `.forge-progress .anvil` → `.queue-progress .mark`
- `@keyframes forge-sheen` → `queue-sheen`
- `.loader .anvil` → `.loader .mark`
- "Heating the forge…" loader text → "Loading…"
The rest of the layout is untouched. Light/dark theme tokens and the
sidebar/topbar structure carry over from v0.2.0 unchanged — the
brief was "keeping the UI similar."
## Validation
- `cargo build --workspace` — clean.
- `cargo clippy --workspace --all-targets` — no warnings.
- `cargo test --workspace` — **66 tests passing**, same as v0.2.0.
- Local smoke run against the rebuilt release binary verifies:
- `/boot.ipxe` emits `Queued Deployment` + `item queue` + chains
`/boot/_queue.ipxe`
- `/api/queue` returns `{count, entries}`
- `/metrics` emits `openpxe_queue_count` (renamed)
- `/assets/logo.svg` and `/assets/loader.svg` serve the new
rainbow brand SVGs
- `/api/status` reports version `0.3.0`
## Migration notes for operators on v0.2.0
- Container image path changed: pull
`gitea.milesward.dev/mward4/openpxe:0.3.0` (not `pxeforge:`).
- Bind mounts: `/var/lib/openpxe/{isos,work,smb}` (not `pxeforge`).
Move the host path or update the template.
- Env vars: replace `PXEFORGE_*` with `OPENPXE_*`. The Unraid
template at `deploy/unraid/openpxe.xml` is already updated.
- `settings.json` carries over transparently — the
`gated_deployment` value is accepted as an alias.
- HTTP API: any external scripts that hit `/api/gate/*` need to
switch to `/api/queue/*`. The JSON envelope key is `entries`
instead of `gates`.
163 lines
5.9 KiB
Rust
163 lines
5.9 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-gated).
|
|
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);
|
|
}
|
|
}
|
|
}
|