Files
OpenPXE/README.md
T
Miles WardandClaude Opus 4.8 5f98e6e03f v0.8.1: add ISO by URL, zero-touch admin bootstrap
Ease-of-use pass inspired by Bootimus (Dnsmasq-PXE is a manual dnsmasq
setup guide — nothing to adopt; OpenPXE already replaces that stack).

Add ISO by URL:
- New http-api `fetch` module: a small FetchJobs registry + a background
  streaming download (reqwest) that pipes a remote .iso through the same
  UploadHandle + introspection path as an upload, so a URL-fetched image
  classifies and gains boot entries identically. Progress is polled by the
  Storage view and rendered as rows, mirroring uploads.
- Routes POST/GET/DELETE /api/isos/fetch. http/https only; .iso-only
  filename derived from Content-Disposition / URL basename with path
  traversal stripped; 16 GiB cap; cancel; credential-stripped URL display.
  Operator-gated, no boot-time outbound — offline boot is untouched.
- Storage upload card gains an "Or add by URL" field with progress + cancel.

Zero-touch admin bootstrap:
- OPENPXE_ADMIN_USERNAME + OPENPXE_ADMIN_PASSWORD (or _PASSWORD_FILE for
  Docker/K8s secrets) auto-create the admin on first run, so a fresh
  container is usable with no setup wizard. Seeds the first run only — a
  lingering env var can't reset a rotated password.

Tests: URL parse / filename / Content-Disposition unit tests + a wiremock
end-to-end fetch-into-store integration test. clippy/fmt/node clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-16 15:56:27 -04:00

13 KiB

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.