Files
bootapi/docs/endpoints.md
T
unkinben 8f356346eb
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
Address PR review: PXE gate + callback, git-sync templates, distro catalog, k8s targets, http+https
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
2026-07-28 22:34:44 +10:00

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.