diff --git a/README.md b/README.md index 758abaf..42d2be8 100644 --- a/README.md +++ b/README.md @@ -1,301 +1,270 @@ -# OpenPXE +

+ OpenPXE +

-Container-native PXE boot server. A Rust reimplementation of -[iVentoy (ventoy/PXE)](https://github.com/ventoy/PXE), designed from scratch -for Docker/OCI and OpenShift. Upload `.iso` files via the web UI; network -clients PXE-boot them. +

OpenPXE

-> **Status:** v0.4.1 / pre-beta. Phases 1–5 complete: full PXE stack, -> Queued Deployment queue, NFS-share ISO sources, live tracing log + an -> operator terminal, per-MAC host bindings, Prometheus `/metrics`, -> light/dark theme toggle, animated OpenPXE imaging-progress widget, -> chunked ISO uploads, and per-ISO boot passwords. The test suite and -> clippy are part of the release checklist. Ready for real-hardware validation. +

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

-## Design non-negotiables +

+ 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. +

-1. **Fully offline / air-gap deployable.** Zero CDN assets. Zero external - HTTP calls from the server, the browser, or the generated iPXE scripts. - Build the container once, run forever disconnected. -2. **iPXE is a backend implementation detail.** No `.ipxe` upload path, no - manual script editing, no iPXE terminology in the UI. Every knob in the - web UI maps to a specific script-generation behavior inside the binary. -3. **The client trust store is off-limits.** No test-signed drivers, no - `bcdedit /set testsigning on`, no certificates injected into WinPE or - the target OS. +

+ release + license + rust + container + binary +

-## What it does +--- -1. **DHCP proxy** (RFC 4578). Coexists with your existing DHCP server — - never assigns IPs. Listens on UDP 67 + UDP 4011. -2. **TFTP server** (RFC 1350 + RFC 2347/2348/2349/7440 option negotiation) - that serves architecture-specific iPXE binaries to firmware PXE ROMs. -3. **HTTP server** that serves the web UI, the generated iPXE boot scripts, - raw ISOs (with Range), and files inside ISOs without prior extraction. -4. **ISO introspection**: auto-detects the distro family and generates the - appropriate kernel+initrd or wimboot chain. No manual config. -5. **Hierarchical PXE menu** mirroring the Phase 2 spec: - ``` - Default > Boot from Local HDD - Installers > Linux Installers / Windows Installers - Tools > Utilities / OpenPXE Shell / Network Card Info - Queued Deployment - ``` -6. **Queued Deployment queue** — the coordinated launch flow. A client that - selects *Queued Deployment* gets a numbered position and waits. The - operator picks an ISO in the web UI and fires it to every waiting - client simultaneously. -7. **Web UI** (Netbox-style): sidebar nav (Dashboard / Network / Queue / - Storage / Hosts / Terminal / About), light + dark themes - (toggle top-right or press `T`), animated OpenPXE progress - widget when devices are imaging. All assets served from the binary — - no external requests. -8. **Per-MAC host bindings.** Pin a MAC to a boot target and the client - skips the menu, chains straight through. -9. **Prometheus metrics** at `/metrics` — DHCP replies by arch, TFTP - transfer counts and bytes, HTTP request counts by route, queue / - imaging gauges, uptime, build info. Plain text exposition format, - no external metrics framework dependency. -8. **Settings API** lets you change the default boot-menu timeout (default - 600s), the timeout action (stay / Local HDD / Queued Deployment), and - feature toggles like Windows ISO support. The iPXE scripts regenerate - on every request using current settings. +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. -### Architectures supported on day one +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.** -| DHCP option 93 | Architecture | Binary 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` | +> **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. -UEFI firmware that sends `HTTPClient` in option 60 is handled too — we -skip TFTP and respond with an HTTP URL. +## Why OpenPXE -## Quick start — MVP container (recommended) +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 -# 1. Pull bundled iPXE binaries (~2 MB, one-time). -./scripts/fetch-ipxe.sh +# Build the self-contained image (iPXE binaries are fetched + built inside the Dockerfile). +docker build -f deploy/docker/Dockerfile -t openpxe:0.5.5 . -# 2. Build the container image (~3 min first time). -docker buildx build -f deploy/docker/Dockerfile -t openpxe:0.4.1 --load . - -# 3. Run it on the box plugged into your PXE network. Set PUBLIC_IP to -# this host's LAN address so advertised iPXE URLs are reachable. -docker run -d --name openpxe \ - --network host \ +# 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.4.1 + openpxe:0.5.5 -# 4. Open the UI and drop an ISO in. +# Open the UI and drop an ISO in. open http://10.0.0.5 ``` -Host networking is required in proxy mode so the container sees DHCPDISCOVER -broadcasts from the PXE VLAN. On macOS/Windows hosts Docker runs in a Linux -VM, so "host" means the VM — use `openpxe-dev` in `docker-compose.yml` for -API-only testing on a laptop. +> 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`. -### Quick start — docker compose +### Build from source ```bash -# MVP / API testing on a laptop (no DHCP, high ports): -OPENPXE_PUBLIC_IP=127.0.0.1 docker compose up openpxe-dev -# Real PXE deployment on a Linux host (host network, DHCP proxy on): -OPENPXE_PUBLIC_IP=10.0.0.5 docker compose up openpxe +./scripts/fetch-ipxe.sh # populate assets/ipxe/ (embedded at compile time) +cargo run --release # needs root or CAP_NET_BIND_SERVICE for :80/:69 ``` -### Multi-arch build + push - -For deploying to x86_64 servers, build both arches in one manifest: - -```bash -# One-time: bootstrap a multi-arch builder. -docker buildx create --name openpxe-multi --driver docker-container --use - -# Build + push both linux/amd64 and linux/arm64 under one tag. -docker buildx build --builder openpxe-multi \ - --platform linux/amd64,linux/arm64 \ - -t ghcr.io/YOUR-ORG/openpxe:0.4.1 \ - --push \ - -f deploy/docker/Dockerfile . -``` - -On an Apple Silicon host, the amd64 stage runs under QEMU emulation (~10-15 min for a cold cache). On a Linux x86_64 host, both arches build natively at normal speed. CI runners on GitHub Actions with `docker/build-push-action@v5` handle this cleanly. - -### Build from source (no container) - -```bash -./scripts/fetch-ipxe.sh -cargo run --release # needs NET_BIND_SERVICE or root for :80/:69 -``` - -### Container health probes - -| Endpoint | Purpose | -|-------------|---------------------------------------------------------------| -| `/healthz` | Liveness — HTTP stack alive. Always 200. | -| `/readyz` | Readiness — 200 only if iPXE binaries bundled + ISO dir OK. | -| `/api/status` | Full JSON status: versions, assets, counts, live settings, SMB state. | - -### Pre-seeding ISOs from a directory - -For CI, pre-baked homelab deployments, or a fresh PVC, the binary has a -`seed` subcommand that imports every `*.iso` from a host path through the -same pipeline the web UI uses (introspection + boot-entry generation): +### 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.4.1 seed --from /seed - -# Dry run first to see what would be imported: -docker run --rm -v /my/iso-library:/seed:ro openpxe:0.4.1 seed --from /seed --dry-run + openpxe:0.5.5 seed --from /seed # add --dry-run to preview ``` -### Environment overrides +## Remote ISO libraries -| Var | Default | Meaning | -|------------------------|-----------------------------|----------------------------------------| -| `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_DHCP_MODE` | `proxy` | `proxy` or `disabled` | -| `OPENPXE_PUBLIC_IP` | auto-detect | Advertised IP for clients. Startup **fails** if unset and auto-detect returns loopback. | -| `OPENPXE_ISO_DIR` | `/var/lib/openpxe/isos` | Where uploaded ISOs live | -| `OPENPXE_WORK_DIR` | `/var/lib/openpxe/work` | Scratch + runtime settings | -| `OPENPXE_LOG` | `info,openpxe=debug` | `tracing` filter | +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. -## What the boot menu looks like on a real client +| 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 + OpenPXE — network boot menu ------------------------- Default ------------------------- Boot from Local HDD - ----------------------- Installers ----------------------- + ----------------------- Installers ------------------------ Linux Installers > Windows Installers > (only if enabled in Settings) -------------------------- Tools -------------------------- - Tools > Utilities / Shell / - NIC Info / Reboot / - Exit and continue BIOS + Tools > Utilities / OpenPXE Shell / NIC Info / Reboot ---------------------- Queued Deployment ------------------ Queued Deployment (join queue) ``` -Linux/Windows submenus show file sizes iVentoy-style: +Linux/Windows submenus list images iVentoy-style with sizes: ``` - OpenPXE - Linux Installers + OpenPXE — Linux Installers - [ 4376 MB] CentOS-7-x86_64-DVD-1810 - [ 2002 MB] Fedora-Workstation-Live-x86_64-38-1.6 [ 4699 MB] ubuntu-22.04.2-desktop-amd64 + [ 2002 MB] Fedora-Workstation-Live-x86_64-38-1.6 < Back to main menu ``` -iPXE never appears in the UI — the whole hierarchy above is generated from -ISOs you upload via drag-and-drop in the web UI plus toggles in Settings. +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 all oc -n openpxe get route openpxe -o jsonpath='{.spec.host}' ``` -### Why a custom SCC? +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. -The default `restricted-v2` blocks `hostNetwork` and all capabilities. PXE -cannot work without host network (CNI overlays don't deliver L2 broadcast -into pod netns), and we need `NET_BIND_SERVICE` to bind <1024. The custom -`openpxe-scc` grants exactly those two and nothing else. No raw sockets, -no privileged mode — proxy-mode DHCP sidesteps the usual requirements. +## Health & observability -### What's on host ports - -| Port | Proto | Purpose | -|----------|-------|---------------------------------| -| 67 | UDP | DHCP server (proxy replies) | -| 69 | UDP | TFTP | -| 4011 | UDP | PXE Boot Server discovery | -| 80 | TCP | Web UI + HTTP boot assets | - -The OpenShift Route only covers 80/TCP. Clients on the PXE network talk to -the node's host IP directly for UDP. - -## Windows support - -Enabled by toggling **Windows ISO support** under Settings. The flow: - -1. Upload a stock Microsoft Windows install ISO (vanilla, no pre-processing). -2. On upload, OpenPXE extracts the ISO and uses `wimlib-imagex` to rewrite - image index 2 (WinPE) of `sources/boot.wim`. It injects exactly two - plain-text files: - - `Windows/System32/winpeshl.ini` — tells WinPE to run `startnet.cmd`. - - `Windows/System32/startnet.cmd` — runs `wpeinit`, waits for the SMB - host to be reachable, `net use Z: \\\ /user:guest`, - then `Z:\setup.exe`. -3. The container's Samba `smbd` serves the extracted install tree on :445. -4. The client gets chainloaded into wimboot → patched WinPE → Windows Setup - running off the SMB share. **Every binary the client executes is stock - Microsoft-signed.** - -### What we never do - -- Ship drivers — signed, test-signed, or otherwise — that load on the client. -- Install certificates into the target's trust store or WinPE boot policy. -- Recommend `bcdedit /set testsigning on` or any equivalent signing-policy - weakening. - -### Credit & limitations - -The SMB-based approach is adapted from [Bootimus](https://github.com/garybowers/bootimus) -(Apache-2.0). Re-implemented in Rust; no code was copied verbatim. Known -operational constraints inherited from the design: - -- **Port 445 must be directly reachable from PXE clients.** `net use` - ignores alternate ports. In OpenShift this means `hostPort: 445` on the - deployment; on a host that already runs SMB it will collide. -- Windows 10/11 client SKUs are the tested target. Server SKUs untested. -- Hardware with NICs/storage controllers missing from WinPE's bundled - drivers will need a driver-pack injection step (not yet implemented). - -## Queued Deployment - -The coordinated launch flow, end to end: - -1. A client boots and picks **Queued Deployment** in the PXE menu (or falls - through on timeout with the default `timeout_action`). -2. The client joins the queue, gets a numbered queue position, and enters a - long-poll loop (25s per request, auto-renewed). -3. In the web UI's **Queued Deployment** tab, the operator sees each waiting - client with its MAC, IP, arch, and position. -4. The operator selects an image and clicks **Launch for all waiting**. - The server broadcasts the assignment to every queued client via a - `tokio::sync::Notify`; each client's next poll returns the boot script - for the chosen image. -5. Every client chains the same image at effectively the same moment. The - queue stays visible until the operator releases entries, which keeps a - useful audit trail during hardware testing. - -No user-facing iPXE anywhere in this flow. The client only ever runs -scripts we generate; the operator only interacts with the web UI. +| 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 -See [`docs/architecture.md`](docs/architecture.md) for the protocol stack, -crate layout, and the full decision log. +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. -## Licence +## License -MIT OR Apache-2.0. +Dual-licensed under **MIT OR Apache-2.0** — use whichever fits your project. diff --git a/docs/openpxe-logo.svg b/docs/openpxe-logo.svg new file mode 100644 index 0000000..52e1664 --- /dev/null +++ b/docs/openpxe-logo.svg @@ -0,0 +1,21 @@ + + OpenPXE + + + + + + + + + + + + + + + +