Files
bootapi/docs/endpoints.md
T
unkinben 274c480b09
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
Initial bootapi: NetBox-driven PXE/kickstart boot service
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
2026-07-28 20:57:11 +10:00

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 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.