Initial bootapi: NetBox-driven PXE/kickstart boot service
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

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
This commit is contained in:
2026-07-28 17:20:06 +10:00
parent 98e69d2fcb
commit 274c480b09
37 changed files with 3290 additions and 1 deletions
+52
View File
@@ -0,0 +1,52 @@
# bootapi configuration (environment variables).
#
# bootapi is configured entirely from the environment (12-factor style, same as
# encapi). In Kubernetes these come from the Deployment env + a Vault-sourced
# Secret (see docs/deployment.md). Locally, `env $(grep -v '^#' config.example.env | xargs) ./bin/bootapi`.
# --- HTTP ---
BOOTAPI_LISTEN_ADDR=:8000
# --- NetBox (source of truth for host -> boot data) ---
BOOTAPI_NETBOX_URL=https://netbox.k8s.syd1.au.unkin.net
# Provide the token inline OR (preferred in k8s) via a file mounted from Vault:
BOOTAPI_NETBOX_TOKEN=
# BOOTAPI_NETBOX_TOKEN_FILE=/var/run/secrets/netbox/api_token
BOOTAPI_NETBOX_TIMEOUT=5s
BOOTAPI_NETBOX_INSECURE=false
# --- caching ---
# Short by design: a re-provisioned host must pick up NetBox changes on its next
# boot. Set 0 to disable.
BOOTAPI_CACHE_TTL=30s
# --- templates ---
# Optional override directory (a ConfigMap mount in k8s); files here win over
# the embedded defaults. Leave empty to use only the built-in templates.
# BOOTAPI_TEMPLATE_DIR=/etc/bootapi/templates
# Template used when NetBox provides no platform/role/override selection key.
BOOTAPI_DEFAULT_TEMPLATE=almalinux9
# --- URLs baked into rendered output ---
# bootapi's own externally-reachable base URL (goes into the iPXE inst.ks=).
BOOTAPI_BASE_URL=http://bootapi.k8s.syd1.au.unkin.net
# Base URL of the OS install trees (kernel/initrd + inst.repo).
BOOTAPI_BOOT_BASE_URL=http://mirror.k8s.syd1.au.unkin.net/almalinux/9
# --- puppet bootstrap targets (baked into kickstart %post) ---
BOOTAPI_PUPPET_SERVER=puppet.query.consul
BOOTAPI_PUPPET_CA_SERVER=puppetca.query.consul
# --- network defaults (used when NetBox does not record them per-device) ---
BOOTAPI_DOMAIN=main.unkin.net
BOOTAPI_NAMESERVERS=198.18.19.19
# --- render-time secrets (NEVER stored in NetBox; from Vault in k8s) ---
# crypt(3) hash for the root account. Empty => root account locked.
BOOTAPI_ROOT_PASSWORD_HASH=
# BOOTAPI_ROOT_PASSWORD_HASH_FILE=/var/run/secrets/bootapi/root_password_hash
# Newline-separated SSH public keys installed for root.
BOOTAPI_SSH_AUTHORIZED_KEYS=
# --- unknown-MAC fallback: "local" (safe: boot local disk) or "shell" (debug) ---
BOOTAPI_UNKNOWN_MAC_FALLBACK=local