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
5.9 KiB
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:
- namespace
bootapi. - VaultAuth (
default) — kubernetes method, mountk8s/au/syd1, roledefault, SAdefault(copy netbox'svaultauth.yaml). - VaultStaticSecret → k8s Secret
bootapi-secrets, from Vault kv pathkubernetes/namespace/bootapi/default/bootapi-secretswith keys:netbox_token— a dedicated NetBox API token for bootapi. It needs read on devices/interfaces/ip-addresses AND write on the devicepxe_enabledcustom field (the provisioned callback PATCHes it — see security.md). Create abootapiNetBox user/token via terraform-netbox rather than reusing the seeded superuser token atkv/kubernetes/namespace/netbox/default/netbox-superuser.provision_token— the shared bearer token guardingPOST /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 eyamldefault_password_crypted).ssh_authorized_keys— optional, newline-separated.
- Templates: prefer git-sync — set
BOOTAPI_TEMPLATE_GIT_URLtohttps://git.unkin.net/unkin/bootapi-templates.git(public; no token needed) and bootapi pulls it everyBOOTAPI_TEMPLATE_GIT_INTERVAL(default 3m). No ConfigMap or pod restart is needed to change templates — merge to the repo'smainand bootapi reloads within the interval (last-good kept on a bad push). The embedded defaults remain the fallback if the repo is unreachable. (ABOOTAPI_TEMPLATE_DIRConfigMap is still supported for air-gapped installs.) - Deployment — image above, env from
config.example.env, secret keys wired asBOOTAPI_NETBOX_TOKEN_FILE/BOOTAPI_PROVISION_TOKEN_FILE/BOOTAPI_ROOT_PASSWORD_HASH_FILE(mount the Secret). Least-privilege securityContext (runAsNonRoot,drop: [all]). Baseline resources: requests512Mi/1, limits2Gi/2cpu. The pod needsgiton 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). - Service + exposure: see the Gateway section below.
- 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_URLDHCP/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-templatesrepo (this PR's sibling) holds the live template set + distro catalog + validation CI. - argocd-apps: add a
serviceaccount_*underapps/base/woodpecker/if the bootapi pipelines need a dedicated SA (they usedefaulttoday). - terraform-vault: add the k8s auth role + kv policy granting the
bootapinamespace read onkv/kubernetes/namespace/bootapi/default/*. - terraform-netbox: create the
bootapiNetBox token (read + write on thepxe_enableddevice custom field) and seed it,provision_tokenandroot_password_hashinto the Vault kv path above. Also define thepxe_enabledboolean 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 the198.18.19.19anycast 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 bakechain 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'schain 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.