# 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:` (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:///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.