8f356346eb
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
101 lines
5.4 KiB
Markdown
101 lines
5.4 KiB
Markdown
# 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](deployment.md#gateway-http-and-https).
|
|
|
|
## 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 by `BOOTAPI_UNKNOWN_MAC_FALLBACK`:
|
|
- `local` (default): `sanboot` the 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 because `pxe_enabled=false`.
|
|
- `bootapi_template_sync_total` / `bootapi_template_sync_failures_total` / `bootapi_template_generation` — template git-sync (see [template-authoring.md](template-authoring.md)).
|
|
- standard Go/process collectors.
|