Files
OpenPXE/deploy/unraid
Miles Ward e3452fe976 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`.
2026-05-06 14:13:38 -04:00
..

OpenPXE on Unraid

Three paths from "I have an Unraid box with Gitea on it" to "PXE clients boot from OpenPXE". Pick the one that matches what you have.

Path A — build on Unraid, push to Gitea registry, pull by tag

Recommended once you've done it once. Image is published to gitea.milesward.dev/mward4/openpxe:0.1.0 (or your equivalent) and every Unraid template / docker-compose just references the tag.

Pre-flight:

  • Gitea has the Container Registry enabled (Site Admin → Configuration → set [packages] ENABLED = true in app.ini; default-on in Gitea ≥ 1.18).
  • You have a Gitea personal access token with write:package scope (User → Settings → Applications → Generate New Token, check package read+write).

Run on the Unraid host (Settings → Terminal, or ssh root@unraid):

# Pull just the build script straight from Gitea (no clone needed first):
GITEA_TOKEN=<your-token>
curl -fsSL \
  -H "Authorization: token $GITEA_TOKEN" \
  http://localhost:3000/mward4/OpenPXE/raw/branch/main/scripts/build-and-publish-unraid.sh \
  -o /tmp/openpxe-publish.sh

# Run it. ~6 min on Unraid hardware (native amd64, no QEMU).
chmod +x /tmp/openpxe-publish.sh
GITEA_TOKEN=$GITEA_TOKEN /tmp/openpxe-publish.sh

What it does:

  1. Shallow-clones the repo from local Gitea (no Cloudflare, no LAN issues).
  2. Runs scripts/fetch-ipxe.sh to grab the iPXE binaries from the official release URLs.
  3. docker build against deploy/docker/Dockerfile.
  4. docker login gitea.milesward.dev:3000 using a temp DOCKER_CONFIG so the credential never lands in your real ~/.docker/config.json.
  5. docker push both :0.1.0 and :latest.
  6. Logout, scrub the temp config, delete the workspace.

After it finishes, in Unraid → Docker → Add Container, set:

Field Value
Repository gitea.milesward.dev/mward4/openpxe:0.1.0
Network host
Extra args --cap-add=NET_BIND_SERVICE

Volume mounts (paths inside container in bold):

  • /var/lib/openpxe/isos/mnt/user/appdata/openpxe/isos
  • /var/lib/openpxe/work/mnt/user/appdata/openpxe/work
  • /var/lib/openpxe/smb/mnt/user/appdata/openpxe/smb

Or skip the manual UI by dropping openpxe.xml (in this directory) into /boot/config/plugins/dockerMan/templates-user/ and Unraid will list it as a one-click template.

Web UI: http://<unraid-ip>/.

Path B — docker-compose up directly from cloned repo

Skip the registry entirely. Useful for "hack on it locally" iterations.

ssh root@unraid
cd /mnt/user/appdata
git clone http://localhost:3000/mward4/OpenPXE.git openpxe-src
cd openpxe-src
bash scripts/fetch-ipxe.sh
docker compose -f docker-compose.yml up -d --build openpxe

The bundled docker-compose.yml already wires host networking, the right cap_add, and bind-mounts to ./data/. Edit those bind-mount paths if you want them under /mnt/user/appdata/openpxe/.

Path C — docker load from a tarball I built off-box

For when the build host can reach Unraid but Unraid can't reach the internet. Build the image somewhere with a cargo + buildx environment, then:

# On the build host
docker save openpxe:0.1.0 | gzip > openpxe-0.1.0.tar.gz

# Transfer (rsync / scp / SMB / ZFS-replicate / sneakernet)
scp openpxe-0.1.0.tar.gz root@unraid:/tmp/

# On Unraid
gunzip -c /tmp/openpxe-0.1.0.tar.gz | docker load
docker tag openpxe:0.1.0 gitea.milesward.dev/mward4/openpxe:0.1.0

If you want it pullable by tag from other Unraid templates, push to Gitea afterward (path A's last step).

Verifying it works

Once the container is up, from any LAN host:

# Should return 'ok'
curl -s http://<unraid-ip>/healthz
# Should return 'ready' if iPXE binaries are bundled
curl -s http://<unraid-ip>/readyz

Web UI: http://<unraid-ip>/. The Dashboard's top-bar pill should read ● ready. Upload an ISO, boot a test machine PXE → it should show up in the Dashboard's "Recent connections" table within seconds.

Common gotchas

  • DHCP collision. Don't run two PXE proxies on the same broadcast domain. OpenPXE runs in proxy mode and never offers IP leases, so it coexists with whatever DHCP server is already on the network — but two proxies racing each other will whichever-wins at random.
  • Host networking only. Bridge mode containers don't see broadcast DHCP. There's no working bridge-mode config for a PXE server.
  • Permissions on /mnt/user/appdata/openpxe. The container runs as uid 10001 by default. The entrypoint chowns the bind mounts to 10001 on first start, but only if the container itself has root — --user=root isn't needed; the multi-stage Dockerfile starts as root, fixes perms, then drops to openpxe via gosu.
  • NFS mounts in the Storage tab. Mounting NFS inside the container needs CAP_SYS_ADMIN. To enable, add --cap-add=SYS_ADMIN to the Unraid template's "Extra args" — but understand that's a meaningful capability, comparable to --privileged. Skip it if you only need uploads.