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)](https://github.com/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.5.5, 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. - **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. ## 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 ```bash # 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 ```bash ./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 ```bash 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](https://github.com/garybowers/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 | ## OpenShift ```bash 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`](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.