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
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# 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/24`–`198.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.
|
||||
Reference in New Issue
Block a user