v0.3.0 — rebrand: PXEForge → OpenPXE, Gated → Queued Deployment
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`.
This commit is contained in:
@@ -36,7 +36,7 @@ pub async fn run_command(
|
||||
|
||||
// Echo the typed command into the live log so the Terminal tab shows
|
||||
// operator activity in-band with server-emitted log lines.
|
||||
state.log_bus.push("info", "pxeforge::terminal", format!("> {line}"));
|
||||
state.log_bus.push("info", "openpxe::terminal", format!("> {line}"));
|
||||
|
||||
let argv = shell_split(line);
|
||||
if argv.is_empty() {
|
||||
@@ -60,9 +60,9 @@ pub async fn run_command(
|
||||
output.clone()
|
||||
};
|
||||
if ok {
|
||||
state.log_bus.push("info", "pxeforge::terminal", mirror);
|
||||
state.log_bus.push("info", "openpxe::terminal", mirror);
|
||||
} else {
|
||||
state.log_bus.push("warn", "pxeforge::terminal", mirror);
|
||||
state.log_bus.push("warn", "openpxe::terminal", mirror);
|
||||
}
|
||||
|
||||
(StatusCode::OK, Json(json!({ "output": output, "ok": ok })))
|
||||
@@ -73,12 +73,12 @@ async fn dispatch(state: &AppState, argv: &[String]) -> Result<String, String> {
|
||||
let tail = &argv[1..];
|
||||
match head {
|
||||
"help" | "?" => Ok(HELP_TEXT.to_string()),
|
||||
"version" => Ok(format!("pxeforge {}", env!("CARGO_PKG_VERSION"))),
|
||||
"version" => Ok(format!("openpxe {}", env!("CARGO_PKG_VERSION"))),
|
||||
"uptime" => Ok(uptime_string(state)),
|
||||
"status" => Ok(status_text(state)),
|
||||
"isos" | "images" => Ok(isos_text(state)),
|
||||
"clients" => Ok(clients_text(state)),
|
||||
"gate" => gate_command(state, tail).await,
|
||||
"queue" => gate_command(state, tail).await,
|
||||
"nfs" => nfs_command(state, tail).await,
|
||||
"smb" => smb_command(state, tail).await,
|
||||
"log" => log_command(state, tail),
|
||||
@@ -96,18 +96,18 @@ async fn dispatch(state: &AppState, argv: &[String]) -> Result<String, String> {
|
||||
fn status_text(s: &AppState) -> String {
|
||||
let isos = s.iso_store.list();
|
||||
let clients = s.clients.list();
|
||||
let gates = s.gates.list();
|
||||
let gates = s.queue.list();
|
||||
let smb = s.smb.as_ref().map(|m| m.snapshot());
|
||||
let nfs = s.nfs.list();
|
||||
let nfs_active = nfs.iter().filter(|m| m.mounted).count();
|
||||
format!(
|
||||
"PXEForge {ver}\n\
|
||||
"OpenPXE {ver}\n\
|
||||
base url: {base}\n\
|
||||
interface: {nic}\n\
|
||||
uptime: {up}\n\
|
||||
isos: {n_isos} (local: {n_local}, nfs: {n_nfs})\n\
|
||||
clients: {n_clients}\n\
|
||||
gates: {n_gates}\n\
|
||||
queue: {n_entries}\n\
|
||||
smb: {smb}\n\
|
||||
nfs mounts: {n_total} configured ({n_active} active)\n",
|
||||
ver = env!("CARGO_PKG_VERSION"),
|
||||
@@ -115,10 +115,10 @@ fn status_text(s: &AppState) -> String {
|
||||
nic = if s.nic_name.is_empty() { "?" } else { s.nic_name.as_str() },
|
||||
up = uptime_string(s),
|
||||
n_isos = isos.len(),
|
||||
n_local = isos.iter().filter(|i| matches!(i.source, pxeforge_iso_store::IsoSource::Local)).count(),
|
||||
n_nfs = isos.iter().filter(|i| !matches!(i.source, pxeforge_iso_store::IsoSource::Local)).count(),
|
||||
n_local = isos.iter().filter(|i| matches!(i.source, openpxe_iso_store::IsoSource::Local)).count(),
|
||||
n_nfs = isos.iter().filter(|i| !matches!(i.source, openpxe_iso_store::IsoSource::Local)).count(),
|
||||
n_clients = clients.len(),
|
||||
n_gates = gates.len(),
|
||||
n_entries = gates.len(),
|
||||
smb = smb.map_or_else(|| "(disabled)".into(), |s| format!("{s:?}")),
|
||||
n_total = nfs.len(),
|
||||
n_active = nfs_active,
|
||||
@@ -138,8 +138,8 @@ fn isos_text(s: &AppState) -> String {
|
||||
);
|
||||
for i in isos {
|
||||
let src = match i.source {
|
||||
pxeforge_iso_store::IsoSource::Local => "local".to_string(),
|
||||
pxeforge_iso_store::IsoSource::Nfs { mount_id, .. } => format!("nfs:{mount_id}"),
|
||||
openpxe_iso_store::IsoSource::Local => "local".to_string(),
|
||||
openpxe_iso_store::IsoSource::Nfs { mount_id, .. } => format!("nfs:{mount_id}"),
|
||||
};
|
||||
let _ = writeln!(
|
||||
out,
|
||||
@@ -188,7 +188,7 @@ fn clients_text(s: &AppState) -> String {
|
||||
async fn gate_command(s: &AppState, args: &[String]) -> Result<String, String> {
|
||||
match args.first().map(String::as_str) {
|
||||
None | Some("list") => {
|
||||
let gs = s.gates.list();
|
||||
let gs = s.queue.list();
|
||||
if gs.is_empty() {
|
||||
return Ok("(no gates)".into());
|
||||
}
|
||||
@@ -217,28 +217,28 @@ async fn gate_command(s: &AppState, args: &[String]) -> Result<String, String> {
|
||||
if !found {
|
||||
return Err(format!("no such boot entry: {target}"));
|
||||
}
|
||||
let ids: Vec<_> = s.gates.list().into_iter().map(|g| g.id).collect();
|
||||
let n = s.gates.assign(&ids, target);
|
||||
let ids: Vec<_> = s.queue.list().into_iter().map(|g| g.id).collect();
|
||||
let n = s.queue.assign(&ids, target);
|
||||
Ok(format!("assigned {n} gates -> {target}"))
|
||||
}
|
||||
Some("assign") => {
|
||||
let gate_id = args
|
||||
let entry_id = args
|
||||
.get(1)
|
||||
.ok_or_else(|| "usage: gate assign <gate_id> <iso_boot_entry_id>".to_string())?;
|
||||
.ok_or_else(|| "usage: gate assign <entry_id> <iso_boot_entry_id>".to_string())?;
|
||||
let target = args
|
||||
.get(2)
|
||||
.ok_or_else(|| "usage: gate assign <gate_id> <iso_boot_entry_id>".to_string())?;
|
||||
let n = s.gates.assign(std::slice::from_ref(gate_id), target);
|
||||
.ok_or_else(|| "usage: gate assign <entry_id> <iso_boot_entry_id>".to_string())?;
|
||||
let n = s.queue.assign(std::slice::from_ref(entry_id), target);
|
||||
if n == 0 {
|
||||
return Err(format!("no such gate: {gate_id}"));
|
||||
return Err(format!("no such gate: {entry_id}"));
|
||||
}
|
||||
Ok(format!("assigned 1 gate -> {target}"))
|
||||
}
|
||||
Some("release") => {
|
||||
let gate_id = args.get(1).ok_or_else(|| "usage: gate release <gate_id>".to_string())?;
|
||||
match s.gates.release(gate_id) {
|
||||
Some(_) => Ok(format!("released {gate_id}")),
|
||||
None => Err(format!("no such gate: {gate_id}")),
|
||||
let entry_id = args.get(1).ok_or_else(|| "usage: gate release <entry_id>".to_string())?;
|
||||
match s.queue.release(entry_id) {
|
||||
Some(_) => Ok(format!("released {entry_id}")),
|
||||
None => Err(format!("no such gate: {entry_id}")),
|
||||
}
|
||||
}
|
||||
Some(other) => Err(format!(
|
||||
@@ -269,8 +269,8 @@ async fn nfs_command(s: &AppState, args: &[String]) -> Result<String, String> {
|
||||
"{:<24} {:<6} {:<7} {:<6} {}:{}",
|
||||
truncate(&m.id, 24),
|
||||
match m.version {
|
||||
pxeforge_iso_store::NfsVersion::V3 => "v3",
|
||||
pxeforge_iso_store::NfsVersion::V41 => "v4.1",
|
||||
openpxe_iso_store::NfsVersion::V3 => "v3",
|
||||
openpxe_iso_store::NfsVersion::V41 => "v4.1",
|
||||
},
|
||||
status,
|
||||
m.iso_count,
|
||||
@@ -292,12 +292,12 @@ async fn nfs_command(s: &AppState, args: &[String]) -> Result<String, String> {
|
||||
.split_once(':')
|
||||
.ok_or_else(|| "target must be 'server:/export'".to_string())?;
|
||||
let version = match args.get(2).map(String::as_str) {
|
||||
Some("v3") => pxeforge_iso_store::NfsVersion::V3,
|
||||
Some("v41") | None => pxeforge_iso_store::NfsVersion::V41,
|
||||
Some("v3") => openpxe_iso_store::NfsVersion::V3,
|
||||
Some("v41") | None => openpxe_iso_store::NfsVersion::V41,
|
||||
Some(other) => return Err(format!("unknown nfs version: {other} (expect v3 or v41)")),
|
||||
};
|
||||
let read_only = !matches!(args.get(3).map(String::as_str), Some("rw"));
|
||||
let req = pxeforge_iso_store::NfsAddRequest {
|
||||
let req = openpxe_iso_store::NfsAddRequest {
|
||||
server: server.to_string(),
|
||||
export: export.to_string(),
|
||||
version,
|
||||
@@ -453,7 +453,7 @@ pub fn shell_split(input: &str) -> Vec<String> {
|
||||
}
|
||||
|
||||
const HELP_TEXT: &str = "\
|
||||
PXEForge terminal — available commands:
|
||||
OpenPXE terminal — available commands:
|
||||
|
||||
help show this help
|
||||
version print server version
|
||||
@@ -463,9 +463,9 @@ PXEForge terminal — available commands:
|
||||
isos list registered ISOs
|
||||
clients list PXE clients seen this session
|
||||
gate list list gated-deployment queue
|
||||
gate assign <gate_id> <target> assign one gate to a boot entry
|
||||
gate assign <entry_id> <target> assign one gate to a boot entry
|
||||
gate assign-all <target> assign every waiting gate
|
||||
gate release <gate_id> release one gate
|
||||
gate release <entry_id> release one gate
|
||||
|
||||
nfs list list NFS mounts
|
||||
nfs mount <s>:<e> [v3|v41] [ro|rw] add and mount an NFS share
|
||||
|
||||
Reference in New Issue
Block a user