Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
6.1 KiB
netboot
Self-hosted 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:
-
Firmware → netboot.xyz iPXE. Raw firmware (not iPXE) gets the binary above. proxyDHCP requires
pxe-servicehere — plaindhcp-bootproduces no boot offer in proxy mode. -
netboot.xyz iPXE → menu. The loaded
.efi/.kpxere-does DHCP (announcing itself via option 175) and dnsmasq answers withdhcp-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), notmenu.ipxe. The embedded bootstrap only chains the menu locally (its:tftpmenubranch) when the bootfile matches one of its own binary names; any other name skips that branch and boots the publicboot.netboot.xyzmenu instead. - The router's DHCP
next-servermust point at192.168.10.127(see below). Under proxyDHCP the bootstrap fetches itslocal-vars.ipxefrom${next-server}— the value from the real DHCP server (the NEC IX router), not from dnsmasq's${proxydhcp/next-server}.local-vars.ipxeis what setsuse_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 apkeypress or falls back to the public menu.
- The bootfile must be a recognised binary name (
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
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 getTID mismatch). Host net serves TFTP straight off the LAN interface. NGINX_PORTis ignored by the image; the nginx listen port is pinned to8080inconfig/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 thelisten 8080and/menus/bits.)dnsmasqbinds onlybr0(interface=br0+bind-interfaces) so it doesn't clash with libvirt's own dnsmasq onvirbr0/virbr1.config/menus/local-vars.ipxesetsuse_proxydhcp_settings trueso proxyDHCP clients boot without a keypress prompt.- The NEC IX router needs no PXE configuration — proxyDHCP handles everything.
- Editing
dnsmasq.confrequiresdocker 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 detectionconfig/— netbootxyz appliance state (menus, nginx conf); managed by the containerassets/— optional locally-mirrored distro imagesbuildout/— leftover from an earlier manual build; no longer used (safe to delete)