Files
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

4.3 KiB

bootapi

bootapi is a small Go service that replaces Cobbler's PXE/kickstart side. It renders kickstart files and iPXE boot scripts from NetBox device data and serves them over HTTP to PXE-booting hosts.

The ENC half of Cobbler already moved to encapi. With bootapi, provisioning a new host is:

  1. terraform the host into NetBox (device, interfaces/MACs, IP, platform, role) and encapi (classification), then
  2. rack it and let it PXE-boot — DHCP points it at bootapi, which serves the iPXE script and the rendered kickstart; the kickstart hands off to the existing Puppet firstrun bootstrap.

How it works

DHCP next-server ─▶ iPXE ─▶ GET /ipxe/<mac> ─▶ boot kernel+initrd, inst.ks=<bootapi>/ks/<host>
                                                        └▶ GET /ks/<host> ─▶ rendered kickstart ─▶ puppet firstrun

bootapi identifies the booting host by the MAC it booted from (NetBox interface lookup → device → primary IP, platform, role, interfaces), renders a Go text/template selected from the host's platform/role/custom-field, and serves it. Templates are embedded defaults, overridable from a directory (ConfigMap in k8s).

Endpoints (summary)

Path Purpose
GET /ipxe/{mac} · GET /boot/ipxe?mac= iPXE boot script
GET /ks/{ident} rendered kickstart (MAC or hostname)
POST /provisioned/{ident} end-of-kickstart callback (token) → clears pxe_enabled in NetBox
GET /healthz · /readyz · /metrics health + Prometheus

The boot path is served over plain HTTP (PXE installers have no internal-CA trust); HTTPS is offered in parallel. Unknown MAC → iPXE gets a safe fallback (local-disk boot, HTTP 200), never a 404; a host with pxe_enabled=false gets the same fallback so it won't re-install. Unknown kickstart host → 404. Full rationale in docs/endpoints.md.

Multi-distro + live templates

  • Distro catalog (catalog/*.yaml): each OS maps a NetBox platform/family to its boot images (artifactapi remotes), kernel args and kickstart template. Adding Fedora/Debian/Talos is a YAML + template change, no code change. Ships almalinux9 + fedora.
  • Template git-sync: bootapi pulls the bootapi-templates repo every 3m (like argocd) and hot-swaps the template set (last-good kept on a bad push); embedded defaults are the startup fallback.

Documentation

Configuration

Env-based (12-factor), see config.example.env. Key vars: BOOTAPI_NETBOX_URL, BOOTAPI_NETBOX_TOKEN[_FILE], BOOTAPI_BASE_URL, BOOTAPI_BOOT_BASE_URL, BOOTAPI_ROOT_PASSWORD_HASH[_FILE], BOOTAPI_UNKNOWN_MAC_FALLBACK.

Development

make build       # build ./bin/bootapi
make test-race   # go test -race ./...
make lint        # golangci-lint (v2) if installed, else go vet
make run         # build + run

CI (Woodpecker): pre-commit, golangci-lint v2 + go test -race, and a docker build on PRs; on a v* tag, a container image push and a Gitea binary release. Cut a release with make patch|minor|major (tags + pushes).

Layout

cmd/bootapi/         main
internal/config/     env config
internal/model/      Host/Interface data model (incl. pxe_enabled gate)
internal/netbox/     NetBox client (reads + pxe_enabled write) + TTL cache, behind an interface
internal/catalog/    distro catalog: NetBox host -> boot images/kickstart
internal/render/     text/template engine (swappable Set), selection, loader
internal/gitsync/    periodic git pull + atomic template reload (last-good)
internal/server/     chi HTTP handlers + Prometheus metrics
templates/           embedded defaults: kickstart, iPXE, catalog/*.yaml
docs/                see above