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

108 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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** NetBox API token for bootapi. It needs
**read on devices/interfaces/ip-addresses AND write on the device
`pxe_enabled` custom field** (the provisioned callback PATCHes it — see
[security.md](security.md#netbox-write-scope)). Create a `bootapi` NetBox
user/token via terraform-netbox rather than reusing the seeded superuser
token at `kv/kubernetes/namespace/netbox/default/netbox-superuser`.
- `provision_token` — the shared bearer token guarding `POST /provisioned`
(also embedded in rendered kickstarts). Generate a random value.
- `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. **Templates**: prefer git-sync — set `BOOTAPI_TEMPLATE_GIT_URL` to
`https://git.unkin.net/unkin/bootapi-templates.git` (public; no token needed)
and bootapi pulls it every `BOOTAPI_TEMPLATE_GIT_INTERVAL` (default 3m). No
ConfigMap or pod restart is needed to change templates — merge to the repo's
`main` and bootapi reloads within the interval (last-good kept on a bad push).
The embedded defaults remain the fallback if the repo is unreachable. (A
`BOOTAPI_TEMPLATE_DIR` ConfigMap is still supported for air-gapped installs.)
5. **Deployment** — image above, env from `config.example.env`, secret keys wired
as `BOOTAPI_NETBOX_TOKEN_FILE` / `BOOTAPI_PROVISION_TOKEN_FILE` /
`BOOTAPI_ROOT_PASSWORD_HASH_FILE` (mount the Secret). Least-privilege
securityContext (`runAsNonRoot`, `drop: [all]`). Baseline resources: requests
`512Mi`/`1`, limits `2Gi`/`2` cpu. The pod needs `git` on PATH for template
sync (the distroless image includes only the static binary — either add a git
layer, use an initContainer that seeds the checkout, or fall back to a
ConfigMap; simplest is a small alpine+git base for this service).
6. **Service + exposure**: see the Gateway section below.
7. Register in `argocd/applicationsets/platform.yaml` (`apps/overlays/*/bootapi`)
and the platform AppProject destinations.
### Gateway: HTTP and HTTPS
PXE installers do **not** trust the internal CA, so the boot path must be served
over **plain HTTP**. Unlike the estate default, the bootapi HTTPRoute must **not
blanket-301 HTTP→HTTPS**:
- A **plain-HTTP** listener/HTTPRoute (or a LoadBalancer Service on port 80→8000)
reachable by PXE clients at a stable address/hostname on the PXE VLAN — this is
the `BOOTAPI_BASE_URL` DHCP/iPXE points at. No redirect.
- Optionally an **HTTPS** HTTPRoute for humans/tooling that do trust the CA
(bootapi can serve TLS directly via `BOOTAPI_TLS_*`, or terminate at the
gateway). This is additive; it must not replace or redirect the HTTP boot path.
The end-of-kickstart callback (`POST /provisioned`) runs over the same plain-HTTP
base by default (the token authenticates it; the install has no CA trust yet). If
you install the internal CA early in `%post`, you may set
`BOOTAPI_CALLBACK_BASE_URL` to the HTTPS URL instead.
### Cross-repo dependencies (per estate conventions)
- **terraform-git**: `unkin/bootapi-templates` repo (this PR's sibling) holds the
live template set + distro catalog + validation CI.
- **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 `bootapi` NetBox token (read + write on the
`pxe_enabled` device custom field) and seed it, `provision_token` and
`root_password_hash` into the Vault kv path above. Also define the `pxe_enabled`
boolean custom field on the Device model.
## 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.