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
3.7 KiB
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).
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 byBOOTAPI_UNKNOWN_MAC_FALLBACK:local(default):sanbootthe 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.