Implements the six review comments on PR #1: - Per-host PXE-enable gate: read NetBox pxe_enabled custom field; a known host with it false gets the safe local-boot script (Cobbler netboot_enabled). Add a token-guarded POST /provisioned/{ident} callback that clears pxe_enabled in NetBox, plus a %post snippet in the default kickstarts that calls it. - Templates from a git repo: bootapi clones a templates repo and re-pulls every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m), atomically swapping the template set (last-good kept on parse failure; embedded defaults are the startup fallback). Metrics for syncs/failures/generation. - Distro catalog (catalog/*.yaml): NetBox host -> boot images/kickstart, so adding an OS is a YAML + template change. Ships almalinux + fedora entries (artifactapi remotes); debian/talos path documented. - Boot images from the artifactapi almalinux/fedora remotes via the catalog. - Bind resolvers, puppet server/CA and PUPPETCA_URL env file now target the k8s services (198.18.200.7; puppet(ca).k8s.syd1.au.unkin.net). - Boot path served over plain HTTP (installers lack CA trust) with an optional parallel HTTPS listener; docs say do not 301 the boot endpoints. New packages: internal/catalog, internal/gitsync. NetBox client gains a pxe_enabled write (token needs that scope - noted in docs). `bootapi validate` subcommand validates a template/catalog set for the templates-repo CI. go build/vet clean, go test -race green, golangci-lint v2 clean, pre-commit clean. Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
5.4 KiB
bootapi HTTP endpoints
HTTP and HTTPS — the boot path is plain HTTP by design
bootapi always serves the boot path (/ipxe, /boot/ipxe, /ks) over plain
HTTP on BOOTAPI_LISTEN_ADDR. A PXE installer environment has no internal-CA
trust, so an HTTPS-only boot URL (with our private CA cert) would fail the TLS
handshake. iPXE and the kickstart therefore use http:// URLs (from
BOOTAPI_BASE_URL).
Optionally bootapi also serves HTTPS in parallel (BOOTAPI_TLS_LISTEN_ADDR +
cert/key), for clients that do trust the CA. The Kubernetes exposure must not
blanket-301 HTTP→HTTPS for the boot endpoints — see
deployment.md.
The PXE flow
DHCP ── next-server + filename (ipxe.efi / undionly.kpxe) ──▶ firmware loads iPXE
iPXE ── GET http://<base>/ipxe/<mac> ─────────────────────▶ bootapi renders a boot script
boot ── kernel + initrd + inst.ks=http://<base>/ks/<host> ─▶ Anaconda fetches the kickstart
KS ── GET http://<base>/ks/<host> ──────────────────────▶ bootapi renders the kickstart
post ── POST http://<base>/provisioned/<host> (token) ────▶ bootapi clears pxe_enabled in NetBox
This mirrors Cobbler, which chained iPXE to /cblr/svc/op/gpxe/mac/<mac>, served
a per-system script carrying inst.ks=, and cleared netboot_enabled at the end
of the install.
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}. |
| GET | /ks/{ident} |
Rendered kickstart. {ident} is a MAC (auto-detected) or a hostname; trailing .ks/.cfg is stripped. |
| POST | /provisioned/{ident} |
End-of-kickstart callback; clears pxe_enabled in NetBox. Token-guarded (Authorization: Bearer <BOOTAPI_PROVISION_TOKEN>). |
| GET | /healthz |
Liveness: always 200 ok. |
| GET | /readyz |
Readiness: 200 once templates parsed. Does not probe NetBox. |
| 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}), resolved via NetBox GET /api/dcim/interfaces/?mac_address=<mac>
→ device → primary IP, platform, role, interfaces. /ks/{ident} and
/provisioned/{ident} also accept a hostname (NetBox device name).
Per-host PXE-enable gate (pxe_enabled)
/ipxe/{mac} checks the device's pxe_enabled NetBox custom field (Cobbler's
netboot_enabled):
- unset or
true→ normal installer boot script. false→ the safe local-boot fallback, even for a known host, so a machine that has already been provisioned does not re-install on its next PXE.
The /provisioned/{ident} callback (called from the kickstart %post) sets the
field to false when the install finishes; so a host installs once, then gates
itself off. Flip it back to true in NetBox to re-image.
Error behavior (deliberate)
The boot 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. An unknown MAC — or any NetBox error, or a gated host — returns HTTP 200 with the fallback script selected byBOOTAPI_UNKNOWN_MAC_FALLBACK:local(default):sanbootthe local disk. Safe: an accidental PXE (or a NetBox blip) boots the installed OS; a genuinely new machine loops back to PXE next time. We never start an installer for a machine we can't identify.shell: interactive iPXE shell for an operator to 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 (502 on a NetBox error). By the time Anaconda fetches the kickstart it has committed to installing; a clear failure beats an empty/wrong kickstart.
The provisioned callback
POST /provisioned/{ident} requires the shared token in an Authorization: Bearer (or bare token) header. Responses: 204 on success, 401 on a
bad/missing token, 404 for an unknown host, 503 when no
BOOTAPI_PROVISION_TOKEN is configured (fail closed), 502 on a NetBox write
failure. The default kickstart templates call it from %post over plain HTTP
(the token authenticates the call; no CA trust needed at install time).
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.bootapi_provisioned_total{result}— result =ok|unauthorized|notfound|error|disabled.bootapi_ipxe_gated_total— known hosts served local-boot becausepxe_enabled=false.bootapi_template_sync_total/bootapi_template_sync_failures_total/bootapi_template_generation— template git-sync (see template-authoring.md).- standard Go/process collectors.