Files
homelab-infra/apps/netboot/README.md
T
panxiao81 88a02ababa
lint / yaml (push) Has been cancelled
lint / ansible (push) Has been cancelled
lint / terraform (push) Has been cancelled
Establish clean homelab infrastructure baseline
Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
2026-09-09 16:47:20 +00:00

110 lines
6.1 KiB
Markdown

# netboot
Self-hosted [netboot.xyz](https://netboot.xyz) PXE boot server for the LAN, using the
official appliance container plus a `dnsmasq` proxyDHCP so it coexists with the existing
DHCP server (the **NEC IX router**, which is left untouched).
## Architecture
The NEC IX router keeps leasing IPs. `dnsmasq` runs in **proxyDHCP** mode and only answers
the PXE/boot part of the conversation, pointing clients at this host (`192.168.10.127`).
Client type (announced via DHCP option 60/93) → what it gets:
| Client | Transport | File |
|------------------------------------------|-----------|----------------------------------------------|
| Legacy BIOS (`arch 0`) | TFTP | `netboot.xyz.kpxe` |
| UEFI, normal PXE (`arch 7/9`) | TFTP | `netboot.xyz.efi` |
| UEFI with HTTP Boot (`vendor HTTPClient`)| HTTP | `http://192.168.10.127:8080/menus/netboot.xyz.efi` |
Fallback is automatic: a UEFI box only announces `HTTPClient` when HTTP Boot is actually
enabled/supported; otherwise it does normal PXE and lands on the TFTP `.efi` branch.
This is a **two-stage** chain, which matters for the dnsmasq config:
1. **Firmware → netboot.xyz iPXE.** Raw firmware (not iPXE) gets the binary above. proxyDHCP
*requires* `pxe-service` here — plain `dhcp-boot` produces no boot offer in proxy mode.
2. **netboot.xyz iPXE → menu.** The loaded `.efi`/`.kpxe` re-does DHCP (announcing itself via
option 175) and dnsmasq answers with `dhcp-boot=tag:ipxe,netboot.xyz.efi,,192.168.10.127`.
Two details matter, both dictated by the bootstrap **embedded in the netboot.xyz binary**:
- **The bootfile must be a *recognised binary name*** (`netboot.xyz.efi`), not `menu.ipxe`.
The embedded bootstrap only chains the menu **locally** (its `:tftpmenu` branch) when the
bootfile matches one of its own binary names; any other name skips that branch and boots
the **public** `boot.netboot.xyz` menu instead.
- **The router's DHCP `next-server` must point at `192.168.10.127`** (see below). Under
proxyDHCP the bootstrap fetches its `local-vars.ipxe` from `${next-server}` — the value
from the *real* DHCP server (the NEC IX router), **not** from dnsmasq's
`${proxydhcp/next-server}`. `local-vars.ipxe` is what sets `use_proxydhcp_settings true`
(the no-keypress switch), so if it can't be fetched the UEFI client stalls fetching from
the router, then prompts for a `p` keypress or falls back to the public menu.
The boot binaries then chain the menu **locally** over TFTP from `192.168.10.127`, so clients
boot *this* host's menu, not the public site. Only the version check and distro mirrors reach
the internet.
### Required NEC IX router setting
The router keeps leasing IPs as before, but its DHCP scope for the LAN must advertise
**`next-server 192.168.10.127`** (a.k.a. the `siaddr` / BOOTP server field) on the LAN DHCP
pool. This is the one piece of PXE config the router *does* need — it does not otherwise
PXE-boot anything, and regular (non-PXE) DHCP clients ignore `next-server`. Set it via the
DHCP-server/boot-server (`siaddr`) option of the IX DHCP profile serving the `192.168.10.0/24`
scope; leave the bootfile name unset (dnsmasq's proxyDHCP still supplies it).
## Services (all on host `192.168.10.127`)
| Port | Service | Provided by | Purpose |
|-------------|-----------|------------------------|------------------------------------------|
| `67/udp` | proxyDHCP | `dnsmasq` (host net) | PXE boot offers (no IP leasing) |
| `69/udp` | TFTP | `netbootxyz` | serves `/config/menus` (binaries + menu) |
| `8080` | HTTP | `netbootxyz` nginx | `/` = `/assets` mirror; `/menus/` = binaries (UEFI HTTP Boot) |
| `3000` | Web UI | `netbootxyz` | manage menus / download assets |
## Usage
```bash
docker compose up -d # start
docker compose logs -f dnsmasq # watch DHCP offers during a client boot
docker compose down # stop
```
Config manager (add/update distros, edit menus): <http://192.168.10.127:3000>
## Verified
Both PXE paths were tested end-to-end with QEMU VMs bridged onto `br0` (real SeaBIOS and
OVMF/UEFI firmware), booting all the way to the local netboot.xyz menu:
- **Legacy BIOS** → proxyDHCP offered `netboot.xyz.kpxe` → TFTP → local menu rendered ✅
- **UEFI x64** → proxyDHCP offered `netboot.xyz.efi` → TFTP → local menu rendered ✅
- **UEFI HTTP Boot** → configured, not yet VM-tested (hard to trigger in QEMU).
To re-test: create a tap on `br0`, run a diskless QEMU VM with `-boot n`, and watch
`docker compose logs -f dnsmasq` + `docker logs -f netbootxyz`.
## Notes / gotchas
- **Host networking is required** for `netbootxyz`: TFTP renegotiates to an ephemeral
port that Docker's bridge NAT mangles (clients get `TID mismatch`). Host net serves TFTP
straight off the LAN interface.
- `NGINX_PORT` is ignored by the image; the nginx listen port is pinned to `8080` in
`config/nginx/site-confs/default`, which also adds a `/menus/` location so UEFI HTTP Boot
can fetch the first-stage `.efi`. (If the appliance ever regenerates that file on upgrade,
re-add the `listen 8080` and `/menus/` bits.)
- `dnsmasq` binds **only `br0`** (`interface=br0` + `bind-interfaces`) so it doesn't clash
with libvirt's own dnsmasq on `virbr0`/`virbr1`.
- `config/menus/local-vars.ipxe` sets `use_proxydhcp_settings true` so proxyDHCP clients
boot without a keypress prompt.
- The NEC IX router needs **no PXE configuration** — proxyDHCP handles everything.
- Editing `dnsmasq.conf` requires `docker restart netboot-dnsmasq` (compose won't
auto-recreate on a bind-mounted file content change).
## Files
- `compose.yaml` — the two services (`netbootxyz` + `dnsmasq`)
- `dnsmasq.conf` — proxyDHCP + arch detection
- `config/` — netbootxyz appliance state (menus, nginx conf); managed by the container
- `assets/` — optional locally-mirrored distro images
- `buildout/` — leftover from an earlier manual build; **no longer used** (safe to delete)