Miles Ward e80a66b0da v0.8.2: fix iPXE menu item selection (issue #2)
In 0.8.1 on x86_64 UEFI (snponly.efi) the boot menu drew and the keyboard
worked, but every item except the first one bounced straight back to the
menu with the default Queued Deployment entry highlighted; the `x` Exit
item did the same. Root cause was the generated dispatch lines:

  iseq ${target} linux && chain .../boot/_linux_menu.ipxe || goto menu

iPXE evaluates `&&`/`||` strictly left to right (src/core/exec.c
`system()`), so for `A && B || C` the `goto menu` runs whenever A fails --
for every item except the first. Only `local` ever reached its chain line,
which is exactly the reported symptom. The same shape made `x` useless,
and `goto menu` redraws with `--default queue`. The bug report's Ctrl-B
test (chaining an entry script by hand) booted fine, confirming the
scripts and HTTP/TFTP paths were healthy.

Fix, all in crates/http-api/src/ipxe_script.rs:

- Dispatch with `goto ${target} || goto menu` to `:label` blocks (the
  canonical iPXE menu pattern) in the top menu, both installer submenus,
  the tools menu and the utilities menu, so a non-matching item can never
  fall through into a stray `goto menu`.
- Menu-to-menu transitions use `chain --replace`, so the previous menu
  image is replaced: `exit` from any submenu reaches the firmware and
  repeated Back/forward navigation no longer nests script images.
- Boot-entry scripts now recover to the menu themselves (kernel/initrd
  fetches `|| goto failed`, `:failed` chain --replace boot.ipxe), since
  with --replace the calling menu can no longer catch their errors.
- Arch-label mapping: three `|| echo` probes instead of one chain ending
  in `|| true` (`true` is not an iPXE command; non-allowlisted pairs such
  as i386+efi aborted the script before the menu was drawn).
- Utilities menu Back really returns to Tools (it used to redraw
  Utilities); `prompt --timeout` expiry no longer aborts NIC info;
  password prompt tests emptiness with `isset` instead of a literal `""`
  token (iPXE has no quoting, so an empty Enter skipped the main-menu
  path); queue entry view falls back to the main menu if the queue
  endpoint is unreachable.

Tests: new `menu_dispatch_tests` interpreter that mirrors iPXE's
`system()`/`process_script()` (whitespace tokenisation, left-associative
`&&`/`||`, goto labels, abort on nonzero line) and asserts each top-menu
and submenu item reaches its expected chain URL (the old lines would
fail these), plus exit/reboot/firmware, unknown-architecture, password
prompt, shell and queue flows. Text assertions, the insta snapshot and the
tools-menu wiring assertion in tests/full_flow.rs updated to match.
`cargo fmt --check` clean, no new clippy warnings, `cargo test
--workspace` green (all crates).
2026-10-18 06:36:23 -04:00
2026-04-29 02:47:00 -04:00
2026-05-21 02:13:08 -04:00
2026-04-29 02:47:00 -04:00
2026-04-29 02:47:00 -04:00

OpenPXE

OpenPXE

Container-native network boot & OS deployment — built in Rust.

Drag in an ISO. PXE-boot and image an entire fleet from a browser.
No iPXE scripting. No dnsmasq + tftpd + Samba glue. No glibc. No garbage collector.

release license rust container binary


OpenPXE turns bare-metal provisioning into a single container with a web UI. It's a ground-up Rust reimplementation of iVentoy (ventoy/PXE), designed for Docker/OCI and OpenShift instead of a Windows desktop — so it drops onto an Unraid box, a Linux server, or a Kubernetes cluster and just runs.

Upload .iso files (or point at a remote share), and any machine on the network boots them — Linux installers, live tools, or stock Windows setup — with zero iPXE knowledge required by the operator.

Status — v0.6.0, late pre-beta. The full PXE stack, web UI, remote ISO libraries (SMB/NFS/SFTP), Windows deployment, queued fleet rollout, SAML SSO, and Prometheus metrics are implemented and test-covered. The release checklist gates every tag on the full test suite + clippy. Currently in real-hardware validation.

Why OpenPXE

Standing up network boot the traditional way means hand-wiring dnsmasq, a TFTP daemon, hand-written iPXE menu scripts, an HTTP server, and Samba — then keeping that fragile stack alive, and discovering none of it containerizes cleanly (kernel-mount NFS, raw sockets, CAP_SYS_ADMIN). iVentoy solved the UX beautifully, but it's a Windows GUI app.

OpenPXE collapses that whole stack into one statically-linked binary in one container:

  • A web UI does everything. iPXE is an internal implementation detail — there is no script upload, no .ipxe editing, no PXE jargon in the interface.
  • It runs anywhere a container runs. No kernel modules, no privileged mode — proxy-mode DHCP + NET_BIND_SERVICE is the entire requirement. Verified on Unraid, plain Docker, and OpenShift's restricted SCC.
  • It's air-gap native. Zero CDN assets, zero outbound calls from the server, browser, or generated boot scripts. Build the image once, run it forever, disconnected.

Highlights

Boot stack

  • DHCP proxy (RFC 4578) that coexists with your existing DHCP — it never hands out IPs.
  • TFTP (RFC 1350 + 2347/2348/2349/7440 option negotiation) serving arch-correct iPXE firmware.
  • HTTP serving the UI, generated boot scripts, raw ISOs (with byte-range), and files inside ISOs with no prior extraction.
  • Graphical iPXE boot menu built from your uploads, with a PNG background and a clean hierarchy — generated fresh on every request from current settings.

ISO management & remote libraries

  • Drag-and-drop chunked uploads that don't 502 on multi-GB images — drop several at once and they upload concurrently.
  • Add by URL — paste an ISO link and the server streams it straight into storage and auto-detects it, with progress; no download-then-reupload.
  • Automatic introspection — detects the distro family and generates the right kernel+initrd or Windows wimboot chain. No manual config.
  • Remote ISO libraries, streamed on demand (no local cache) over SMB, NFS, or SFTP — see the table below.

Fleet deployment

  • Queued Deployment — clients join a queue and wait; the operator fires one image at every waiting machine simultaneously.
  • Per-MAC host bindings — pin a MAC straight to a target (with optional auto hostname, auto IP, and an unattended answer file); it skips the menu and chains through.
  • Unattended installs — upload Kickstart / Preseed / Autoinstall / Windows answer files; they're templated per-host (hostname / IP / MAC) and served only to booting clients.
  • Windows deployment from a stock Microsoft ISO — every binary the client runs stays Microsoft-signed (details below).

Operations & access

  • SAML 2.0 single sign-on (pure-Rust SP, no OpenSSL/xmlsec) alongside local accounts.
  • Custom branding — light / dark / PXE-client logos and favicon.
  • Notifications — Slack / Teams / Discord webhooks and SMTP email on boot events.
  • Prometheus /metrics, a built-in operator terminal, live tracing log, and /healthz · /readyz probes.
  • Layered config — defaults → TOML file → OPENPXE_* env, in that order.
  • Zero-touch first run — set OPENPXE_ADMIN_USERNAME + OPENPXE_ADMIN_PASSWORD and a fresh container comes up with the admin already created, no setup wizard. Automate the rest from scripts/Postman with a per-install API key (x-api-key), shown in Settings → Advanced.

Built in Rust

Rust isn't a checkbox here — it's why OpenPXE deploys the way it does:

  • One static binary, ~18 MB. Compiled to x86_64-unknown-linux-musl — no glibc, no interpreter, no sidecar runtime. The runtime image is "binary + a few CLI tools."
  • No garbage collector, async throughout. A Tokio runtime drives DHCP, TFTP, HTTP, and many concurrent multi-GB ISO streams on a tiny, predictable memory footprint — it idles near-zero and never GC-pauses mid-transfer.
  • Memory-safe by construction. unsafe is denied workspace-wide; the only exceptions are two small, individually-audited FFI calls (statvfs for disk usage and a Samba SIGHUP).
  • OpenSSL-free, pure-Rust crypto. TLS via rustls/ring; the SAML Service Provider does XML-DSig verification with RustCrypto — no xmlsec, no libxml2, no C crypto to CVE-patch. Even the SMB/NFS/SFTP clients avoid C libraries.
  • Sub-minute, reproducible container builds. Cross-compiled with cargo-zigbuild (zig as the linker) — a full image builds in well under a minute on a warm cache, with no QEMU emulation.

Quick start

Run the container

# Build the self-contained image (iPXE binaries are fetched + built inside the Dockerfile).
docker build -f deploy/docker/Dockerfile -t openpxe:0.5.5 .

# Run it on the box plugged into your PXE network. Host networking is required in
# proxy mode so the container sees DHCPDISCOVER broadcasts; set PUBLIC_IP to this
# host's LAN address so advertised boot URLs are reachable.
docker run -d --name openpxe --network host \
  -e OPENPXE_PUBLIC_IP=10.0.0.5 \
  -e OPENPXE_DHCP_MODE=proxy \
  -v $PWD/data/isos:/var/lib/openpxe/isos \
  -v $PWD/data/work:/var/lib/openpxe/work \
  openpxe:0.5.5

# Open the UI and drop an ISO in.
open http://10.0.0.5

On macOS/Windows, Docker runs inside a Linux VM, so "host network" means the VM — use the openpxe-dev service in docker-compose.yml for API-only testing on a laptop: OPENPXE_PUBLIC_IP=127.0.0.1 docker compose up openpxe-dev.

Build from source

./scripts/fetch-ipxe.sh          # populate assets/ipxe/ (embedded at compile time)
cargo run --release              # needs root or CAP_NET_BIND_SERVICE for :80/:69

Pre-seed ISOs from a directory

docker run --rm \
  -v /my/iso-library:/seed:ro \
  -v openpxe-data:/var/lib/openpxe/isos \
  -e OPENPXE_PUBLIC_IP=10.0.0.5 \
  openpxe:0.5.5 seed --from /seed          # add --dry-run to preview

Remote ISO libraries

Point OpenPXE at a NAS and boot ISOs straight off it — read on demand, no local copy, so a 50-ISO library costs zero disk on the OpenPXE host. All three clients are userspace (no kernel mounts, no CAP_SYS_ADMIN); pick whichever your storage speaks.

Protocol Implementation Auth HTTP Range¹
NFS (v3) Pure-Rust in-process client Client-IP (server export list) ✅
SFTP (SSH) Pure-Rust in-process client (russh) Password or SSH key · host-key TOFU ✅
SMB / CIFS Userspace smbclient Guest or username/password —

¹ Range support lets clients seek into a multi-GB ISO without downloading what comes before it — needed for kernel/initrd extraction and httpdisk-style boots. NFS and SFTP expose explicit offsets; the SMB CLI streams sequentially, so SMB-sourced ISOs serve whole-file.

Supported client architectures

DHCP option 93 Architecture Firmware served
0x0000 Legacy x86 BIOS undionly.kpxe
0x0006 IA32 UEFI snponly-i386.efi
0x0007 / 0x0009 x86_64 UEFI snponly.efi
0x000B ARM64 UEFI snponly-arm64.efi

UEFI firmware that advertises HTTPClient (option 60) skips TFTP entirely and is handed an HTTP URL.

The boot menu, on a real client

                        OpenPXE — network boot menu

 ------------------------- Default -------------------------
 Boot from Local HDD
 ----------------------- Installers ------------------------
 Linux Installers   >
 Windows Installers >                (only if enabled in Settings)
 -------------------------- Tools --------------------------
 Tools              >    Utilities / OpenPXE Shell / NIC Info / Reboot
 ---------------------- Queued Deployment ------------------
 Queued Deployment   (join queue)

Linux/Windows submenus list images iVentoy-style with sizes:

                   OpenPXE — Linux Installers

 [  4699 MB]  ubuntu-22.04.2-desktop-amd64
 [  2002 MB]  Fedora-Workstation-Live-x86_64-38-1.6
            < Back to main menu

The entire hierarchy is generated from what you upload and toggle — iPXE never surfaces.

Windows deployment

Enable Windows ISO support in Settings, then upload a stock, unmodified Microsoft ISO:

  1. On upload, OpenPXE uses wimlib-imagex to inject exactly two plain-text files into the WinPE image (winpeshl.ini + startnet.cmd) — no drivers, no certificates.
  2. The container's Samba smbd serves the extracted install tree on :445.
  3. The client chainloads wimboot → patched WinPE → Windows Setup running off the share.

Every executable the client runs is stock Microsoft-signed. OpenPXE never ships drivers, never installs certificates into the client trust store, and never recommends bcdedit /set testsigning on. The SMB approach is adapted (re-implemented, not copied) from Bootimus (Apache-2.0). Port 445 must be directly reachable from clients; Windows 10/11 client SKUs are the tested target.

Configuration

All settings have defaults and layer defaults → TOML (--config / OPENPXE_CONFIG) → OPENPXE_* env. The common knobs:

Var Default Meaning
OPENPXE_PUBLIC_IP auto-detect IP advertised to clients. Startup fails if unset and auto-detect yields loopback.
OPENPXE_DHCP_MODE proxy proxy or disabled
OPENPXE_HTTP_PORT 80 Web UI + boot-script HTTP port
OPENPXE_TFTP_PORT 69 TFTP port
OPENPXE_DHCP_PORT 67 DHCP server-side port
OPENPXE_ISO_DIR /var/lib/openpxe/isos Uploaded ISOs
OPENPXE_WORK_DIR /var/lib/openpxe/work Scratch, settings, share + branding state
OPENPXE_LOG info,openpxe=info tracing filter
OPENPXE_ADMIN_USERNAME — First-run only: with OPENPXE_ADMIN_PASSWORD, auto-creates the admin so no setup wizard is needed. Ignored once an admin exists.
OPENPXE_ADMIN_PASSWORD — First-run admin password. Use OPENPXE_ADMIN_PASSWORD_FILE to read it from a file (Docker/K8s secret).

OpenShift

oc apply -f deploy/openshift/
oc -n openpxe get route openpxe -o jsonpath='{.spec.host}'

The bundled openpxe-scc grants exactly hostNetwork (CNI overlays don't deliver L2 broadcast into pod netns) and NET_BIND_SERVICE (to bind ports <1024) — nothing else. No raw sockets, no privileged mode. The Route covers 80/TCP; PXE clients reach UDP 67/69/4011 on the node's host IP directly.

Health & observability

Endpoint Purpose
/healthz Liveness — always 200 if the HTTP stack is up.
/readyz Readiness — 200 only once iPXE firmware is bundled and the ISO dir is reachable.
/api/status Full JSON: version, assets, counts, live settings, share + SMB state.
/metrics Prometheus text format — DHCP replies by arch, TFTP/HTTP counts, queue gauges, uptime.

Architecture

Workspace of focused crates — core, dhcp-proxy, tftp, http-api, iso-store, ipxe-assets, webui, and the openpxe binary. See docs/architecture.md for the protocol stack, crate layout, and the full decision log.

License

Dual-licensed under MIT OR Apache-2.0 — use whichever fits your project.

S
Description
Modern, secure, air-gapped PXE boot solution. Container-native Rust.
https://openpxe.com/
Readme
2.1 MiB
0 Stars 1 Watchers 0 Forks
2026-10-18 06:54:27 -04:00
Languages
Rust 81.1%
JavaScript 12.9%
CSS 3.2%
Shell 1.3%
Dockerfile 0.9%
Other 0.6%