274c480b09
bootapi replaces Cobbler's PXE/kickstart side. It resolves a PXE-booting host
from NetBox (by MAC or hostname), renders an iPXE boot script and a kickstart
from Go text/templates, and serves them over HTTP. The ENC half already moved to
encapi; this covers the provisioning/boot half.
What's here:
- cmd/bootapi + internal/{config,model,netbox,render,server}; embedded default
templates under templates/ (AlmaLinux 9 + Fedora kickstarts, iPXE boot +
unknown-MAC fallbacks) ported from Cobbler's boot/bootstrap contract.
- NetBox client (v4.x API) behind a Resolver interface with a short-TTL cache;
tested against httptest fixtures using real NetBox JSON shapes.
- chi HTTP server: /ipxe/{mac}, /boot/ipxe?mac=, /ks/{ident}, healthz/readyz,
Prometheus /metrics. Unknown MAC -> safe fallback iPXE (200), unknown KS -> 404.
- Secrets (root pw hash, ssh keys) injected at render time from env/Vault, never
NetBox. Config is env-based per estate convention.
- Makefile (build/test/lint/docker + patch/minor/major), Dockerfile (distroless),
.woodpecker (pre-commit, golangci-lint v2 + go test -race, docker build on PR;
image push + Gitea binary release on v* tag), docs/ and example config.
go build/vet clean, go test -race green, golangci-lint v2 clean, pre-commit clean.
Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
69 lines
3.7 KiB
Markdown
69 lines
3.7 KiB
Markdown
# bootapi HTTP endpoints
|
|
|
|
bootapi speaks plain HTTP. It is fronted by the same Vault-issued TLS the Cobbler
|
|
server used; the booting firmware reaches it at the DHCP `next-server` (see
|
|
[deployment.md](deployment.md)).
|
|
|
|
## The PXE flow
|
|
|
|
```
|
|
DHCP ── next-server + filename (ipxe.efi / undionly.kpxe) ──▶ firmware loads iPXE
|
|
iPXE ── GET /ipxe/<mac> ───────────────────────────────────▶ bootapi renders a boot script
|
|
boot ── kernel + initrd + inst.ks=<BASE_URL>/ks/<host> ────▶ Anaconda fetches the kickstart
|
|
KS ── GET /ks/<host> ────────────────────────────────────▶ bootapi renders the kickstart
|
|
```
|
|
|
|
This mirrors Cobbler, which chained iPXE to `/cblr/svc/op/gpxe/mac/<mac>` and
|
|
served a per-system script carrying `inst.ks=`.
|
|
|
|
## Endpoints
|
|
|
|
| Method | Path | Purpose |
|
|
|--------|------|---------|
|
|
| GET | `/ipxe/{mac}` | iPXE boot script for the host owning `{mac}`. `{mac}` may use `:`/`-`/`.` separators or be bare hex; a trailing `.ipxe` is stripped. |
|
|
| GET | `/boot/ipxe?mac=...` | Query-string alias of `/ipxe/{mac}` (some firmware finds this shape easier to template). |
|
|
| GET | `/ks/{ident}` | Rendered kickstart. `{ident}` is a MAC (auto-detected) or a hostname; trailing `.ks`/`.cfg` is stripped. |
|
|
| GET | `/healthz` | Liveness: always `200 ok`. |
|
|
| GET | `/readyz` | Readiness: `200` once templates parsed. Does **not** probe NetBox (a NetBox outage still lets iPXE serve the safe fallback). |
|
|
| GET | `/metrics` | Prometheus metrics (see below). |
|
|
|
|
## Host identification
|
|
|
|
A booting host is identified by the **MAC** of the NIC it PXE-booted from
|
|
(`/ipxe/{mac}`), which bootapi resolves via NetBox
|
|
`GET /api/dcim/interfaces/?mac_address=<mac>` → device → primary IP, platform,
|
|
role, interfaces. `/ks/{ident}` additionally accepts a **hostname** (NetBox
|
|
device name), for hand-testing and for installers that template the hostname
|
|
into the kickstart URL.
|
|
|
|
## Error behavior (important, and deliberate)
|
|
|
|
The two endpoints fail **differently** on an unknown host, because the cost of a
|
|
wrong answer differs:
|
|
|
|
- **`/ipxe/{mac}` never returns 404.** iPXE needs a syntactically valid script or
|
|
the boot chain simply errors. An unknown MAC — or *any* NetBox error — returns
|
|
HTTP 200 with the **fallback script** selected by `BOOTAPI_UNKNOWN_MAC_FALLBACK`:
|
|
- `local` (default): `sanboot` the local disk. Safe: a machine that PXE-booted
|
|
by accident (or a NetBox blip) just boots its installed OS; a genuinely new
|
|
machine loops back to PXE next time, by which point NetBox should know it. We
|
|
deliberately do **not** start an installer for a machine we can't identify —
|
|
that could wipe a production box.
|
|
- `shell`: drop to an interactive iPXE shell so an operator racking a new box
|
|
can read `${net0/mac}` and register it. Opt-in; unsafe as a default because
|
|
it halts the boot.
|
|
- **`/ks/{ident}` returns 404** for an unknown host (and 502 on a NetBox error).
|
|
By the time Anaconda fetches the kickstart it has already committed to
|
|
installing; a clear failure is safer than serving an empty or wrong kickstart.
|
|
|
|
## Metrics
|
|
|
|
All on `/metrics`, prefix `bootapi_`:
|
|
|
|
- `bootapi_http_requests_total{endpoint,status}` — endpoint = `ipxe|ks|healthz|readyz`, status = `2xx|3xx|4xx|5xx`.
|
|
- `bootapi_render_total{kind,result}` — kind = `kickstart|ipxe`, result = `ok|error`.
|
|
- `bootapi_netbox_lookups_total{field,result}` — field = `mac|name`, result = `ok|notfound|error`.
|
|
- `bootapi_netbox_lookup_duration_seconds{field}` — histogram.
|
|
- `bootapi_netbox_cache_hits_total` / `bootapi_netbox_cache_misses_total`.
|
|
- standard Go/process collectors.
|