Compare commits
3
Commits
c607f2e31c
..
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49d0b00a8a | ||
|
|
a9c4f408a9 | ||
|
|
3517c67831 |
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(cargo check *)",
|
|
||||||
"Bash(cargo build *)",
|
|
||||||
"Bash(cargo clippy *)",
|
|
||||||
"Bash(cargo fmt *)",
|
|
||||||
"Bash(cargo tree *)",
|
|
||||||
"Bash(cargo doc *)",
|
|
||||||
"Bash(cargo test --workspace --lib)",
|
|
||||||
"Bash(cargo test --workspace)",
|
|
||||||
"Bash(cargo --version)",
|
|
||||||
"Bash(rustc --version)"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
# Phase 6 — recommendations
|
|
||||||
|
|
||||||
The v0.2.0 cut leaves PXEForge in a state where the entire protocol stack
|
|
||||||
and operator UI are exercised by 66 automated tests, the container is
|
|
||||||
multi-arch buildable, and the image ships at ~97 MB. What's left before
|
|
||||||
this looks and feels like a 1.0 product is mostly **real-hardware
|
|
||||||
validation** plus a small batch of features that can only sensibly be
|
|
||||||
designed once we've watched real machines image.
|
|
||||||
|
|
||||||
This doc is a punch list, ordered by what I'd do first if I had a week.
|
|
||||||
|
|
||||||
## Tier 1 — must-do before we call anything "stable"
|
|
||||||
|
|
||||||
### 1. Real-hardware validation matrix
|
|
||||||
|
|
||||||
We have CI tests for every protocol leg, but no end-to-end PXE on real
|
|
||||||
firmware. Build a small matrix:
|
|
||||||
|
|
||||||
| client | firmware | OS family | pass criteria |
|
|
||||||
|-------------------------------------|-----------|------------|---------------------------|
|
|
||||||
| any 10-y-old mini-PC | Legacy BIOS | Ubuntu Server 24.04 | gets to GRUB / installer |
|
|
||||||
| Intel NUC / similar | UEFI x64 | Windows 11 | reaches "where do you want to install" |
|
|
||||||
| Raspberry Pi 4 | UEFI ARM64 | Raspberry Pi OS | gets to login prompt |
|
|
||||||
| Dell / HP business laptop | UEFI x64 | Fedora | one of: kernel boot or wimboot |
|
|
||||||
|
|
||||||
Add a `docs/HARDWARE_VALIDATION.md` checklist that records what worked,
|
|
||||||
firmware versions, and any quirks. Anything weird gets a regression
|
|
||||||
test in the relevant crate.
|
|
||||||
|
|
||||||
### 2. Boot menu hotkey + UI accessibility audit
|
|
||||||
|
|
||||||
The iPXE menu has number-key + letter hotkeys but no documentation on
|
|
||||||
what they map to. Generate a printable cheat-sheet from
|
|
||||||
`crates/http-api/src/ipxe_script.rs` so operators don't have to read
|
|
||||||
the source. Run a screen-reader pass over the web UI — most of it
|
|
||||||
should be fine since we're mostly tables + form labels, but the
|
|
||||||
Terminal pane and the SSE log output need explicit `aria-live`
|
|
||||||
regions.
|
|
||||||
|
|
||||||
### 3. Boot.wim re-patch detection
|
|
||||||
|
|
||||||
Bootimus v0.1.62's "fingerprint of patched inputs + Save & Re-patch"
|
|
||||||
pattern is a small but high-value feature: when an operator changes
|
|
||||||
the SMB host override or upgrades wimboot, the existing patched
|
|
||||||
boot.wim is silently stale. We should:
|
|
||||||
|
|
||||||
- Hash the inputs (smb_host, smb_share, startnet.cmd content,
|
|
||||||
wimboot binary digest) into the IsoMeta;
|
|
||||||
- Surface a "needs re-patch" warning on the Storage tab when the
|
|
||||||
hash drifts;
|
|
||||||
- Add a "Re-patch SMB" button that re-runs the WimPatcher.
|
|
||||||
|
|
||||||
## Tier 2 — features that round out pre-beta
|
|
||||||
|
|
||||||
### 4. Auto-install file library
|
|
||||||
|
|
||||||
iVentoy and Bootimus both support attaching `autounattend.xml` /
|
|
||||||
`preseed.cfg` / `kickstart.cfg` to an image. The mechanics are
|
|
||||||
straightforward: store files under `<work_dir>/autoinstall/<distro>/`,
|
|
||||||
expose CRUD via `/api/autoinstall-files`, and modify the WimPatcher
|
|
||||||
+ Linux kernel cmdline to fetch + apply the right file. Placeholders
|
|
||||||
worth supporting (Bootimus pattern): `{{MAC}}`, `{{HOSTNAME}}`,
|
|
||||||
`{{IP}}`, `{{SERVER_ADDR}}`, `{{IMAGE_FILENAME}}`, substituted
|
|
||||||
serve-side per request.
|
|
||||||
|
|
||||||
### 5. Wake-on-LAN trigger
|
|
||||||
|
|
||||||
A natural pair with per-MAC host bindings: bind a MAC to an image,
|
|
||||||
then click "Wake & Image" to send the magic packet and let PXEForge
|
|
||||||
do the rest. Implementation is small (`udp/9` broadcast, magic packet
|
|
||||||
construction) but it makes the bound-host workflow feel instant.
|
|
||||||
|
|
||||||
### 6. Distro profile manifest
|
|
||||||
|
|
||||||
Today, distro detection lives as Rust match arms in `introspect.rs`
|
|
||||||
and the kernel cmdline templates live in `store.rs`. Bootimus extracts
|
|
||||||
this into a JSON manifest that ships embedded in the binary AND is
|
|
||||||
overridable by the operator at runtime — so a new distro can be added
|
|
||||||
without rebuilding the container. Worth porting; it'd let community
|
|
||||||
contributions land as PRs to a single JSON file.
|
|
||||||
|
|
||||||
### 7. Syslog receiver
|
|
||||||
|
|
||||||
`smee` ships one. The use case: WinPE / Linux installers can be
|
|
||||||
configured to syslog over the network to the PXE server; if we have
|
|
||||||
an endpoint and a place in the UI to view per-client diagnostics,
|
|
||||||
post-mortem on a failed install gets dramatically easier.
|
|
||||||
|
|
||||||
### 8. UEFI HTTP Boot validation
|
|
||||||
|
|
||||||
Option 60 = `HTTPClient` is wired up in `decide()` already, but
|
|
||||||
we've never tested it on real firmware. Some Dell + Lenovo UEFIs
|
|
||||||
prefer it over PXE-via-TFTP. A quick check on a real machine
|
|
||||||
(disable TFTP boot in firmware, force HTTP boot) and a regression
|
|
||||||
test would be nice.
|
|
||||||
|
|
||||||
## Tier 3 — bigger lifts, only if there's demand
|
|
||||||
|
|
||||||
### 9. Pure-Rust SMB server
|
|
||||||
|
|
||||||
`smbd` from Samba is ~80 MB of the runtime image. There are pure-Rust
|
|
||||||
SMB2 server crates (`smbd-server`, `smb-rs`) of varying maturity.
|
|
||||||
Replacing the dep would slim the image by ~40% and remove the
|
|
||||||
`CAP_SYS_ADMIN` requirement for SMB. Worth a spike, not necessarily
|
|
||||||
landable in Phase 6.
|
|
||||||
|
|
||||||
### 10. IPv6 / DHCPv6
|
|
||||||
|
|
||||||
PXE-over-IPv6 is real (RFC 5970). Some sites are v6-only. Worth
|
|
||||||
implementing once we know we have one. Until then, IPv4-only is the
|
|
||||||
right default — flipping the bit on v6 without v6 testing is asking
|
|
||||||
for silent breakage.
|
|
||||||
|
|
||||||
### 11. Multi-replica deployment
|
|
||||||
|
|
||||||
The current design assumes one PXEForge per broadcast domain. Two
|
|
||||||
proxies on the same L2 will race; the gate queue is in-memory, etc.
|
|
||||||
For HA we'd need to:
|
|
||||||
- Externalize the gate queue (Redis, etcd) or lean into "the menu is
|
|
||||||
cheap to refetch if a replica dies";
|
|
||||||
- Ensure DHCP proxy replies are deterministic so a client always
|
|
||||||
gets the same answer regardless of which replica replied;
|
|
||||||
- Document the L2 collision domain story.
|
|
||||||
|
|
||||||
This is a large lift and should only happen if someone's actually
|
|
||||||
asking for it.
|
|
||||||
|
|
||||||
### 12. Pi 4 / SBC quirks
|
|
||||||
|
|
||||||
Raspberry Pi netboot uses a specific DHCP option-43 vendor field +
|
|
||||||
TFTP path layout that PXEForge doesn't currently special-case. There's
|
|
||||||
a spec; the work is small once we have a Pi to test on.
|
|
||||||
|
|
||||||
## What I'd skip
|
|
||||||
|
|
||||||
- **A custom DHCP server (not proxy).** The proxy mode is the right
|
|
||||||
abstraction; full DHCP would need raw sockets + a lot of corner-case
|
|
||||||
handling for problems no operator wants us to solve.
|
|
||||||
- **A pluggable backend abstraction à la Tinkerbell.** Tinkerbell does
|
|
||||||
it because they integrate with k8s CRDs. PXEForge's "the file system
|
|
||||||
IS the database" model is simpler and good enough for the target
|
|
||||||
audience. Don't add a Backend trait until something asks for it.
|
|
||||||
- **Multiple language UIs.** Bootimus added these in v0.1.62 and the
|
|
||||||
translations are LLM-generated. Skip until we have real users
|
|
||||||
asking for non-English.
|
|
||||||
|
|
||||||
## Quick wins (could land in a single afternoon)
|
|
||||||
|
|
||||||
- Add a Grafana dashboard JSON to `deploy/grafana/` driven off the
|
|
||||||
new `/metrics` endpoint.
|
|
||||||
- A `pxeforge bench` subcommand that runs a 10-second internal load
|
|
||||||
test (synthetic gate joins) so an operator can sanity-check tuning.
|
|
||||||
- Ship a basic `docker-compose.yml` for the Unraid path that demos
|
|
||||||
the new themes / progress widget.
|
|
||||||
- Generate a printable single-page operator runbook from the README
|
|
||||||
+ architecture.md (e.g. `cargo xtask runbook`).
|
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
# Runbook: Boot a Linux machine from an ISO over the network
|
|
||||||
|
|
||||||
End-to-end walkthrough: spin up PXEForge, load an Ubuntu (or any
|
|
||||||
Linux) ISO into it, target a specific bare-metal or VM client by its
|
|
||||||
MAC address, and have that machine PXE-boot the installer over the
|
|
||||||
LAN — no USB stick, no console babysitting.
|
|
||||||
|
|
||||||
This runbook assumes:
|
|
||||||
|
|
||||||
- You have **one Linux host** to run the PXEForge container (any
|
|
||||||
distro with Docker / Podman; 2 GB RAM, ~50 GB disk for the ISO
|
|
||||||
library).
|
|
||||||
- That host sits on the **same broadcast domain / VLAN** as the
|
|
||||||
client you want to boot. PXE is L2-broadcast — routed/VLAN’d
|
|
||||||
networks need a DHCP relay and are out of scope here.
|
|
||||||
- An **existing DHCP server** is already handing out IP leases on
|
|
||||||
that VLAN (your home router, OPNsense, Windows Server, etc.).
|
|
||||||
PXEForge runs as a *DHCP proxy* — it never leases IPs, it only
|
|
||||||
layers the boot information on top of the existing DHCP exchange.
|
|
||||||
- The target client is configured to **PXE-boot** in BIOS/UEFI
|
|
||||||
firmware (usually `F12` boot menu → Network, or set as first boot
|
|
||||||
device).
|
|
||||||
|
|
||||||
If those don’t hold, stop and read [troubleshooting.md](troubleshooting.md)
|
|
||||||
or [docs/architecture.md](../docs/architecture.md) first.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. Pick your host’s LAN IP
|
|
||||||
|
|
||||||
You need the IPv4 address PXEForge will advertise to clients. From
|
|
||||||
the host:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ip -4 -o addr show | awk '{print $2, $4}'
|
|
||||||
```
|
|
||||||
|
|
||||||
Pick the address on the interface that faces the PXE VLAN — for
|
|
||||||
example `10.0.0.5/24` on `eno1`. From here on we call it
|
|
||||||
`PXE_HOST_IP`.
|
|
||||||
|
|
||||||
> **Why this matters.** Every URL handed to clients (TFTP server,
|
|
||||||
> iPXE chain URL, ISO URL) is built from this IP. If PXEForge
|
|
||||||
> auto-detects the wrong interface or loopback, clients will fetch
|
|
||||||
> from an unreachable address and silently fail. The startup will
|
|
||||||
> *fail loudly* if it can only auto-detect a loopback address.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Run PXEForge
|
|
||||||
|
|
||||||
The MVP path is a single `docker run` against the published image,
|
|
||||||
with `--network host` so the container can see DHCP broadcasts on
|
|
||||||
the LAN.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/pxeforge/isos ~/pxeforge/work
|
|
||||||
|
|
||||||
docker run -d --name pxeforge \
|
|
||||||
--restart unless-stopped \
|
|
||||||
--network host \
|
|
||||||
-e PXEFORGE_PUBLIC_IP=10.0.0.5 \
|
|
||||||
-e PXEFORGE_DHCP_MODE=proxy \
|
|
||||||
-v ~/pxeforge/isos:/var/lib/pxeforge/isos \
|
|
||||||
-v ~/pxeforge/work:/var/lib/pxeforge/work \
|
|
||||||
ghcr.io/YOUR-ORG/pxeforge:0.2.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Substitute your `PXEFORGE_PUBLIC_IP`, of course. If you’re building
|
|
||||||
from this repo instead of pulling, see the
|
|
||||||
[README quick start](../README.md#quick-start--mvp-container-recommended).
|
|
||||||
|
|
||||||
### Verify it’s alive
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsS http://10.0.0.5/healthz # → 200 ok
|
|
||||||
curl -fsS http://10.0.0.5/readyz # → 200 ready (iPXE binaries present)
|
|
||||||
curl -fsS http://10.0.0.5/api/status | jq .
|
|
||||||
```
|
|
||||||
|
|
||||||
If `/readyz` is **not** 200, your container is missing iPXE binaries.
|
|
||||||
Fix that before going further — clients have nothing to boot
|
|
||||||
otherwise. See [README — Container health probes](../README.md#container-health-probes).
|
|
||||||
|
|
||||||
### Check the listening ports
|
|
||||||
|
|
||||||
PXEForge holds three privileged UDP/TCP ports. From another shell on
|
|
||||||
the host:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo ss -lnup | grep -E ':(67|69|4011)\b' # DHCP proxy + TFTP
|
|
||||||
sudo ss -lntp | grep ':80\b' # HTTP UI / boot scripts
|
|
||||||
```
|
|
||||||
|
|
||||||
All four should be present. If port 67 is taken by `dnsmasq` or the
|
|
||||||
host’s own DHCP, stop that service or run PXEForge on a separate box —
|
|
||||||
two listeners on `:67` will fight.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Load the ISO
|
|
||||||
|
|
||||||
Two options. Pick one.
|
|
||||||
|
|
||||||
### 2a. Web UI upload (recommended for one-offs)
|
|
||||||
|
|
||||||
1. Open `http://10.0.0.5/` in a browser.
|
|
||||||
2. Sidebar → **Storage**.
|
|
||||||
3. Click **Upload ISO**, pick e.g. `ubuntu-24.04.1-live-server-amd64.iso`.
|
|
||||||
4. Wait for upload + introspection. The row turns into a card showing:
|
|
||||||
- Distro family (`debian_ubuntu`)
|
|
||||||
- Volume label
|
|
||||||
- Detected kernel/initrd paths (`/casper/vmlinuz`, `/casper/initrd`)
|
|
||||||
- File size and SHA-256
|
|
||||||
|
|
||||||
Big ISOs stream — there is no 2 GB limit, but expect upload to be
|
|
||||||
gated by your browser ↔ host link. The UI shows a progress bar; the
|
|
||||||
animated anvil on the Dashboard tab fires up while imaging is in
|
|
||||||
flight.
|
|
||||||
|
|
||||||
### 2b. Bulk seed from a directory (recommended for fresh deploys / CI)
|
|
||||||
|
|
||||||
If you already have a folder of ISOs on the host, skip the browser:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Dry run first — see what would be imported, no writes:
|
|
||||||
docker exec pxeforge pxeforge seed \
|
|
||||||
--from /seed \
|
|
||||||
--dry-run
|
|
||||||
|
|
||||||
# For real, mount the source dir read-only into the container:
|
|
||||||
docker run --rm \
|
|
||||||
-v /my/iso-library:/seed:ro \
|
|
||||||
-v ~/pxeforge/isos:/var/lib/pxeforge/isos \
|
|
||||||
-v ~/pxeforge/work:/var/lib/pxeforge/work \
|
|
||||||
-e PXEFORGE_PUBLIC_IP=10.0.0.5 \
|
|
||||||
ghcr.io/YOUR-ORG/pxeforge:0.2.0 seed --from /seed
|
|
||||||
```
|
|
||||||
|
|
||||||
Each `*.iso` in `/seed` runs through the same upload pipeline as the
|
|
||||||
web UI: copy → introspection → boot-entry generation → metadata
|
|
||||||
sidecar. Re-running is idempotent.
|
|
||||||
|
|
||||||
### Confirm the ISO is registered
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsS http://10.0.0.5/api/isos | jq '.[] | {id, name, family, size}'
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see something like:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "ubuntu-24-04-1-live-server-amd64",
|
|
||||||
"name": "ubuntu-24.04.1-live-server-amd64.iso",
|
|
||||||
"family": "debian_ubuntu",
|
|
||||||
"size": 2748000000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `id` is the **slug**. Remember it — you’ll bind a MAC to it in
|
|
||||||
the next step.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Find the target machine’s MAC address
|
|
||||||
|
|
||||||
You need the MAC of the **NIC that will PXE**, not the OS’s
|
|
||||||
loopback or wifi.
|
|
||||||
|
|
||||||
### 3a. From the target itself (if it’s already running an OS)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ip -o link | awk '/ether/ {print $2, $17}' # Linux
|
|
||||||
```
|
|
||||||
|
|
||||||
Pick the line for the wired NIC plugged into the PXE VLAN.
|
|
||||||
|
|
||||||
### 3b. From the firmware (if it’s a fresh box)
|
|
||||||
|
|
||||||
Most BIOS/UEFI screens display the NIC MAC during the network-boot
|
|
||||||
attempt — usually as `MAC: AA-BB-CC-DD-EE-FF` flashing on the splash
|
|
||||||
right before "PXE-E53: No boot filename received". Write it down.
|
|
||||||
|
|
||||||
### 3c. By letting it boot once and watching PXEForge
|
|
||||||
|
|
||||||
Easiest if the box is in front of you:
|
|
||||||
|
|
||||||
1. Power on, hit `F12`, pick **Network boot**.
|
|
||||||
2. Without any binding configured, the client will land on the
|
|
||||||
PXEForge menu (Default / Installers / Tools / Gated Deployment).
|
|
||||||
3. Don’t pick anything. On your laptop:
|
|
||||||
```bash
|
|
||||||
curl -fsS http://10.0.0.5/api/clients | jq .
|
|
||||||
```
|
|
||||||
4. The most-recent entry is your target. Copy its `mac`.
|
|
||||||
|
|
||||||
From here on we call this MAC `TARGET_MAC` (e.g. `aa:bb:cc:dd:ee:ff`).
|
|
||||||
Hyphens vs colons, upper vs lower case — PXEForge normalizes both.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Pin that machine to the Ubuntu ISO
|
|
||||||
|
|
||||||
This is the **per-MAC host binding**. With it set, the client won’t
|
|
||||||
see the menu at all — it goes straight to the bound boot entry,
|
|
||||||
Tinkerbell-style.
|
|
||||||
|
|
||||||
### 4a. Via the web UI
|
|
||||||
|
|
||||||
1. Sidebar → **Hosts**.
|
|
||||||
2. **Add binding**:
|
|
||||||
- **MAC**: `aa:bb:cc:dd:ee:ff`
|
|
||||||
- **Target**: pick `ubuntu-24-04-1-live-server-amd64` from the dropdown.
|
|
||||||
- **Label**: free-form, e.g. `lab-rack3-node07`.
|
|
||||||
3. Save.
|
|
||||||
|
|
||||||
### 4b. Via the API
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsS -X POST http://10.0.0.5/api/hosts \
|
|
||||||
-H 'content-type: application/json' \
|
|
||||||
-d '{
|
|
||||||
"mac": "aa:bb:cc:dd:ee:ff",
|
|
||||||
"target": "ubuntu-24-04-1-live-server-amd64",
|
|
||||||
"label": "lab-rack3-node07"
|
|
||||||
}' | jq .
|
|
||||||
```
|
|
||||||
|
|
||||||
The binding is persisted to `~/pxeforge/work/hosts.json` and survives
|
|
||||||
container restart.
|
|
||||||
|
|
||||||
### Confirm
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsS http://10.0.0.5/api/hosts | jq '.[] | select(.mac=="aa:bb:cc:dd:ee:ff")'
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see your entry with `created_at` and `updated_at`
|
|
||||||
timestamps.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Trigger the network boot on the target
|
|
||||||
|
|
||||||
Now actually boot the machine.
|
|
||||||
|
|
||||||
### 5a. Boot order
|
|
||||||
|
|
||||||
In firmware setup, set the wired NIC as the **first** boot device
|
|
||||||
(or hold `F12` / `F9` / `Esc` — vendor-specific — to pick "Network
|
|
||||||
Boot" interactively).
|
|
||||||
|
|
||||||
### 5b. What you should see on the target screen
|
|
||||||
|
|
||||||
In order, with timing:
|
|
||||||
|
|
||||||
| Stage | Approximate duration | What appears |
|
|
||||||
|-------|---------------------:|--------------|
|
|
||||||
| Firmware DHCPDISCOVER | ~1 s | `Start PXE over IPv4` / `Station IP address …` |
|
|
||||||
| TFTP iPXE binary fetch | ~1 s | `TFTP… snponly.efi` (or `undionly.kpxe` for legacy BIOS) |
|
|
||||||
| iPXE banner | ~1 s | The blue iPXE splash, version string |
|
|
||||||
| iPXE second-stage DHCP | ~1 s | `Configuring (net0 …)` then `ok` |
|
|
||||||
| HTTP boot script fetch | <1 s | `http://10.0.0.5/boot.ipxe?mac=…` |
|
|
||||||
| Per-MAC chain | <1 s | `PXEForge: per-MAC binding -> ubuntu-24-04-1-…` |
|
|
||||||
| Kernel + initrd HTTP | 5–30 s | Two 200-OK fetches against `/iso/<id>/casper/vmlinuz` and `…/initrd` |
|
|
||||||
| Kernel boot | 5–10 s | Kernel banner, then the Ubuntu/cloud-init splash |
|
|
||||||
| Installer comes up | 30–60 s | The distro’s normal Live/installer environment |
|
|
||||||
|
|
||||||
If everything works, you’re looking at the Ubuntu Server installer
|
|
||||||
welcome screen end-to-end **without ever touching a USB stick**.
|
|
||||||
|
|
||||||
### 5c. Watch it from the server
|
|
||||||
|
|
||||||
In a third shell, tail the live log:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -N http://10.0.0.5/api/log/stream
|
|
||||||
```
|
|
||||||
|
|
||||||
You’ll see each protocol step as it happens:
|
|
||||||
|
|
||||||
```
|
|
||||||
INFO pxeforge::dhcp: reply mac=aa:bb:cc:dd:ee:ff arch=X8664Uefi target=tftp/snponly.efi
|
|
||||||
INFO pxeforge::tftp: RRQ snponly.efi blksize=1468 windowsize=8 → 982 KiB in 412 ms
|
|
||||||
INFO pxeforge::dhcp: reply mac=aa:bb:cc:dd:ee:ff (iPXE) target=http/boot.ipxe
|
|
||||||
INFO pxeforge::http: GET /boot.ipxe?mac=aa:bb:cc:dd:ee:ff → host binding hit
|
|
||||||
INFO pxeforge::http: GET /iso/ubuntu-…/casper/vmlinuz Range=bytes=0- 200 OK 14 MiB
|
|
||||||
INFO pxeforge::http: GET /iso/ubuntu-…/casper/initrd Range=bytes=0- 200 OK 75 MiB
|
|
||||||
```
|
|
||||||
|
|
||||||
The **Terminal** tab in the web UI shows the same thing live, plus a
|
|
||||||
short whitelisted command palette (`status`, `clients`, `gate`,
|
|
||||||
`hosts`, `log`).
|
|
||||||
|
|
||||||
### 5d. Internet-side ISO sources
|
|
||||||
|
|
||||||
The runbook title says “via the internet” — the **client** itself
|
|
||||||
boots from your LAN, but the underlying ISO can come from anywhere
|
|
||||||
your *host* can reach:
|
|
||||||
|
|
||||||
- **Direct upload** from a remote workstation via the web UI (HTTPS
|
|
||||||
reverse-proxied if you put PXEForge behind nginx/Caddy).
|
|
||||||
- **NFS mount** of a remote share — Sidebar → **Storage** → **NFS** →
|
|
||||||
`nfs://files.lab.example.com/exports/isos`. Mounted ISOs show up in
|
|
||||||
the same list and are PXE-bootable directly without copying.
|
|
||||||
- **Pre-seed** from a CI job that `curl`s a vendor mirror and runs
|
|
||||||
`pxeforge seed --from`.
|
|
||||||
|
|
||||||
PXEForge itself never reaches out to the internet at boot time — all
|
|
||||||
client traffic stays on the LAN, served from the host.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. After the install
|
|
||||||
|
|
||||||
Once Ubuntu has finished installing to the target’s disk, you want
|
|
||||||
the next reboot to come up off the new local disk, **not** PXE
|
|
||||||
again. Two ways:
|
|
||||||
|
|
||||||
### 6a. One-shot — release the binding
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsS -X DELETE http://10.0.0.5/api/hosts/aa:bb:cc:dd:ee:ff
|
|
||||||
```
|
|
||||||
|
|
||||||
Without a binding, the client either gets the menu (BIOS still set
|
|
||||||
to PXE first) or boots local disk normally.
|
|
||||||
|
|
||||||
### 6b. Permanent — pin to local disk
|
|
||||||
|
|
||||||
Re-bind to the reserved local-boot target:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsS -X POST http://10.0.0.5/api/hosts \
|
|
||||||
-H 'content-type: application/json' \
|
|
||||||
-d '{ "mac": "aa:bb:cc:dd:ee:ff", "target": "_local", "label": "lab-rack3-node07 (installed)" }'
|
|
||||||
```
|
|
||||||
|
|
||||||
Now if anyone hits `F12 → Network` by accident, PXEForge replies
|
|
||||||
with a script that says *"chain back to local HDD"* and the box
|
|
||||||
boots its real OS instead of re-imaging itself. This is the safest
|
|
||||||
default for production hardware.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Re-imaging — the “Gated Deployment” flow
|
|
||||||
|
|
||||||
Different scenario: you have **a rack of 30 servers** to image
|
|
||||||
identically, all at once. Don’t bind 30 MACs by hand. Use the gate.
|
|
||||||
|
|
||||||
1. **Don’t** create host bindings.
|
|
||||||
2. PXE-boot every machine. They land on the menu.
|
|
||||||
3. On each: select **Gated Deployment**. They get position #1, #2,
|
|
||||||
…, #30 and start long-polling.
|
|
||||||
4. In the UI: **Forge Gate** tab shows all 30 lined up. Pick the
|
|
||||||
ISO, click **Assign to all waiting**.
|
|
||||||
5. Every client’s open long-poll wakes up at the same instant and
|
|
||||||
chains the same boot script. They all start imaging
|
|
||||||
simultaneously — the “horse race gate” opens.
|
|
||||||
|
|
||||||
The animated anvil widget on the Dashboard runs while any client is
|
|
||||||
still in the kernel-fetch phase.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cheat sheet
|
|
||||||
|
|
||||||
| Goal | Command |
|
|
||||||
|------|---------|
|
|
||||||
| Health check | `curl http://$IP/healthz` |
|
|
||||||
| List ISOs | `curl http://$IP/api/isos \| jq .` |
|
|
||||||
| List clients seen | `curl http://$IP/api/clients \| jq .` |
|
|
||||||
| Bind MAC → ISO | `POST /api/hosts` with `{mac,target,label}` |
|
|
||||||
| Bind MAC → local disk | same with `target=_local` |
|
|
||||||
| Release binding | `DELETE /api/hosts/<mac>` |
|
|
||||||
| Live log | `curl -N http://$IP/api/log/stream` |
|
|
||||||
| Prometheus metrics | `curl http://$IP/metrics` |
|
|
||||||
| Bulk import folder | `pxeforge seed --from /path` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Where to look when things break
|
|
||||||
|
|
||||||
- **Client gets `PXE-E53: No boot filename received`** — DHCP proxy
|
|
||||||
isn’t replying. Check `:67` is bound (`ss -lnup`), check
|
|
||||||
`--network host`, check the host firewall on UDP 67/69/4011.
|
|
||||||
- **iPXE shows `No more network devices`** — firmware NIC isn’t in
|
|
||||||
PXE mode, or VLAN tagging is wrong.
|
|
||||||
- **iPXE prints `Connection timed out (http://…)`** — `PXEFORGE_PUBLIC_IP`
|
|
||||||
is wrong. Clients can’t reach that IP. Check `/api/status` →
|
|
||||||
`public_base_url` and `ping` it from the client subnet.
|
|
||||||
- **Kernel panics during initrd load** — corrupt ISO upload. Check
|
|
||||||
`/api/isos`, compare the SHA-256 to the vendor’s, re-upload.
|
|
||||||
- **Boot menu shows but the bound entry doesn’t fire** — the binding
|
|
||||||
target slug doesn’t match any ISO `id`. Recheck
|
|
||||||
`GET /api/hosts` against `GET /api/isos`. The binding falls back
|
|
||||||
to the menu on miss (by design — never lock a client out).
|
|
||||||
- **General confusion** — Terminal tab → `status`, then `log`. That
|
|
||||||
tells you what protocol stages have run and which haven’t.
|
|
||||||
|
|
||||||
For deeper protocol-level debugging, see
|
|
||||||
[docs/architecture.md](../docs/architecture.md).
|
|
||||||
Reference in New Issue
Block a user