Files
bootapi/docs/deployment.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

4.2 KiB
Raw Blame History

Deploying bootapi

The actual argocd-apps deployment is a follow-up task and is intentionally not part of this repo. This document is the spec for that follow-up plus the DHCP change the estate needs.

bootapi is a stateless HTTP service. It mirrors encapi's deployment shape: a Go binary in a distroless image, config from env, secrets from Vault via the Vault Secrets Operator (VSO).

Container image

git.unkin.net/unkin/bootapi:<tag> (built + pushed by .woodpecker/docker.yaml on a v* tag). Also mirror to the artifactapi local docker registry if desired.

Kubernetes wiring (argocd-apps follow-up)

Create apps/base/bootapi/ following the argocd-apps AGENTS.md pattern:

  1. namespace bootapi.
  2. VaultAuth (default) — kubernetes method, mount k8s/au/syd1, role default, SA default (copy netbox's vaultauth.yaml).
  3. VaultStaticSecret → k8s Secret bootapi-secrets, from Vault kv path kubernetes/namespace/bootapi/default/bootapi-secrets with keys:
    • netbox_token — a dedicated, read-only NetBox API token for bootapi (create a bootapi NetBox user/token via terraform-netbox rather than reusing the seeded superuser token at kv/kubernetes/namespace/netbox/default/netbox-superuser).
    • root_password_hash — crypt(3) hash for the installed root account (the successor to Cobbler's eyaml default_password_crypted).
    • ssh_authorized_keys — optional, newline-separated.
  4. ConfigMap bootapi-templates (optional) — override *.ks.tmpl / *.ipxe.tmpl, mounted at BOOTAPI_TEMPLATE_DIR=/etc/bootapi/templates. Omit to use the embedded defaults. Annotate the Deployment with reloader.stakater.com/auto: "true" so template edits roll the pods.
  5. Deployment — image above, env from config.example.env, secret keys wired as BOOTAPI_NETBOX_TOKEN_FILE/BOOTAPI_ROOT_PASSWORD_HASH_FILE (mount the Secret) or ...FROM secretKeyRef. Least-privilege securityContext (runAsNonRoot, drop: [all]). Baseline resources: requests 512Mi/1, limits 2Gi/2 cpu.
  6. Service bootapi (ClusterIP, port 80 → 8000) plus a LoadBalancer (or Gateway HTTPRoute) reachable by PXE clients at a stable address/hostname — this is what DHCP points at. Reuse the Vault-issued TLS the Cobbler vhost used if you terminate TLS at a gateway; note that iPXE fetches are plain HTTP, so a plain HTTP listener on the PXE VLAN is required either way.
  7. Register in argocd/applicationsets/platform.yaml (apps/overlays/*/bootapi) and the platform AppProject destinations.

Cross-repo dependencies (per estate conventions)

  • argocd-apps: add a serviceaccount_* under apps/base/woodpecker/ if the bootapi pipelines need a dedicated SA (they use default today).
  • terraform-vault: add the k8s auth role + kv policy granting the bootapi namespace read on kv/kubernetes/namespace/bootapi/default/*.
  • terraform-netbox: create the read-only bootapi NetBox token and seed it (plus root_password_hash) into the Vault kv path above.

DHCP change (the cutover)

Cobbler advertised itself at anycast 198.18.19.19 as the DHCP next-server, with filename "/ipxe.efi" (UEFI arch 7/9) or /undionly.kpxe (BIOS arch 0). Today those are set in puppet-prod hieradata hieradata/roles/infra/dhcp/server.yaml (pools.*.pxeserver and the UEFI-64/Legacy dhcp classes).

To cut a subnet over to bootapi, repoint DHCP for that pool:

  • next-server → bootapi's LB IP (or keep the 198.18.19.19 anycast and move the anycast advertisement to bootapi's node/LB).
  • filename → the iPXE binary as before (/ipxe.efi / /undionly.kpxe); bootapi does not serve the NBP itself. The chained iPXE must then be told to fetch bootapi's script — either bake chain http://<bootapi>/ipxe/${net0/mac} into the site iPXE binary/embedded script, or set DHCP option 67 to that URL for iPXE user-class requests. This replaces Cobbler's chain http://${next-server}/cblr/svc/op/gpxe/mac/${net0/mac}.

Roll one pool at a time (the PXE subnets are 198.18.13.0/24198.18.17.0/24); Puppet autosign already trusts those subnets and *.main.unkin.net, so a host installed via bootapi checks in exactly as before.