Initial bootapi: NetBox-driven PXE/kickstart boot service #1

Merged
benvin merged 2 commits from benvin/bootapi-initial into main 2026-07-28 23:35:04 +10:00
32 changed files with 2119 additions and 357 deletions
Showing only changes of commit 8f356346eb - Show all commits
+22 -7
View File
2
@@ -32,11 +32,24 @@ serves it. Templates are embedded defaults, overridable from a directory
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
|------|---------|
| `GET /ipxe/{mac}` · `GET /boot/ipxe?mac=` | iPXE boot script |
| `GET /ks/{ident}` | rendered kickstart (MAC or hostname) |
| `POST /provisioned/{ident}` | end-of-kickstart callback (token) → clears `pxe_enabled` in NetBox |
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
| `GET /healthz` · `/readyz` · `/metrics` | health + Prometheus |
Unknown MAC → iPXE gets a **safe fallback** (local-disk boot, HTTP 200), never a
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
404. Unknown kickstart host → **404** (fail loud once installing). Full rationale
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
in [docs/endpoints.md](docs/endpoints.md).
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
The boot path is served over **plain HTTP** (PXE installers have no internal-CA
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
trust); HTTPS is offered in parallel. Unknown MAC → iPXE gets a **safe fallback**
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
(local-disk boot, HTTP 200), never a 404; a host with `pxe_enabled=false` gets the
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
same fallback so it won't re-install. Unknown kickstart host → **404**. Full
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
rationale in [docs/endpoints.md](docs/endpoints.md).
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
## Multi-distro + live templates
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
- **Distro catalog** (`catalog/*.yaml`): each OS maps a NetBox platform/family to
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
its boot images (artifactapi remotes), kernel args and kickstart template.
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
Adding Fedora/Debian/Talos is a YAML + template change, no code change. Ships
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
`almalinux9` + `fedora`.
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
- **Template git-sync**: bootapi pulls the `bootapi-templates` repo every 3m
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
(like argocd) and hot-swaps the template set (last-good kept on a bad push);
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
embedded defaults are the startup fallback.
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
## Documentation
@@ -71,10 +84,12 @@ release. Cut a release with `make patch|minor|major` (tags + pushes).
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
```
cmd/bootapi/ main
internal/config/ env config
internal/model/ Host/Interface data model
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
internal/netbox/ NetBox client (+ TTL cache), behind a Resolver interface
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
internal/render/ text/template engine, selection, embedded-defaults loader
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
internal/model/ Host/Interface data model (incl. pxe_enabled gate)
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
internal/netbox/ NetBox client (reads + pxe_enabled write) + TTL cache, behind an interface
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
internal/catalog/ distro catalog: NetBox host -> boot images/kickstart
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
internal/render/ text/template engine (swappable Set), selection, loader
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
internal/gitsync/ periodic git pull + atomic template reload (last-good)
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
internal/server/ chi HTTP handlers + Prometheus metrics
templates/ embedded default kickstart + iPXE templates
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
templates/ embedded defaults: kickstart, iPXE, catalog/*.yaml
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
docs/ see above
```
benvin marked this conversation as resolved
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. Added a NetBox pxe_enabled boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded POST /provisioned/{ident} that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).

Done. Added a NetBox `pxe_enabled` boolean custom field (Cobbler's netboot_enabled): a known host with it false now gets the safe local-boot script from /ipxe. And 'something the client unticks at the end of kickstart' -> new token-guarded `POST /provisioned/{ident}` that clears pxe_enabled in NetBox, called from a %post snippet in the default kickstarts. NetBox token now needs write on that one custom field (flagged in docs/security.md + deployment.md).
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
Review

Done. bootapi now git-syncs a templates repo (unkin/bootapi-templates, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (bootapi validate) + shellcheck on %post.

Done. bootapi now git-syncs a templates repo (`unkin/bootapi-templates`, terraform-git PR #50) every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m, like argocd), atomically swapping the template set; last-good is kept on a parse failure and embedded defaults are the startup fallback. The templates repo gets CI that validates every template/catalog renders (`bootapi validate`) + shellcheck on %post.
+104 -8
View File
@@ -5,12 +5,15 @@ package main
import (
"context"
"fmt"
"log/slog"
"os"
"os/signal"
"path/filepath"
"syscall"
"git.unkin.net/unkin/bootapi/internal/config"
"git.unkin.net/unkin/bootapi/internal/gitsync"
"git.unkin.net/unkin/bootapi/internal/netbox"
"git.unkin.net/unkin/bootapi/internal/render"
"git.unkin.net/unkin/bootapi/internal/server"
@@ -20,6 +23,16 @@ import (
var version = "dev"
func main() {
// `bootapi validate [dir]` checks a template/catalog set (used by the
// bootapi-templates repo CI) and exits without starting the server.
if len(os.Args) > 1 && os.Args[1] == "validate" {
dir := "."
if len(os.Args) > 2 {
dir = os.Args[2]
}
os.Exit(runValidate(dir))
}
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
slog.Info("starting bootapi", "version", version)
@@ -34,22 +47,37 @@ func main() {
if cfg.NetBoxToken == "" {
slog.Warn("no NetBox token set (BOOTAPI_NETBOX_TOKEN/_FILE); NetBox reads will likely be denied")
}
if cfg.ProvisionToken == "" {
slog.Warn("no BOOTAPI_PROVISION_TOKEN set; the /provisioned callback is disabled (pxe_enabled will not auto-clear)")
}
engine, err := render.NewEngine(templates.FS, cfg.TemplateDir, render.RenderConfig{
rcfg := render.RenderConfig{
PuppetServer: cfg.PuppetServer,
PuppetCAServer: cfg.PuppetCAServer,
PuppetCAURL: cfg.PuppetCAURL,
BaseURL: cfg.BaseURL,
CallbackBaseURL: cfg.CallbackBaseURL,
ArtifactBase: cfg.ArtifactBaseURL,
BootBaseURL: cfg.BootBaseURL,
ProvisionToken: cfg.ProvisionToken,
DefaultDomain: cfg.Domain,
DefaultNS: cfg.Nameservers,
RootPasswordHash: cfg.RootPasswordHash,
SSHAuthorizedKeys: cfg.SSHAuthorizedKeys,
DefaultTemplate: cfg.DefaultTemplate,
})
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
engine, syncer, err := buildEngine(ctx, cfg, rcfg)
if err != nil {
slog.Error("load templates", "err", err)
os.Exit(1)
}
if syncer != nil {
go syncer.Run(ctx)
}
nb := netbox.New(netbox.Options{
BaseURL: cfg.NetBoxURL,
@@ -59,18 +87,86 @@ func main() {
})
cache := netbox.NewCache(nb, cfg.CacheTTL)
srv := server.New(server.Options{
Resolver: cache,
opts := server.Options{
NetBox: cache,
Engine: engine,
Cache: cache,
UnknownMACFallback: cfg.UnknownMACFallback,
})
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
ProvisionToken: cfg.ProvisionToken,
TLSAddr: cfg.TLSListenAddr,
TLSCertFile: cfg.TLSCertFile,
TLSKeyFile: cfg.TLSKeyFile,
}
if syncer != nil {
opts.GitStats = syncer
}
srv := server.New(opts)
if err := srv.ListenAndServe(ctx, cfg.ListenAddr); err != nil {
slog.Error("server", "err", err)
os.Exit(1)
}
}
// runValidate loads a template/catalog dir over the embedded defaults and
// renders every catalog distro, returning a process exit code.
func runValidate(dir string) int {
set, err := render.BuildSet(templates.FS, os.DirFS(dir))
if err != nil {
fmt.Fprintf(os.Stderr, "bootapi validate: %v\n", err)
return 1
}
// Fixture render-time config: concrete enough that every field resolves.
eng := render.NewEngine(render.RenderConfig{
PuppetServer: "puppet.k8s.syd1.au.unkin.net", PuppetCAServer: "puppetca.k8s.syd1.au.unkin.net",
PuppetCAURL: "puppetca.k8s.syd1.au.unkin.net",
BaseURL: "http://bootapi.example.net", CallbackBaseURL: "http://bootapi.example.net",
ArtifactBase: "https://artifactapi.example.net/api/v1/remote", ProvisionToken: "validate-token",
DefaultDomain: "example.net", DefaultNS: []string{"10.0.0.1"},
RootPasswordHash: "$6$fixture$hash", DefaultTemplate: "almalinux9",
}, set)
if err := eng.Validate(); err != nil {
fmt.Fprintf(os.Stderr, "bootapi validate: %v\n", err)
return 1
}
fmt.Printf("bootapi validate: OK (%s + embedded defaults)\n", dir)
return 0
}
// buildEngine constructs the render Engine and, when a templates git repo is
// configured, a Syncer that reloads it periodically. Precedence: git repo →
// local override dir → embedded defaults only. Git/dir failures degrade to the
// embedded defaults rather than failing startup.
func buildEngine(ctx context.Context, cfg *config.Config, rcfg render.RenderConfig) (*render.Engine, *gitsync.Syncer, error) {
switch {
case cfg.TemplateGitURL != "":
syncer := gitsync.New(gitsync.Options{
URL: cfg.TemplateGitURL,
Branch: cfg.TemplateGitBranch,
Token: cfg.TemplateGitToken,
Interval: cfg.TemplateGitInterval,
WorkDir: filepath.Join(os.TempDir(), "bootapi-templates"),
}, templates.FS)
set, gerr := syncer.Bootstrap(ctx)
if gerr != nil {
slog.Warn("template git bootstrap degraded to embedded defaults", "err", gerr)
}
engine := render.NewEngine(rcfg, set)
syncer.SetEngine(engine)
return engine, syncer, nil
case cfg.TemplateDir != "":
set, err := render.BuildSet(templates.FS, os.DirFS(cfg.TemplateDir))
if err != nil {
return nil, nil, err
}
return render.NewEngine(rcfg, set), nil, nil
default:
set, err := render.BuildSet(templates.FS, nil)
if err != nil {
return nil, nil, err
}
return render.NewEngine(rcfg, set), nil, nil
}
}
+47 -21
View File
@@ -1,45 +1,71 @@
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# bootapi configuration (environment variables).
#
# bootapi is configured entirely from the environment (12-factor style, same as
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# encapi). In Kubernetes these come from the Deployment env + a Vault-sourced
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Secret (see docs/deployment.md). Locally, `env $(grep -v '^#' config.example.env | xargs) ./bin/bootapi`.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# bootapi is configured entirely from the environment (12-factor, like encapi).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# In Kubernetes these come from the Deployment env + a Vault-sourced Secret and
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# a templates ConfigMap/git repo (see docs/deployment.md). Locally:
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# env $(grep -v '^#' config.example.env | xargs) ./bin/bootapi
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- HTTP ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- HTTP (boot path is ALWAYS plain HTTP: PXE installers have no CA trust) ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_LISTEN_ADDR=:8000
# Optional parallel HTTPS listener for clients that DO trust the internal CA.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# The boot path still works over plain HTTP; do not 301 HTTP->HTTPS (see docs).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_TLS_LISTEN_ADDR=:8443
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_TLS_CERT_FILE=/etc/bootapi/tls/tls.crt
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_TLS_KEY_FILE=/etc/bootapi/tls/tls.key
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- 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:
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Provide the token inline OR (preferred in k8s) via a Vault-mounted file.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# NOTE: the token needs WRITE scope on the device pxe_enabled custom field for
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# the /provisioned callback (see docs/security.md).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_NETBOX_TOKEN=
# BOOTAPI_NETBOX_TOKEN_FILE=/var/run/secrets/netbox/api_token
BOOTAPI_NETBOX_TIMEOUT=5s
BOOTAPI_NETBOX_INSECURE=false
# --- caching ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Short by design: a re-provisioned host must pick up NetBox changes on its next
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# boot. Set 0 to disable.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- caching (short: a re-provisioned host must pick up changes next boot) ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_CACHE_TTL=30s
# --- templates ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Optional override directory (a ConfigMap mount in k8s); files here win over
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# the embedded defaults. Leave empty to use only the built-in templates.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_TEMPLATE_DIR=/etc/bootapi/templates
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Template used when NetBox provides no platform/role/override selection key.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- templates: git-sync (preferred) OR a local override dir OR embedded ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Pull a templates repo every interval (default 3m, like argocd); a parse
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# failure keeps the last-good set. Embedded defaults are the startup fallback.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_TEMPLATE_GIT_URL=https://git.unkin.net/unkin/bootapi-templates.git
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_TEMPLATE_GIT_BRANCH=main
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_TEMPLATE_GIT_INTERVAL=3m
benvin marked this conversation as resolved Outdated
Outdated
Review

there is already an almalinux remote in artifactapi. almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/

but I also want to be able to boot other os, like fedora. is there a way we can describe distros in netbox? or should this be environment variables? What happens if we want debian or tailos later?

there is already an almalinux remote in artifactapi. `almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/` but I also want to be able to boot other os, like fedora. is there a way we can describe distros in netbox? or should this be environment variables? What happens if we want debian or tailos later?
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_TEMPLATE_GIT_TOKEN= # only for a private templates repo
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_TEMPLATE_DIR=/etc/bootapi/templates # used only when GIT_URL is unset
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_DEFAULT_TEMPLATE=almalinux9
benvin marked this conversation as resolved
Review

puppet in kubernetes instead

puppet in kubernetes instead
# --- URLs baked into rendered output ---
# bootapi's own externally-reachable base URL (goes into the iPXE inst.ks=).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# bootapi's own PLAIN-HTTP base (goes into iPXE inst.ks= and /ks URLs). Must be
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# reachable without CA trust.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_BASE_URL=http://bootapi.k8s.syd1.au.unkin.net
benvin marked this conversation as resolved
Review

this should be the bind resolvers in k8s now

this should be the bind resolvers in k8s now
# Base URL of the OS install trees (kernel/initrd + inst.repo).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_BOOT_BASE_URL=http://mirror.k8s.syd1.au.unkin.net/almalinux/9
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Base the end-of-kickstart callback posts to; defaults to BOOTAPI_BASE_URL
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# (plain HTTP, works before the internal CA is installed).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_CALLBACK_BASE_URL=http://bootapi.k8s.syd1.au.unkin.net
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# artifactapi remote base the distro catalog builds kernel/initrd URLs from.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_ARTIFACT_BASE_URL=https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Legacy fallback OS-tree base, used only if no catalog entry matches. Normally
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# empty (the distro catalog drives boot images).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_BOOT_BASE_URL=
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- puppet bootstrap targets (baked into kickstart %post) ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_PUPPET_SERVER=puppet.query.consul
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_PUPPET_CA_SERVER=puppetca.query.consul
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- end-of-kickstart callback token (guards POST /provisioned) ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Empty disables the callback (pxe_enabled will not auto-clear). Embedded in the
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# rendered kickstart, so treat as a provisioning secret (docs/security.md).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_PROVISION_TOKEN=
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# BOOTAPI_PROVISION_TOKEN_FILE=/var/run/secrets/bootapi/provision_token
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- network defaults (used when NetBox does not record them per-device) ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- puppet bootstrap targets (k8s puppetserver; baked into kickstart %post) ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_PUPPET_SERVER=puppet.k8s.syd1.au.unkin.net
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_PUPPET_CA_SERVER=puppetca.k8s.syd1.au.unkin.net
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# Written to /etc/sysconfig/puppet-initial as PUPPETCA_URL (read by the
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# puppet-initial RPM's systemd bootstrap unit).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_PUPPET_CA_URL=puppetca.k8s.syd1.au.unkin.net
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- network defaults (used when NetBox records none per-device) ---
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_DOMAIN=main.unkin.net
BOOTAPI_NAMESERVERS=198.18.19.19
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# k8s bind-resolvers LoadBalancer (replaces the legacy VM resolvers).
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
BOOTAPI_NAMESERVERS=198.18.200.7
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
# --- render-time secrets (NEVER stored in NetBox; from Vault in k8s) ---
# crypt(3) hash for the root account. Empty => root account locked.
benvin marked this conversation as resolved
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).

Done. Default nameserver is now the k8s bind-resolvers LB 198.18.200.7 (BOOTAPI_NAMESERVERS).
Review

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.

Done. Puppet now targets the k8s puppetserver: server=puppet.k8s.syd1.au.unkin.net, ca_server=puppetca.k8s.syd1.au.unkin.net, and %post writes /etc/sysconfig/puppet-initial with PUPPETCA_URL (default puppetca.k8s.syd1.au.unkin.net) for the puppet-initial unit instead of hardcoding.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
Review

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a distro catalog (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.

Done. Kernel/initrd now come from the artifactapi almalinux remote: {ArtifactBase}/almalinux/${version}/BaseOS/${arch}/os/images/pxeboot/. On 'describe distros in NetBox or env?': neither hardcoded - implemented a **distro catalog** (catalog/*.yaml in the templates repo), one file per OS (match, kernel/initrd URL templates -> artifactapi remotes, kickstart ref, kernel args). Host->distro selection stays NetBox-driven (platform slug / family / provision_template). Shipped almalinux9 + fedora (fedora remote exists). Adding debian/talos later = a yaml + template (+ maybe a new artifactapi remote), no bootapi code change; their differing artifact shapes are documented in docs/template-authoring.md and the catalog README. Env vars are now only for bootapi's own endpoints.
+35 -18
View File
@@ -23,11 +23,15 @@ against. It is assembled in `internal/render.dataFor` from a NetBox device
| `.Nameservers` | `[]string` | NetBox CF `nameservers`, else `BOOTAPI_NAMESERVERS` | |
| `.RootPasswordHash` | string | **render-time** (`BOOTAPI_ROOT_PASSWORD_HASH[_FILE]`) | crypt(3) hash; empty ⇒ lock root. **Never** from NetBox — see [security.md](security.md) |
| `.SSHAuthorizedKeys` | `[]string` | **render-time** (`BOOTAPI_SSH_AUTHORIZED_KEYS`) | |
| `.PuppetServer` | string | `BOOTAPI_PUPPET_SERVER` | default `puppet.query.consul` |
| `.PuppetCAServer` | string | `BOOTAPI_PUPPET_CA_SERVER` | default `puppetca.query.consul` |
| `.BaseURL` | string | `BOOTAPI_BASE_URL` | bootapi's own URL |
| `.BootBaseURL` | string | `BOOTAPI_BOOT_BASE_URL` | OS install-tree base |
| `.PuppetServer` | string | `BOOTAPI_PUPPET_SERVER` | default `puppet.k8s.syd1.au.unkin.net` |
| `.PuppetCAServer` | string | `BOOTAPI_PUPPET_CA_SERVER` | default `puppetca.k8s.syd1.au.unkin.net` |
| `.PuppetCAURL` | string | `BOOTAPI_PUPPET_CA_URL` | written to `/etc/sysconfig/puppet-initial` as `PUPPETCA_URL` |
| `.BaseURL` | string | `BOOTAPI_BASE_URL` | bootapi's own **http** URL |
| `.KickstartURL` | string | derived | `BaseURL/ks/Hostname` |
| `.CallbackURL` | string | derived | `CallbackBaseURL/provisioned/Hostname` |
| `.ProvisionToken` | string | **render-time** (`BOOTAPI_PROVISION_TOKEN[_FILE]`) | bearer token the `%post` callback sends; empty ⇒ callback snippet omitted |
| `.DistroVars` | `map[string]string` | selected catalog entry's evaluated `vars` | e.g. `.DistroVars.mirror` (install-tree base); empty when no catalog entry matched |
| `.BootBaseURL` | string | `BOOTAPI_BOOT_BASE_URL` | legacy OS-tree base; empty when catalog-driven |
| `.Custom` | `map[string]any` | **all** NetBox custom fields, verbatim | escape hatch for site-specific knobs without a code change |
### `Interface`
@@ -49,8 +53,10 @@ Rendered with everything above **plus**:
| Field | Type | Notes |
|-------|------|-------|
| `.KernelURL` | string | `BootBaseURL/images/pxeboot/vmlinuz` (empty if `BootBaseURL` unset) |
| `.InitrdURL` | string | `BootBaseURL/images/pxeboot/initrd.img` |
| `.KernelURL` | string | from the selected catalog entry's `kernel_url` (else legacy `BootBaseURL/images/pxeboot/vmlinuz`) |
| `.InitrdURL` | string | catalog `initrd_url` (else legacy path) |
| `.RepoURL` | string | OS install-tree root (`KernelURL` minus `images/pxeboot/vmlinuz`); passed as `inst.repo=` |
| `.KernelArgs` | `[]string` | catalog entry's extra kernel args |
The fallback templates (`fallback-local`, `fallback-shell`) are rendered with an
empty value — they take no host data by design.
@@ -60,18 +66,29 @@ empty value — they take no host data by design.
Define these on the *device* (or, where noted, the *IP address*) in NetBox.
All are optional; sensible fallbacks apply.
| Custom field | On | Effect |
|--------------|----|--------|
| `domain` | device | DNS domain; overrides `BOOTAPI_DOMAIN` |
| `gateway` | device / IP address | default gateway (IP-level wins) |
| `nameservers` | device | comma-separated resolvers; overrides `BOOTAPI_NAMESERVERS` |
| `provision_template` | device | force a specific template name (see below) |
| Custom field | On | Type | Effect |
|--------------|----|------|--------|
| `domain` | device | text | DNS domain; overrides `BOOTAPI_DOMAIN` |
| `gateway` | device / IP address | text | default gateway (IP-level wins) |
| `nameservers` | device | text | comma-separated resolvers; overrides `BOOTAPI_NAMESERVERS` |
| `provision_template` | device | text | force a specific catalog entry / template name |
| `pxe_enabled` | device | boolean | gate network install (Cobbler's `netboot_enabled`). Unset ⇒ treated as enabled. Set `false` (or let the callback clear it) to boot local disk instead of re-installing. |
## Template selection precedence
## Distro selection and the catalog
`SelectKickstart` picks the first template name that exists, in order:
Host → distro is resolved through the **distro catalog** (`catalog/*.yaml` in the
templates repo / embedded defaults). Each entry names a kickstart template, the
kernel/initrd URL templates (artifactapi remotes) and extra kernel args. See
[template-authoring.md](template-authoring.md#the-distro-catalog).
1. `provision_template` custom field (exact template name)
2. `.Platform` slug (e.g. `almalinux9`)
3. `.OSFamily` (e.g. `almalinux`, or `fedora`)
4. `BOOTAPI_DEFAULT_TEMPLATE` (default `almalinux9`)
Selection precedence (both catalog `Select` and the kickstart-name fallback):
1. `provision_template` custom field — exact catalog entry / template name.
2. `.Platform` slug (e.g. `almalinux9`) matched against a catalog entry's
`match.platforms`, else a template of that name.
3. `.OSFamily` (e.g. `fedora`) matched against `match.family`, else a template
of that name.
4. `BOOTAPI_DEFAULT_TEMPLATE` (default `almalinux9`).
The version substituted into the catalog URLs is `.OSVersion` (the numeric
suffix of the platform slug), falling back to the entry's `version_default`.
+47 -19
View File
@@ -22,38 +22,66 @@ Create `apps/base/bootapi/` following the argocd-apps `AGENTS.md` pattern:
`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`).
- `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. **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.
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_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.
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 read-only `bootapi` NetBox token and seed it
(plus `root_password_hash`) into the Vault kv path above.
- **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)
+63 -31
View File
@@ -1,60 +1,89 @@
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
# bootapi HTTP endpoints
bootapi speaks plain HTTP. It is fronted by the same Vault-issued TLS the Cobbler
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
server used; the booting firmware reaches it at the DHCP `next-server` (see
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
[deployment.md](deployment.md)).
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
## HTTP and HTTPS — the boot path is plain HTTP by design
benvin marked this conversation as resolved Outdated
Outdated
Review

it should be accessible via http and https, since pxebooting hosts may not trust the https cert we use.

it should be accessible via http and https, since pxebooting hosts may not trust the https cert we use.
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
bootapi always serves the boot path (`/ipxe`, `/boot/ipxe`, `/ks`) over **plain
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
HTTP** on `BOOTAPI_LISTEN_ADDR`. A PXE installer environment has no internal-CA
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
trust, so an HTTPS-only boot URL (with our private CA cert) would fail the TLS
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
handshake. iPXE and the kickstart therefore use `http://` URLs (from
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
`BOOTAPI_BASE_URL`).
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
Optionally bootapi *also* serves HTTPS in parallel (`BOOTAPI_TLS_LISTEN_ADDR` +
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
cert/key), for clients that do trust the CA. The Kubernetes exposure must **not**
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
blanket-301 HTTP→HTTPS for the boot endpoints — see
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
[deployment.md](deployment.md#gateway-http-and-https).
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
## The PXE flow
```
DHCP ── next-server + filename (ipxe.efi / undionly.kpxe) ──▶ firmware loads iPXE
iPXE ── GET /ipxe/<mac> ───────────────────────────────────▶ bootapi renders a boot script
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
boot ── kernel + initrd + inst.ks=<BASE_URL>/ks/<host> ────▶ Anaconda fetches the kickstart
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
KS ── GET /ks/<host> ────────────────────────────────────▶ bootapi renders the kickstart
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
iPXE ── GET http://<base>/ipxe/<mac> ─────────────────────▶ bootapi renders a boot script
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
boot ── kernel + initrd + inst.ks=http://<base>/ks/<host> ─▶ Anaconda fetches the kickstart
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
KS ── GET http://<base>/ks/<host> ──────────────────────▶ bootapi renders the kickstart
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
post ── POST http://<base>/provisioned/<host> (token) ────▶ bootapi clears pxe_enabled in NetBox
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
```
This mirrors Cobbler, which chained iPXE to `/cblr/svc/op/gpxe/mac/<mac>` and
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
served a per-system script carrying `inst.ks=`.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
This mirrors Cobbler, which chained iPXE to `/cblr/svc/op/gpxe/mac/<mac>`, served
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
a per-system script carrying `inst.ks=`, and cleared `netboot_enabled` at the end
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
of the install.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
## Endpoints
| Method | Path | Purpose |
|--------|------|---------|
| GET | `/ipxe/{mac}` | iPXE boot script for the host owning `{mac}`. `{mac}` may use `:`/`-`/`.` separators or be bare hex; a trailing `.ipxe` is stripped. |
| GET | `/boot/ipxe?mac=...` | Query-string alias of `/ipxe/{mac}` (some firmware finds this shape easier to template). |
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
| GET | `/boot/ipxe?mac=...` | Query-string alias of `/ipxe/{mac}`. |
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
| GET | `/ks/{ident}` | Rendered kickstart. `{ident}` is a MAC (auto-detected) or a hostname; trailing `.ks`/`.cfg` is stripped. |
| POST | `/provisioned/{ident}` | End-of-kickstart callback; clears `pxe_enabled` in NetBox. **Token-guarded** (`Authorization: Bearer <BOOTAPI_PROVISION_TOKEN>`). |
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
| GET | `/healthz` | Liveness: always `200 ok`. |
| GET | `/readyz` | Readiness: `200` once templates parsed. Does **not** probe NetBox (a NetBox outage still lets iPXE serve the safe fallback). |
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
| GET | `/readyz` | Readiness: `200` once templates parsed. Does **not** probe NetBox. |
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
| GET | `/metrics` | Prometheus metrics (see below). |
## Host identification
A booting host is identified by the **MAC** of the NIC it PXE-booted from
(`/ipxe/{mac}`), which bootapi resolves via NetBox
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
`GET /api/dcim/interfaces/?mac_address=<mac>` → device → primary IP, platform,
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
role, interfaces. `/ks/{ident}` additionally accepts a **hostname** (NetBox
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
device name), for hand-testing and for installers that template the hostname
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
into the kickstart URL.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
(`/ipxe/{mac}`), resolved via NetBox `GET /api/dcim/interfaces/?mac_address=<mac>`
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
→ device → primary IP, platform, role, interfaces. `/ks/{ident}` and
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
`/provisioned/{ident}` also accept a **hostname** (NetBox device name).
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
## Error behavior (important, and deliberate)
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
## Per-host PXE-enable gate (`pxe_enabled`)
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
The two endpoints fail **differently** on an unknown host, because the cost of a
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
`/ipxe/{mac}` checks the device's `pxe_enabled` NetBox custom field (Cobbler's
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
`netboot_enabled`):
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- **unset or `true`** → normal installer boot script.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- **`false`** → the safe **local-boot** fallback, *even for a known host*, so a
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
machine that has already been provisioned does not re-install on its next PXE.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
The `/provisioned/{ident}` callback (called from the kickstart `%post`) sets the
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
field to `false` when the install finishes; so a host installs once, then gates
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
itself off. Flip it back to `true` in NetBox to re-image.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
## Error behavior (deliberate)
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
The boot endpoints fail **differently** on an unknown host, because the cost of a
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
wrong answer differs:
- **`/ipxe/{mac}` never returns 404.** iPXE needs a syntactically valid script or
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
the boot chain simply errors. An unknown MAC — or *any* NetBox error — returns
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
HTTP 200 with the **fallback script** selected by `BOOTAPI_UNKNOWN_MAC_FALLBACK`:
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- `local` (default): `sanboot` the local disk. Safe: a machine that PXE-booted
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
by accident (or a NetBox blip) just boots its installed OS; a genuinely new
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
machine loops back to PXE next time, by which point NetBox should know it. We
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
deliberately do **not** start an installer for a machine we can't identify —
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
that could wipe a production box.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- `shell`: drop to an interactive iPXE shell so an operator racking a new box
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
can read `${net0/mac}` and register it. Opt-in; unsafe as a default because
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
it halts the boot.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- **`/ks/{ident}` returns 404** for an unknown host (and 502 on a NetBox error).
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
By the time Anaconda fetches the kickstart it has already committed to
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
installing; a clear failure is safer than serving an empty or wrong kickstart.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- **`/ipxe/{mac}` never returns 404.** iPXE needs a syntactically valid script.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
An unknown MAC — or *any* NetBox error, or a gated host — returns HTTP 200 with
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
the **fallback script** selected by `BOOTAPI_UNKNOWN_MAC_FALLBACK`:
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- `local` (default): `sanboot` the local disk. Safe: an accidental PXE (or a
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
NetBox blip) boots the installed OS; a genuinely new machine loops back to PXE
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
next time. We never start an installer for a machine we can't identify.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- `shell`: interactive iPXE shell for an operator to read `${net0/mac}` and
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
register it. Opt-in; unsafe as a default because it halts the boot.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- **`/ks/{ident}` returns 404** for an unknown host (502 on a NetBox error). By
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
the time Anaconda fetches the kickstart it has committed to installing; a clear
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
failure beats an empty/wrong kickstart.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
## The provisioned callback
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
`POST /provisioned/{ident}` requires the shared token in an `Authorization:
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
Bearer` (or bare `token`) header. Responses: `204` on success, `401` on a
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
bad/missing token, `404` for an unknown host, `503` when no
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
`BOOTAPI_PROVISION_TOKEN` is configured (fail closed), `502` on a NetBox write
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
failure. The default kickstart templates call it from `%post` over plain HTTP
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
(the token authenticates the call; no CA trust needed at install time).
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
## Metrics
@@ -65,4 +94,7 @@ All on `/metrics`, prefix `bootapi_`:
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- `bootapi_netbox_lookups_total{field,result}` — field = `mac|name`, result = `ok|notfound|error`.
- `bootapi_netbox_lookup_duration_seconds{field}` — histogram.
- `bootapi_netbox_cache_hits_total` / `bootapi_netbox_cache_misses_total`.
- `bootapi_provisioned_total{result}` — result = `ok|unauthorized|notfound|error|disabled`.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- `bootapi_ipxe_gated_total` — known hosts served local-boot because `pxe_enabled=false`.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- `bootapi_template_sync_total` / `bootapi_template_sync_failures_total` / `bootapi_template_generation` — template git-sync (see [template-authoring.md](template-authoring.md)).
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
- standard Go/process collectors.
benvin marked this conversation as resolved
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
Review

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.

Done. The boot path (/ipxe, /boot/ipxe, /ks) is always served over plain HTTP so installers with no internal-CA trust can reach it; iPXE/kickstart URLs are http:// (BOOTAPI_BASE_URL). bootapi also serves HTTPS in parallel when BOOTAPI_TLS_* is set, for clients that trust the CA. The %post provisioned callback also runs over plain HTTP by default (the token authenticates it, before any CA is installed); BOOTAPI_CALLBACK_BASE_URL can point it at https if you install the CA early. docs/deployment.md now says the Gateway/HTTPRoute must NOT 301 HTTP->HTTPS for the boot endpoints.
+25
View File
@@ -15,6 +15,7 @@ render time from Vault/env.**
| template selection knobs (`provision_template`, `nameservers`, `gateway`) | NetBox custom fields | `.Custom` / typed fields |
| **root password hash** | Vault → `BOOTAPI_ROOT_PASSWORD_HASH[_FILE]` | `.RootPasswordHash` |
| **SSH authorized keys** | Vault → `BOOTAPI_SSH_AUTHORIZED_KEYS` | `.SSHAuthorizedKeys` |
| **provision token** | Vault → `BOOTAPI_PROVISION_TOKEN[_FILE]` | `.ProvisionToken` |
| puppet CA/server names | env (not secret) | `.PuppetServer` / `.PuppetCAServer` |
`BOOTAPI_ROOT_PASSWORD_HASH_FILE` and `BOOTAPI_NETBOX_TOKEN_FILE` let the values
@@ -38,6 +39,30 @@ secret, never in the NetBox/inventory layer.
the puppetmaster autosigns it based on source subnet + `*.main.unkin.net`
(unchanged from Cobbler). So the kickstart carries no puppet secret.
## The provisioned callback token
`POST /provisioned/{ident}` (which flips `pxe_enabled` off in NetBox) is guarded
by `BOOTAPI_PROVISION_TOKEN`. The default kickstart `%post` calls it with that
token in an `Authorization: Bearer` header, so **the token is embedded in every
rendered kickstart** — treat it as a provisioning secret (same exposure class as
the root hash: visible to anything on the provisioning VLAN). It only authorizes
clearing a boot gate, not reading data. Rotate it in Vault as normal; empty
disables the callback (fail closed). The call runs over plain HTTP by default
because `%post` has no internal-CA trust yet; the token — not TLS — is what
authenticates it.
## NetBox write scope
bootapi performs exactly one NetBox write: `PATCH /api/dcim/devices/{id}/` setting
`custom_fields.pxe_enabled=false` from the provisioned callback. Its NetBox token
therefore needs **write on the device `pxe_enabled` custom field** in addition to
read on devices/interfaces/ip-addresses. Scope the `bootapi` NetBox
role/permission to just that (a NetBox object-permission constrained to
`dcim.device` with the `pxe_enabled` field) rather than granting broad write.
This is a deliberate, minimal escalation from the read-only design; it is called
out here and in the deployment doc so the token is provisioned with the right
(and only the right) scope.
## Follow-up: per-template Vault lookups
Today all render-time secrets are process-wide env/files (one root hash, one key
+51 -5
View File
@@ -4,11 +4,22 @@ bootapi ships an embedded default set and lets you override or extend it.
## Where templates live
- **Embedded defaults**: `templates/kickstart/*.ks.tmpl` and
`templates/ipxe/*.ipxe.tmpl`, compiled into the binary (`templates/embed.go`).
- **Overrides**: any directory pointed to by `BOOTAPI_TEMPLATE_DIR`. Files there
with the same base name **replace** the embedded one; new names **add** to the
set. In Kubernetes this is a ConfigMap mount (see [deployment.md](deployment.md)).
- **Embedded defaults**: `templates/kickstart/*.ks.tmpl`,
`templates/ipxe/*.ipxe.tmpl` and `templates/catalog/*.yaml`, compiled into the
binary (`templates/embed.go`). These are the always-available startup fallback.
- **Template git repo** (preferred in prod): `BOOTAPI_TEMPLATE_GIT_URL`. bootapi
clones it at startup and re-pulls every `BOOTAPI_TEMPLATE_GIT_INTERVAL`
(default 3m, like argocd), atomically swapping the loaded set on change. A
parse failure keeps the **last-good** set and is only logged + counted
(`bootapi_template_sync_failures_total`), so a bad push can't take bootapi
down. If the repo is unreachable at startup, bootapi runs on the embedded
defaults. The repo is `unkin/bootapi-templates` (seeded from these embedded
files) and has its own CI validating templates + catalog.
- **Override directory**: `BOOTAPI_TEMPLATE_DIR` (a ConfigMap mount), used only
when no git URL is set. Files there override embedded ones by base name.
In all cases the embedded defaults are the base layer; the git repo / override
dir is layered on top, replacing files of the same base name and adding new ones.
## Naming
@@ -23,6 +34,41 @@ Reserved iPXE names bootapi renders directly:
- `boot` — the per-host boot script (`/ipxe/{mac}` for a known host).
- `fallback-local`, `fallback-shell` — unknown-MAC fallbacks.
## The distro catalog
`catalog/*.yaml` describes each bootable OS, so adding a distro is a YAML +
template change (and, if needed, a new artifactapi remote) — **no bootapi code
change**. One file per distro:
```yaml
name: almalinux9 # catalog key; also what provision_template matches
match:
platforms: [almalinux9] # exact NetBox platform slugs
family: almalinux # OR an OS family (matches almalinux8/9/...)
kickstart: almalinux9 # kickstart template name to render
version_default: "9" # used when the platform slug carries no version
kernel_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/vmlinuz"
initrd_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/initrd.img"
kernel_args: [inst.text, net.ifnames=0]
vars: # arbitrary templated strings -> .DistroVars.<key>
mirror: "{{.ArtifactBase}}/almalinux/{{.Version}}"
```
`kernel_url`, `initrd_url` and each `vars` value are Go templates rendered with
`{{.ArtifactBase}}` (`BOOTAPI_ARTIFACT_BASE_URL`), `{{.Version}}`, `{{.Arch}}`,
`{{.Hostname}}`, `{{.Platform}}`, `{{.OSFamily}}`. The kickstart template reads
`.DistroVars.mirror` to build its `url`/`repo` lines, so the install-tree layout
lives entirely in the catalog. bootapi derives `inst.repo` for iPXE by trimming
`/images/pxeboot/vmlinuz` off `kernel_url`.
Shipped entries: `almalinux9` (artifactapi `almalinux` remote) and `fedora`
(`fedora` remote). **debian / talos** are documented but not implemented — their
artifact shapes differ (Debian netboot `linux`+`initrd.gz` under
`dists/<rel>/main/installer-<arch>/current/images/netboot/`; Talos ships factory
`vmlinuz`+`initramfs.xz` images) so they need their own catalog fields/template
and possibly a new artifactapi remote. See the catalog README in the templates
repo for the intended path.
## Engine and functions
Standard Go `text/template`. Available funcs: `join`, `upper`, `lower`,
+2
View File
@@ -5,12 +5,14 @@ go 1.25
require (
github.com/go-chi/chi/v5 v5.3.0
github.com/prometheus/client_golang v1.23.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/kr/text v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
+10
View File
@@ -2,6 +2,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
@@ -10,6 +11,10 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -24,6 +29,8 @@ github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -32,5 +39,8 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+227
View File
@@ -0,0 +1,227 @@
// Package catalog is the distro catalog: a set of YAML descriptors (one per
// bootable OS) that map a NetBox host to its boot images, kernel args and
// kickstart template. Catalog files live in the templates git repo (or the
// embedded defaults), so adding Fedora/Debian/Talos later is a YAML + template
// change with no bootapi code change. Host -> distro selection stays
// NetBox-driven (platform slug / family / provision_template override).
package catalog
import (
"bytes"
"fmt"
"sort"
"strings"
"text/template"
"gopkg.in/yaml.v3"
"git.unkin.net/unkin/bootapi/internal/model"
)
// Distro is one catalog entry (one YAML file).
type Distro struct {
// Name is the catalog key, also what a provision_template override matches.
Name string `yaml:"name"`
// Match decides which hosts this distro applies to.
Match Match `yaml:"match"`
// Kickstart is the kickstart template name to render for this distro.
Kickstart string `yaml:"kickstart"`
// KernelURL / InitrdURL are Go-template strings rendered with Ctx (they may
// reference {{.ArtifactBase}}, {{.Version}}, {{.Arch}}).
KernelURL string `yaml:"kernel_url"`
InitrdURL string `yaml:"initrd_url"`
// KernelArgs are extra iPXE kernel arguments appended verbatim.
KernelArgs []string `yaml:"kernel_args"`
// VersionDefault is used when the host's platform slug carries no version.
VersionDefault string `yaml:"version_default"`
// Vars are arbitrary named Go-template strings (rendered with Ctx) exposed
// to kickstart/iPXE templates as .DistroVars.<key>. This is how a template
// gets e.g. the install-tree mirror base without per-distro Go code.
Vars map[string]string `yaml:"vars"`
kernelTmpl *template.Template
initrdTmpl *template.Template
varTmpls map[string]*template.Template
}
// Match selects hosts for a Distro.
type Match struct {
// Platforms are exact NetBox platform slugs, e.g. ["almalinux9"].
Platforms []string `yaml:"platforms"`
// Family is a NetBox platform family, e.g. "fedora" (matches fedora42 etc).
Family string `yaml:"family"`
}
// Ctx is the value catalog URL/var templates are rendered against.
type Ctx struct {
ArtifactBase string
Version string
Arch string
Hostname string
Platform string
OSFamily string
}
// Resolved is a Distro with its templated fields evaluated for a specific host.
type Resolved struct {
Name string
Kickstart string
KernelURL string
InitrdURL string
KernelArgs []string
Vars map[string]string
}
// Catalog is the parsed, validated set of distros.
type Catalog struct {
distros []*Distro
}
// Parse builds a Catalog from named YAML documents (filename -> contents),
// validating each and compiling its templates. It is deterministic: distros are
// sorted by name so selection is stable regardless of map iteration order.
func Parse(files map[string][]byte) (*Catalog, error) {
var distros []*Distro
names := make([]string, 0, len(files))
for f := range files {
names = append(names, f)
}
sort.Strings(names)
for _, f := range names {
d := &Distro{}
if err := yaml.Unmarshal(files[f], d); err != nil {
return nil, fmt.Errorf("catalog %s: %w", f, err)
}
if err := d.compile(); err != nil {
return nil, fmt.Errorf("catalog %s: %w", f, err)
}
distros = append(distros, d)
}
sort.Slice(distros, func(i, j int) bool { return distros[i].Name < distros[j].Name })
return &Catalog{distros: distros}, nil
}
func (d *Distro) compile() error {
if d.Name == "" {
return fmt.Errorf("missing name")
}
if d.Kickstart == "" {
return fmt.Errorf("%s: missing kickstart", d.Name)
}
if d.KernelURL == "" || d.InitrdURL == "" {
return fmt.Errorf("%s: kernel_url and initrd_url are required", d.Name)
}
if len(d.Match.Platforms) == 0 && d.Match.Family == "" {
return fmt.Errorf("%s: match needs at least one platform or a family", d.Name)
}
var err error
if d.kernelTmpl, err = template.New("kernel").Parse(d.KernelURL); err != nil {
return fmt.Errorf("%s: kernel_url: %w", d.Name, err)
}
if d.initrdTmpl, err = template.New("initrd").Parse(d.InitrdURL); err != nil {
return fmt.Errorf("%s: initrd_url: %w", d.Name, err)
}
d.varTmpls = map[string]*template.Template{}
for k, v := range d.Vars {
t, err := template.New(k).Parse(v)
if err != nil {
return fmt.Errorf("%s: var %q: %w", d.Name, k, err)
}
d.varTmpls[k] = t
}
return nil
}
// All returns the catalog's distros (sorted by name).
func (c *Catalog) All() []*Distro { return c.distros }
// Names returns the catalog distro names (sorted); handy for tests/logging.
func (c *Catalog) Names() []string {
out := make([]string, len(c.distros))
for i, d := range c.distros {
out[i] = d.Name
}
return out
}
// Select returns the distro for a host, following precedence:
// 1. provision_template override that names a distro exactly,
// 2. exact platform-slug match,
// 3. OS-family match.
//
// It reports false when nothing matches (caller falls back to legacy behavior).
func (c *Catalog) Select(h *model.Host) (*Distro, bool) {
if h.TemplateOverride != "" {
for _, d := range c.distros {
if d.Name == h.TemplateOverride {
return d, true
}
}
}
for _, d := range c.distros {
for _, p := range d.Match.Platforms {
if p == h.Platform && h.Platform != "" {
return d, true
}
}
}
for _, d := range c.distros {
if d.Match.Family != "" && d.Match.Family == h.OSFamily {
return d, true
}
}
return nil, false
}
// Resolve evaluates a distro's templated fields for a host against artifactBase.
func (d *Distro) Resolve(h *model.Host, artifactBase string) (*Resolved, error) {
version := h.OSVersion
if version == "" {
version = d.VersionDefault
}
arch := h.Arch
if arch == "" {
arch = "x86_64"
}
ctx := Ctx{
ArtifactBase: strings.TrimRight(artifactBase, "/"),
Version: version,
Arch: arch,
Hostname: h.Hostname,
Platform: h.Platform,
OSFamily: h.OSFamily,
}
kernel, err := exec(d.kernelTmpl, ctx)
if err != nil {
return nil, fmt.Errorf("%s kernel_url: %w", d.Name, err)
}
initrd, err := exec(d.initrdTmpl, ctx)
if err != nil {
return nil, fmt.Errorf("%s initrd_url: %w", d.Name, err)
}
vars := map[string]string{}
for k, t := range d.varTmpls {
v, err := exec(t, ctx)
if err != nil {
return nil, fmt.Errorf("%s var %q: %w", d.Name, k, err)
}
vars[k] = v
}
return &Resolved{
Name: d.Name,
Kickstart: d.Kickstart,
KernelURL: kernel,
InitrdURL: initrd,
KernelArgs: d.KernelArgs,
Vars: vars,
}, nil
}
func exec(t *template.Template, ctx Ctx) (string, error) {
var buf bytes.Buffer
if err := t.Execute(&buf, ctx); err != nil {
return "", err
}
return buf.String(), nil
}
+126
View File
@@ -0,0 +1,126 @@
package catalog
import (
"strings"
"testing"
"git.unkin.net/unkin/bootapi/internal/model"
)
const almaYAML = `
name: almalinux9
match:
platforms: [almalinux9]
family: almalinux
kickstart: almalinux9
version_default: "9"
kernel_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/vmlinuz"
initrd_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/initrd.img"
kernel_args: [inst.text]
vars:
mirror: "{{.ArtifactBase}}/almalinux/{{.Version}}"
`
const fedoraYAML = `
name: fedora
match:
family: fedora
kickstart: fedora
version_default: "41"
kernel_url: "{{.ArtifactBase}}/fedora/releases/{{.Version}}/Everything/{{.Arch}}/os/images/pxeboot/vmlinuz"
initrd_url: "{{.ArtifactBase}}/fedora/releases/{{.Version}}/Everything/{{.Arch}}/os/images/pxeboot/initrd.img"
`
func testCatalog(t *testing.T) *Catalog {
t.Helper()
c, err := Parse(map[string][]byte{
"almalinux9.yaml": []byte(almaYAML),
"fedora.yaml": []byte(fedoraYAML),
})
if err != nil {
t.Fatalf("Parse: %v", err)
}
return c
}
func TestSelect(t *testing.T) {
c := testCatalog(t)
cases := []struct {
host *model.Host
want string
ok bool
}{
{&model.Host{Platform: "almalinux9", OSFamily: "almalinux"}, "almalinux9", true}, // exact platform
{&model.Host{Platform: "fedora42", OSFamily: "fedora"}, "fedora", true}, // family
{&model.Host{Platform: "almalinux9", TemplateOverride: "fedora"}, "fedora", true}, // override wins
{&model.Host{Platform: "debian12", OSFamily: "debian"}, "", false}, // no match
}
for _, tc := range cases {
d, ok := c.Select(tc.host)
if ok != tc.ok {
t.Errorf("Select(%+v) ok=%v, want %v", tc.host, ok, tc.ok)
continue
}
if ok && d.Name != tc.want {
t.Errorf("Select(%+v) = %q, want %q", tc.host, d.Name, tc.want)
}
}
}
func TestResolve(t *testing.T) {
c := testCatalog(t)
h := &model.Host{Platform: "almalinux9", OSFamily: "almalinux", OSVersion: "9", Arch: "x86_64"}
d, ok := c.Select(h)
if !ok {
t.Fatal("expected a match")
}
r, err := d.Resolve(h, "https://af/api/v1/remote")
if err != nil {
t.Fatal(err)
}
if r.KernelURL != "https://af/api/v1/remote/almalinux/9/BaseOS/x86_64/os/images/pxeboot/vmlinuz" {
t.Errorf("kernel = %q", r.KernelURL)
}
if r.Vars["mirror"] != "https://af/api/v1/remote/almalinux/9" {
t.Errorf("mirror = %q", r.Vars["mirror"])
}
if len(r.KernelArgs) != 1 || r.KernelArgs[0] != "inst.text" {
t.Errorf("kernel_args = %v", r.KernelArgs)
}
}
func TestResolveVersionDefault(t *testing.T) {
c := testCatalog(t)
// Host with no OSVersion falls back to the catalog's version_default.
h := &model.Host{Platform: "fedora", OSFamily: "fedora", Arch: "x86_64"}
d, _ := c.Select(h)
r, err := d.Resolve(h, "https://af")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(r.KernelURL, "/releases/41/") {
t.Errorf("expected version_default 41 in %q", r.KernelURL)
}
}
func TestParseValidation(t *testing.T) {
bad := map[string]string{
"no-kernel": "name: x\nmatch: {platforms: [x]}\nkickstart: x\ninitrd_url: y",
"no-match": "name: x\nkickstart: x\nkernel_url: k\ninitrd_url: i",
"no-name": "kickstart: x\nmatch: {family: x}\nkernel_url: k\ninitrd_url: i",
"bad-template": "name: x\nmatch: {family: x}\nkickstart: x\nkernel_url: \"{{ .Nope\"\ninitrd_url: i",
}
for name, y := range bad {
if _, err := Parse(map[string][]byte{name + ".yaml": []byte(y)}); err == nil {
t.Errorf("%s: expected a validation error, got nil", name)
}
}
}
func TestNames(t *testing.T) {
c := testCatalog(t)
got := strings.Join(c.Names(), ",")
if got != "almalinux9,fedora" {
t.Errorf("Names() = %q", got)
}
}
+124 -57
View File
@@ -11,67 +11,99 @@ import (
// Config is the fully-resolved server configuration.
type Config struct {
// ListenAddr is the HTTP bind address, e.g. ":8000".
// ListenAddr is the plain-HTTP bind address, e.g. ":8000". The boot path
// (iPXE + kickstart) is always served here so installers with no internal
// CA trust can reach it.
ListenAddr string
// TLSListenAddr, when set with TLSCertFile/TLSKeyFile, additionally serves
// HTTPS. Boot endpoints work on both; the plain-HTTP listener is mandatory,
// HTTPS is opt-in (see docs/endpoints.md).
TLSListenAddr string
TLSCertFile string
TLSKeyFile string
// NetBoxURL is the base URL of the NetBox API,
// e.g. "https://netbox.k8s.syd1.au.unkin.net".
NetBoxURL string
// NetBoxToken is the NetBox API token. Prefer NetBoxTokenFile in k8s.
// NetBoxToken is the NetBox API token. Prefer NetBoxTokenFile in k8s. Needs
// WRITE scope on the device pxe_enabled custom field for the callback.
NetBoxToken string
// NetBoxTimeout bounds each NetBox HTTP request.
NetBoxTimeout time.Duration
// NetBoxInsecure disables TLS verification against NetBox (dev only).
NetBoxInsecure bool
// CacheTTL is how long a resolved host is cached in memory. Short by
// design: NetBox is the source of truth and a machine's provisioning data
// can change between boots.
// CacheTTL is how long a resolved host is cached in memory.
CacheTTL time.Duration
// TemplateDir, when set, is a directory of override templates layered on
// top of the embedded defaults (a Kubernetes ConfigMap mount in prod).
// top of the embedded defaults (a ConfigMap mount). Ignored when a template
// git repo is configured.
TemplateDir string
// DefaultTemplate is the kickstart template used when NetBox provides no
// platform/role/override selection key.
// DefaultTemplate is the kickstart template used when no catalog/platform
// selection key matches.
DefaultTemplate string
// BaseURL is bootapi's own externally-reachable base URL, baked into the
// iPXE script's inst.ks= and repo URLs so a booting host calls back here.
// e.g. "http://bootapi.k8s.syd1.au.unkin.net".
BaseURL string
// --- template git-sync (preferred over TemplateDir) ---
// TemplateGitURL, when set, makes bootapi clone a templates repo and re-pull
// it every TemplateGitInterval, atomically swapping the loaded set on change
// and keeping the last-good set on a parse failure.
TemplateGitURL string
TemplateGitBranch string
TemplateGitInterval time.Duration
// TemplateGitToken is an optional token for a private templates repo,
// injected into the HTTPS clone URL. Empty for a public repo.
TemplateGitToken string
// BootBaseURL is the base URL of the OS install trees (kernel/initrd +
// inst.repo), e.g. "http://mirror.k8s.syd1.au.unkin.net/almalinux".
// BaseURL is the http:// base PXE clients use to reach bootapi. It is baked
// into the iPXE inst.ks= and /ks URLs, so it MUST be reachable without CA
// trust (plain HTTP). e.g. "http://bootapi.k8s.syd1.au.unkin.net".
BaseURL string
// CallbackBaseURL is the base the end-of-kickstart callback uses. Defaults
// to BaseURL (plain HTTP, works before the internal CA is installed). Set to
// an https:// URL only if the kickstart installs the internal CA before the
// callback runs.
CallbackBaseURL string
// ArtifactBaseURL is the artifactapi remote base the distro catalog builds
// kernel/initrd URLs from,
// e.g. "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote".
ArtifactBaseURL string
// BootBaseURL is a legacy fallback OS-tree base used only when no catalog
// entry matches a host. Normally empty (the catalog drives boot images).
BootBaseURL string
// ProvisionToken guards POST /provisioned. Empty disables the callback
// endpoint (fail closed). Prefer ProvisionTokenFile in k8s.
ProvisionToken string
// PuppetServer / PuppetCAServer are baked into kickstart %post so the
// freshly-installed host checks in to the right place.
// freshly-installed host checks in to the k8s puppetserver.
PuppetServer string
PuppetCAServer string
// PuppetCAURL is written to the puppet-initial EnvironmentFile as
// PUPPETCA_URL (consumed by that RPM's systemd bootstrap unit).
PuppetCAURL string
// Domain is the default DNS domain applied when NetBox does not record one
// for a device.
// Domain is the default DNS domain applied when NetBox records none.
Domain string
// Nameservers is the default resolver list applied when NetBox records
// none for a device.
// Nameservers is the default resolver list applied when NetBox records none.
Nameservers []string
// RootPasswordHash is a crypt(3) hash injected into kickstarts at render
// time (sourced from Vault in k8s). Empty locks the root account.
// time (Vault in k8s). Empty locks the root account.
RootPasswordHash string
// SSHAuthorizedKeys are public keys installed for root at render time.
SSHAuthorizedKeys []string
// UnknownMACFallback selects what the iPXE endpoint returns for a MAC that
// NetBox does not know: "local" (chain to local disk, the safe default) or
// "shell" (drop to an iPXE shell for debugging). See docs/endpoints.md.
// UnknownMACFallback selects the iPXE script for an unknown MAC: "local"
// (boot local disk, safe default) or "shell" (iPXE shell for debugging).
UnknownMACFallback string
}
// Load reads configuration from the environment, applying defaults, and reads a
// token file when BOOTAPI_NETBOX_TOKEN_FILE is set (Vault-mounted secret).
// Load reads configuration from the environment, applying defaults. *_FILE
// variants (Vault-mounted secrets) win over their inline counterparts.
func Load() (*Config, error) {
cacheTTL, err := time.ParseDuration(getenv("BOOTAPI_CACHE_TTL", "30s"))
if err != nil {
@@ -81,14 +113,22 @@ func Load() (*Config, error) {
if err != nil {
return nil, fmt.Errorf("invalid BOOTAPI_NETBOX_TIMEOUT: %w", err)
}
gitInterval, err := time.ParseDuration(getenv("BOOTAPI_TEMPLATE_GIT_INTERVAL", "3m"))
if err != nil {
return nil, fmt.Errorf("invalid BOOTAPI_TEMPLATE_GIT_INTERVAL: %w", err)
}
token := os.Getenv("BOOTAPI_NETBOX_TOKEN")
if tf := os.Getenv("BOOTAPI_NETBOX_TOKEN_FILE"); tf != "" {
b, err := os.ReadFile(tf)
if err != nil {
return nil, fmt.Errorf("read BOOTAPI_NETBOX_TOKEN_FILE %q: %w", tf, err)
}
token = strings.TrimSpace(string(b))
token, err := readSecret("BOOTAPI_NETBOX_TOKEN")
if err != nil {
return nil, err
}
rootHash, err := readSecret("BOOTAPI_ROOT_PASSWORD_HASH")
if err != nil {
return nil, err
}
provToken, err := readSecret("BOOTAPI_PROVISION_TOKEN")
if err != nil {
return nil, err
}
fallback := getenv("BOOTAPI_UNKNOWN_MAC_FALLBACK", "local")
@@ -96,36 +136,63 @@ func Load() (*Config, error) {
return nil, fmt.Errorf("invalid BOOTAPI_UNKNOWN_MAC_FALLBACK %q: want \"local\" or \"shell\"", fallback)
}
rootHash := os.Getenv("BOOTAPI_ROOT_PASSWORD_HASH")
if rf := os.Getenv("BOOTAPI_ROOT_PASSWORD_HASH_FILE"); rf != "" {
b, err := os.ReadFile(rf)
if err != nil {
return nil, fmt.Errorf("read BOOTAPI_ROOT_PASSWORD_HASH_FILE %q: %w", rf, err)
}
rootHash = strings.TrimSpace(string(b))
baseURL := strings.TrimRight(os.Getenv("BOOTAPI_BASE_URL"), "/")
callbackBase := strings.TrimRight(os.Getenv("BOOTAPI_CALLBACK_BASE_URL"), "/")
if callbackBase == "" {
callbackBase = baseURL
}
ns := splitList(os.Getenv("BOOTAPI_NAMESERVERS"))
if len(ns) == 0 {
ns = []string{"198.18.200.7"} // k8s bind-resolvers LB
}
return &Config{
ListenAddr: getenv("BOOTAPI_LISTEN_ADDR", ":8000"),
NetBoxURL: strings.TrimRight(os.Getenv("BOOTAPI_NETBOX_URL"), "/"),
NetBoxToken: token,
NetBoxTimeout: nbTimeout,
NetBoxInsecure: getenv("BOOTAPI_NETBOX_INSECURE", "false") == "true",
CacheTTL: cacheTTL,
TemplateDir: os.Getenv("BOOTAPI_TEMPLATE_DIR"),
DefaultTemplate: getenv("BOOTAPI_DEFAULT_TEMPLATE", "almalinux9"),
BaseURL: strings.TrimRight(os.Getenv("BOOTAPI_BASE_URL"), "/"),
BootBaseURL: strings.TrimRight(os.Getenv("BOOTAPI_BOOT_BASE_URL"), "/"),
PuppetServer: getenv("BOOTAPI_PUPPET_SERVER", "puppet.query.consul"),
PuppetCAServer: getenv("BOOTAPI_PUPPET_CA_SERVER", "puppetca.query.consul"),
Domain: getenv("BOOTAPI_DOMAIN", "main.unkin.net"),
Nameservers: splitList(os.Getenv("BOOTAPI_NAMESERVERS")),
RootPasswordHash: rootHash,
SSHAuthorizedKeys: splitLines(os.Getenv("BOOTAPI_SSH_AUTHORIZED_KEYS")),
UnknownMACFallback: fallback,
ListenAddr: getenv("BOOTAPI_LISTEN_ADDR", ":8000"),
TLSListenAddr: getenv("BOOTAPI_TLS_LISTEN_ADDR", ""),
TLSCertFile: os.Getenv("BOOTAPI_TLS_CERT_FILE"),
TLSKeyFile: os.Getenv("BOOTAPI_TLS_KEY_FILE"),
NetBoxURL: strings.TrimRight(os.Getenv("BOOTAPI_NETBOX_URL"), "/"),
NetBoxToken: token,
NetBoxTimeout: nbTimeout,
NetBoxInsecure: getenv("BOOTAPI_NETBOX_INSECURE", "false") == "true",
CacheTTL: cacheTTL,
TemplateDir: os.Getenv("BOOTAPI_TEMPLATE_DIR"),
DefaultTemplate: getenv("BOOTAPI_DEFAULT_TEMPLATE", "almalinux9"),
TemplateGitURL: strings.TrimRight(os.Getenv("BOOTAPI_TEMPLATE_GIT_URL"), "/"),
TemplateGitBranch: getenv("BOOTAPI_TEMPLATE_GIT_BRANCH", "main"),
TemplateGitInterval: gitInterval,
TemplateGitToken: os.Getenv("BOOTAPI_TEMPLATE_GIT_TOKEN"),
BaseURL: baseURL,
CallbackBaseURL: callbackBase,
ArtifactBaseURL: strings.TrimRight(getenv("BOOTAPI_ARTIFACT_BASE_URL", "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote"), "/"),
BootBaseURL: strings.TrimRight(os.Getenv("BOOTAPI_BOOT_BASE_URL"), "/"),
ProvisionToken: provToken,
PuppetServer: getenv("BOOTAPI_PUPPET_SERVER", "puppet.k8s.syd1.au.unkin.net"),
PuppetCAServer: getenv("BOOTAPI_PUPPET_CA_SERVER", "puppetca.k8s.syd1.au.unkin.net"),
PuppetCAURL: getenv("BOOTAPI_PUPPET_CA_URL", "puppetca.k8s.syd1.au.unkin.net"),
Domain: getenv("BOOTAPI_DOMAIN", "main.unkin.net"),
Nameservers: ns,
RootPasswordHash: rootHash,
SSHAuthorizedKeys: splitLines(os.Getenv("BOOTAPI_SSH_AUTHORIZED_KEYS")),
UnknownMACFallback: fallback,
}, nil
}
// readSecret returns the value of env key, or the trimmed contents of the file
// named by key+"_FILE" when that is set (the file wins).
func readSecret(key string) (string, error) {
v := os.Getenv(key)
if f := os.Getenv(key + "_FILE"); f != "" {
b, err := os.ReadFile(f)
if err != nil {
return "", fmt.Errorf("read %s_FILE %q: %w", key, f, err)
}
v = strings.TrimSpace(string(b))
}
return v, nil
}
func getenv(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
+42 -1
View File
@@ -22,12 +22,53 @@ func TestLoadDefaults(t *testing.T) {
if c.DefaultTemplate != "almalinux9" {
t.Errorf("DefaultTemplate = %q", c.DefaultTemplate)
}
if c.PuppetServer != "puppet.query.consul" || c.PuppetCAServer != "puppetca.query.consul" {
if c.PuppetServer != "puppet.k8s.syd1.au.unkin.net" || c.PuppetCAServer != "puppetca.k8s.syd1.au.unkin.net" {
t.Errorf("puppet servers = %q / %q", c.PuppetServer, c.PuppetCAServer)
}
if c.PuppetCAURL != "puppetca.k8s.syd1.au.unkin.net" {
t.Errorf("PuppetCAURL = %q", c.PuppetCAURL)
}
if c.UnknownMACFallback != "local" {
t.Errorf("UnknownMACFallback = %q", c.UnknownMACFallback)
}
if len(c.Nameservers) != 1 || c.Nameservers[0] != "198.18.200.7" {
t.Errorf("default nameservers = %v, want [198.18.200.7]", c.Nameservers)
}
if c.TemplateGitInterval != 3*time.Minute {
t.Errorf("TemplateGitInterval = %v, want 3m", c.TemplateGitInterval)
}
if c.ArtifactBaseURL != "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote" {
t.Errorf("ArtifactBaseURL = %q", c.ArtifactBaseURL)
}
}
func TestCallbackBaseDefaultsToBase(t *testing.T) {
clearEnv(t)
t.Setenv("BOOTAPI_BASE_URL", "http://bootapi.example.net/")
c, err := Load()
if err != nil {
t.Fatal(err)
}
if c.BaseURL != "http://bootapi.example.net" || c.CallbackBaseURL != "http://bootapi.example.net" {
t.Errorf("base=%q callback=%q; callback should default to base", c.BaseURL, c.CallbackBaseURL)
}
}
func TestProvisionTokenFile(t *testing.T) {
clearEnv(t)
dir := t.TempDir()
tf := filepath.Join(dir, "tok")
if err := os.WriteFile(tf, []byte(" prov-secret\n"), 0o600); err != nil {
t.Fatal(err)
}
t.Setenv("BOOTAPI_PROVISION_TOKEN_FILE", tf)
c, err := Load()
if err != nil {
t.Fatal(err)
}
if c.ProvisionToken != "prov-secret" {
t.Errorf("ProvisionToken = %q", c.ProvisionToken)
}
}
func TestLoadTokenFile(t *testing.T) {
+178
View File
@@ -0,0 +1,178 @@
// Package gitsync keeps bootapi's template Set in step with a git repo. It
// clones the templates repo at startup and re-pulls it every interval (default
// 3m, like argocd), atomically swapping the Engine's active Set when the repo
// changes. A parse failure keeps the last-good Set and is only logged/counted,
// so a bad template push can never take bootapi down. The embedded defaults
// remain the fallback when git is unreachable at startup.
package gitsync
import (
"context"
"fmt"
"io/fs"
"log/slog"
"os"
"os/exec"
"strings"
"sync/atomic"
"time"
"git.unkin.net/unkin/bootapi/internal/render"
)
// Options configures the syncer.
type Options struct {
URL string
Branch string
Token string // optional; injected into the HTTPS URL for a private repo
Interval time.Duration
WorkDir string // local checkout path
}
// Syncer pulls a templates repo and reloads an Engine on change.
type Syncer struct {
opt Options
embedded fs.FS
engine *render.Engine
syncs atomic.Int64 // successful reloads (Set swapped)
failures atomic.Int64 // pull or parse failures (last-good kept)
generation atomic.Int64 // increments on every successful swap
}
// New builds a Syncer. embedded is the fallback template FS. Call SetEngine
// before Run so reloads have an Engine to swap into (the Engine needs the
// initial Set from Bootstrap first, hence the two-step wiring).
func New(opt Options, embedded fs.FS) *Syncer {
if opt.Branch == "" {
opt.Branch = "main"
}
if opt.Interval <= 0 {
opt.Interval = 3 * time.Minute
}
return &Syncer{opt: opt, embedded: embedded}
}
// SetEngine points the syncer at the live Engine whose Set it swaps on reload.
func (s *Syncer) SetEngine(e *render.Engine) { s.engine = e }
// Syncs/Failures/Generation are exported for the server's metrics collector.
func (s *Syncer) Syncs() int64 { return s.syncs.Load() }
func (s *Syncer) Failures() int64 { return s.failures.Load() }
func (s *Syncer) Generation() int64 { return s.generation.Load() }
// Bootstrap clones the repo and builds the initial Set from embedded + the
// checkout. On any git/parse failure it returns an embedded-only Set plus a
// non-nil error (which the caller logs but treats as non-fatal, so bootapi
// always starts with at least the embedded defaults).
func (s *Syncer) Bootstrap(ctx context.Context) (*render.Set, error) {
if err := s.clone(ctx); err != nil {
set, berr := render.BuildSet(s.embedded, nil)
if berr != nil {
return nil, berr // embedded defaults broken: genuinely fatal
}
return set, fmt.Errorf("git clone failed, using embedded defaults: %w", err)
}
set, err := render.BuildSet(s.embedded, os.DirFS(s.opt.WorkDir))
if err != nil {
emb, berr := render.BuildSet(s.embedded, nil)
if berr != nil {
return nil, berr
}
return emb, fmt.Errorf("git templates failed to parse, using embedded defaults: %w", err)
}
s.generation.Add(1)
return set, nil
}
// Run polls the repo every interval until ctx is cancelled.
func (s *Syncer) Run(ctx context.Context) {
t := time.NewTicker(s.opt.Interval)
defer t.Stop()
slog.Info("template git-sync started", "url", s.opt.URL, "branch", s.opt.Branch, "interval", s.opt.Interval)
for {
select {
case <-ctx.Done():
return
case <-t.C:
s.pollOnce(ctx)
}
}
}
func (s *Syncer) pollOnce(ctx context.Context) {
changed, head, err := s.pull(ctx)
if err != nil {
s.failures.Add(1)
slog.Error("template git pull failed; keeping last-good set", "err", err)
return
}
if !changed {
return
}
set, err := render.BuildSet(s.embedded, os.DirFS(s.opt.WorkDir))
if err != nil {
s.failures.Add(1)
slog.Error("template reload failed to parse; keeping last-good set", "commit", head, "err", err)
return
}
s.engine.Swap(set)
s.syncs.Add(1)
s.generation.Add(1)
slog.Info("templates reloaded from git", "commit", head, "generation", s.generation.Load())
}
// authURL injects a token into the HTTPS clone URL when configured.
func (s *Syncer) authURL() string {
if s.opt.Token == "" {
return s.opt.URL
}
if rest, ok := strings.CutPrefix(s.opt.URL, "https://"); ok {
return "https://" + s.opt.Token + "@" + rest
}
return s.opt.URL
}
func (s *Syncer) clone(ctx context.Context) error {
if err := os.RemoveAll(s.opt.WorkDir); err != nil {
return err
}
return run(ctx, "", "git", "clone", "--depth", "1", "--branch", s.opt.Branch, s.authURL(), s.opt.WorkDir)
}
// pull fetches origin/branch and hard-resets to it, reporting whether HEAD moved.
func (s *Syncer) pull(ctx context.Context) (changed bool, head string, err error) {
old, _ := s.head(ctx)
if err := run(ctx, s.opt.WorkDir, "git", "fetch", "--depth", "1", "origin", s.opt.Branch); err != nil {
return false, "", err
}
if err := run(ctx, s.opt.WorkDir, "git", "reset", "--hard", "origin/"+s.opt.Branch); err != nil {
return false, "", err
}
newHead, err := s.head(ctx)
if err != nil {
return false, "", err
}
return old != newHead, newHead, nil
}
func (s *Syncer) head(ctx context.Context) (string, error) {
out, err := output(ctx, s.opt.WorkDir, "git", "rev-parse", "HEAD")
return strings.TrimSpace(out), err
}
func run(ctx context.Context, dir, name string, args ...string) error {
cmd := exec.CommandContext(ctx, name, args...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("%s %s: %w: %s", name, strings.Join(args, " "), err, strings.TrimSpace(string(out)))
}
return nil
}
func output(ctx context.Context, dir, name string, args ...string) (string, error) {
cmd := exec.CommandContext(ctx, name, args...)
cmd.Dir = dir
out, err := cmd.Output()
return string(out), err
}
+142
View File
@@ -0,0 +1,142 @@
package gitsync
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"git.unkin.net/unkin/bootapi/internal/model"
"git.unkin.net/unkin/bootapi/internal/render"
"git.unkin.net/unkin/bootapi/templates"
)
// gitRepo creates a real git repo at dir with an initial almalinux9 override.
func gitRepo(t *testing.T, dir string) {
t.Helper()
gitCmd(t, "", "git", "init", "-b", "main", dir)
gitCmd(t, dir, "git", "config", "user.email", "t@example.net")
gitCmd(t, dir, "git", "config", "user.name", "test")
writeKS(t, dir, "GITSYNC-V1 {{ .Hostname }}\n")
gitCmd(t, dir, "git", "add", "-A")
gitCmd(t, dir, "git", "commit", "-m", "v1")
}
func writeKS(t *testing.T, dir, body string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, "almalinux9.ks.tmpl"), []byte(body), 0o600); err != nil {
t.Fatal(err)
}
}
func gitCmd(t *testing.T, dir, name string, args ...string) {
t.Helper()
cmd := exec.Command(name, args...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%s %v: %v: %s", name, args, err, out)
}
}
func renderKS(t *testing.T, e *render.Engine) string {
t.Helper()
h := &model.Host{Hostname: "web01", Platform: "almalinux9", OSFamily: "almalinux", OSVersion: "9", Arch: "x86_64"}
out, _, err := e.RenderKickstart(h)
if err != nil {
t.Fatalf("RenderKickstart: %v", err)
}
return string(out)
}
func TestBootstrapAndReload(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not available")
}
src := t.TempDir()
gitRepo(t, src)
s := New(Options{URL: src, Branch: "main", Interval: time.Hour, WorkDir: filepath.Join(t.TempDir(), "co")}, templates.FS)
set, err := s.Bootstrap(context.Background())
if err != nil {
t.Fatalf("Bootstrap: %v", err)
}
eng := render.NewEngine(render.RenderConfig{DefaultTemplate: "almalinux9", ArtifactBase: "https://af"}, set)
s.SetEngine(eng)
if got := renderKS(t, eng); !contains(got, "GITSYNC-V1 web01") {
t.Fatalf("initial render missing v1 override:\n%s", got)
}
gen1 := s.Generation()
// Commit v2 upstream, then poll: the engine must swap to the new content.
writeKS(t, src, "GITSYNC-V2 {{ .Hostname }}\n")
gitCmd(t, src, "git", "add", "-A")
gitCmd(t, src, "git", "commit", "-m", "v2")
s.pollOnce(context.Background())
if got := renderKS(t, eng); !contains(got, "GITSYNC-V2 web01") {
t.Fatalf("after reload, render missing v2:\n%s", got)
}
if s.Generation() <= gen1 {
t.Errorf("generation did not advance: %d <= %d", s.Generation(), gen1)
}
if s.Syncs() != 1 {
t.Errorf("syncs = %d, want 1", s.Syncs())
}
}
func TestReloadKeepsLastGoodOnParseError(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not available")
}
src := t.TempDir()
gitRepo(t, src)
s := New(Options{URL: src, Branch: "main", Interval: time.Hour, WorkDir: filepath.Join(t.TempDir(), "co")}, templates.FS)
set, err := s.Bootstrap(context.Background())
if err != nil {
t.Fatal(err)
}
eng := render.NewEngine(render.RenderConfig{DefaultTemplate: "almalinux9", ArtifactBase: "https://af"}, set)
s.SetEngine(eng)
// Push a template that fails to parse.
writeKS(t, src, "BROKEN {{ .Hostname \n")
gitCmd(t, src, "git", "add", "-A")
gitCmd(t, src, "git", "commit", "-m", "broken")
s.pollOnce(context.Background())
// The last-good v1 set must still be served, and a failure recorded.
if got := renderKS(t, eng); !contains(got, "GITSYNC-V1 web01") {
t.Fatalf("last-good not kept after parse failure:\n%s", got)
}
if s.Failures() != 1 {
t.Errorf("failures = %d, want 1", s.Failures())
}
if s.Syncs() != 0 {
t.Errorf("syncs = %d, want 0 (bad push must not count as a sync)", s.Syncs())
}
}
func TestBootstrapDegradesToEmbedded(t *testing.T) {
// A bogus URL must not fail startup: Bootstrap returns the embedded set.
s := New(Options{URL: "/nonexistent/repo", Branch: "main", Interval: time.Hour, WorkDir: filepath.Join(t.TempDir(), "co")}, templates.FS)
set, err := s.Bootstrap(context.Background())
if err == nil {
t.Error("expected a non-nil (non-fatal) error describing the degrade")
}
if set == nil {
t.Fatal("expected the embedded fallback Set, got nil")
}
eng := render.NewEngine(render.RenderConfig{DefaultTemplate: "almalinux9", ArtifactBase: "https://af"}, set)
// Embedded almalinux9 template still renders.
if got := renderKS(t, eng); !contains(got, "rootpw") {
t.Errorf("embedded fallback did not render a real kickstart:\n%s", got)
}
}
func contains(s, sub string) bool { return strings.Contains(s, sub) }
+19
View File
@@ -10,6 +10,10 @@ package model
// zero value of a field means "NetBox did not provide it"; templates should
// guard optional fields (e.g. Gateway) accordingly.
type Host struct {
// DeviceID is the NetBox device id, used by the provisioned-callback to
// PATCH the pxe_enabled custom field.
DeviceID int
// Hostname is the short name (NetBox device name), e.g. "web01".
Hostname string
// Domain is the DNS domain the host lives in, e.g. "syd1.au.unkin.net".
@@ -57,12 +61,27 @@ type Host struct {
// bypassing platform/role selection. Sourced from a NetBox custom field.
TemplateOverride string
// PXEEnabled gates network install for this host, mirroring Cobbler's
// netboot_enabled. When false, bootapi serves the safe local-boot script
// from /ipxe even for a KNOWN host, so a provisioned machine does not
// re-install on its next PXE. nil means the NetBox custom field is unset,
// which is treated as ENABLED (a host without the field still installs).
// The end-of-kickstart callback (POST /provisioned) flips this to false.
PXEEnabled *bool
// Custom carries every NetBox custom field verbatim so templates can read
// site-specific knobs without a code change. Keys are the custom-field
// names as defined in NetBox.
Custom map[string]any
}
// ShouldPXEInstall reports whether bootapi should serve an installer boot script
// for this host. Unset (nil) is treated as enabled so hosts predating the
// custom field still provision.
func (h *Host) ShouldPXEInstall() bool {
return h.PXEEnabled == nil || *h.PXEEnabled
}
// Interface is one network interface of a Host.
type Interface struct {
// Name is the NetBox interface name, e.g. "eth0" / "bond0".
+15 -2
View File
@@ -15,7 +15,7 @@ import (
// while keeping the data fresh enough that a re-provisioned host picks up
// changes on its next boot.
type Cache struct {
inner Resolver
inner API
ttl time.Duration
now func() time.Time // injectable for tests
@@ -38,7 +38,7 @@ type cacheEntry struct {
}
// NewCache wraps inner with a TTL cache. A non-positive ttl disables caching.
func NewCache(inner Resolver, ttl time.Duration) *Cache {
func NewCache(inner API, ttl time.Duration) *Cache {
return &Cache{
inner: inner,
ttl: ttl,
@@ -47,6 +47,19 @@ func NewCache(inner Resolver, ttl time.Duration) *Cache {
}
}
// SetPXEEnabled writes through to NetBox and drops the whole cache, so the next
// /ipxe lookup reflects the flipped gate immediately rather than serving a
// stale "enabled" host for up to the TTL.
func (c *Cache) SetPXEEnabled(ctx context.Context, deviceID int, enabled bool) error {
if err := c.inner.SetPXEEnabled(ctx, deviceID, enabled); err != nil {
return err
}
c.mu.Lock()
clear(c.entries)
c.mu.Unlock()
return nil
}
// HostByMAC returns a cached host or resolves and caches one.
func (c *Cache) HostByMAC(ctx context.Context, mac string) (*model.Host, error) {
return c.lookup(ctx, "mac:"+normalizeMAC(mac), func() (*model.Host, error) {
+34 -4
View File
@@ -12,10 +12,11 @@ import (
// countingResolver records how many times the underlying resolver is hit.
type countingResolver struct {
mu sync.Mutex
calls int
host *model.Host
err error
mu sync.Mutex
calls int
writes int
host *model.Host
err error
}
func (c *countingResolver) HostByMAC(context.Context, string) (*model.Host, error) {
@@ -27,6 +28,12 @@ func (c *countingResolver) HostByMAC(context.Context, string) (*model.Host, erro
func (c *countingResolver) HostByName(context.Context, string) (*model.Host, error) {
return c.HostByMAC(context.Background(), "")
}
func (c *countingResolver) SetPXEEnabled(context.Context, int, bool) error {
c.mu.Lock()
defer c.mu.Unlock()
c.writes++
return c.err
}
func TestCacheHitAndExpiry(t *testing.T) {
inner := &countingResolver{host: &model.Host{Hostname: "web01"}}
@@ -60,6 +67,29 @@ func TestCacheHitAndExpiry(t *testing.T) {
}
}
func TestCacheInvalidatedOnWrite(t *testing.T) {
inner := &countingResolver{host: &model.Host{Hostname: "web01"}}
cache := NewCache(inner, time.Minute)
// Warm the cache.
if _, err := cache.HostByMAC(context.Background(), "aa:bb:cc:00:11:22"); err != nil {
t.Fatal(err)
}
// A write must drop the cache so the next read re-resolves.
if err := cache.SetPXEEnabled(context.Background(), 12, false); err != nil {
t.Fatal(err)
}
if inner.writes != 1 {
t.Fatalf("inner writes = %d, want 1", inner.writes)
}
if _, err := cache.HostByMAC(context.Background(), "aa:bb:cc:00:11:22"); err != nil {
t.Fatal(err)
}
if inner.calls != 2 {
t.Fatalf("inner calls = %d, want 2 (cache dropped by write)", inner.calls)
}
}
func TestCacheDisabled(t *testing.T) {
inner := &countingResolver{host: &model.Host{Hostname: "web01"}}
cache := NewCache(inner, 0) // ttl <= 0 disables caching
+71
View File
@@ -5,6 +5,7 @@
package netbox
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
@@ -29,6 +30,19 @@ type Resolver interface {
HostByName(ctx context.Context, name string) (*model.Host, error)
}
// Writer mutates NetBox. Today it only flips the pxe_enabled gate (the
// end-of-kickstart callback). Kept separate from Resolver so read-only callers
// need not depend on write scope.
type Writer interface {
SetPXEEnabled(ctx context.Context, deviceID int, enabled bool) error
}
// API is the full NetBox surface bootapi uses (reads + the pxe_enabled write).
type API interface {
Resolver
Writer
}
// Client is the HTTP-backed Resolver.
type Client struct {
baseURL string
@@ -199,11 +213,13 @@ func buildHost(dev *nbDevice, ifaces []nbInterface, ips []nbIPAddress) *model.Ho
domain := cfString(cf, "domain")
h := &model.Host{
DeviceID: dev.ID,
Hostname: dev.Name,
Domain: domain,
Custom: cf,
Nameservers: cfStringList(cf, "nameservers"),
TemplateOverride: cfString(cf, "provision_template"),
PXEEnabled: cfBool(cf, "pxe_enabled"),
Arch: "x86_64",
}
if dev.Platform != nil {
@@ -278,6 +294,39 @@ func sortPrimaryFirst(ifaces []model.Interface) {
}
}
// SetPXEEnabled PATCHes the device's pxe_enabled custom field. This is the only
// write bootapi performs; the NetBox token therefore needs write scope on the
// device custom field (see docs/security.md).
func (c *Client) SetPXEEnabled(ctx context.Context, deviceID int, enabled bool) error {
body := map[string]any{"custom_fields": map[string]any{"pxe_enabled": enabled}}
b, err := json.Marshal(body)
if err != nil {
return err
}
path := fmt.Sprintf("/api/dcim/devices/%d/", deviceID)
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, c.baseURL+path, bytes.NewReader(b))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if c.token != "" {
req.Header.Set("Authorization", "Token "+c.token)
}
resp, err := c.http.Do(req)
if err != nil {
return fmt.Errorf("netbox patch device %d: %w", deviceID, err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode == http.StatusNotFound {
return ErrNotFound
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("netbox patch device %d: HTTP %d", deviceID, resp.StatusCode)
}
return nil
}
// get performs a GET against the NetBox API and decodes the JSON body into out.
func (c *Client) get(ctx context.Context, path string, q url.Values, out any) error {
u := c.baseURL + path
@@ -393,6 +442,28 @@ func cfString(cf map[string]any, key string) string {
return ""
}
// cfBool reads a boolean custom field. Returns nil when the field is absent or
// null so callers can distinguish "unset" from "false".
func cfBool(cf map[string]any, key string) *bool {
if cf == nil {
return nil
}
switch v := cf[key].(type) {
case bool:
return &v
case string: // tolerate "true"/"false" string encodings
switch strings.ToLower(v) {
case "true", "1", "yes":
b := true
return &b
case "false", "0", "no":
b := false
return &b
}
}
return nil
}
func cfStringList(cf map[string]any, key string) []string {
if cf == nil {
return nil
+55 -2
View File
@@ -6,9 +6,13 @@ import (
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
)
// patchedDevice records whether the fake NetBox saw a PATCH on device 12.
var patchedDevice atomic.Bool
// fakeNetBox serves canned NetBox v4.x JSON for the endpoints bootapi calls.
// The payloads are trimmed but structurally faithful to real API responses.
func fakeNetBox(t *testing.T) *httptest.Server {
@@ -38,8 +42,13 @@ func fakeNetBox(t *testing.T) *httptest.Server {
}
})
// Device detail.
// Device detail (GET) + pxe_enabled write (PATCH).
mux.HandleFunc("/api/dcim/devices/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPatch && strings.HasSuffix(r.URL.Path, "/12/") {
patchedDevice.Store(true)
writeJSON(w, `{"id":12,"name":"web01"}`)
return
}
if strings.HasSuffix(r.URL.Path, "/12/") {
writeJSON(w, `{
"id":12,"name":"web01",
@@ -47,7 +56,7 @@ func fakeNetBox(t *testing.T) *httptest.Server {
"role":{"id":2,"name":"K8s Worker","slug":"kubernetes-worker"},
"site":{"slug":"syd1"},
"primary_ip":{"address":"10.0.1.20/24"},
"custom_fields":{"domain":"syd1.au.unkin.net","gateway":"10.0.1.254","nameservers":"10.0.0.1,10.0.0.2","provision_template":null}}`)
"custom_fields":{"domain":"syd1.au.unkin.net","gateway":"10.0.1.254","nameservers":"10.0.0.1,10.0.0.2","provision_template":null,"pxe_enabled":true}}`)
return
}
// name= query (HostByName)
@@ -102,6 +111,12 @@ func TestHostByMAC(t *testing.T) {
if h.PrimaryIP != "10.0.1.20" {
t.Errorf("primaryIP = %q", h.PrimaryIP)
}
if h.DeviceID != 12 {
t.Errorf("deviceID = %d, want 12", h.DeviceID)
}
if h.PXEEnabled == nil || !*h.PXEEnabled || !h.ShouldPXEInstall() {
t.Errorf("pxe_enabled = %v, want true", h.PXEEnabled)
}
if len(h.Nameservers) != 2 || h.Nameservers[0] != "10.0.0.1" {
t.Errorf("nameservers = %v", h.Nameservers)
}
@@ -181,6 +196,44 @@ func TestAuthTokenRequired(t *testing.T) {
}
}
func TestSetPXEEnabled(t *testing.T) {
srv := fakeNetBox(t)
defer srv.Close()
patchedDevice.Store(false)
c := newTestClient(t, srv.URL)
if err := c.SetPXEEnabled(context.Background(), 12, false); err != nil {
t.Fatalf("SetPXEEnabled: %v", err)
}
if !patchedDevice.Load() {
t.Error("expected a PATCH to device 12, got none")
}
}
func TestCfBool(t *testing.T) {
tr := true
cases := []struct {
cf map[string]any
want *bool
}{
{map[string]any{"pxe_enabled": true}, &tr},
{map[string]any{"pxe_enabled": "false"}, boolp(false)},
{map[string]any{"pxe_enabled": nil}, nil},
{map[string]any{}, nil},
}
for _, c := range cases {
got := cfBool(c.cf, "pxe_enabled")
switch {
case got == nil && c.want == nil:
case got != nil && c.want != nil && *got == *c.want:
default:
t.Errorf("cfBool(%v) = %v, want %v", c.cf, got, c.want)
}
}
}
func boolp(b bool) *bool { return &b }
func TestNormalizeMAC(t *testing.T) {
cases := map[string]string{
"AA:BB:CC:00:11:22": "aa:bb:cc:00:11:22",
+214 -57
View File
@@ -1,18 +1,20 @@
// Package render turns a resolved model.Host into a kickstart file or an iPXE
// boot script using Go text/template. Templates come from an embedded default
// set (ported from Cobbler's kickstarts) optionally layered with an override
// directory (a Kubernetes ConfigMap mount in production).
// set, optionally overlaid with an override source (a ConfigMap directory or a
// git-synced templates repo). The active template Set is swappable at runtime so
// the git-sync loop can atomically reload without dropping requests.
package render
import (
"bytes"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"sync/atomic"
"text/template"
"git.unkin.net/unkin/bootapi/internal/catalog"
"git.unkin.net/unkin/bootapi/internal/model"
)
@@ -43,9 +45,17 @@ type Data struct {
// --- infra pointers (render-time config) ---
PuppetServer string
PuppetCAServer string
BaseURL string // bootapi's own base URL
BootBaseURL string // OS install-tree base URL
PuppetCAURL string // written to the puppet-initial PUPPETCA_URL env file
BaseURL string // bootapi's own (http) base URL
BootBaseURL string // legacy OS install-tree base (empty when catalog-driven)
KickstartURL string // absolute URL a booting host fetches its KS from
CallbackURL string // absolute URL the %post posts to when install finishes
ProvisionToken string // bearer token for the callback (embedded in the KS)
// --- distro catalog ---
// DistroVars are the selected catalog entry's evaluated vars (e.g. mirror
// base). Empty when no catalog entry matched.
DistroVars map[string]string
// --- escape hatch: every NetBox custom field, verbatim ---
Custom map[string]any
@@ -55,8 +65,12 @@ type Data struct {
type RenderConfig struct {
PuppetServer string
PuppetCAServer string
PuppetCAURL string
BaseURL string
CallbackBaseURL string
ArtifactBase string
BootBaseURL string
ProvisionToken string
DefaultDomain string
DefaultNS []string
RootPasswordHash string
@@ -64,50 +78,55 @@ type RenderConfig struct {
DefaultTemplate string
}
// Engine holds parsed templates and render-time defaults.
type Engine struct {
ks *template.Template // kickstart templates, named "<key>"
ipxe *template.Template // ipxe templates, named "<key>"
cfg RenderConfig
ksSet map[string]bool // which kickstart template names exist
}
const (
ksExt = ".ks.tmpl"
ipxeExt = ".ipxe.tmpl"
)
// NewEngine parses the embedded defaults, then overlays overrideDir when
// non-empty (files there win over embedded ones of the same name).
func NewEngine(embedded fs.FS, overrideDir string, cfg RenderConfig) (*Engine, error) {
// Set is an immutable, parsed collection of templates + the distro catalog.
type Set struct {
ks *template.Template
ipxe *template.Template
ksSet map[string]bool
cat *catalog.Catalog
}
// BuildSet parses the embedded default sources, then overlays override (a
// directory or git working tree) when non-nil, with override files winning by
// base name. It parses *.ks.tmpl, *.ipxe.tmpl and catalog/*.yaml.
func BuildSet(embedded fs.FS, override fs.FS) (*Set, error) {
funcs := funcMap()
ks := template.New("kickstart").Funcs(funcs)
ipxe := template.New("ipxe").Funcs(funcs)
set := map[string]bool{}
ksNames := map[string]bool{}
catFiles := map[string][]byte{}
if err := parseTree(ks, ipxe, set, embedded, ".", true); err != nil {
if err := walkSet(embedded, ks, ipxe, ksNames, catFiles, true); err != nil {
return nil, fmt.Errorf("parse embedded templates: %w", err)
}
if overrideDir != "" {
if err := parseTree(ks, ipxe, set, os.DirFS(overrideDir), ".", false); err != nil {
return nil, fmt.Errorf("parse override templates in %q: %w", overrideDir, err)
if override != nil {
if err := walkSet(override, ks, ipxe, ksNames, catFiles, false); err != nil {
return nil, fmt.Errorf("parse override templates: %w", err)
}
}
return &Engine{ks: ks, ipxe: ipxe, cfg: cfg, ksSet: set}, nil
cat, err := catalog.Parse(catFiles)
if err != nil {
return nil, err
}
return &Set{ks: ks, ipxe: ipxe, ksSet: ksNames, cat: cat}, nil
}
// parseTree walks fsys under root, registering *.ks.tmpl into ks and
// *.ipxe.tmpl into ipxe under their base name (extension stripped).
func parseTree(ks, ipxe *template.Template, set map[string]bool, fsys fs.FS, root string, mustExist bool) error {
// walkSet walks fsys registering templates and collecting catalog YAML.
func walkSet(fsys fs.FS, ks, ipxe *template.Template, ksNames map[string]bool, catFiles map[string][]byte, mustExist bool) error {
walked := false
err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
walked = true
if d.IsDir() {
return nil
}
walked = true
b, err := fs.ReadFile(fsys, path)
if err != nil {
return err
@@ -119,12 +138,14 @@ func parseTree(ks, ipxe *template.Template, set map[string]bool, fsys fs.FS, roo
if _, err := ks.New(name).Parse(string(b)); err != nil {
return fmt.Errorf("%s: %w", path, err)
}
set[name] = true
ksNames[name] = true
case strings.HasSuffix(base, ipxeExt):
name := strings.TrimSuffix(base, ipxeExt)
if _, err := ipxe.New(name).Parse(string(b)); err != nil {
return fmt.Errorf("%s: %w", path, err)
}
case (strings.HasSuffix(base, ".yaml") || strings.HasSuffix(base, ".yml")) && strings.Contains(path, "catalog"):
catFiles[base] = b
}
return nil
})
@@ -132,25 +153,59 @@ func parseTree(ks, ipxe *template.Template, set map[string]bool, fsys fs.FS, roo
return err
}
if mustExist && !walked {
return fmt.Errorf("no templates found under %q", root)
return fmt.Errorf("no templates found")
}
return nil
}
// SelectKickstart returns the template name chosen for host, following the
// documented precedence: custom-field override → platform slug → OS family →
// configured default. It reports whether a concrete template was found.
// Engine holds render-time config and the current (swappable) template Set.
type Engine struct {
cfg RenderConfig
cur atomic.Pointer[Set]
}
// NewEngine builds an Engine over an initial Set.
func NewEngine(cfg RenderConfig, initial *Set) *Engine {
e := &Engine{cfg: cfg}
e.cur.Store(initial)
return e
}
// Swap atomically replaces the active template Set (used by git-sync on reload).
func (e *Engine) Swap(s *Set) { e.cur.Store(s) }
// Current returns the active Set.
func (e *Engine) Current() *Set { return e.cur.Load() }
// resolve returns the catalog entry for host (evaluated against artifactBase),
// or nil when no catalog entry matches.
func (e *Engine) resolve(set *Set, h *model.Host) (*catalog.Resolved, error) {
d, ok := set.cat.Select(h)
if !ok {
return nil, nil
}
return d.Resolve(h, e.cfg.ArtifactBase)
}
// SelectKickstart returns the kickstart template name for host: the catalog
// entry's kickstart if one matches, else the legacy precedence
// (override → platform → family → default). Reports whether it exists.
func (e *Engine) SelectKickstart(h *model.Host) (string, bool) {
set := e.cur.Load()
if d, ok := set.cat.Select(h); ok && set.ksSet[d.Kickstart] {
return d.Kickstart, true
}
for _, cand := range []string{h.TemplateOverride, h.Platform, h.OSFamily, e.cfg.DefaultTemplate} {
if cand != "" && e.ksSet[cand] {
if cand != "" && set.ksSet[cand] {
return cand, true
}
}
return e.cfg.DefaultTemplate, e.ksSet[e.cfg.DefaultTemplate]
return e.cfg.DefaultTemplate, set.ksSet[e.cfg.DefaultTemplate]
}
// dataFor builds the flat Data view for a host, merging render-time config.
func (e *Engine) dataFor(h *model.Host) Data {
// dataFor builds the flat Data view for a host, merging render-time config and
// the selected catalog entry's vars.
func (e *Engine) dataFor(h *model.Host, vars map[string]string) Data {
ns := h.Nameservers
if len(ns) == 0 {
ns = e.cfg.DefaultNS
@@ -173,7 +228,11 @@ func (e *Engine) dataFor(h *model.Host) Data {
}
ksURL := ""
if e.cfg.BaseURL != "" {
ksURL = strings.TrimRight(e.cfg.BaseURL, "/") + "/ks/" + h.Hostname
ksURL = e.cfg.BaseURL + "/ks/" + h.Hostname
}
cbURL := ""
if e.cfg.CallbackBaseURL != "" {
cbURL = e.cfg.CallbackBaseURL + "/provisioned/" + h.Hostname
}
return Data{
Hostname: h.Hostname,
@@ -192,9 +251,13 @@ func (e *Engine) dataFor(h *model.Host) Data {
SSHAuthorizedKeys: keys,
PuppetServer: e.cfg.PuppetServer,
PuppetCAServer: e.cfg.PuppetCAServer,
PuppetCAURL: e.cfg.PuppetCAURL,
BaseURL: e.cfg.BaseURL,
BootBaseURL: e.cfg.BootBaseURL,
KickstartURL: ksURL,
CallbackURL: cbURL,
ProvisionToken: e.cfg.ProvisionToken,
DistroVars: vars,
Custom: h.Custom,
}
}
@@ -202,12 +265,27 @@ func (e *Engine) dataFor(h *model.Host) Data {
// RenderKickstart renders the selected kickstart template for host. It returns
// the rendered bytes and the template name used.
func (e *Engine) RenderKickstart(h *model.Host) ([]byte, string, error) {
name, ok := e.SelectKickstart(h)
if !ok {
return nil, name, fmt.Errorf("no kickstart template for host %q (tried override/platform/family/default %q)", h.Hostname, name)
set := e.cur.Load()
resolved, err := e.resolve(set, h)
if err != nil {
return nil, "", err
}
var vars map[string]string
name := ""
if resolved != nil {
vars = resolved.Vars
if set.ksSet[resolved.Kickstart] {
name = resolved.Kickstart
}
}
if name == "" {
var ok bool
if name, ok = e.SelectKickstart(h); !ok {
return nil, name, fmt.Errorf("no kickstart template for host %q (catalog + override/platform/family/default %q)", h.Hostname, name)
}
}
var buf bytes.Buffer
if err := e.ks.ExecuteTemplate(&buf, name, e.dataFor(h)); err != nil {
if err := set.ks.ExecuteTemplate(&buf, name, e.dataFor(h, vars)); err != nil {
return nil, name, fmt.Errorf("render kickstart %q: %w", name, err)
}
return buf.Bytes(), name, nil
@@ -216,43 +294,122 @@ func (e *Engine) RenderKickstart(h *model.Host) ([]byte, string, error) {
// IPXEData is the value passed to iPXE templates.
type IPXEData struct {
Data
// KernelURL/InitrdURL point at the OS install tree; empty when BootBaseURL
// is unset, in which case the template should fall back to a static path.
KernelURL string
InitrdURL string
// KernelURL/InitrdURL point at the OS install tree (from the catalog, else
// the legacy BootBaseURL). Empty when neither is configured, in which case
// the template falls back to local boot.
KernelURL string
InitrdURL string
KernelArgs []string
// RepoURL is the OS install-tree root (KernelURL minus images/pxeboot/vmlinuz),
// passed to anaconda as inst.repo=.
RepoURL string
}
// RenderIPXE renders the "boot" iPXE script that chains kernel+initrd with
// inst.ks= pointing back at bootapi.
func (e *Engine) RenderIPXE(h *model.Host) ([]byte, error) {
d := e.dataFor(h)
id := IPXEData{Data: d}
if d.BootBaseURL != "" {
tree := strings.TrimRight(d.BootBaseURL, "/")
set := e.cur.Load()
resolved, err := e.resolve(set, h)
if err != nil {
return nil, err
}
var vars map[string]string
if resolved != nil {
vars = resolved.Vars
}
id := IPXEData{Data: e.dataFor(h, vars)}
switch {
case resolved != nil:
id.KernelURL = resolved.KernelURL
id.InitrdURL = resolved.InitrdURL
id.KernelArgs = resolved.KernelArgs
case e.cfg.BootBaseURL != "": // legacy fallback
tree := strings.TrimRight(e.cfg.BootBaseURL, "/")
id.KernelURL = tree + "/images/pxeboot/vmlinuz"
id.InitrdURL = tree + "/images/pxeboot/initrd.img"
}
return e.execIPXE("boot", id)
id.RepoURL = strings.TrimSuffix(id.KernelURL, "/images/pxeboot/vmlinuz")
return e.execIPXE(set, "boot", id)
}
// RenderFallback renders a fallback iPXE script ("local" or "shell") for an
// unknown MAC. See docs/endpoints.md for the safety rationale.
// RenderFallback renders a fallback iPXE script ("local" or "shell").
func (e *Engine) RenderFallback(kind string) ([]byte, error) {
name := "fallback-" + kind
return e.execIPXE(name, IPXEData{})
return e.execIPXE(e.cur.Load(), "fallback-"+kind, IPXEData{})
}
func (e *Engine) execIPXE(name string, d IPXEData) ([]byte, error) {
if e.ipxe.Lookup(name) == nil {
func (e *Engine) execIPXE(set *Set, name string, d IPXEData) ([]byte, error) {
if set.ipxe.Lookup(name) == nil {
return nil, fmt.Errorf("no iPXE template %q", name)
}
var buf bytes.Buffer
if err := e.ipxe.ExecuteTemplate(&buf, name, d); err != nil {
if err := set.ipxe.ExecuteTemplate(&buf, name, d); err != nil {
return nil, fmt.Errorf("render ipxe %q: %w", name, err)
}
return buf.Bytes(), nil
}
// Validate renders every catalog distro's kickstart and iPXE script against a
// representative fixture host, checking that each parses, resolves and leaves no
// unresolved template values. It is used by the templates-repo CI
// (`bootapi validate <dir>`) to reject a bad template/catalog before it ships.
func (e *Engine) Validate() error {
set := e.cur.Load()
distros := set.cat.All()
if len(distros) == 0 {
return fmt.Errorf("catalog is empty: no distros to validate")
}
var errs []string
for _, d := range distros {
h := fixtureHost(d)
ks, name, err := e.RenderKickstart(h)
if err != nil {
errs = append(errs, fmt.Sprintf("%s: kickstart: %v", d.Name, err))
} else if bad := unresolved(ks); bad != "" {
errs = append(errs, fmt.Sprintf("%s: kickstart %q has unresolved value near %q", d.Name, name, bad))
}
ipxe, err := e.RenderIPXE(h)
if err != nil {
errs = append(errs, fmt.Sprintf("%s: ipxe: %v", d.Name, err))
} else if bad := unresolved(ipxe); bad != "" {
errs = append(errs, fmt.Sprintf("%s: ipxe has unresolved value near %q", d.Name, bad))
}
}
if len(errs) > 0 {
return fmt.Errorf("catalog validation failed:\n - %s", strings.Join(errs, "\n - "))
}
return nil
}
// fixtureHost builds a representative host that selects distro d (via an exact
// override) with a plausible version/network, for validation rendering.
func fixtureHost(d *catalog.Distro) *model.Host {
version := d.VersionDefault
platform := d.Name
if len(d.Match.Platforms) > 0 {
platform = d.Match.Platforms[0]
}
return &model.Host{
Hostname: "fixture", Domain: "example.net", Platform: platform,
OSFamily: d.Match.Family, OSVersion: version, Arch: "x86_64",
TemplateOverride: d.Name, PrimaryIP: "10.0.0.10",
Interfaces: []model.Interface{{
Name: "eth0", MAC: "aa:bb:cc:00:11:22", IP: "10.0.0.10",
PrefixLen: 24, Netmask: "255.255.255.0", Gateway: "10.0.0.1", Primary: true,
}},
}
}
// unresolved returns the surrounding text of the first Go-template "<no value>"
// marker, or "" if none — a cheap check that the data model covered the template.
func unresolved(b []byte) string {
s := string(b)
if i := strings.Index(s, "<no value>"); i >= 0 {
start := max(0, i-30)
return s[start : i+10]
}
return ""
}
func funcMap() template.FuncMap {
return template.FuncMap{
"join": strings.Join,
+62 -35
View File
@@ -1,6 +1,7 @@
package render
import (
"io/fs"
"os"
"path/filepath"
"strings"
@@ -10,23 +11,32 @@ import (
"git.unkin.net/unkin/bootapi/templates"
)
func testEngine(t *testing.T, override string) *Engine {
t.Helper()
e, err := NewEngine(templates.FS, override, RenderConfig{
PuppetServer: "puppet.query.consul",
PuppetCAServer: "puppetca.query.consul",
const artifactBase = "https://artifactapi.example.net/api/v1/remote"
func testCfg() RenderConfig {
return RenderConfig{
PuppetServer: "puppet.k8s.syd1.au.unkin.net",
PuppetCAServer: "puppetca.k8s.syd1.au.unkin.net",
PuppetCAURL: "puppetca.k8s.syd1.au.unkin.net",
BaseURL: "http://bootapi.example.net",
BootBaseURL: "http://mirror.example.net/almalinux/9",
CallbackBaseURL: "http://bootapi.example.net",
ArtifactBase: artifactBase,
ProvisionToken: "prov-secret",
DefaultDomain: "main.unkin.net",
DefaultNS: []string{"10.0.0.1"},
DefaultNS: []string{"198.18.200.7"},
RootPasswordHash: "$6$rounds=4096$abc$deadbeef",
SSHAuthorizedKeys: []string{"ssh-ed25519 AAAAC3xxx root@ops"},
DefaultTemplate: "almalinux9",
})
if err != nil {
t.Fatalf("NewEngine: %v", err)
}
return e
}
func testEngine(t *testing.T, override fs.FS) *Engine {
t.Helper()
set, err := BuildSet(templates.FS, override)
if err != nil {
t.Fatalf("BuildSet: %v", err)
}
return NewEngine(testCfg(), set)
}
func almaHost() *model.Host {
@@ -42,13 +52,13 @@ func almaHost() *model.Host {
PrimaryIP: "10.0.1.20",
Interfaces: []model.Interface{
{Name: "eth0", MAC: "aa:bb:cc:00:11:22", IP: "10.0.1.20", PrefixLen: 24, Netmask: "255.255.255.0", Gateway: "10.0.1.254", VLAN: 100, Primary: true},
{Name: "eth1", MAC: "aa:bb:cc:00:11:33"}, // no IP -> must be skipped in network stanza
{Name: "eth1", MAC: "aa:bb:cc:00:11:33"}, // no IP -> skipped in network stanza
},
}
}
func TestRenderKickstartAlma(t *testing.T) {
e := testEngine(t, "")
e := testEngine(t, nil)
out, name, err := e.RenderKickstart(almaHost())
if err != nil {
t.Fatalf("RenderKickstart: %v", err)
@@ -59,29 +69,33 @@ func TestRenderKickstartAlma(t *testing.T) {
ks := string(out)
mustContain(t, ks, "rootpw --iscrypted $6$rounds=4096$abc$deadbeef")
// The primary interface must produce a full static network line incl hostname.
mustContain(t, ks, "network --bootproto=static --device=aa:bb:cc:00:11:22 --ip=10.0.1.20 --netmask=255.255.255.0 --gateway=10.0.1.254 --nameserver=10.0.0.1 --hostname=web01.syd1.au.unkin.net")
mustContain(t, ks, `"$PUPPET_BIN" config set --section main server "puppet.query.consul"`)
mustContain(t, ks, `config set --section main ca_server "puppetca.query.consul"`)
mustContain(t, ks, "url --url=http://mirror.example.net/almalinux/9/BaseOS/x86_64/os/")
mustContain(t, ks, "network --bootproto=static --device=aa:bb:cc:00:11:22 --ip=10.0.1.20 --netmask=255.255.255.0 --gateway=10.0.1.254 --nameserver=198.18.200.7 --hostname=web01.syd1.au.unkin.net")
// install source comes from the catalog mirror (artifactapi almalinux remote).
mustContain(t, ks, "url --url="+artifactBase+"/almalinux/9/BaseOS/x86_64/os/")
mustContain(t, ks, "repo --name=AppStream --baseurl="+artifactBase+"/almalinux/9/AppStream/x86_64/os/")
// puppet points at the k8s server/CA.
mustContain(t, ks, `config set --section main server "puppet.k8s.syd1.au.unkin.net"`)
mustContain(t, ks, `config set --section main ca_server "puppetca.k8s.syd1.au.unkin.net"`)
// puppet-initial env file.
mustContain(t, ks, "PUPPETCA_URL=puppetca.k8s.syd1.au.unkin.net")
// end-of-install callback with the provision token.
mustContain(t, ks, `-H "Authorization: Bearer prov-secret"`)
mustContain(t, ks, `"http://bootapi.example.net/provisioned/web01"`)
mustContain(t, ks, "ssh-ed25519 AAAAC3xxx root@ops")
mustContain(t, ks, "dnf install -y puppet-agent")
mustContain(t, ks, "%packages")
mustContain(t, ks, "%post")
// eth1 has no IP, so it must NOT appear as a network device line.
if strings.Contains(ks, "--device=aa:bb:cc:00:11:33") {
t.Error("interface without an IP leaked into a network stanza")
}
}
func TestRenderKickstartLockedRoot(t *testing.T) {
// With no root hash configured, the account must be locked, not blank.
e, err := NewEngine(templates.FS, "", RenderConfig{DefaultTemplate: "almalinux9", BootBaseURL: "http://m/9"})
cfg := testCfg()
cfg.RootPasswordHash = ""
set, err := BuildSet(templates.FS, nil)
if err != nil {
t.Fatal(err)
}
out, _, err := e.RenderKickstart(almaHost())
out, _, err := NewEngine(cfg, set).RenderKickstart(almaHost())
if err != nil {
t.Fatal(err)
}
@@ -93,14 +107,14 @@ func TestRenderKickstartLockedRoot(t *testing.T) {
}
func TestSelectKickstartPrecedence(t *testing.T) {
e := testEngine(t, "")
e := testEngine(t, nil)
cases := []struct {
host *model.Host
want string
}{
{&model.Host{TemplateOverride: "fedora", Platform: "almalinux9"}, "fedora"}, // override wins
{&model.Host{Platform: "almalinux9"}, "almalinux9"}, // platform
{&model.Host{Platform: "fedora42", OSFamily: "fedora"}, "fedora"}, // family fallback
{&model.Host{TemplateOverride: "fedora", Platform: "almalinux9"}, "fedora"}, // override wins (catalog name)
{&model.Host{Platform: "almalinux9", OSFamily: "almalinux"}, "almalinux9"}, // platform
{&model.Host{Platform: "fedora42", OSFamily: "fedora"}, "fedora"}, // family fallback (catalog)
{&model.Host{Platform: "unknownos"}, "almalinux9"}, // default
}
for _, c := range cases {
@@ -111,21 +125,34 @@ func TestSelectKickstartPrecedence(t *testing.T) {
}
}
func TestRenderIPXE(t *testing.T) {
e := testEngine(t, "")
func TestRenderIPXECatalog(t *testing.T) {
e := testEngine(t, nil)
out, err := e.RenderIPXE(almaHost())
if err != nil {
t.Fatalf("RenderIPXE: %v", err)
}
s := string(out)
mustContain(t, s, "#!ipxe")
mustContain(t, s, "kernel http://mirror.example.net/almalinux/9/images/pxeboot/vmlinuz")
mustContain(t, s, "kernel "+artifactBase+"/almalinux/9/BaseOS/x86_64/os/images/pxeboot/vmlinuz")
mustContain(t, s, "initrd "+artifactBase+"/almalinux/9/BaseOS/x86_64/os/images/pxeboot/initrd.img")
mustContain(t, s, "inst.repo="+artifactBase+"/almalinux/9/BaseOS/x86_64/os")
mustContain(t, s, "inst.ks=http://bootapi.example.net/ks/web01")
mustContain(t, s, "initrd http://mirror.example.net/almalinux/9/images/pxeboot/initrd.img")
mustContain(t, s, "inst.text") // catalog kernel arg
mustContain(t, s, "net.ifnames=0")
}
func TestRenderIPXEFedoraCatalog(t *testing.T) {
e := testEngine(t, nil)
h := &model.Host{Hostname: "f1", Platform: "fedora41", OSFamily: "fedora", OSVersion: "41", Arch: "x86_64"}
out, err := e.RenderIPXE(h)
if err != nil {
t.Fatal(err)
}
mustContain(t, string(out), "kernel "+artifactBase+"/fedora/releases/41/Everything/x86_64/os/images/pxeboot/vmlinuz")
}
func TestRenderFallback(t *testing.T) {
e := testEngine(t, "")
e := testEngine(t, nil)
local, err := e.RenderFallback("local")
if err != nil {
t.Fatal(err)
@@ -143,7 +170,7 @@ func TestOverrideDirWins(t *testing.T) {
if err := os.WriteFile(filepath.Join(dir, "almalinux9.ks.tmpl"), []byte("OVERRIDDEN {{ .Hostname }}\n"), 0o600); err != nil {
t.Fatal(err)
}
e := testEngine(t, dir)
e := testEngine(t, os.DirFS(dir))
out, _, err := e.RenderKickstart(almaHost())
if err != nil {
t.Fatal(err)
+51 -2
View File
@@ -11,6 +11,13 @@ type cacheStats interface {
Misses() int64
}
// gitStats is the read side of the template git-syncer the collector publishes.
type gitStats interface {
Syncs() int64
Failures() int64
Generation() int64
}
// metrics holds bootapi's Prometheus instruments, registered on a private
// registry so tests can construct isolated servers.
type metrics struct {
@@ -20,9 +27,11 @@ type metrics struct {
renders *prometheus.CounterVec // by kind,result
netboxLookups *prometheus.CounterVec // by field,result
netboxDuration *prometheus.HistogramVec
provisioned *prometheus.CounterVec // by result
ipxeGated prometheus.Counter
}
func newMetrics(cache cacheStats) *metrics {
func newMetrics(cache cacheStats, git gitStats) *metrics {
reg := prometheus.NewRegistry()
m := &metrics{
reg: reg,
@@ -43,11 +52,22 @@ func newMetrics(cache cacheStats) *metrics {
Help: "Latency of NetBox host resolutions.",
Buckets: prometheus.DefBuckets,
}, []string{"field"}),
provisioned: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "bootapi_provisioned_total",
Help: "Provisioned callbacks, by result (ok|unauthorized|notfound|error|disabled).",
}, []string{"result"}),
ipxeGated: prometheus.NewCounter(prometheus.CounterOpts{
Name: "bootapi_ipxe_gated_total",
Help: "Known hosts served the local-boot fallback because pxe_enabled=false.",
}),
}
reg.MustRegister(m.httpRequests, m.renders, m.netboxLookups, m.netboxDuration)
reg.MustRegister(m.httpRequests, m.renders, m.netboxLookups, m.netboxDuration, m.provisioned, m.ipxeGated)
if cache != nil {
reg.MustRegister(newCacheCollector(cache))
}
if git != nil {
reg.MustRegister(newGitCollector(git))
}
reg.MustRegister(
collectors.NewGoCollector(),
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
@@ -55,6 +75,35 @@ func newMetrics(cache cacheStats) *metrics {
return m
}
// gitCollector publishes the template git-syncer counters.
type gitCollector struct {
stats gitStats
syncs *prometheus.Desc
failures *prometheus.Desc
generation *prometheus.Desc
}
func newGitCollector(s gitStats) *gitCollector {
return &gitCollector{
stats: s,
syncs: prometheus.NewDesc("bootapi_template_sync_total", "Successful template reloads from git.", nil, nil),
failures: prometheus.NewDesc("bootapi_template_sync_failures_total", "Template git pull/parse failures (last-good kept).", nil, nil),
generation: prometheus.NewDesc("bootapi_template_generation", "Monotonic counter of the active template generation.", nil, nil),
}
}
func (c *gitCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.syncs
ch <- c.failures
ch <- c.generation
}
func (c *gitCollector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(c.syncs, prometheus.CounterValue, float64(c.stats.Syncs()))
ch <- prometheus.MustNewConstMetric(c.failures, prometheus.CounterValue, float64(c.stats.Failures()))
ch <- prometheus.MustNewConstMetric(c.generation, prometheus.GaugeValue, float64(c.stats.Generation()))
}
// cacheCollector publishes the NetBox cache hit/miss counters, which live on
// the Cache itself (atomic ints) rather than in a CounterVec.
type cacheCollector struct {
+134 -28
View File
@@ -4,10 +4,13 @@ package server
import (
"context"
"crypto/subtle"
"errors"
"fmt"
"log/slog"
"net/http"
"strings"
"sync"
"time"
"github.com/go-chi/chi/v5"
@@ -19,23 +22,35 @@ import (
"git.unkin.net/unkin/bootapi/internal/render"
)
// Server wires the NetBox resolver and template engine into HTTP handlers.
// Server wires the NetBox API and template engine into HTTP handlers.
type Server struct {
resolver netbox.Resolver
engine *render.Engine
metrics *metrics
nb netbox.API
engine *render.Engine
metrics *metrics
// fallback is the unknown-MAC iPXE behavior: "local" (safe default) or
// "shell" (debug).
fallback string
// provisionToken guards POST /provisioned; empty disables the endpoint.
provisionToken string
// TLS listener (optional); the plain-HTTP listener is always on.
tlsAddr string
tlsCert string
tlsKey string
}
// Options configures a Server.
type Options struct {
Resolver netbox.Resolver
Engine *render.Engine
// Cache, when non-nil, has its hit/miss counters published as metrics.
NetBox netbox.API
Engine *render.Engine
// Cache/GitStats, when non-nil, have their counters published as metrics.
Cache cacheStats
GitStats gitStats
UnknownMACFallback string
ProvisionToken string
TLSAddr string
TLSCertFile string
TLSKeyFile string
}
// New builds a Server.
@@ -45,10 +60,14 @@ func New(o Options) *Server {
fb = "local"
}
return &Server{
resolver: o.Resolver,
engine: o.Engine,
metrics: newMetrics(o.Cache),
fallback: fb,
nb: o.NetBox,
engine: o.Engine,
metrics: newMetrics(o.Cache, o.GitStats),
fallback: fb,
provisionToken: o.ProvisionToken,
tlsAddr: o.TLSAddr,
tlsCert: o.TLSCertFile,
tlsKey: o.TLSKeyFile,
}
}
@@ -70,6 +89,9 @@ func (s *Server) Router() http.Handler {
// Rendered kickstart, keyed by MAC or hostname.
r.Get("/ks/{ident}", s.handleKickstart)
// End-of-kickstart callback: flips pxe_enabled off in NetBox. Token-guarded.
r.Post("/provisioned/{ident}", s.handleProvisioned)
return r
}
@@ -116,6 +138,15 @@ func (s *Server) serveIPXE(w http.ResponseWriter, r *http.Request, mac string) {
s.renderFallback(w, "ipxe", "unknown or unresolvable MAC")
return
}
// Per-host PXE-enable gate (Cobbler's netboot_enabled): a KNOWN host whose
// pxe_enabled is false must NOT re-install. Serve the safe local-boot script
// so an already-provisioned machine just boots its disk.
if !host.ShouldPXEInstall() {
s.metrics.ipxeGated.Inc()
slog.Info("ipxe gated: pxe_enabled=false; serving local boot", "host", host.Hostname)
s.renderFallback(w, "ipxe", "pxe disabled for host")
return
}
body, err := s.engine.RenderIPXE(host)
if err != nil {
s.metrics.renders.WithLabelValues("ipxe", "error").Inc()
@@ -180,15 +211,68 @@ func (s *Server) handleKickstart(w http.ResponseWriter, r *http.Request) {
s.ok(w, http.StatusOK, "text/plain", body, "ks")
}
// handleProvisioned is the end-of-kickstart callback. The %post posts here with
// the shared provision token when the install finishes; bootapi flips the host's
// pxe_enabled custom field to false in NetBox so the next PXE boots local disk
// instead of re-installing. This is bootapi's only NetBox write.
func (s *Server) handleProvisioned(w http.ResponseWriter, r *http.Request) {
if s.provisionToken == "" {
http.Error(w, "provisioned callback disabled: no token configured", http.StatusServiceUnavailable)
s.metrics.provisioned.WithLabelValues("disabled").Inc()
return
}
if subtle.ConstantTimeCompare([]byte(bearer(r)), []byte(s.provisionToken)) != 1 {
http.Error(w, "invalid or missing provision token", http.StatusUnauthorized)
s.metrics.provisioned.WithLabelValues("unauthorized").Inc()
return
}
ident := chi.URLParam(r, "ident")
field := "name"
if looksLikeMAC(ident) {
field = "mac"
}
host, err := s.lookup(r.Context(), field, ident)
if err != nil {
if errors.Is(err, netbox.ErrNotFound) {
http.Error(w, "no host in NetBox for "+ident, http.StatusNotFound)
s.metrics.provisioned.WithLabelValues("notfound").Inc()
return
}
http.Error(w, "netbox lookup failed", http.StatusBadGateway)
s.metrics.provisioned.WithLabelValues("error").Inc()
return
}
if err := s.nb.SetPXEEnabled(r.Context(), host.DeviceID, false); err != nil {
slog.Error("provisioned: failed to clear pxe_enabled", "host", host.Hostname, "err", err)
http.Error(w, "failed to update NetBox", http.StatusBadGateway)
s.metrics.provisioned.WithLabelValues("error").Inc()
return
}
s.metrics.provisioned.WithLabelValues("ok").Inc()
slog.Info("host provisioned; pxe_enabled cleared", "host", host.Hostname)
w.WriteHeader(http.StatusNoContent)
}
// bearer extracts a token from "Authorization: Bearer <t>" or a bare "token"
// header.
func bearer(r *http.Request) string {
if h := r.Header.Get("Authorization"); h != "" {
if after, ok := strings.CutPrefix(h, "Bearer "); ok {
return after
}
}
return r.Header.Get("token")
}
// lookup resolves a host by field ("mac" or "name"), recording metrics.
func (s *Server) lookup(ctx context.Context, field, value string) (*model.Host, error) {
start := time.Now()
var host *model.Host
var err error
if field == "mac" {
host, err = s.resolver.HostByMAC(ctx, value)
host, err = s.nb.HostByMAC(ctx, value)
} else {
host, err = s.resolver.HostByName(ctx, value)
host, err = s.nb.HostByName(ctx, value)
}
s.metrics.netboxDuration.WithLabelValues(field).Observe(time.Since(start).Seconds())
switch {
@@ -209,24 +293,46 @@ func (s *Server) ok(w http.ResponseWriter, status int, contentType string, body
s.metrics.httpRequests.WithLabelValues(endpoint, statusClass(status)).Inc()
}
// ListenAndServe runs the HTTP server until ctx is cancelled.
// ListenAndServe runs the plain-HTTP server (always) plus, when a TLS listener
// is configured, an HTTPS server sharing the same handler — both until ctx is
// cancelled. The boot path works over plain HTTP because PXE installers have no
// internal CA trust; HTTPS is offered in parallel for clients that do.
func (s *Server) ListenAndServe(ctx context.Context, addr string) error {
srv := &http.Server{
Addr: addr,
Handler: s.Router(),
ReadHeaderTimeout: 10 * time.Second,
h := s.Router()
var wg sync.WaitGroup
errc := make(chan error, 2)
serve := func(name string, srv *http.Server, tls bool) {
defer wg.Done()
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
}()
slog.Info("bootapi listening", "listener", name, "addr", srv.Addr)
var err error
if tls {
err = srv.ListenAndServeTLS(s.tlsCert, s.tlsKey)
} else {
err = srv.ListenAndServe()
}
if err != nil && !errors.Is(err, http.ErrServerClosed) {
errc <- fmt.Errorf("%s listener: %w", name, err)
}
}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
}()
slog.Info("bootapi listening", "addr", addr)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
wg.Add(1)
go serve("http", &http.Server{Addr: addr, Handler: h, ReadHeaderTimeout: 10 * time.Second}, false)
if s.tlsAddr != "" && s.tlsCert != "" && s.tlsKey != "" {
wg.Add(1)
go serve("https", &http.Server{Addr: s.tlsAddr, Handler: h, ReadHeaderTimeout: 10 * time.Second}, true)
}
return nil
wg.Wait()
close(errc)
return <-errc // first error, or nil (channel closed empty)
}
// looksLikeMAC reports whether s is plausibly a MAC (12 hex nibbles, ignoring
+117 -24
View File
@@ -13,14 +13,16 @@ import (
"git.unkin.net/unkin/bootapi/templates"
)
// fakeResolver is a canned netbox.Resolver for handler tests.
type fakeResolver struct {
byMAC map[string]*model.Host
byName map[string]*model.Host
err error
// fakeNB is a canned netbox.API (reads + pxe_enabled write) for handler tests.
type fakeNB struct {
byMAC map[string]*model.Host
byName map[string]*model.Host
err error
writeErr error
writes []int // device IDs written via SetPXEEnabled
}
func (f *fakeResolver) HostByMAC(_ context.Context, mac string) (*model.Host, error) {
func (f *fakeNB) HostByMAC(_ context.Context, mac string) (*model.Host, error) {
if f.err != nil {
return nil, f.err
}
@@ -31,7 +33,7 @@ func (f *fakeResolver) HostByMAC(_ context.Context, mac string) (*model.Host, er
}
return nil, netbox.ErrNotFound
}
func (f *fakeResolver) HostByName(_ context.Context, name string) (*model.Host, error) {
func (f *fakeNB) HostByName(_ context.Context, name string) (*model.Host, error) {
if f.err != nil {
return nil, f.err
}
@@ -40,9 +42,17 @@ func (f *fakeResolver) HostByName(_ context.Context, name string) (*model.Host,
}
return nil, netbox.ErrNotFound
}
func (f *fakeNB) SetPXEEnabled(_ context.Context, deviceID int, _ bool) error {
if f.writeErr != nil {
return f.writeErr
}
f.writes = append(f.writes, deviceID)
return nil
}
func testHost() *model.Host {
return &model.Host{
DeviceID: 12,
Hostname: "web01", Domain: "syd1.au.unkin.net", FQDN: "web01.syd1.au.unkin.net",
Platform: "almalinux9", OSFamily: "almalinux", OSVersion: "9", Arch: "x86_64",
PrimaryIP: "10.0.1.20",
@@ -52,18 +62,25 @@ func testHost() *model.Host {
}
}
func newTestServer(t *testing.T, res netbox.Resolver, fallback string) *Server {
func newTestServer(t *testing.T, nb netbox.API, fallback string) *Server {
t.Helper()
eng, err := render.NewEngine(templates.FS, "", render.RenderConfig{
PuppetServer: "puppet.query.consul", PuppetCAServer: "puppetca.query.consul",
BaseURL: "http://bootapi.example.net", BootBaseURL: "http://mirror.example.net/almalinux/9",
DefaultDomain: "main.unkin.net", DefaultTemplate: "almalinux9",
RootPasswordHash: "$6$abc$def",
})
return newTestServerToken(t, nb, fallback, "")
}
func newTestServerToken(t *testing.T, nb netbox.API, fallback, provToken string) *Server {
t.Helper()
set, err := render.BuildSet(templates.FS, nil)
if err != nil {
t.Fatal(err)
}
return New(Options{Resolver: res, Engine: eng, UnknownMACFallback: fallback})
eng := render.NewEngine(render.RenderConfig{
PuppetServer: "puppet.k8s.syd1.au.unkin.net", PuppetCAServer: "puppetca.k8s.syd1.au.unkin.net",
BaseURL: "http://bootapi.example.net", CallbackBaseURL: "http://bootapi.example.net",
ArtifactBase: "https://af.example/api/v1/remote", ProvisionToken: provToken,
DefaultDomain: "main.unkin.net", DefaultTemplate: "almalinux9",
RootPasswordHash: "$6$abc$def",
}, set)
return New(Options{NetBox: nb, Engine: eng, UnknownMACFallback: fallback, ProvisionToken: provToken})
}
func do(t *testing.T, h http.Handler, path string) *httptest.ResponseRecorder {
@@ -73,8 +90,19 @@ func do(t *testing.T, h http.Handler, path string) *httptest.ResponseRecorder {
return rec
}
func post(t *testing.T, h http.Handler, path, token string) *httptest.ResponseRecorder {
t.Helper()
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, path, nil)
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
h.ServeHTTP(rec, req)
return rec
}
func TestIPXEKnownMAC(t *testing.T) {
res := &fakeResolver{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
res := &fakeNB{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
h := newTestServer(t, res, "local").Router()
rec := do(t, h, "/ipxe/aa:bb:cc:00:11:22")
@@ -88,7 +116,7 @@ func TestIPXEKnownMAC(t *testing.T) {
}
func TestIPXEUnknownMACServesFallback200(t *testing.T) {
h := newTestServer(t, &fakeResolver{}, "local").Router()
h := newTestServer(t, &fakeNB{}, "local").Router()
rec := do(t, h, "/ipxe/de:ad:be:ef:00:00")
// Unknown MAC must NOT 404 — iPXE needs a valid script. Safe local-boot.
if rec.Code != http.StatusOK {
@@ -100,7 +128,7 @@ func TestIPXEUnknownMACServesFallback200(t *testing.T) {
}
func TestIPXEUnknownMACShellFallback(t *testing.T) {
h := newTestServer(t, &fakeResolver{}, "shell").Router()
h := newTestServer(t, &fakeNB{}, "shell").Router()
rec := do(t, h, "/ipxe/de:ad:be:ef:00:00")
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "shell") {
t.Fatalf("shell fallback not served: %d\n%s", rec.Code, rec.Body.String())
@@ -108,7 +136,7 @@ func TestIPXEUnknownMACShellFallback(t *testing.T) {
}
func TestIPXEQueryAlias(t *testing.T) {
res := &fakeResolver{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
res := &fakeNB{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
h := newTestServer(t, res, "local").Router()
rec := do(t, h, "/boot/ipxe?mac=AA:BB:CC:00:11:22")
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "inst.ks=") {
@@ -117,7 +145,7 @@ func TestIPXEQueryAlias(t *testing.T) {
}
func TestKickstartByMAC(t *testing.T) {
res := &fakeResolver{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
res := &fakeNB{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
h := newTestServer(t, res, "local").Router()
rec := do(t, h, "/ks/aa:bb:cc:00:11:22")
if rec.Code != http.StatusOK {
@@ -132,7 +160,7 @@ func TestKickstartByMAC(t *testing.T) {
}
func TestKickstartByHostname(t *testing.T) {
res := &fakeResolver{byName: map[string]*model.Host{"web01": testHost()}}
res := &fakeNB{byName: map[string]*model.Host{"web01": testHost()}}
h := newTestServer(t, res, "local").Router()
rec := do(t, h, "/ks/web01.cfg") // .cfg suffix must be stripped
if rec.Code != http.StatusOK {
@@ -141,7 +169,7 @@ func TestKickstartByHostname(t *testing.T) {
}
func TestKickstartUnknownIs404(t *testing.T) {
h := newTestServer(t, &fakeResolver{}, "local").Router()
h := newTestServer(t, &fakeNB{}, "local").Router()
rec := do(t, h, "/ks/nosuchhost")
if rec.Code != http.StatusNotFound {
t.Fatalf("status = %d, want 404 (kickstart must fail loudly)", rec.Code)
@@ -149,7 +177,7 @@ func TestKickstartUnknownIs404(t *testing.T) {
}
func TestHealthAndReady(t *testing.T) {
h := newTestServer(t, &fakeResolver{}, "local").Router()
h := newTestServer(t, &fakeNB{}, "local").Router()
if rec := do(t, h, "/healthz"); rec.Code != http.StatusOK {
t.Errorf("healthz = %d", rec.Code)
}
@@ -159,7 +187,7 @@ func TestHealthAndReady(t *testing.T) {
}
func TestMetricsEndpoint(t *testing.T) {
res := &fakeResolver{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
res := &fakeNB{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
srv := newTestServer(t, res, "local")
h := srv.Router()
@@ -184,6 +212,71 @@ func TestMetricsEndpoint(t *testing.T) {
}
}
func TestIPXEGatedWhenPXEDisabled(t *testing.T) {
disabled := false
host := testHost()
host.PXEEnabled = &disabled // pxe_enabled=false: known host must NOT reinstall
res := &fakeNB{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": host}}
srv := newTestServer(t, res, "local")
h := srv.Router()
rec := do(t, h, "/ipxe/aa:bb:cc:00:11:22")
if rec.Code != http.StatusOK {
t.Fatalf("status = %d", rec.Code)
}
body := rec.Body.String()
if !strings.Contains(body, "sanboot") || strings.Contains(body, "inst.ks=") {
t.Errorf("gated host should get local-boot fallback, not an installer:\n%s", body)
}
if !strings.Contains(do(t, h, "/metrics").Body.String(), "bootapi_ipxe_gated_total 1") {
t.Error("gate metric not incremented")
}
}
func TestProvisionedCallbackOK(t *testing.T) {
res := &fakeNB{byName: map[string]*model.Host{"web01": testHost()}}
h := newTestServerToken(t, res, "local", "prov-secret").Router()
rec := post(t, h, "/provisioned/web01", "prov-secret")
if rec.Code != http.StatusNoContent {
t.Fatalf("status = %d, want 204\n%s", rec.Code, rec.Body.String())
}
if len(res.writes) != 1 || res.writes[0] != 12 {
t.Errorf("expected SetPXEEnabled on device 12, got writes=%v", res.writes)
}
}
func TestProvisionedCallbackAuth(t *testing.T) {
res := &fakeNB{byName: map[string]*model.Host{"web01": testHost()}}
h := newTestServerToken(t, res, "local", "prov-secret").Router()
if rec := post(t, h, "/provisioned/web01", "wrong"); rec.Code != http.StatusUnauthorized {
t.Errorf("wrong token: status = %d, want 401", rec.Code)
}
if rec := post(t, h, "/provisioned/web01", ""); rec.Code != http.StatusUnauthorized {
t.Errorf("no token: status = %d, want 401", rec.Code)
}
if len(res.writes) != 0 {
t.Errorf("unauthorized calls must not write NetBox, got %v", res.writes)
}
}
func TestProvisionedCallbackDisabled(t *testing.T) {
// No provision token configured -> endpoint fails closed.
res := &fakeNB{byName: map[string]*model.Host{"web01": testHost()}}
h := newTestServer(t, res, "local").Router()
if rec := post(t, h, "/provisioned/web01", "anything"); rec.Code != http.StatusServiceUnavailable {
t.Errorf("status = %d, want 503 when no token configured", rec.Code)
}
}
func TestProvisionedCallbackUnknownHost(t *testing.T) {
h := newTestServerToken(t, &fakeNB{}, "local", "prov-secret").Router()
if rec := post(t, h, "/provisioned/nosuch", "prov-secret"); rec.Code != http.StatusNotFound {
t.Errorf("status = %d, want 404", rec.Code)
}
}
func TestLooksLikeMAC(t *testing.T) {
yes := []string{"aa:bb:cc:00:11:22", "aa-bb-cc-00-11-22", "aabbcc001122", "aabb.cc00.1122"}
no := []string{"web01", "web01.example.net", "aa:bb:cc", "zz:bb:cc:00:11:22"}
+18
View File
@@ -0,0 +1,18 @@
# Distro catalog entry: AlmaLinux 9.
# Boot images are proxied through the artifactapi "almalinux" remote. Host ->
# distro selection is NetBox-driven (platform slug almalinux9, or the almalinux
# family, or a provision_template override naming "almalinux9").
name: almalinux9
match:
platforms: [almalinux9]
family: almalinux
kickstart: almalinux9
version_default: "9"
kernel_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/vmlinuz"
initrd_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/initrd.img"
kernel_args:
- inst.text
- net.ifnames=0
vars:
# Version-level mirror base; the kickstart appends BaseOS/AppStream under it.
mirror: "{{.ArtifactBase}}/almalinux/{{.Version}}"
+16
View File
@@ -0,0 +1,16 @@
# Distro catalog entry: Fedora (family-level, matches any fedoraNN platform).
# Boot images are proxied through the artifactapi "fedora" remote, whose tree
# lives under releases/<ver>/Everything/<arch>/os/.
name: fedora
match:
family: fedora
kickstart: fedora
version_default: "41"
kernel_url: "{{.ArtifactBase}}/fedora/releases/{{.Version}}/Everything/{{.Arch}}/os/images/pxeboot/vmlinuz"
initrd_url: "{{.ArtifactBase}}/fedora/releases/{{.Version}}/Everything/{{.Arch}}/os/images/pxeboot/initrd.img"
kernel_args:
- inst.text
- net.ifnames=0
vars:
# Install-tree root; the kickstart appends <arch>/os/ under it.
mirror: "{{.ArtifactBase}}/fedora/releases/{{.Version}}/Everything"
+3 -2
View File
@@ -5,7 +5,8 @@ package templates
import "embed"
// FS holds the default template tree: kickstart/*.ks.tmpl and ipxe/*.ipxe.tmpl.
// FS holds the default template tree: kickstart/*.ks.tmpl, ipxe/*.ipxe.tmpl and
// catalog/*.yaml (the distro catalog).
//
//go:embed kickstart ipxe
//go:embed kickstart ipxe catalog
var FS embed.FS
+10 -10
View File
@@ -1,20 +1,20 @@
{{- /*
iPXE boot script for a known host. Chains the OS installer kernel+initrd and
points inst.ks= back at bootapi's /ks/<hostname> endpoint, mirroring how Cobbler
generated a per-MAC gPXE script that carried inst.ks=.
iPXE boot script for a known, PXE-enabled host. Chains the OS installer
kernel+initrd (from the distro catalog) and points inst.ks= back at bootapi's
/ks/<host> over plain HTTP, so an installer with no internal-CA trust can fetch
it. Mirrors how Cobbler generated a per-MAC gPXE script carrying inst.ks=.
Requires BOOTAPI_BOOT_BASE_URL (KernelURL/InitrdURL) and BOOTAPI_BASE_URL
(KickstartURL) to be configured.
KernelURL/InitrdURL/RepoURL come from the selected catalog entry (artifactapi
remote); KernelArgs are the catalog's extra args. KickstartURL uses
BOOTAPI_BASE_URL (http://).
*/ -}}
#!ipxe
echo bootapi: provisioning {{ .FQDN }} ({{ .Platform }})
{{ if and .KernelURL .InitrdURL -}}
kernel {{ .KernelURL }} initrd=initrd.img inst.repo={{ .BootBaseURL }} inst.ks={{ .KickstartURL }} inst.text ip=dhcp net.ifnames=0
kernel {{ .KernelURL }} initrd=initrd.img{{ if .RepoURL }} inst.repo={{ .RepoURL }}{{ end }} inst.ks={{ .KickstartURL }} ip=dhcp{{ range .KernelArgs }} {{ . }}{{ end }}
initrd {{ .InitrdURL }}
boot
{{- else -}}
echo bootapi: BOOTAPI_BOOT_BASE_URL not configured; cannot build a boot line
echo Falling back to local disk in 5s
sleep 5
exit
echo bootapi: no boot images resolved for {{ .Platform }} (no catalog entry / BOOTAPI_BOOT_BASE_URL); booting local disk
sanboot --no-describe --drive 0x80 || exit
{{- end }}
+35 -18
View File
@@ -1,15 +1,17 @@
{{- /*
AlmaLinux 9 kickstart, ported from the Cobbler default.ks contract.
Rendered by bootapi from NetBox data + render-time secrets. The %post hands off
to the existing Puppet firstrun bootstrap: it installs the agent, points it at
the Consul-discovered puppet servers, and triggers the first run. Autosign
(*.main.unkin.net + the PXE subnets) and the `profiles::firstrun` class do the
rest, exactly as they did under Cobbler.
Rendered by bootapi from NetBox data + render-time secrets + the distro catalog.
Install source comes from the artifactapi almalinux remote (via the catalog
mirror var). The %post installs the Puppet agent and points it at the k8s
puppetserver (puppet.k8s.syd1.au.unkin.net / puppetca.k8s...), writes the
puppet-initial PUPPETCA_URL env file, then posts back to bootapi so pxe_enabled
flips off (Cobbler's netboot_enabled flow).
Data model: see docs/data-model.md. `.RootPasswordHash` comes from Vault at
render time, never from NetBox.
Data model: see docs/data-model.md. `.RootPasswordHash` and `.ProvisionToken`
come from Vault/env at render time, never from NetBox.
*/ -}}
{{- $mirror := .DistroVars.mirror -}}
#version=RHEL9
# Rendered by bootapi for {{ .FQDN }} (platform {{ .Platform }}, role {{ default "none" .Role }})
text
@@ -17,9 +19,9 @@ eula --agreed
firstboot --disable
reboot
# --- install source (served by bootapi's configured mirror) ---
url --url={{ .BootBaseURL }}/BaseOS/{{ .Arch }}/os/
repo --name=AppStream --baseurl={{ .BootBaseURL }}/AppStream/{{ .Arch }}/os/
# --- install source (artifactapi almalinux remote, from the distro catalog) ---
url --url={{ $mirror }}/BaseOS/{{ .Arch }}/os/
repo --name=AppStream --baseurl={{ $mirror }}/AppStream/{{ .Arch }}/os/
# --- localization ---
keyboard --xlayouts='us'
@@ -61,7 +63,7 @@ git
-iwl*-firmware
%end
# --- bootstrap: hand off to Puppet firstrun ---
# --- bootstrap: puppet (k8s) + end-of-install callback ---
%post --log=/root/bootapi-post.log
set -x
@@ -82,15 +84,30 @@ rpm -q puppet-agent >/dev/null 2>&1 || \
dnf install -y https://yum.puppet.com/puppet8-release-el-9.noarch.rpm
dnf install -y puppet-agent
# Point the agent at the Consul-discovered servers (matches the pre-bootapi
# Cobbler kickstart + hieradata/roles/infra/puppet).
# Point the agent at the k8s puppetserver / CA.
PUPPET_BIN=/opt/puppetlabs/bin/puppet
"$PUPPET_BIN" config set --section main certname "{{ .FQDN }}"
"$PUPPET_BIN" config set --section main server "{{ .PuppetServer }}"
"$PUPPET_BIN" config set --section main ca_server "{{ .PuppetCAServer }}"
"$PUPPET_BIN" config set --section main certname "{{ .FQDN }}"
"$PUPPET_BIN" config set --section main server "{{ .PuppetServer }}"
"$PUPPET_BIN" config set --section main ca_server "{{ .PuppetCAServer }}"
"$PUPPET_BIN" config set --section main report_server "{{ .PuppetServer }}"
"$PUPPET_BIN" config set --section main environment production
"$PUPPET_BIN" config set --section main environment production
# Enable the agent; the first boot triggers firstrun (autosign handles the CSR).
# puppet-initial bootstrap unit reads PUPPETCA_URL from this EnvironmentFile.
install -d -m0755 /etc/sysconfig
cat > /etc/sysconfig/puppet-initial <<'EOF'
PUPPETCA_URL={{ .PuppetCAURL }}
EOF
# Enable the agent; first boot triggers firstrun (autosign handles the CSR).
systemctl enable puppet
{{ if and .ProvisionToken .CallbackURL -}}
# Tell bootapi the install is done so it clears pxe_enabled in NetBox and the
# next PXE boots local disk. Runs over plain HTTP (no internal CA trust yet);
# the token authenticates the call. Non-fatal if it fails (the local-disk
# fallback still protects a re-provisioned host on the following boot).
curl -fsS -m 15 -X POST \
-H "Authorization: Bearer {{ .ProvisionToken }}" \
"{{ .CallbackURL }}" || echo "bootapi: provisioned callback failed (non-fatal)"
{{- end }}
%end
+20 -6
View File
@@ -1,17 +1,19 @@
{{- /*
Fedora kickstart (family-level template: matches any "fedoraNN" platform slug
via the OS-family selection fallback). Kept close to the AlmaLinux template so
the two stay comparable; the differences are the install tree layout and that
Fedora ships a recent-enough dnf/agent story out of the box.
via the catalog family match). Kept close to the AlmaLinux template so the two
stay comparable; the differences are the install-tree layout (releases/.../
Everything) and the puppet release RPM. Install source + boot images come from
the artifactapi fedora remote via the distro catalog.
*/ -}}
{{- $mirror := .DistroVars.mirror -}}
#version=F{{ default "" .OSVersion }}
# Rendered by bootapi for {{ .FQDN }} (platform {{ .Platform }})
text
firstboot --disable
reboot
# --- install source ---
url --url={{ .BootBaseURL }}/releases/{{ default "rawhide" .OSVersion }}/Everything/{{ .Arch }}/os/
# --- install source (artifactapi fedora remote, from the distro catalog) ---
url --url={{ $mirror }}/{{ .Arch }}/os/
keyboard --xlayouts='us'
lang en_AU.UTF-8
@@ -56,11 +58,23 @@ cat > /root/.ssh/authorized_keys <<'EOF'
{{ end }}EOF
chmod 0600 /root/.ssh/authorized_keys
{{- end }}
dnf install -y https://yum.puppet.com/puppet8-release-fedora-{{ default "40" .OSVersion }}.noarch.rpm || true
dnf install -y "https://yum.puppet.com/puppet8-release-fedora-{{ default "40" .OSVersion }}.noarch.rpm" || true
dnf install -y puppet-agent
PUPPET_BIN=/opt/puppetlabs/bin/puppet
"$PUPPET_BIN" config set --section main certname "{{ .FQDN }}"
"$PUPPET_BIN" config set --section main server "{{ .PuppetServer }}"
"$PUPPET_BIN" config set --section main ca_server "{{ .PuppetCAServer }}"
install -d -m0755 /etc/sysconfig
cat > /etc/sysconfig/puppet-initial <<'EOF'
PUPPETCA_URL={{ .PuppetCAURL }}
EOF
systemctl enable puppet
{{ if and .ProvisionToken .CallbackURL -}}
curl -fsS -m 15 -X POST \
-H "Authorization: Bearer {{ .ProvisionToken }}" \
"{{ .CallbackURL }}" || echo "bootapi: provisioned callback failed (non-fatal)"
{{- end }}
%end