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

96 lines
4.3 KiB
Markdown

# 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](https://git.unkin.net/unkin/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](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
- [docs/endpoints.md](docs/endpoints.md) — the PXE flow, every endpoint, error/fallback behavior, metrics.
- [docs/data-model.md](docs/data-model.md) — the exact template data model + NetBox custom fields + template selection.
- [docs/template-authoring.md](docs/template-authoring.md) — writing/overriding kickstart & iPXE templates.
- [docs/deployment.md](docs/deployment.md) — Kubernetes/argocd wiring, Vault secrets, and the DHCP cutover.
- [docs/security.md](docs/security.md) — what belongs in NetBox vs Vault; secrets in kickstarts.
## Configuration
Env-based (12-factor), see [`config.example.env`](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
```bash
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
```