<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.6.0-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.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.
- **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.
