Files
OpenPXE/README.md
T
Miles WardandClaude Opus 4.8 edf3a69daa docs: rewrite README — production/VC-ready, logo + v0.5.5 feature set
Replaces the stale v0.4.1 README with a polished, accurate overview:
centered brand-mark header + tagline + badges, a "Why OpenPXE" pitch,
a scannable Highlights section, and a "Built in Rust" section framed on
real properties (single ~18MB static musl binary, no GC, async Tokio,
workspace-wide unsafe deny, OpenSSL-free pure-Rust crypto, sub-minute
zigbuild images).

Surfaces everything shipped since v0.4.1: SMB + NFS + SFTP remote ISO
libraries (with a comparison table), SAML SSO, branding, notifications,
unattended installs, per-MAC host bindings, and layered figment config.
Quick-start, env table, OpenShift, and health/observability all updated
to v0.5.5. Adds docs/openpxe-logo.svg (render-safe static copy of the
web-UI mark) for the header.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 12:07:31 -04:00

271 lines
12 KiB
Markdown

<p align="center">
<img src="docs/openpxe-logo.svg" alt="OpenPXE" width="104" height="104" />
</p>
<h1 align="center">OpenPXE</h1>
<p align="center">
<strong>Container-native network boot &amp; OS deployment — built in Rust.</strong>
</p>
<p align="center">
Drag in an ISO. PXE-boot and image an entire fleet from a browser.<br/>
No iPXE scripting. No <code>dnsmasq</code> + <code>tftpd</code> + Samba glue. No glibc. No garbage collector.
</p>
<p align="center">
<img alt="release" src="https://img.shields.io/badge/release-v0.5.5-2874d7" />
<img alt="license" src="https://img.shields.io/badge/license-MIT%20%7C%20Apache--2.0-59824f" />
<img alt="rust" src="https://img.shields.io/badge/built%20with-Rust-fb8841?logo=rust&logoColor=white" />
<img alt="container" src="https://img.shields.io/badge/container--native-OCI%20%C2%B7%20OpenShift-2496ED?logo=docker&logoColor=white" />
<img alt="binary" src="https://img.shields.io/badge/static-musl%20%C2%B7%20~18MB-330f1f" />
</p>
---
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.