Initial bootapi: NetBox-driven PXE/kickstart boot service #1
@@ -0,0 +1,4 @@
|
||||
/bin/
|
||||
/dist/
|
||||
*.out
|
||||
*.test
|
||||
@@ -0,0 +1,27 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- id: check-merge-conflict
|
||||
|
||||
- repo: https://github.com/dnephin/pre-commit-golang
|
||||
rev: v0.5.1
|
||||
hooks:
|
||||
- id: go-fmt
|
||||
- id: go-mod-tidy
|
||||
|
||||
# bootapi has no root-level Go files (all under cmd/, internal/, templates/),
|
||||
# so the dnephin go-vet hook (which runs `go vet` at the repo root) fails with
|
||||
# "no Go files". Vet the whole module instead, mirroring encapi.
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: go-vet
|
||||
name: go vet
|
||||
entry: go vet ./...
|
||||
language: system
|
||||
types: [go]
|
||||
pass_filenames: false
|
||||
@@ -0,0 +1,19 @@
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
steps:
|
||||
- name: docker-build
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
repo: git.unkin.net/unkin/bootapi
|
||||
dry_run: true
|
||||
backend_options:
|
||||
kubernetes:
|
||||
serviceAccountName: default
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 1
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 2
|
||||
@@ -0,0 +1,28 @@
|
||||
when:
|
||||
- event: tag
|
||||
ref: refs/tags/v*
|
||||
|
||||
steps:
|
||||
- name: docker-bootapi
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
registry: git.unkin.net
|
||||
repo: git.unkin.net/unkin/bootapi
|
||||
build_args:
|
||||
VERSION: ${CI_COMMIT_TAG}
|
||||
username: droneci
|
||||
password:
|
||||
from_secret: DRONECI_PASSWORD
|
||||
tags:
|
||||
- ${CI_COMMIT_TAG}
|
||||
- latest
|
||||
backend_options:
|
||||
kubernetes:
|
||||
serviceAccountName: default
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 1
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 2
|
||||
@@ -0,0 +1,18 @@
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
steps:
|
||||
- name: pre-commit
|
||||
image: git.unkin.net/unkin/almalinux9-gobuilder:20260606
|
||||
commands:
|
||||
- uvx pre-commit run --all-files
|
||||
backend_options:
|
||||
kubernetes:
|
||||
serviceAccountName: default
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 1
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 2
|
||||
@@ -0,0 +1,79 @@
|
||||
when:
|
||||
- event: tag
|
||||
ref: refs/tags/v*
|
||||
|
||||
# Cuts a Gitea release with cross-compiled bootapi binaries attached. The
|
||||
# container image is built+pushed separately by docker.yaml.
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.25
|
||||
commands:
|
||||
- go test -race ./...
|
||||
backend_options:
|
||||
kubernetes:
|
||||
serviceAccountName: default
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 1
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 2
|
||||
|
||||
- name: build
|
||||
image: git.unkin.net/unkin/almalinux9-gobuilder:20260606
|
||||
commands:
|
||||
- make release-binaries VERSION=${CI_COMMIT_TAG}
|
||||
depends_on: [test]
|
||||
backend_options:
|
||||
kubernetes:
|
||||
serviceAccountName: default
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 1
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 2
|
||||
|
||||
- name: release
|
||||
image: git.unkin.net/unkin/almalinux9-base:20260606
|
||||
environment:
|
||||
RELEASER_TOKEN:
|
||||
from_secret: RELEASER_TOKEN
|
||||
commands:
|
||||
- |
|
||||
curl --output /usr/local/bin/tea https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/gitea-dl/tea/0.12.0/tea-0.12.0-linux-amd64 && chmod +x /usr/local/bin/tea
|
||||
tea logins add --name gitea --url https://git.unkin.net --token "$${RELEASER_TOKEN}" --no-version-check
|
||||
# $$ escapes shell vars so Woodpecker doesn't substitute them at parse
|
||||
# time; ${CI_COMMIT_TAG}/${CI_REPO} are real Woodpecker vars.
|
||||
CUR_SHA=$$(git rev-list -n1 "${CI_COMMIT_TAG}")
|
||||
PREV_TAG=""
|
||||
for t in $$(git tag --sort=-v:refname); do
|
||||
[ "$$t" = "${CI_COMMIT_TAG}" ] && continue
|
||||
[ "$$(git rev-list -n1 "$$t")" = "$$CUR_SHA" ] && continue
|
||||
if git merge-base --is-ancestor "$$t" "${CI_COMMIT_TAG}" 2>/dev/null; then
|
||||
PREV_TAG="$$t"; break
|
||||
fi
|
||||
done
|
||||
if [ -n "$$PREV_TAG" ]; then
|
||||
NOTES=$$(git log "$${PREV_TAG}..${CI_COMMIT_TAG}" --pretty=format:"- %s")
|
||||
else
|
||||
NOTES=$$(git log --pretty=format:"- %s")
|
||||
fi
|
||||
tea releases create --tag "${CI_COMMIT_TAG}" --title "${CI_COMMIT_TAG}" --note "$${NOTES}" --login gitea --repo "${CI_REPO}"
|
||||
ASSETS="dist/bootapi-linux-amd64 dist/bootapi-linux-arm64 dist/bootapi-darwin-amd64 dist/bootapi-darwin-arm64"
|
||||
sha256sum $$ASSETS > dist/sha256sums.txt
|
||||
tea releases assets create "${CI_COMMIT_TAG}" $$ASSETS dist/sha256sums.txt \
|
||||
--login gitea --repo "${CI_REPO}"
|
||||
depends_on: [build]
|
||||
backend_options:
|
||||
kubernetes:
|
||||
serviceAccountName: default
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
cpu: 100m
|
||||
limits:
|
||||
memory: 512Mi
|
||||
cpu: 500m
|
||||
@@ -0,0 +1,33 @@
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
steps:
|
||||
- name: lint
|
||||
image: golangci/golangci-lint:v2.5.0
|
||||
commands:
|
||||
- golangci-lint run ./...
|
||||
backend_options:
|
||||
kubernetes:
|
||||
serviceAccountName: default
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 1
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 2
|
||||
|
||||
- name: test
|
||||
image: golang:1.25
|
||||
commands:
|
||||
- go test -race ./...
|
||||
backend_options:
|
||||
kubernetes:
|
||||
serviceAccountName: default
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 1
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 2
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM golang:1.25-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG VERSION=dev
|
||||
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION}" -o bootapi ./cmd/bootapi
|
||||
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
|
||||
COPY --from=builder /build/bootapi /usr/local/bin/bootapi
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["bootapi"]
|
||||
@@ -0,0 +1,77 @@
|
||||
.PHONY: build test test-race lint fmt vet docker run clean tidy check-go release-binaries patch minor major
|
||||
|
||||
MODULE := git.unkin.net/unkin/bootapi
|
||||
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "0.0.0-dev")
|
||||
DIST := dist
|
||||
OS ?= $(shell go env GOOS)
|
||||
ARCH ?= $(shell go env GOARCH)
|
||||
|
||||
GO_VERSION_REQUIRED := 1.25
|
||||
GO_VERSION_ACTUAL := $(shell go version | sed 's/go version go\([0-9]*\.[0-9]*\).*/\1/')
|
||||
|
||||
check-go:
|
||||
@if [ "$$(printf '%s\n%s' "$(GO_VERSION_REQUIRED)" "$(GO_VERSION_ACTUAL)" | sort -V | head -1)" != "$(GO_VERSION_REQUIRED)" ]; then \
|
||||
echo "ERROR: Go >= $(GO_VERSION_REQUIRED) required, found $(GO_VERSION_ACTUAL)"; exit 1; \
|
||||
fi
|
||||
|
||||
build: check-go tidy
|
||||
go build -ldflags="-s -w -X main.version=$(VERSION)" -o bin/bootapi ./cmd/bootapi
|
||||
|
||||
test: check-go
|
||||
go test -count=1 ./...
|
||||
|
||||
test-race: check-go
|
||||
go test -race -count=1 ./...
|
||||
|
||||
# golangci-lint v2 runs in CI via the golangci/golangci-lint container; locally
|
||||
# `make vet` is the quick check and `make lint` runs golangci-lint if present.
|
||||
vet: check-go
|
||||
go vet ./...
|
||||
|
||||
lint: check-go
|
||||
@if command -v golangci-lint >/dev/null 2>&1; then golangci-lint run ./...; else echo "golangci-lint not installed; running go vet"; go vet ./...; fi
|
||||
|
||||
fmt: check-go
|
||||
gofmt -w .
|
||||
|
||||
docker:
|
||||
docker build -t bootapi:$(VERSION) .
|
||||
|
||||
run: build
|
||||
./bin/bootapi
|
||||
|
||||
# Cross-compiled binaries attached to the Gitea release (mirrors node-lookup).
|
||||
release-binaries: check-go
|
||||
@mkdir -p $(DIST)
|
||||
@for osarch in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do \
|
||||
os=$${osarch%/*}; arch=$${osarch#*/}; \
|
||||
echo "building bootapi-$$os-$$arch"; \
|
||||
CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch \
|
||||
go build -ldflags="-s -w -X main.version=$(VERSION)" \
|
||||
-o "$(DIST)/bootapi-$$os-$$arch" ./cmd/bootapi; \
|
||||
done
|
||||
|
||||
clean:
|
||||
rm -rf bin/ $(DIST)/
|
||||
|
||||
tidy:
|
||||
go mod tidy
|
||||
|
||||
# --- version bump: tag + push triggers the release pipeline ---
|
||||
_LATEST := $(shell git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' | head -1)
|
||||
_BASE := $(if $(_LATEST),$(_LATEST),v0.0.0)
|
||||
_MAJ := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f1)
|
||||
_MIN := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f2)
|
||||
_PAT := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f3)
|
||||
|
||||
patch:
|
||||
@NEW=v$(_MAJ).$(_MIN).$(shell expr $(_PAT) + 1); \
|
||||
git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW
|
||||
|
||||
minor:
|
||||
@NEW=v$(_MAJ).$(shell expr $(_MIN) + 1).0; \
|
||||
git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW
|
||||
|
||||
major:
|
||||
@NEW=v$(shell expr $(_MAJ) + 1).0.0; \
|
||||
git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW
|
||||
@@ -1,3 +1,95 @@
|
||||
|
benvin marked this conversation as resolved
|
||||
# bootapi
|
||||
|
||||
PXE/kickstart boot service replacing Cobbler. Renders kickstart + iPXE from NetBox data over HTTP. Go API.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
`bootapi` is a small Go service that replaces Cobbler's PXE/kickstart side. It
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
renders kickstart files and iPXE boot scripts from **NetBox** device data and
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
serves them over HTTP to PXE-booting hosts.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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 ENC half of Cobbler already moved to [encapi](https://git.unkin.net/unkin/encapi).
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
With bootapi, provisioning a new host is:
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
1. `terraform` the host into **NetBox** (device, interfaces/MACs, IP, platform,
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
role) and **encapi** (classification), then
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
2. rack it and let it **PXE-boot** — DHCP points it at bootapi, which serves the
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
iPXE script and the rendered kickstart; the kickstart hands off to the
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
existing Puppet firstrun bootstrap.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
## How it works
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
DHCP next-server ─▶ iPXE ─▶ GET /ipxe/<mac> ─▶ boot kernel+initrd, inst.ks=<bootapi>/ks/<host>
|
||||
|
benvin marked this conversation as resolved
benvin
commented
there should be an option to set if the host should pxeboot or not, something the client unticks at the end of kickstart. there should be an option to set if the host should pxeboot or not, something the client unticks at the end of kickstart.
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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 /ks/<host> ─▶ rendered kickstart ─▶ puppet firstrun
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
bootapi identifies the booting host by the **MAC** it booted from (NetBox
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
interface lookup → device → primary IP, platform, role, interfaces), renders a
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
Go `text/template` selected from the host's platform/role/custom-field, and
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
serves it. Templates are embedded defaults, overridable from a directory
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
(ConfigMap in k8s).
|
||||
|
benvin marked this conversation as resolved
benvin
commented
can we use a repo in git? have bootapi pull that repo every x minutes (do 3, like arogcd). the repo should have CI to ensure that the templates match strict requirements. can we use a repo in git? have bootapi pull that repo every x minutes (do 3, like arogcd). the repo should have CI to ensure that the templates match strict requirements.
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
## Endpoints (summary)
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
| Path | Purpose |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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 |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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 /ks/{ident}` | rendered kickstart (MAC or hostname) |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
| `POST /provisioned/{ident}` | end-of-kickstart callback (token) → clears `pxe_enabled` in NetBox |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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 |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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/endpoints.md](docs/endpoints.md) — the PXE flow, every endpoint, error/fallback behavior, metrics.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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/data-model.md](docs/data-model.md) — the exact template data model + NetBox custom fields + template selection.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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/template-authoring.md](docs/template-authoring.md) — writing/overriding kickstart & iPXE templates.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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/deployment.md](docs/deployment.md) — Kubernetes/argocd wiring, Vault secrets, and the DHCP cutover.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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/security.md](docs/security.md) — what belongs in NetBox vs Vault; secrets in kickstarts.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
## Configuration
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
Env-based (12-factor), see [`config.example.env`](config.example.env). Key vars:
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
`BOOTAPI_NETBOX_URL`, `BOOTAPI_NETBOX_TOKEN[_FILE]`, `BOOTAPI_BASE_URL`,
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
`BOOTAPI_BOOT_BASE_URL`, `BOOTAPI_ROOT_PASSWORD_HASH[_FILE]`,
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
`BOOTAPI_UNKNOWN_MAC_FALLBACK`.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
## Development
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
```bash
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
make build # build ./bin/bootapi
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
make test-race # go test -race ./...
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
make lint # golangci-lint (v2) if installed, else go vet
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
make run # build + run
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
CI (Woodpecker): `pre-commit`, `golangci-lint v2` + `go test -race`, and a
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
docker build on PRs; on a `v*` tag, a container image push and a Gitea binary
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
release. Cut a release with `make patch|minor|major` (tags + pushes).
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
## Layout
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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/config/ env config
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. Added a NetBox 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).
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
unkinben
commented
Done. bootapi now git-syncs a templates repo ( 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.
|
||||
@@ -0,0 +1,172 @@
|
||||
// Command bootapi is the PXE/kickstart boot service: it renders kickstart files
|
||||
// and iPXE boot scripts from NetBox device data and serves them to PXE-booting
|
||||
// hosts, replacing Cobbler's provisioning side.
|
||||
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"
|
||||
"git.unkin.net/unkin/bootapi/templates"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
slog.Error("load config", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if cfg.NetBoxURL == "" {
|
||||
slog.Warn("BOOTAPI_NETBOX_URL is empty; every host lookup will fail and iPXE will serve the fallback")
|
||||
}
|
||||
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)")
|
||||
}
|
||||
|
||||
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,
|
||||
Token: cfg.NetBoxToken,
|
||||
Timeout: cfg.NetBoxTimeout,
|
||||
Insecure: cfg.NetBoxInsecure,
|
||||
})
|
||||
cache := netbox.NewCache(nb, cfg.CacheTTL)
|
||||
|
||||
opts := server.Options{
|
||||
NetBox: cache,
|
||||
Engine: engine,
|
||||
Cache: cache,
|
||||
UnknownMACFallback: cfg.UnknownMACFallback,
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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.
unkinben
commented
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).
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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 parallel HTTPS listener for clients that DO trust the internal CA.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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) ---
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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_URL=https://netbox.k8s.syd1.au.unkin.net
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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=
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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_FILE=/var/run/secrets/netbox/api_token
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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_TIMEOUT=5s
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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_INSECURE=false
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
benvin
commented
there is already an almalinux remote in artifactapi. 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?
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
benvin
commented
puppet in kubernetes instead puppet in kubernetes instead
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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.
|
||||
# --- URLs baked into rendered output ---
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
benvin
commented
this should be the bind resolvers in k8s now this should be the bind resolvers in k8s now
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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) ---
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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.
|
||||
# crypt(3) hash for the root account. Empty => root account locked.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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_ROOT_PASSWORD_HASH=
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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_ROOT_PASSWORD_HASH_FILE=/var/run/secrets/bootapi/root_password_hash
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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.
|
||||
# Newline-separated SSH public keys installed for root.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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_SSH_AUTHORIZED_KEYS=
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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.
|
||||
# --- unknown-MAC fallback: "local" (safe: boot local disk) or "shell" (debug) ---
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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_UNKNOWN_MAC_FALLBACK=local
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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).
unkinben
commented
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.
unkinben
commented
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.
|
||||
@@ -0,0 +1,94 @@
|
||||
# Template data model
|
||||
|
||||
Every kickstart and iPXE template is rendered with Go `text/template` against a
|
||||
single flat value. This is the exact, stable contract template authors code
|
||||
against. It is assembled in `internal/render.dataFor` from a NetBox device
|
||||
(`internal/netbox`) plus render-time config (env/Vault).
|
||||
|
||||
## Kickstart templates (`*.ks.tmpl`)
|
||||
|
||||
| Field | Type | Source | Notes |
|
||||
|-------|------|--------|-------|
|
||||
| `.Hostname` | string | NetBox device name | short name, e.g. `web01` |
|
||||
| `.Domain` | string | NetBox CF `domain`, else `BOOTAPI_DOMAIN` | |
|
||||
| `.FQDN` | string | derived | `Hostname.Domain` (or `Hostname` if no domain) |
|
||||
| `.Platform` | string | NetBox platform slug | e.g. `almalinux9` — primary template-selection key |
|
||||
| `.OSFamily` | string | derived from platform | e.g. `almalinux` |
|
||||
| `.OSVersion` | string | derived from platform | e.g. `9` |
|
||||
| `.Arch` | string | fixed `x86_64` (today) | |
|
||||
| `.Role` | string | NetBox device role slug | e.g. `kubernetes-worker` |
|
||||
| `.Interfaces` | `[]Interface` | NetBox interfaces + IPs | primary interface sorted first |
|
||||
| `.PrimaryInterface` | `*Interface` | derived | the NIC carrying the primary IP (or first) |
|
||||
| `.PrimaryIP` | string | NetBox device `primary_ip` | address only, no prefix |
|
||||
| `.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.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`
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `.Name` | string | NetBox interface name, e.g. `eth0` |
|
||||
| `.MAC` | string | normalized lower-case colon form |
|
||||
| `.IP` | string | address only (empty ⇒ no IP; skip in the network stanza) |
|
||||
| `.PrefixLen` | int | CIDR length, e.g. `24` |
|
||||
| `.Netmask` | string | dotted-quad, e.g. `255.255.255.0` |
|
||||
| `.Gateway` | string | per-IP CF `gateway`, else device CF `gateway`, else empty |
|
||||
| `.VLAN` | int | untagged VLAN id, or 0 |
|
||||
| `.Primary` | bool | true for the NIC with the primary IP |
|
||||
|
||||
## iPXE templates (`*.ipxe.tmpl`)
|
||||
|
||||
Rendered with everything above **plus**:
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `.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.
|
||||
|
||||
## NetBox custom fields bootapi reads
|
||||
|
||||
Define these on the *device* (or, where noted, the *IP address*) in NetBox.
|
||||
All are optional; sensible fallbacks apply.
|
||||
|
||||
| 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. |
|
||||
|
||||
## Distro selection and the catalog
|
||||
|
||||
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).
|
||||
|
||||
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`.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Deploying bootapi
|
||||
|
||||
> The actual argocd-apps deployment is a **follow-up task** and is intentionally
|
||||
> not part of this repo. This document is the spec for that follow-up plus the
|
||||
> DHCP change the estate needs.
|
||||
|
||||
bootapi is a stateless HTTP service. It mirrors encapi's deployment shape: a Go
|
||||
binary in a distroless image, config from env, secrets from Vault via the Vault
|
||||
Secrets Operator (VSO).
|
||||
|
||||
## Container image
|
||||
|
||||
`git.unkin.net/unkin/bootapi:<tag>` (built + pushed by `.woodpecker/docker.yaml`
|
||||
on a `v*` tag). Also mirror to the artifactapi local docker registry if desired.
|
||||
|
||||
## Kubernetes wiring (argocd-apps follow-up)
|
||||
|
||||
Create `apps/base/bootapi/` following the argocd-apps `AGENTS.md` pattern:
|
||||
|
||||
1. **namespace** `bootapi`.
|
||||
2. **VaultAuth** (`default`) — kubernetes method, mount `k8s/au/syd1`, role
|
||||
`default`, SA `default` (copy netbox's `vaultauth.yaml`).
|
||||
3. **VaultStaticSecret** → k8s Secret `bootapi-secrets`, from Vault kv path
|
||||
`kubernetes/namespace/bootapi/default/bootapi-secrets` with keys:
|
||||
- `netbox_token` — a **dedicated** NetBox API token for bootapi. It needs
|
||||
**read on devices/interfaces/ip-addresses AND write on the device
|
||||
`pxe_enabled` custom field** (the provisioned callback PATCHes it — see
|
||||
[security.md](security.md#netbox-write-scope)). Create a `bootapi` NetBox
|
||||
user/token via terraform-netbox rather than reusing the seeded superuser
|
||||
token at `kv/kubernetes/namespace/netbox/default/netbox-superuser`.
|
||||
- `provision_token` — the shared bearer token guarding `POST /provisioned`
|
||||
(also embedded in rendered kickstarts). Generate a random value.
|
||||
- `root_password_hash` — crypt(3) hash for the installed root account
|
||||
(the successor to Cobbler's eyaml `default_password_crypted`).
|
||||
- `ssh_authorized_keys` — optional, newline-separated.
|
||||
4. **Templates**: prefer git-sync — set `BOOTAPI_TEMPLATE_GIT_URL` to
|
||||
`https://git.unkin.net/unkin/bootapi-templates.git` (public; no token needed)
|
||||
and bootapi pulls it every `BOOTAPI_TEMPLATE_GIT_INTERVAL` (default 3m). No
|
||||
ConfigMap or pod restart is needed to change templates — merge to the repo's
|
||||
`main` and bootapi reloads within the interval (last-good kept on a bad push).
|
||||
The embedded defaults remain the fallback if the repo is unreachable. (A
|
||||
`BOOTAPI_TEMPLATE_DIR` ConfigMap is still supported for air-gapped installs.)
|
||||
5. **Deployment** — image above, env from `config.example.env`, secret keys wired
|
||||
as `BOOTAPI_NETBOX_TOKEN_FILE` / `BOOTAPI_PROVISION_TOKEN_FILE` /
|
||||
`BOOTAPI_ROOT_PASSWORD_HASH_FILE` (mount the Secret). Least-privilege
|
||||
securityContext (`runAsNonRoot`, `drop: [all]`). Baseline resources: requests
|
||||
`512Mi`/`1`, limits `2Gi`/`2` cpu. The pod needs `git` on PATH for template
|
||||
sync (the distroless image includes only the static binary — either add a git
|
||||
layer, use an initContainer that seeds the checkout, or fall back to a
|
||||
ConfigMap; simplest is a small alpine+git base for this service).
|
||||
6. **Service + exposure**: see the Gateway section below.
|
||||
7. Register in `argocd/applicationsets/platform.yaml` (`apps/overlays/*/bootapi`)
|
||||
and the platform AppProject destinations.
|
||||
|
||||
### Gateway: HTTP and HTTPS
|
||||
|
||||
PXE installers do **not** trust the internal CA, so the boot path must be served
|
||||
over **plain HTTP**. Unlike the estate default, the bootapi HTTPRoute must **not
|
||||
blanket-301 HTTP→HTTPS**:
|
||||
|
||||
- A **plain-HTTP** listener/HTTPRoute (or a LoadBalancer Service on port 80→8000)
|
||||
reachable by PXE clients at a stable address/hostname on the PXE VLAN — this is
|
||||
the `BOOTAPI_BASE_URL` DHCP/iPXE points at. No redirect.
|
||||
- Optionally an **HTTPS** HTTPRoute for humans/tooling that do trust the CA
|
||||
(bootapi can serve TLS directly via `BOOTAPI_TLS_*`, or terminate at the
|
||||
gateway). This is additive; it must not replace or redirect the HTTP boot path.
|
||||
|
||||
The end-of-kickstart callback (`POST /provisioned`) runs over the same plain-HTTP
|
||||
base by default (the token authenticates it; the install has no CA trust yet). If
|
||||
you install the internal CA early in `%post`, you may set
|
||||
`BOOTAPI_CALLBACK_BASE_URL` to the HTTPS URL instead.
|
||||
|
||||
### Cross-repo dependencies (per estate conventions)
|
||||
|
||||
- **terraform-git**: `unkin/bootapi-templates` repo (this PR's sibling) holds the
|
||||
live template set + distro catalog + validation CI.
|
||||
- **argocd-apps**: add a `serviceaccount_*` under `apps/base/woodpecker/` if the
|
||||
bootapi pipelines need a dedicated SA (they use `default` today).
|
||||
- **terraform-vault**: add the k8s auth role + kv policy granting the `bootapi`
|
||||
namespace read on `kv/kubernetes/namespace/bootapi/default/*`.
|
||||
- **terraform-netbox**: create the `bootapi` NetBox token (read + write on the
|
||||
`pxe_enabled` device custom field) and seed it, `provision_token` and
|
||||
`root_password_hash` into the Vault kv path above. Also define the `pxe_enabled`
|
||||
boolean custom field on the Device model.
|
||||
|
||||
## DHCP change (the cutover)
|
||||
|
||||
Cobbler advertised itself at anycast `198.18.19.19` as the DHCP `next-server`,
|
||||
with `filename "/ipxe.efi"` (UEFI arch 7/9) or `/undionly.kpxe` (BIOS arch 0).
|
||||
Today those are set in `puppet-prod` hieradata
|
||||
`hieradata/roles/infra/dhcp/server.yaml` (`pools.*.pxeserver` and the
|
||||
`UEFI-64`/`Legacy` dhcp classes).
|
||||
|
||||
To cut a subnet over to bootapi, repoint DHCP for that pool:
|
||||
|
||||
- `next-server` → bootapi's LB IP (or keep the `198.18.19.19` anycast and move
|
||||
the anycast advertisement to bootapi's node/LB).
|
||||
- `filename` → the iPXE binary as before (`/ipxe.efi` / `/undionly.kpxe`); bootapi
|
||||
does not serve the NBP itself. The chained iPXE must then be told to fetch
|
||||
bootapi's script — either bake `chain http://<bootapi>/ipxe/${net0/mac}` into
|
||||
the site iPXE binary/embedded script, or set DHCP option 67 to that URL for
|
||||
iPXE user-class requests. This replaces Cobbler's
|
||||
`chain http://${next-server}/cblr/svc/op/gpxe/mac/${net0/mac}`.
|
||||
|
||||
Roll one pool at a time (the PXE subnets are `198.18.13.0/24`–`198.18.17.0/24`);
|
||||
Puppet autosign already trusts those subnets and `*.main.unkin.net`, so a host
|
||||
installed via bootapi checks in exactly as before.
|
||||
@@ -0,0 +1,100 @@
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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.
unkinben
commented
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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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
benvin
commented
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.
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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.
|
||||
DHCP ── next-server + filename (ipxe.efi / undionly.kpxe) ──▶ firmware loads iPXE
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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.
|
||||
| Method | Path | Purpose |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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 | `/ipxe/{mac}` | iPXE boot script for the host owning `{mac}`. `{mac}` may use `:`/`-`/`.` separators or be bare hex; a trailing `.ipxe` is stripped. |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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. |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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}` | End-of-kickstart callback; clears `pxe_enabled` in NetBox. **Token-guarded** (`Authorization: Bearer <BOOTAPI_PROVISION_TOKEN>`). |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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`. |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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). |
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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.
|
||||
## Host identification
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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 booting host is identified by the **MAC** of the NIC it PXE-booted from
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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:
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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.
|
||||
All on `/metrics`, prefix `bootapi_`:
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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_requests_total{endpoint,status}` — endpoint = `ipxe|ks|healthz|readyz`, status = `2xx|3xx|4xx|5xx`.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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_render_total{kind,result}` — kind = `kickstart|ipxe`, result = `ok|error`.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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`.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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_lookup_duration_seconds{field}` — histogram.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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_cache_hits_total` / `bootapi_netbox_cache_misses_total`.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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_provisioned_total{result}` — result = `ok|unauthorized|notfound|error|disabled`.
|
||||
|
benvin marked this conversation as resolved
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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
unkinben
commented
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.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Security: secrets in kickstarts
|
||||
|
||||
A kickstart is fetched over the network by an unauthenticated installer and can
|
||||
embed real secrets: the root password hash, SSH keys, bootstrap tokens, repo
|
||||
credentials. bootapi's rule is:
|
||||
|
||||
**NetBox holds identity and topology, never secrets. Secrets are injected at
|
||||
render time from Vault/env.**
|
||||
|
||||
## What goes where
|
||||
|
||||
| Value | Where it lives | How it reaches the template |
|
||||
|-------|----------------|-----------------------------|
|
||||
| hostname, domain, IPs, MACs, VLANs, gateway, platform, role | NetBox | `internal/netbox` → `model.Host` |
|
||||
| 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
|
||||
arrive as Vault-mounted files rather than env, which is the k8s norm (see
|
||||
[deployment.md](deployment.md)). If no root hash is configured, the default
|
||||
templates emit `rootpw --lock` rather than a blank/guessable password.
|
||||
|
||||
This matches the pre-bootapi setup, where the root hash was Cobbler's eyaml
|
||||
`default_password_crypted` injected into `settings.yaml` — an operator-managed
|
||||
secret, never in the NetBox/inventory layer.
|
||||
|
||||
## Exposure notes
|
||||
|
||||
- Kickstarts are served over **HTTP** to the installer, so treat any embedded
|
||||
secret as visible to anything on the provisioning VLAN. Keep bootapi's
|
||||
kickstart endpoint on the trusted PXE network, exactly as Cobbler's was.
|
||||
- The root password hash *is* in the rendered kickstart by necessity (Anaconda
|
||||
needs it). Prefer SSH-key login + a locked or strong-random root password, and
|
||||
rotate the hash in Vault as normal.
|
||||
- The **puppet bootstrap uses no long-lived token**: the host generates a CSR and
|
||||
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
|
||||
set for the fleet), which covers the current estate. If per-host or per-role
|
||||
secrets are ever needed (e.g. a distinct bootstrap token per role), the seam is
|
||||
`internal/render.dataFor`: add a Vault kv fetch keyed by host/role there, behind
|
||||
an interface, the same way encapi's `internal/distro` resolver injects per-host
|
||||
params behind an interface. Tracked as a follow-up, not implemented, to avoid
|
||||
giving bootapi broad Vault read scope before it's needed.
|
||||
@@ -0,0 +1,117 @@
|
||||
# Authoring templates
|
||||
|
||||
bootapi ships an embedded default set and lets you override or extend it.
|
||||
|
||||
## Where templates live
|
||||
|
||||
- **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
|
||||
|
||||
- Kickstart: `<name>.ks.tmpl` → registered as template `<name>`.
|
||||
- iPXE: `<name>.ipxe.tmpl` → registered as template `<name>`.
|
||||
|
||||
`<name>` is what template selection matches against (platform slug, OS family,
|
||||
`provision_template`, or the configured default — see
|
||||
[data-model.md](data-model.md#template-selection-precedence)).
|
||||
|
||||
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`,
|
||||
`default` (`{{ default "x" .Maybe }}` → `.Maybe` unless empty). The data model is
|
||||
in [data-model.md](data-model.md).
|
||||
|
||||
Example network stanza (iterate interfaces, skip those without an IP, set the
|
||||
hostname on the primary):
|
||||
|
||||
```gotemplate
|
||||
{{- $primary := .PrimaryInterface }}
|
||||
{{- range .Interfaces }}
|
||||
{{- if .IP }}
|
||||
network --bootproto=static --device={{ .MAC }} --ip={{ .IP }} --netmask={{ .Netmask }}{{ if .Gateway }} --gateway={{ .Gateway }}{{ end }}{{ range $.Nameservers }} --nameserver={{ . }}{{ end }}{{ if and $primary (eq .MAC $primary.MAC) }} --hostname={{ $.FQDN }}{{ end }} --activate
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
## What the default AlmaLinux template does (ported from Cobbler)
|
||||
|
||||
The literal `.ks` bodies from the old Cobbler server are not in version control
|
||||
(they lived in `/var/lib/cobbler/{templates,snippets}` on the Cobbler host). The
|
||||
embedded `almalinux9.ks.tmpl` reproduces the **contract** that estate relied on:
|
||||
|
||||
- static per-interface networking from NetBox, hostname on the primary NIC;
|
||||
- `rootpw --iscrypted` from the render-time hash (Cobbler's
|
||||
`default_password_crypted`), or `--lock` when unset;
|
||||
- a minimal package set + `openssh-server`, `chrony`;
|
||||
- a `%post` that installs the Puppet agent, points it at `puppet.query.consul` /
|
||||
`puppetca.query.consul`, and enables it — handing off to the existing
|
||||
`profiles::firstrun` Puppet bootstrap and autosign, exactly as the Cobbler
|
||||
kickstart did.
|
||||
|
||||
Adjust partitioning, package sets and repos to taste; keep the puppet `%post`
|
||||
handoff so a freshly-installed host still checks in and converges.
|
||||
|
||||
## Testing a template locally
|
||||
|
||||
```bash
|
||||
BOOTAPI_NETBOX_URL=... BOOTAPI_NETBOX_TOKEN=... \
|
||||
BOOTAPI_BASE_URL=http://localhost:8000 \
|
||||
BOOTAPI_BOOT_BASE_URL=http://mirror/almalinux/9 \
|
||||
BOOTAPI_TEMPLATE_DIR=./mytemplates ./bin/bootapi &
|
||||
curl -s localhost:8000/ks/web01 # rendered kickstart
|
||||
curl -s localhost:8000/ipxe/aa:bb:cc:00:11:22
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
module git.unkin.net/unkin/bootapi
|
||||
|
||||
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
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
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=
|
||||
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||
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=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
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=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// Package config loads bootapi server configuration from the environment,
|
||||
// following the same env-first convention as encapi.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config is the fully-resolved server configuration.
|
||||
type Config struct {
|
||||
// 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. 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.
|
||||
CacheTTL time.Duration
|
||||
|
||||
// TemplateDir, when set, is a directory of override templates layered on
|
||||
// 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 no catalog/platform
|
||||
// selection key matches.
|
||||
DefaultTemplate 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
|
||||
|
||||
// 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 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 records none.
|
||||
Domain string
|
||||
// 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 (Vault in k8s). Empty locks the root account.
|
||||
RootPasswordHash string
|
||||
// SSHAuthorizedKeys are public keys installed for root at render time.
|
||||
SSHAuthorizedKeys []string
|
||||
|
||||
// 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. *_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 {
|
||||
return nil, fmt.Errorf("invalid BOOTAPI_CACHE_TTL: %w", err)
|
||||
}
|
||||
nbTimeout, err := time.ParseDuration(getenv("BOOTAPI_NETBOX_TIMEOUT", "5s"))
|
||||
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, 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")
|
||||
if fallback != "local" && fallback != "shell" {
|
||||
return nil, fmt.Errorf("invalid BOOTAPI_UNKNOWN_MAC_FALLBACK %q: want \"local\" or \"shell\"", fallback)
|
||||
}
|
||||
|
||||
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"),
|
||||
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
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// splitList splits a comma-separated env value into a trimmed, non-empty slice.
|
||||
func splitList(v string) []string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
for _, p := range strings.Split(v, ",") {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// splitLines splits a newline-separated env value (e.g. multiple SSH keys) into
|
||||
// a trimmed, non-empty slice.
|
||||
func splitLines(v string) []string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
for _, p := range strings.Split(v, "\n") {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoadDefaults(t *testing.T) {
|
||||
clearEnv(t)
|
||||
c, err := Load()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.ListenAddr != ":8000" {
|
||||
t.Errorf("ListenAddr = %q", c.ListenAddr)
|
||||
}
|
||||
if c.CacheTTL != 30*time.Second {
|
||||
t.Errorf("CacheTTL = %v", c.CacheTTL)
|
||||
}
|
||||
if c.DefaultTemplate != "almalinux9" {
|
||||
t.Errorf("DefaultTemplate = %q", c.DefaultTemplate)
|
||||
}
|
||||
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) {
|
||||
clearEnv(t)
|
||||
dir := t.TempDir()
|
||||
tf := filepath.Join(dir, "token")
|
||||
if err := os.WriteFile(tf, []byte(" secret-token\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("BOOTAPI_NETBOX_TOKEN_FILE", tf)
|
||||
c, err := Load()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.NetBoxToken != "secret-token" {
|
||||
t.Errorf("token = %q, want trimmed file contents", c.NetBoxToken)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRejectsBadFallback(t *testing.T) {
|
||||
clearEnv(t)
|
||||
t.Setenv("BOOTAPI_UNKNOWN_MAC_FALLBACK", "bogus")
|
||||
if _, err := Load(); err == nil {
|
||||
t.Fatal("expected error for invalid fallback")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadListsAndTrim(t *testing.T) {
|
||||
clearEnv(t)
|
||||
t.Setenv("BOOTAPI_NAMESERVERS", " 10.0.0.1, 10.0.0.2 ,")
|
||||
t.Setenv("BOOTAPI_NETBOX_URL", "https://netbox.example.net/")
|
||||
c, err := Load()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(c.Nameservers) != 2 || c.Nameservers[1] != "10.0.0.2" {
|
||||
t.Errorf("nameservers = %v", c.Nameservers)
|
||||
}
|
||||
if c.NetBoxURL != "https://netbox.example.net" {
|
||||
t.Errorf("NetBoxURL trailing slash not trimmed: %q", c.NetBoxURL)
|
||||
}
|
||||
}
|
||||
|
||||
// clearEnv unsets every BOOTAPI_* var so a developer's shell can't leak into
|
||||
// the test. t.Setenv restores them after the test.
|
||||
func clearEnv(t *testing.T) {
|
||||
t.Helper()
|
||||
for _, kv := range os.Environ() {
|
||||
if k, _, ok := cut(kv, '='); ok && len(k) > 8 && k[:8] == "BOOTAPI_" {
|
||||
// t.Setenv to "" is enough: Load treats empty as unset, and the
|
||||
// test framework restores the original value on cleanup.
|
||||
t.Setenv(k, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cut(s string, sep byte) (before, after string, found bool) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == sep {
|
||||
return s[:i], s[i+1:], true
|
||||
}
|
||||
}
|
||||
return s, "", false
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -0,0 +1,119 @@
|
||||
// Package model holds the provisioning data model bootapi renders templates
|
||||
// against. A Host is the normalized view of a NetBox device: enough to build a
|
||||
// kickstart and an iPXE boot script without the template author needing to know
|
||||
// anything about NetBox's API shapes.
|
||||
package model
|
||||
|
||||
// Host is the fully-resolved provisioning view of a single machine.
|
||||
//
|
||||
// Every field here is safe to reference from a kickstart or iPXE template. The
|
||||
// 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".
|
||||
Domain string
|
||||
// FQDN is Hostname joined to Domain when a domain is known, else Hostname.
|
||||
FQDN string
|
||||
|
||||
// Platform is the NetBox platform slug, e.g. "almalinux9". It is the
|
||||
// primary template-selection key.
|
||||
Platform string
|
||||
// OSFamily is a coarse family derived from Platform ("almalinux",
|
||||
// "fedora", "rocky", ...). Handy for shared template logic.
|
||||
OSFamily string
|
||||
// OSVersion is the major version string when derivable, e.g. "9".
|
||||
OSVersion string
|
||||
// Arch is the CPU architecture, defaulting to "x86_64".
|
||||
Arch string
|
||||
|
||||
// Role is the NetBox device role slug, e.g. "kubernetes-worker". Available
|
||||
// as a secondary template-selection key and for %post logic.
|
||||
Role string
|
||||
|
||||
// Interfaces are the host's network interfaces, primary first.
|
||||
Interfaces []Interface
|
||||
|
||||
// PrimaryIP is the address of the primary interface (no prefix length),
|
||||
// e.g. "10.0.1.20". Empty when NetBox has no primary IP set.
|
||||
PrimaryIP string
|
||||
|
||||
// Nameservers are DNS resolvers to configure, when NetBox provides them
|
||||
// (via a custom field); otherwise empty and templates fall back to a
|
||||
// site default.
|
||||
Nameservers []string
|
||||
|
||||
// RootPasswordHash is a crypt(3) hash for the root account, sourced at
|
||||
// render time (env/Vault), NOT stored in NetBox. Empty means "locked
|
||||
// account / template default".
|
||||
RootPasswordHash string
|
||||
|
||||
// SSHAuthorizedKeys are public keys to install for root, sourced at render
|
||||
// time. Empty means none.
|
||||
SSHAuthorizedKeys []string
|
||||
|
||||
// TemplateOverride, when non-empty, names the template to use verbatim,
|
||||
// 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".
|
||||
Name string
|
||||
// MAC is the normalized (lower-case, colon-separated) hardware address.
|
||||
MAC string
|
||||
// IP is the interface address without prefix, e.g. "10.0.1.20". Empty for
|
||||
// interfaces with no assigned address.
|
||||
IP string
|
||||
// PrefixLen is the CIDR prefix length of IP, e.g. 24. Zero when unknown.
|
||||
PrefixLen int
|
||||
// Netmask is the dotted-quad form of PrefixLen, e.g. "255.255.255.0".
|
||||
Netmask string
|
||||
// Gateway is the default gateway for this interface's prefix, when NetBox
|
||||
// records one on the prefix. Empty otherwise.
|
||||
Gateway string
|
||||
// VLAN is the untagged VLAN id of the interface, or 0 when none.
|
||||
VLAN int
|
||||
// Primary reports whether this interface holds the device's primary IP.
|
||||
Primary bool
|
||||
}
|
||||
|
||||
// PrimaryInterface returns the primary interface (the one carrying the primary
|
||||
// IP), falling back to the first interface, or nil when there are none.
|
||||
func (h *Host) PrimaryInterface() *Interface {
|
||||
for i := range h.Interfaces {
|
||||
if h.Interfaces[i].Primary {
|
||||
return &h.Interfaces[i]
|
||||
}
|
||||
}
|
||||
if len(h.Interfaces) > 0 {
|
||||
return &h.Interfaces[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package netbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/model"
|
||||
)
|
||||
|
||||
// Cache wraps a Resolver with a short-TTL in-memory cache. PXE boots come in
|
||||
// bursts (iPXE fetches the boot script, then the kickstart, then package repos
|
||||
// hit repeatedly), so even a 30s TTL collapses many NetBox lookups per host
|
||||
// while keeping the data fresh enough that a re-provisioned host picks up
|
||||
// changes on its next boot.
|
||||
type Cache struct {
|
||||
inner API
|
||||
ttl time.Duration
|
||||
now func() time.Time // injectable for tests
|
||||
|
||||
mu sync.Mutex
|
||||
entries map[string]cacheEntry
|
||||
|
||||
hits atomic.Int64
|
||||
misses atomic.Int64
|
||||
}
|
||||
|
||||
// Hits returns the cumulative cache-hit count (published as a metric).
|
||||
func (c *Cache) Hits() int64 { return c.hits.Load() }
|
||||
|
||||
// Misses returns the cumulative cache-miss count (published as a metric).
|
||||
func (c *Cache) Misses() int64 { return c.misses.Load() }
|
||||
|
||||
type cacheEntry struct {
|
||||
host *model.Host
|
||||
exp time.Time
|
||||
}
|
||||
|
||||
// NewCache wraps inner with a TTL cache. A non-positive ttl disables caching.
|
||||
func NewCache(inner API, ttl time.Duration) *Cache {
|
||||
return &Cache{
|
||||
inner: inner,
|
||||
ttl: ttl,
|
||||
now: time.Now,
|
||||
entries: map[string]cacheEntry{},
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return c.inner.HostByMAC(ctx, mac)
|
||||
})
|
||||
}
|
||||
|
||||
// HostByName returns a cached host or resolves and caches one.
|
||||
func (c *Cache) HostByName(ctx context.Context, name string) (*model.Host, error) {
|
||||
return c.lookup(ctx, "name:"+name, func() (*model.Host, error) {
|
||||
return c.inner.HostByName(ctx, name)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) lookup(_ context.Context, key string, resolve func() (*model.Host, error)) (*model.Host, error) {
|
||||
if c.ttl <= 0 {
|
||||
return resolve()
|
||||
}
|
||||
now := c.now()
|
||||
|
||||
c.mu.Lock()
|
||||
if e, ok := c.entries[key]; ok && now.Before(e.exp) {
|
||||
c.mu.Unlock()
|
||||
c.hits.Add(1)
|
||||
return e.host, nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.misses.Add(1)
|
||||
|
||||
// Resolve outside the lock so a slow NetBox call doesn't block cache hits.
|
||||
host, err := resolve()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.entries[key] = cacheEntry{host: host, exp: now.Add(c.ttl)}
|
||||
c.mu.Unlock()
|
||||
return host, nil
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package netbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/model"
|
||||
)
|
||||
|
||||
// countingResolver records how many times the underlying resolver is hit.
|
||||
type countingResolver struct {
|
||||
mu sync.Mutex
|
||||
calls int
|
||||
writes int
|
||||
host *model.Host
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *countingResolver) HostByMAC(context.Context, string) (*model.Host, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.calls++
|
||||
return c.host, c.err
|
||||
}
|
||||
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"}}
|
||||
cache := NewCache(inner, time.Minute)
|
||||
|
||||
now := time.Unix(1000, 0)
|
||||
cache.now = func() time.Time { return now }
|
||||
|
||||
// First call misses and resolves.
|
||||
if _, err := cache.HostByMAC(context.Background(), "aa:bb:cc:00:11:22"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Second call within TTL is a hit; the inner resolver is not called again.
|
||||
if _, err := cache.HostByMAC(context.Background(), "AA:BB:CC:00:11:22"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if inner.calls != 1 {
|
||||
t.Fatalf("inner calls = %d, want 1 (second served from cache)", inner.calls)
|
||||
}
|
||||
if cache.Hits() != 1 || cache.Misses() != 1 {
|
||||
t.Fatalf("hits=%d misses=%d, want 1/1", cache.Hits(), cache.Misses())
|
||||
}
|
||||
|
||||
// Advance past the TTL -> next call misses and re-resolves.
|
||||
now = now.Add(2 * time.Minute)
|
||||
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 after expiry", inner.calls)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
for i := 0; i < 3; i++ {
|
||||
if _, err := cache.HostByMAC(context.Background(), "aa:bb:cc:00:11:22"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if inner.calls != 3 {
|
||||
t.Fatalf("inner calls = %d, want 3 (cache disabled)", inner.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheDoesNotCacheErrors(t *testing.T) {
|
||||
inner := &countingResolver{err: ErrNotFound}
|
||||
cache := NewCache(inner, time.Minute)
|
||||
for i := 0; i < 2; i++ {
|
||||
if _, err := cache.HostByMAC(context.Background(), "de:ad:be:ef:00:00"); !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("err = %v, want ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
if inner.calls != 2 {
|
||||
t.Fatalf("inner calls = %d, want 2 (errors are not cached)", inner.calls)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
// Package netbox resolves a PXE-booting machine (by MAC or hostname) into the
|
||||
// normalized model.Host that bootapi renders templates against. It talks to the
|
||||
// NetBox REST API (v4.x) behind the Resolver interface so the server can be
|
||||
// tested with an httptest fake.
|
||||
package netbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/model"
|
||||
)
|
||||
|
||||
// ErrNotFound is returned when NetBox has no device matching the query. The
|
||||
// server maps it to an HTTP 404 (and, for iPXE, a safe fallback boot script).
|
||||
var ErrNotFound = errors.New("netbox: device not found")
|
||||
|
||||
// Resolver turns a MAC or hostname into a fully-resolved Host.
|
||||
type Resolver interface {
|
||||
HostByMAC(ctx context.Context, mac string) (*model.Host, error)
|
||||
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
|
||||
token string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
// Options configures a Client.
|
||||
type Options struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
Timeout time.Duration
|
||||
Insecure bool
|
||||
// HTTPClient overrides the constructed client (used by tests).
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// New builds a NetBox Client.
|
||||
func New(o Options) *Client {
|
||||
hc := o.HTTPClient
|
||||
if hc == nil {
|
||||
tr := &http.Transport{}
|
||||
if o.Insecure {
|
||||
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // opt-in dev flag
|
||||
}
|
||||
timeout := o.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
hc = &http.Client{Timeout: timeout, Transport: tr}
|
||||
}
|
||||
return &Client{
|
||||
baseURL: strings.TrimRight(o.BaseURL, "/"),
|
||||
token: o.Token,
|
||||
http: hc,
|
||||
}
|
||||
}
|
||||
|
||||
// --- NetBox API JSON shapes (only the fields bootapi consumes) ---
|
||||
|
||||
type nbList[T any] struct {
|
||||
Count int `json:"count"`
|
||||
Results []T `json:"results"`
|
||||
}
|
||||
|
||||
type nbRef struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
|
||||
type nbVLAN struct {
|
||||
VID int `json:"vid"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type nbMAC struct {
|
||||
MACAddress string `json:"mac_address"`
|
||||
}
|
||||
|
||||
type nbDevice struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Platform *nbRef `json:"platform"`
|
||||
Role *nbRef `json:"role"`
|
||||
Site *nbRef `json:"site"`
|
||||
PrimaryIP *nbIPRef `json:"primary_ip"`
|
||||
CustomFields map[string]any `json:"custom_fields"`
|
||||
}
|
||||
|
||||
type nbIPRef struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type nbInterface struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MACAddress string `json:"mac_address"`
|
||||
PrimaryMACAddress *nbMAC `json:"primary_mac_address"`
|
||||
UntaggedVLAN *nbVLAN `json:"untagged_vlan"`
|
||||
Device *nbRef `json:"device"`
|
||||
}
|
||||
|
||||
func (i nbInterface) mac() string {
|
||||
if i.MACAddress != "" {
|
||||
return normalizeMAC(i.MACAddress)
|
||||
}
|
||||
if i.PrimaryMACAddress != nil {
|
||||
return normalizeMAC(i.PrimaryMACAddress.MACAddress)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type nbIPAddress struct {
|
||||
Address string `json:"address"`
|
||||
AssignedObjectID int `json:"assigned_object_id"`
|
||||
CustomFields map[string]any `json:"custom_fields"`
|
||||
}
|
||||
|
||||
// --- Resolver implementation ---
|
||||
|
||||
// HostByMAC finds the device owning an interface with the given MAC and
|
||||
// resolves it to a Host.
|
||||
func (c *Client) HostByMAC(ctx context.Context, mac string) (*model.Host, error) {
|
||||
mac = normalizeMAC(mac)
|
||||
if mac == "" {
|
||||
return nil, fmt.Errorf("netbox: empty MAC")
|
||||
}
|
||||
var list nbList[nbInterface]
|
||||
if err := c.get(ctx, "/api/dcim/interfaces/", url.Values{"mac_address": {mac}}, &list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var dev *nbRef
|
||||
for _, i := range list.Results {
|
||||
if i.mac() == mac && i.Device != nil {
|
||||
dev = i.Device
|
||||
break
|
||||
}
|
||||
}
|
||||
if dev == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return c.resolveDevice(ctx, dev.ID)
|
||||
}
|
||||
|
||||
// HostByName finds a device by its NetBox name and resolves it to a Host.
|
||||
func (c *Client) HostByName(ctx context.Context, name string) (*model.Host, error) {
|
||||
// Strip any domain suffix: NetBox device names are short hostnames.
|
||||
short := name
|
||||
if i := strings.IndexByte(short, '.'); i >= 0 {
|
||||
short = short[:i]
|
||||
}
|
||||
var list nbList[nbDevice]
|
||||
if err := c.get(ctx, "/api/dcim/devices/", url.Values{"name": {short}}, &list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(list.Results) == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return c.resolveDevice(ctx, list.Results[0].ID)
|
||||
}
|
||||
|
||||
// resolveDevice fetches the device, its interfaces and IP addresses, and
|
||||
// assembles a Host. It performs three bounded API calls.
|
||||
func (c *Client) resolveDevice(ctx context.Context, id int) (*model.Host, error) {
|
||||
var dev nbDevice
|
||||
if err := c.get(ctx, fmt.Sprintf("/api/dcim/devices/%d/", id), nil, &dev); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devID := url.Values{"device_id": {fmt.Sprint(id)}}
|
||||
var ifaces nbList[nbInterface]
|
||||
if err := c.get(ctx, "/api/dcim/interfaces/", devID, &ifaces); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ips nbList[nbIPAddress]
|
||||
if err := c.get(ctx, "/api/ipam/ip-addresses/", devID, &ips); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildHost(&dev, ifaces.Results, ips.Results), nil
|
||||
}
|
||||
|
||||
// buildHost assembles the normalized Host from raw NetBox objects. It is pure
|
||||
// (no I/O) so it can be unit-tested directly against fixture structs.
|
||||
func buildHost(dev *nbDevice, ifaces []nbInterface, ips []nbIPAddress) *model.Host {
|
||||
cf := dev.CustomFields
|
||||
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 {
|
||||
h.Platform = dev.Platform.Slug
|
||||
h.OSFamily, h.OSVersion = splitPlatform(dev.Platform.Slug)
|
||||
}
|
||||
if dev.Role != nil {
|
||||
h.Role = dev.Role.Slug
|
||||
}
|
||||
|
||||
// Index IP addresses by the interface they are assigned to.
|
||||
ipByIface := map[int]nbIPAddress{}
|
||||
for _, ip := range ips {
|
||||
if _, seen := ipByIface[ip.AssignedObjectID]; !seen {
|
||||
ipByIface[ip.AssignedObjectID] = ip
|
||||
}
|
||||
}
|
||||
|
||||
primaryAddr := ""
|
||||
if dev.PrimaryIP != nil {
|
||||
primaryAddr = dev.PrimaryIP.Address
|
||||
h.PrimaryIP = addrOnly(primaryAddr)
|
||||
}
|
||||
|
||||
deviceGateway := cfString(cf, "gateway")
|
||||
|
||||
for _, in := range ifaces {
|
||||
iface := model.Interface{
|
||||
Name: in.Name,
|
||||
MAC: in.mac(),
|
||||
}
|
||||
if in.UntaggedVLAN != nil {
|
||||
iface.VLAN = in.UntaggedVLAN.VID
|
||||
}
|
||||
if ip, ok := ipByIface[in.ID]; ok {
|
||||
iface.IP = addrOnly(ip.Address)
|
||||
iface.PrefixLen = prefixLen(ip.Address)
|
||||
iface.Netmask = netmaskFor(iface.PrefixLen)
|
||||
if g := cfString(ip.CustomFields, "gateway"); g != "" {
|
||||
iface.Gateway = g
|
||||
}
|
||||
if iface.IP != "" && iface.IP == h.PrimaryIP {
|
||||
iface.Primary = true
|
||||
}
|
||||
}
|
||||
if iface.Gateway == "" {
|
||||
iface.Gateway = deviceGateway
|
||||
}
|
||||
h.Interfaces = append(h.Interfaces, iface)
|
||||
}
|
||||
|
||||
sortPrimaryFirst(h.Interfaces)
|
||||
|
||||
if h.Domain != "" {
|
||||
h.FQDN = h.Hostname + "." + h.Domain
|
||||
} else {
|
||||
h.FQDN = h.Hostname
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// sortPrimaryFirst moves the primary interface to the front, preserving the
|
||||
// relative order of the rest.
|
||||
func sortPrimaryFirst(ifaces []model.Interface) {
|
||||
for i := range ifaces {
|
||||
if ifaces[i].Primary && i != 0 {
|
||||
p := ifaces[i]
|
||||
copy(ifaces[1:i+1], ifaces[0:i])
|
||||
ifaces[0] = p
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if len(q) > 0 {
|
||||
u += "?" + q.Encode()
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 request %s: %w", path, err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return ErrNotFound
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("netbox %s: HTTP %d", path, resp.StatusCode)
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return fmt.Errorf("netbox decode %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
func normalizeMAC(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
// Drop common separators then re-insert colons every 2 hex chars.
|
||||
var hex strings.Builder
|
||||
for _, r := range strings.ToLower(s) {
|
||||
if (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') {
|
||||
hex.WriteRune(r)
|
||||
}
|
||||
}
|
||||
h := hex.String()
|
||||
if len(h) != 12 {
|
||||
// Not a canonical 48-bit MAC; return lower-cased trimmed input.
|
||||
return strings.ToLower(s)
|
||||
}
|
||||
var b strings.Builder
|
||||
for i := 0; i < 12; i += 2 {
|
||||
if i > 0 {
|
||||
b.WriteByte(':')
|
||||
}
|
||||
b.WriteString(h[i : i+2])
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func addrOnly(cidr string) string {
|
||||
if i := strings.IndexByte(cidr, '/'); i >= 0 {
|
||||
return cidr[:i]
|
||||
}
|
||||
return cidr
|
||||
}
|
||||
|
||||
func prefixLen(cidr string) int {
|
||||
_, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
ones, _ := ipnet.Mask.Size()
|
||||
return ones
|
||||
}
|
||||
|
||||
func netmaskFor(prefix int) string {
|
||||
if prefix <= 0 || prefix > 32 {
|
||||
return ""
|
||||
}
|
||||
mask := net.CIDRMask(prefix, 32)
|
||||
return fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3])
|
||||
}
|
||||
|
||||
// splitPlatform derives (family, majorVersion) from a NetBox platform slug such
|
||||
// as "almalinux9" -> ("almalinux","9") or "fedora42" -> ("fedora","42").
|
||||
func splitPlatform(slug string) (family, version string) {
|
||||
slug = strings.ToLower(slug)
|
||||
i := strings.IndexFunc(slug, func(r rune) bool { return r >= '0' && r <= '9' })
|
||||
if i < 0 {
|
||||
return slug, ""
|
||||
}
|
||||
family = strings.Trim(slug[:i], "-_")
|
||||
version = slug[i:]
|
||||
if d := strings.IndexByte(version, '.'); d >= 0 {
|
||||
version = version[:d]
|
||||
}
|
||||
return family, version
|
||||
}
|
||||
|
||||
func cfString(cf map[string]any, key string) string {
|
||||
if cf == nil {
|
||||
return ""
|
||||
}
|
||||
switch v := cf[key].(type) {
|
||||
case string:
|
||||
return v
|
||||
case map[string]any: // NetBox object custom fields serialize as {value,label}
|
||||
if s, ok := v["value"].(string); ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
switch v := cf[key].(type) {
|
||||
case string:
|
||||
return splitComma(v)
|
||||
case []any:
|
||||
var out []string
|
||||
for _, e := range v {
|
||||
if s, ok := e.(string); ok && s != "" {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitComma(v string) []string {
|
||||
var out []string
|
||||
for _, p := range strings.Split(v, ",") {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package netbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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 {
|
||||
t.Helper()
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Interfaces filtered by MAC -> the interface (with nested device brief).
|
||||
mux.HandleFunc("/api/dcim/interfaces/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if got := r.Header.Get("Authorization"); got != "Token testtoken" {
|
||||
http.Error(w, `{"detail":"auth"}`, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
q := r.URL.Query()
|
||||
switch {
|
||||
case q.Get("mac_address") == "aa:bb:cc:00:11:22":
|
||||
writeJSON(w, `{"count":1,"results":[
|
||||
{"id":40,"name":"eth0","mac_address":"AA:BB:CC:00:11:22",
|
||||
"untagged_vlan":{"vid":100,"name":"prod"},
|
||||
"device":{"id":12,"name":"web01","slug":""}}]}`)
|
||||
case q.Get("device_id") == "12":
|
||||
// Full interface list for the device (two NICs).
|
||||
writeJSON(w, `{"count":2,"results":[
|
||||
{"id":40,"name":"eth0","mac_address":"AA:BB:CC:00:11:22","untagged_vlan":{"vid":100,"name":"prod"}},
|
||||
{"id":41,"name":"eth1","mac_address":"AA:BB:CC:00:11:33"}]}`)
|
||||
default:
|
||||
writeJSON(w, `{"count":0,"results":[]}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 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",
|
||||
"platform":{"id":3,"name":"AlmaLinux 9","slug":"almalinux9"},
|
||||
"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,"pxe_enabled":true}}`)
|
||||
return
|
||||
}
|
||||
// name= query (HostByName)
|
||||
if r.URL.Query().Get("name") == "web01" {
|
||||
writeJSON(w, `{"count":1,"results":[{"id":12,"name":"web01"}]}`)
|
||||
return
|
||||
}
|
||||
writeJSON(w, `{"count":0,"results":[]}`)
|
||||
})
|
||||
|
||||
// IP addresses for the device: eth0 has the primary, eth1 a second addr.
|
||||
mux.HandleFunc("/api/ipam/ip-addresses/", func(w http.ResponseWriter, _ *http.Request) {
|
||||
writeJSON(w, `{"count":2,"results":[
|
||||
{"address":"10.0.1.20/24","assigned_object_id":40,"custom_fields":{"gateway":null}},
|
||||
{"address":"10.9.9.5/24","assigned_object_id":41,"custom_fields":{"gateway":"10.9.9.1"}}]}`)
|
||||
})
|
||||
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, body string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(body))
|
||||
}
|
||||
|
||||
func newTestClient(t *testing.T, base string) *Client {
|
||||
t.Helper()
|
||||
return New(Options{BaseURL: base, Token: "testtoken"})
|
||||
}
|
||||
|
||||
func TestHostByMAC(t *testing.T) {
|
||||
srv := fakeNetBox(t)
|
||||
defer srv.Close()
|
||||
c := newTestClient(t, srv.URL)
|
||||
|
||||
h, err := c.HostByMAC(context.Background(), "AA-BB-CC-00-11-22")
|
||||
if err != nil {
|
||||
t.Fatalf("HostByMAC: %v", err)
|
||||
}
|
||||
if h.Hostname != "web01" {
|
||||
t.Errorf("hostname = %q, want web01", h.Hostname)
|
||||
}
|
||||
if h.FQDN != "web01.syd1.au.unkin.net" {
|
||||
t.Errorf("fqdn = %q", h.FQDN)
|
||||
}
|
||||
if h.Platform != "almalinux9" || h.OSFamily != "almalinux" || h.OSVersion != "9" {
|
||||
t.Errorf("platform=%q family=%q version=%q", h.Platform, h.OSFamily, h.OSVersion)
|
||||
}
|
||||
if h.Role != "kubernetes-worker" {
|
||||
t.Errorf("role = %q", h.Role)
|
||||
}
|
||||
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)
|
||||
}
|
||||
if len(h.Interfaces) != 2 {
|
||||
t.Fatalf("interfaces = %d, want 2", len(h.Interfaces))
|
||||
}
|
||||
// Primary interface (eth0, carrying the primary IP) must sort first.
|
||||
pi := h.PrimaryInterface()
|
||||
if pi == nil || pi.Name != "eth0" || !pi.Primary {
|
||||
t.Fatalf("primary interface = %+v", pi)
|
||||
}
|
||||
if pi.MAC != "aa:bb:cc:00:11:22" {
|
||||
t.Errorf("primary MAC = %q (want normalized lower-colon)", pi.MAC)
|
||||
}
|
||||
if pi.IP != "10.0.1.20" || pi.Netmask != "255.255.255.0" || pi.PrefixLen != 24 {
|
||||
t.Errorf("primary iface addr = %+v", pi)
|
||||
}
|
||||
// eth0 gateway comes from the device custom field (its IP had none).
|
||||
if pi.Gateway != "10.0.1.254" {
|
||||
t.Errorf("primary gateway = %q, want device CF 10.0.1.254", pi.Gateway)
|
||||
}
|
||||
if pi.VLAN != 100 {
|
||||
t.Errorf("primary vlan = %d", pi.VLAN)
|
||||
}
|
||||
// eth1's IP custom field gateway wins over the device default.
|
||||
for i := range h.Interfaces {
|
||||
if h.Interfaces[i].Name == "eth1" {
|
||||
if h.Interfaces[i].Gateway != "10.9.9.1" {
|
||||
t.Errorf("eth1 gateway = %q, want per-IP CF 10.9.9.1", h.Interfaces[i].Gateway)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostByMACNotFound(t *testing.T) {
|
||||
srv := fakeNetBox(t)
|
||||
defer srv.Close()
|
||||
c := newTestClient(t, srv.URL)
|
||||
|
||||
_, err := c.HostByMAC(context.Background(), "de:ad:be:ef:00:00")
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("err = %v, want ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostByName(t *testing.T) {
|
||||
srv := fakeNetBox(t)
|
||||
defer srv.Close()
|
||||
c := newTestClient(t, srv.URL)
|
||||
|
||||
// FQDN input should be reduced to the short name for the NetBox query.
|
||||
h, err := c.HostByName(context.Background(), "web01.syd1.au.unkin.net")
|
||||
if err != nil {
|
||||
t.Fatalf("HostByName: %v", err)
|
||||
}
|
||||
if h.Hostname != "web01" || h.PrimaryIP != "10.0.1.20" {
|
||||
t.Errorf("host = %+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostByNameNotFound(t *testing.T) {
|
||||
srv := fakeNetBox(t)
|
||||
defer srv.Close()
|
||||
c := newTestClient(t, srv.URL)
|
||||
if _, err := c.HostByName(context.Background(), "nope"); !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("err = %v, want ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthTokenRequired(t *testing.T) {
|
||||
srv := fakeNetBox(t)
|
||||
defer srv.Close()
|
||||
// Client with the wrong token -> NetBox 403 -> surfaced as an error.
|
||||
c := New(Options{BaseURL: srv.URL, Token: "wrong"})
|
||||
if _, err := c.HostByMAC(context.Background(), "aa:bb:cc:00:11:22"); err == nil {
|
||||
t.Fatal("expected an auth error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
"aa-bb-cc-00-11-22": "aa:bb:cc:00:11:22",
|
||||
"aabb.cc00.1122": "aa:bb:cc:00:11:22",
|
||||
"AABBCC001122": "aa:bb:cc:00:11:22",
|
||||
}
|
||||
for in, want := range cases {
|
||||
if got := normalizeMAC(in); got != want {
|
||||
t.Errorf("normalizeMAC(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitPlatform(t *testing.T) {
|
||||
cases := []struct{ in, fam, ver string }{
|
||||
{"almalinux9", "almalinux", "9"},
|
||||
{"fedora42", "fedora", "42"},
|
||||
{"rocky9.4", "rocky", "9"},
|
||||
{"debian", "debian", ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
f, v := splitPlatform(c.in)
|
||||
if f != c.fam || v != c.ver {
|
||||
t.Errorf("splitPlatform(%q) = (%q,%q), want (%q,%q)", c.in, f, v, c.fam, c.ver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetmaskFor(t *testing.T) {
|
||||
cases := map[int]string{24: "255.255.255.0", 16: "255.255.0.0", 25: "255.255.255.128", 0: ""}
|
||||
for prefix, want := range cases {
|
||||
if got := netmaskFor(prefix); got != want {
|
||||
t.Errorf("netmaskFor(%d) = %q, want %q", prefix, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
// 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, 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"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"text/template"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/catalog"
|
||||
"git.unkin.net/unkin/bootapi/internal/model"
|
||||
)
|
||||
|
||||
// Data is the exact, documented value passed to every kickstart/iPXE template.
|
||||
// It is intentionally flat: template authors get one clear namespace. See
|
||||
// docs/data-model.md.
|
||||
type Data struct {
|
||||
// --- identity (from NetBox) ---
|
||||
Hostname string
|
||||
Domain string
|
||||
FQDN string
|
||||
Platform string // NetBox platform slug, e.g. "almalinux9"
|
||||
OSFamily string // "almalinux", "fedora", ...
|
||||
OSVersion string // "9", "42", ...
|
||||
Arch string // "x86_64"
|
||||
Role string // NetBox device role slug
|
||||
|
||||
// --- network (from NetBox) ---
|
||||
Interfaces []model.Interface
|
||||
PrimaryInterface *model.Interface
|
||||
PrimaryIP string
|
||||
Nameservers []string // resolved: host value, else site default
|
||||
|
||||
// --- secrets (from Vault/env at render time, never NetBox) ---
|
||||
RootPasswordHash string
|
||||
SSHAuthorizedKeys []string
|
||||
|
||||
// --- infra pointers (render-time config) ---
|
||||
PuppetServer string
|
||||
PuppetCAServer string
|
||||
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
|
||||
}
|
||||
|
||||
// RenderConfig carries the render-time infra values merged into each Data.
|
||||
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
|
||||
SSHAuthorizedKeys []string
|
||||
DefaultTemplate string
|
||||
}
|
||||
|
||||
const (
|
||||
ksExt = ".ks.tmpl"
|
||||
ipxeExt = ".ipxe.tmpl"
|
||||
)
|
||||
|
||||
// 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)
|
||||
ksNames := map[string]bool{}
|
||||
catFiles := map[string][]byte{}
|
||||
|
||||
if err := walkSet(embedded, ks, ipxe, ksNames, catFiles, true); err != nil {
|
||||
return nil, fmt.Errorf("parse embedded templates: %w", err)
|
||||
}
|
||||
if override != nil {
|
||||
if err := walkSet(override, ks, ipxe, ksNames, catFiles, false); err != nil {
|
||||
return nil, fmt.Errorf("parse override templates: %w", err)
|
||||
}
|
||||
}
|
||||
cat, err := catalog.Parse(catFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Set{ks: ks, ipxe: ipxe, ksSet: ksNames, cat: cat}, nil
|
||||
}
|
||||
|
||||
// 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, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
walked = true
|
||||
b, err := fs.ReadFile(fsys, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
base := filepath.Base(path)
|
||||
switch {
|
||||
case strings.HasSuffix(base, ksExt):
|
||||
name := strings.TrimSuffix(base, ksExt)
|
||||
if _, err := ks.New(name).Parse(string(b)); err != nil {
|
||||
return fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
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
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mustExist && !walked {
|
||||
return fmt.Errorf("no templates found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 != "" && set.ksSet[cand] {
|
||||
return cand, true
|
||||
}
|
||||
}
|
||||
return e.cfg.DefaultTemplate, set.ksSet[e.cfg.DefaultTemplate]
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
domain := h.Domain
|
||||
if domain == "" {
|
||||
domain = e.cfg.DefaultDomain
|
||||
}
|
||||
fqdn := h.Hostname
|
||||
if domain != "" {
|
||||
fqdn = h.Hostname + "." + domain
|
||||
}
|
||||
root := h.RootPasswordHash
|
||||
if root == "" {
|
||||
root = e.cfg.RootPasswordHash
|
||||
}
|
||||
keys := h.SSHAuthorizedKeys
|
||||
if len(keys) == 0 {
|
||||
keys = e.cfg.SSHAuthorizedKeys
|
||||
}
|
||||
ksURL := ""
|
||||
if e.cfg.BaseURL != "" {
|
||||
ksURL = e.cfg.BaseURL + "/ks/" + h.Hostname
|
||||
}
|
||||
cbURL := ""
|
||||
if e.cfg.CallbackBaseURL != "" {
|
||||
cbURL = e.cfg.CallbackBaseURL + "/provisioned/" + h.Hostname
|
||||
}
|
||||
return Data{
|
||||
Hostname: h.Hostname,
|
||||
Domain: domain,
|
||||
FQDN: fqdn,
|
||||
Platform: h.Platform,
|
||||
OSFamily: h.OSFamily,
|
||||
OSVersion: h.OSVersion,
|
||||
Arch: h.Arch,
|
||||
Role: h.Role,
|
||||
Interfaces: h.Interfaces,
|
||||
PrimaryInterface: h.PrimaryInterface(),
|
||||
PrimaryIP: h.PrimaryIP,
|
||||
Nameservers: ns,
|
||||
RootPasswordHash: root,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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 := 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
|
||||
}
|
||||
|
||||
// IPXEData is the value passed to iPXE templates.
|
||||
type IPXEData struct {
|
||||
Data
|
||||
// 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) {
|
||||
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"
|
||||
}
|
||||
id.RepoURL = strings.TrimSuffix(id.KernelURL, "/images/pxeboot/vmlinuz")
|
||||
return e.execIPXE(set, "boot", id)
|
||||
}
|
||||
|
||||
// RenderFallback renders a fallback iPXE script ("local" or "shell").
|
||||
func (e *Engine) RenderFallback(kind string) ([]byte, error) {
|
||||
return e.execIPXE(e.cur.Load(), "fallback-"+kind, IPXEData{})
|
||||
}
|
||||
|
||||
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 := 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,
|
||||
"upper": strings.ToUpper,
|
||||
"lower": strings.ToLower,
|
||||
"default": func(def, v string) string { // {{ default "x" .Maybe }}
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/model"
|
||||
"git.unkin.net/unkin/bootapi/templates"
|
||||
)
|
||||
|
||||
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",
|
||||
CallbackBaseURL: "http://bootapi.example.net",
|
||||
ArtifactBase: artifactBase,
|
||||
ProvisionToken: "prov-secret",
|
||||
DefaultDomain: "main.unkin.net",
|
||||
DefaultNS: []string{"198.18.200.7"},
|
||||
RootPasswordHash: "$6$rounds=4096$abc$deadbeef",
|
||||
SSHAuthorizedKeys: []string{"ssh-ed25519 AAAAC3xxx root@ops"},
|
||||
DefaultTemplate: "almalinux9",
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return &model.Host{
|
||||
Hostname: "web01",
|
||||
Domain: "syd1.au.unkin.net",
|
||||
FQDN: "web01.syd1.au.unkin.net",
|
||||
Platform: "almalinux9",
|
||||
OSFamily: "almalinux",
|
||||
OSVersion: "9",
|
||||
Arch: "x86_64",
|
||||
Role: "kubernetes-worker",
|
||||
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 -> skipped in network stanza
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderKickstartAlma(t *testing.T) {
|
||||
e := testEngine(t, nil)
|
||||
out, name, err := e.RenderKickstart(almaHost())
|
||||
if err != nil {
|
||||
t.Fatalf("RenderKickstart: %v", err)
|
||||
}
|
||||
if name != "almalinux9" {
|
||||
t.Errorf("selected template = %q, want almalinux9", name)
|
||||
}
|
||||
ks := string(out)
|
||||
|
||||
mustContain(t, ks, "rootpw --iscrypted $6$rounds=4096$abc$deadbeef")
|
||||
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")
|
||||
|
||||
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) {
|
||||
cfg := testCfg()
|
||||
cfg.RootPasswordHash = ""
|
||||
set, err := BuildSet(templates.FS, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, _, err := NewEngine(cfg, set).RenderKickstart(almaHost())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ks := string(out)
|
||||
mustContain(t, ks, "rootpw --lock")
|
||||
if strings.Contains(ks, "--iscrypted") {
|
||||
t.Error("expected locked root, got an --iscrypted line")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectKickstartPrecedence(t *testing.T) {
|
||||
e := testEngine(t, nil)
|
||||
cases := []struct {
|
||||
host *model.Host
|
||||
want string
|
||||
}{
|
||||
{&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 {
|
||||
got, ok := e.SelectKickstart(c.host)
|
||||
if !ok || got != c.want {
|
||||
t.Errorf("SelectKickstart(%+v) = (%q,%v), want %q", c.host, got, ok, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 "+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, "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, nil)
|
||||
local, err := e.RenderFallback("local")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mustContain(t, string(local), "sanboot")
|
||||
shell, err := e.RenderFallback("shell")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mustContain(t, string(shell), "shell")
|
||||
}
|
||||
|
||||
func TestOverrideDirWins(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "almalinux9.ks.tmpl"), []byte("OVERRIDDEN {{ .Hostname }}\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
e := testEngine(t, os.DirFS(dir))
|
||||
out, _, err := e.RenderKickstart(almaHost())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.HasPrefix(string(out), "OVERRIDDEN web01") {
|
||||
t.Errorf("override not applied: %q", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func mustContain(t *testing.T, haystack, needle string) {
|
||||
t.Helper()
|
||||
if !strings.Contains(haystack, needle) {
|
||||
t.Errorf("output missing %q\n--- output ---\n%s", needle, haystack)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
)
|
||||
|
||||
// cacheStats is the read side of the NetBox cache the collector publishes.
|
||||
type cacheStats interface {
|
||||
Hits() int64
|
||||
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 {
|
||||
reg *prometheus.Registry
|
||||
|
||||
httpRequests *prometheus.CounterVec // by endpoint,status
|
||||
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, git gitStats) *metrics {
|
||||
reg := prometheus.NewRegistry()
|
||||
m := &metrics{
|
||||
reg: reg,
|
||||
httpRequests: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "bootapi_http_requests_total",
|
||||
Help: "HTTP requests handled, by endpoint and status class.",
|
||||
}, []string{"endpoint", "status"}),
|
||||
renders: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "bootapi_render_total",
|
||||
Help: "Template renders, by kind (kickstart|ipxe) and result (ok|error).",
|
||||
}, []string{"kind", "result"}),
|
||||
netboxLookups: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "bootapi_netbox_lookups_total",
|
||||
Help: "NetBox host resolutions, by field (mac|name) and result (ok|notfound|error).",
|
||||
}, []string{"field", "result"}),
|
||||
netboxDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "bootapi_netbox_lookup_duration_seconds",
|
||||
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, 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{}),
|
||||
)
|
||||
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 {
|
||||
stats cacheStats
|
||||
hits *prometheus.Desc
|
||||
miss *prometheus.Desc
|
||||
}
|
||||
|
||||
func newCacheCollector(s cacheStats) *cacheCollector {
|
||||
return &cacheCollector{
|
||||
stats: s,
|
||||
hits: prometheus.NewDesc("bootapi_netbox_cache_hits_total", "NetBox cache hits.", nil, nil),
|
||||
miss: prometheus.NewDesc("bootapi_netbox_cache_misses_total", "NetBox cache misses.", nil, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cacheCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- c.hits
|
||||
ch <- c.miss
|
||||
}
|
||||
|
||||
func (c *cacheCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
ch <- prometheus.MustNewConstMetric(c.hits, prometheus.CounterValue, float64(c.stats.Hits()))
|
||||
ch <- prometheus.MustNewConstMetric(c.miss, prometheus.CounterValue, float64(c.stats.Misses()))
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
// Package server exposes bootapi over HTTP: iPXE boot scripts and rendered
|
||||
// kickstarts for PXE-booting hosts, plus health and metrics endpoints.
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/model"
|
||||
"git.unkin.net/unkin/bootapi/internal/netbox"
|
||||
"git.unkin.net/unkin/bootapi/internal/render"
|
||||
)
|
||||
|
||||
// Server wires the NetBox API and template engine into HTTP handlers.
|
||||
type Server struct {
|
||||
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 {
|
||||
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.
|
||||
func New(o Options) *Server {
|
||||
fb := o.UnknownMACFallback
|
||||
if fb == "" {
|
||||
fb = "local"
|
||||
}
|
||||
return &Server{
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// Router returns the fully-wired HTTP handler.
|
||||
func (s *Server) Router() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(s.logRequests)
|
||||
|
||||
r.Get("/healthz", s.handleHealthz)
|
||||
r.Get("/readyz", s.handleReadyz)
|
||||
r.Handle("/metrics", promhttp.HandlerFor(s.metrics.reg, promhttp.HandlerOpts{}))
|
||||
|
||||
// iPXE boot script: primary path-style, plus a query-style alias.
|
||||
r.Get("/ipxe/{mac}", s.handleIPXE)
|
||||
r.Get("/boot/ipxe", s.handleIPXEQuery)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (s *Server) handleHealthz(w http.ResponseWriter, _ *http.Request) {
|
||||
s.ok(w, http.StatusOK, "text/plain", []byte("ok\n"), "healthz")
|
||||
}
|
||||
|
||||
// handleReadyz is ready once templates parsed (engine present). NetBox is a
|
||||
// soft dependency — the iPXE fallback works without it — so readiness does not
|
||||
// probe NetBox.
|
||||
func (s *Server) handleReadyz(w http.ResponseWriter, _ *http.Request) {
|
||||
if s.engine == nil {
|
||||
s.ok(w, http.StatusServiceUnavailable, "text/plain", []byte("no template engine\n"), "readyz")
|
||||
return
|
||||
}
|
||||
s.ok(w, http.StatusOK, "text/plain", []byte("ready\n"), "readyz")
|
||||
}
|
||||
|
||||
// handleIPXE serves the per-MAC iPXE script. An unknown MAC (or a NetBox error)
|
||||
// yields a SAFE fallback script with HTTP 200 — never a 404 — so the booting
|
||||
// firmware always receives a valid iPXE script instead of failing the chain.
|
||||
func (s *Server) handleIPXE(w http.ResponseWriter, r *http.Request) {
|
||||
s.serveIPXE(w, r, chi.URLParam(r, "mac"))
|
||||
}
|
||||
|
||||
func (s *Server) handleIPXEQuery(w http.ResponseWriter, r *http.Request) {
|
||||
s.serveIPXE(w, r, r.URL.Query().Get("mac"))
|
||||
}
|
||||
|
||||
func (s *Server) serveIPXE(w http.ResponseWriter, r *http.Request, mac string) {
|
||||
const ct = "text/plain" // iPXE scripts are served as text/plain
|
||||
mac = strings.TrimSuffix(mac, ".ipxe")
|
||||
if mac == "" {
|
||||
s.renderFallback(w, "ipxe", "missing MAC")
|
||||
return
|
||||
}
|
||||
host, err := s.lookup(r.Context(), "mac", mac)
|
||||
if err != nil {
|
||||
if errors.Is(err, netbox.ErrNotFound) {
|
||||
slog.Info("ipxe unknown MAC; serving fallback", "mac", mac, "fallback", s.fallback)
|
||||
} else {
|
||||
slog.Error("ipxe netbox lookup failed; serving safe fallback", "mac", mac, "err", err)
|
||||
}
|
||||
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()
|
||||
slog.Error("render ipxe", "host", host.Hostname, "err", err)
|
||||
s.renderFallback(w, "ipxe", "render error")
|
||||
return
|
||||
}
|
||||
s.metrics.renders.WithLabelValues("ipxe", "ok").Inc()
|
||||
s.ok(w, http.StatusOK, ct, body, "ipxe")
|
||||
}
|
||||
|
||||
// renderFallback emits the configured unknown-MAC iPXE script (still HTTP 200).
|
||||
func (s *Server) renderFallback(w http.ResponseWriter, endpoint, _ string) {
|
||||
body, err := s.engine.RenderFallback(s.fallback)
|
||||
if err != nil {
|
||||
// Last-resort inline script so the firmware still gets something valid.
|
||||
body = []byte("#!ipxe\necho bootapi: fallback render failed; booting local disk\nsanboot --no-describe --drive 0x80 || exit\n")
|
||||
}
|
||||
s.ok(w, http.StatusOK, "text/plain", body, endpoint)
|
||||
}
|
||||
|
||||
// handleKickstart serves the rendered kickstart for a host identified by MAC or
|
||||
// hostname. Unlike iPXE, an unknown host here is a hard 404: the installer has
|
||||
// already committed to installing and a wrong/empty kickstart is worse than a
|
||||
// clear failure.
|
||||
func (s *Server) handleKickstart(w http.ResponseWriter, r *http.Request) {
|
||||
ident := chi.URLParam(r, "ident")
|
||||
for _, suf := range []string{".ks", ".cfg"} {
|
||||
ident = strings.TrimSuffix(ident, suf)
|
||||
}
|
||||
if ident == "" {
|
||||
http.Error(w, "missing host identifier", http.StatusBadRequest)
|
||||
s.metrics.httpRequests.WithLabelValues("ks", "4xx").Inc()
|
||||
return
|
||||
}
|
||||
|
||||
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.httpRequests.WithLabelValues("ks", "4xx").Inc()
|
||||
return
|
||||
}
|
||||
http.Error(w, "netbox lookup failed", http.StatusBadGateway)
|
||||
s.metrics.httpRequests.WithLabelValues("ks", "5xx").Inc()
|
||||
return
|
||||
}
|
||||
body, name, err := s.engine.RenderKickstart(host)
|
||||
if err != nil {
|
||||
s.metrics.renders.WithLabelValues("kickstart", "error").Inc()
|
||||
slog.Error("render kickstart", "host", host.Hostname, "err", err)
|
||||
http.Error(w, "kickstart render failed", http.StatusInternalServerError)
|
||||
s.metrics.httpRequests.WithLabelValues("ks", "5xx").Inc()
|
||||
return
|
||||
}
|
||||
s.metrics.renders.WithLabelValues("kickstart", "ok").Inc()
|
||||
slog.Info("served kickstart", "host", host.Hostname, "template", name)
|
||||
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.nb.HostByMAC(ctx, value)
|
||||
} else {
|
||||
host, err = s.nb.HostByName(ctx, value)
|
||||
}
|
||||
s.metrics.netboxDuration.WithLabelValues(field).Observe(time.Since(start).Seconds())
|
||||
switch {
|
||||
case err == nil:
|
||||
s.metrics.netboxLookups.WithLabelValues(field, "ok").Inc()
|
||||
case errors.Is(err, netbox.ErrNotFound):
|
||||
s.metrics.netboxLookups.WithLabelValues(field, "notfound").Inc()
|
||||
default:
|
||||
s.metrics.netboxLookups.WithLabelValues(field, "error").Inc()
|
||||
}
|
||||
return host, err
|
||||
}
|
||||
|
||||
func (s *Server) ok(w http.ResponseWriter, status int, contentType string, body []byte, endpoint string) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(body)
|
||||
s.metrics.httpRequests.WithLabelValues(endpoint, statusClass(status)).Inc()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
// common separators). Used to pick the NetBox lookup field for /ks/{ident}.
|
||||
func looksLikeMAC(s string) bool {
|
||||
n := 0
|
||||
for _, r := range strings.ToLower(s) {
|
||||
switch {
|
||||
case r >= '0' && r <= '9', r >= 'a' && r <= 'f':
|
||||
n++
|
||||
case r == ':' || r == '-' || r == '.':
|
||||
// separator, ignore
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return n == 12
|
||||
}
|
||||
|
||||
func statusClass(code int) string {
|
||||
switch {
|
||||
case code < 300:
|
||||
return "2xx"
|
||||
case code < 400:
|
||||
return "3xx"
|
||||
case code < 500:
|
||||
return "4xx"
|
||||
default:
|
||||
return "5xx"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) logRequests(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
defer func() {
|
||||
slog.Info("request",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"status", ww.Status(),
|
||||
"duration_ms", time.Since(start).Milliseconds(),
|
||||
"remote", r.RemoteAddr,
|
||||
"request_id", middleware.GetReqID(r.Context()),
|
||||
)
|
||||
}()
|
||||
next.ServeHTTP(ww, r)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/model"
|
||||
"git.unkin.net/unkin/bootapi/internal/netbox"
|
||||
"git.unkin.net/unkin/bootapi/internal/render"
|
||||
"git.unkin.net/unkin/bootapi/templates"
|
||||
)
|
||||
|
||||
// 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 *fakeNB) HostByMAC(_ context.Context, mac string) (*model.Host, error) {
|
||||
if f.err != nil {
|
||||
return nil, f.err
|
||||
}
|
||||
// The real NetBox client normalizes MAC case/separators before matching;
|
||||
// mirror that here so case-insensitive lookups behave like production.
|
||||
if h, ok := f.byMAC[strings.ToLower(mac)]; ok {
|
||||
return h, nil
|
||||
}
|
||||
return nil, netbox.ErrNotFound
|
||||
}
|
||||
func (f *fakeNB) HostByName(_ context.Context, name string) (*model.Host, error) {
|
||||
if f.err != nil {
|
||||
return nil, f.err
|
||||
}
|
||||
if h, ok := f.byName[name]; ok {
|
||||
return h, nil
|
||||
}
|
||||
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",
|
||||
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", Primary: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTestServer(t *testing.T, nb netbox.API, fallback string) *Server {
|
||||
t.Helper()
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
t.Helper()
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil))
|
||||
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 := &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")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d", rec.Code)
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, "inst.ks=http://bootapi.example.net/ks/web01") {
|
||||
t.Errorf("ipxe body missing inst.ks:\n%s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPXEUnknownMACServesFallback200(t *testing.T) {
|
||||
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 {
|
||||
t.Fatalf("status = %d, want 200 with fallback", rec.Code)
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), "sanboot") {
|
||||
t.Errorf("expected local-boot fallback, got:\n%s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPXEUnknownMACShellFallback(t *testing.T) {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPXEQueryAlias(t *testing.T) {
|
||||
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=") {
|
||||
t.Fatalf("query-style ipxe failed: %d\n%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestKickstartByMAC(t *testing.T) {
|
||||
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 {
|
||||
t.Fatalf("status = %d", rec.Code)
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/plain") {
|
||||
t.Errorf("content-type = %q", ct)
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), "rootpw --iscrypted") {
|
||||
t.Errorf("kickstart body missing rootpw:\n%s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestKickstartByHostname(t *testing.T) {
|
||||
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 {
|
||||
t.Fatalf("status = %d\n%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestKickstartUnknownIs404(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthAndReady(t *testing.T) {
|
||||
h := newTestServer(t, &fakeNB{}, "local").Router()
|
||||
if rec := do(t, h, "/healthz"); rec.Code != http.StatusOK {
|
||||
t.Errorf("healthz = %d", rec.Code)
|
||||
}
|
||||
if rec := do(t, h, "/readyz"); rec.Code != http.StatusOK {
|
||||
t.Errorf("readyz = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsEndpoint(t *testing.T) {
|
||||
res := &fakeNB{byMAC: map[string]*model.Host{"aa:bb:cc:00:11:22": testHost()}}
|
||||
srv := newTestServer(t, res, "local")
|
||||
h := srv.Router()
|
||||
|
||||
do(t, h, "/ipxe/aa:bb:cc:00:11:22") // ok render + netbox ok
|
||||
do(t, h, "/ipxe/de:ad:be:ef:00:00") // notfound + fallback
|
||||
do(t, h, "/ks/aa:bb:cc:00:11:22") // kickstart render
|
||||
|
||||
rec := do(t, h, "/metrics")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("metrics status = %d", rec.Code)
|
||||
}
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{
|
||||
`bootapi_render_total{kind="ipxe",result="ok"} 1`,
|
||||
`bootapi_render_total{kind="kickstart",result="ok"} 1`,
|
||||
`bootapi_netbox_lookups_total{field="mac",result="notfound"} 1`,
|
||||
"bootapi_http_requests_total",
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("metrics missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"}
|
||||
for _, s := range yes {
|
||||
if !looksLikeMAC(s) {
|
||||
t.Errorf("looksLikeMAC(%q) = false, want true", s)
|
||||
}
|
||||
}
|
||||
for _, s := range no {
|
||||
if looksLikeMAC(s) {
|
||||
t.Errorf("looksLikeMAC(%q) = true, want false", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
@@ -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"
|
||||
@@ -0,0 +1,12 @@
|
||||
// Package templates embeds bootapi's default kickstart and iPXE templates.
|
||||
// These are the built-in fallback set; an operator can override any of them by
|
||||
// mounting a ConfigMap at BOOTAPI_TEMPLATE_DIR (see docs/deployment.md).
|
||||
package templates
|
||||
|
||||
import "embed"
|
||||
|
||||
// FS holds the default template tree: kickstart/*.ks.tmpl, ipxe/*.ipxe.tmpl and
|
||||
// catalog/*.yaml (the distro catalog).
|
||||
//
|
||||
//go:embed kickstart ipxe catalog
|
||||
var FS embed.FS
|
||||
@@ -0,0 +1,20 @@
|
||||
{{- /*
|
||||
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=.
|
||||
|
||||
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{{ if .RepoURL }} inst.repo={{ .RepoURL }}{{ end }} inst.ks={{ .KickstartURL }} ip=dhcp{{ range .KernelArgs }} {{ . }}{{ end }}
|
||||
initrd {{ .InitrdURL }}
|
||||
boot
|
||||
{{- else -}}
|
||||
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 }}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{- /*
|
||||
Safe default for an UNKNOWN MAC (NetBox has no matching device). We deliberately
|
||||
do NOT start an installer for a machine we can't identify — that could wipe a
|
||||
production box that PXE-booted by accident. Instead we boot from local disk, so
|
||||
an already-installed host just continues, and a brand-new host loops back to PXE
|
||||
on its next attempt (by which point NetBox should know it).
|
||||
*/ -}}
|
||||
#!ipxe
|
||||
echo bootapi: unknown MAC ${net0/mac}; not provisioning. Booting local disk.
|
||||
sanboot --no-describe --drive 0x80 || exit
|
||||
@@ -0,0 +1,10 @@
|
||||
{{- /*
|
||||
Debug fallback for an unknown MAC (opt in via BOOTAPI_UNKNOWN_MAC_FALLBACK=shell).
|
||||
Drops to an interactive iPXE shell instead of booting anything, so an operator
|
||||
racking a new box can inspect ${net0/mac} and register it in NetBox. Not the
|
||||
default because it halts the boot and is unsafe for an accidental PXE of a prod
|
||||
host.
|
||||
*/ -}}
|
||||
#!ipxe
|
||||
echo bootapi: unknown MAC ${net0/mac}; dropping to iPXE shell for debugging.
|
||||
shell
|
||||
@@ -0,0 +1,113 @@
|
||||
{{- /*
|
||||
AlmaLinux 9 kickstart, ported from the Cobbler default.ks contract.
|
||||
|
||||
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` 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
|
||||
eula --agreed
|
||||
firstboot --disable
|
||||
reboot
|
||||
|
||||
# --- 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'
|
||||
lang en_AU.UTF-8
|
||||
timezone Australia/Sydney --utc
|
||||
|
||||
# --- security ---
|
||||
{{ if .RootPasswordHash -}}
|
||||
rootpw --iscrypted {{ .RootPasswordHash }}
|
||||
{{- else -}}
|
||||
rootpw --lock
|
||||
{{- end }}
|
||||
selinux --enforcing
|
||||
firewall --enabled --service=ssh
|
||||
authselect select sssd with-mkhomedir --force
|
||||
|
||||
# --- networking (static, from NetBox) ---
|
||||
{{- $primary := .PrimaryInterface }}
|
||||
{{- range .Interfaces }}
|
||||
{{- if .IP }}
|
||||
network --bootproto=static --device={{ .MAC }} --ip={{ .IP }} --netmask={{ .Netmask }}{{ if .Gateway }} --gateway={{ .Gateway }}{{ end }}{{ range $.Nameservers }} --nameserver={{ . }}{{ end }}{{ if and $primary (eq .MAC $primary.MAC) }} --hostname={{ $.FQDN }}{{ end }} --activate --onboot=on --noipv6
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
# --- storage ---
|
||||
ignoredisk --only-use=sda
|
||||
clearpart --all --initlabel --drives=sda
|
||||
bootloader --location=mbr --boot-drive=sda --append="crashkernel=auto"
|
||||
autopart --type=lvm --nohome
|
||||
|
||||
# --- packages ---
|
||||
%packages --ignoremissing --excludedocs
|
||||
@^minimal-environment
|
||||
openssh-server
|
||||
chrony
|
||||
vim-minimal
|
||||
tmux
|
||||
git
|
||||
-iwl*-firmware
|
||||
%end
|
||||
|
||||
# --- bootstrap: puppet (k8s) + end-of-install callback ---
|
||||
%post --log=/root/bootapi-post.log
|
||||
set -x
|
||||
|
||||
# chrony: keep time sane before any cert work.
|
||||
systemctl enable chronyd
|
||||
|
||||
{{ if .SSHAuthorizedKeys -}}
|
||||
# root authorized_keys (from render-time config, not NetBox).
|
||||
install -d -m0700 /root/.ssh
|
||||
cat > /root/.ssh/authorized_keys <<'EOF'
|
||||
{{ range .SSHAuthorizedKeys }}{{ . }}
|
||||
{{ end }}EOF
|
||||
chmod 0600 /root/.ssh/authorized_keys
|
||||
{{- end }}
|
||||
|
||||
# Install the Puppet 8 agent from the puppet platform repo.
|
||||
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 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 report_server "{{ .PuppetServer }}"
|
||||
"$PUPPET_BIN" config set --section main environment production
|
||||
|
||||
# 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
|
||||
@@ -0,0 +1,80 @@
|
||||
{{- /*
|
||||
Fedora kickstart (family-level template: matches any "fedoraNN" platform slug
|
||||
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 (artifactapi fedora remote, from the distro catalog) ---
|
||||
url --url={{ $mirror }}/{{ .Arch }}/os/
|
||||
|
||||
keyboard --xlayouts='us'
|
||||
lang en_AU.UTF-8
|
||||
timezone Australia/Sydney --utc
|
||||
|
||||
{{ if .RootPasswordHash -}}
|
||||
rootpw --iscrypted {{ .RootPasswordHash }}
|
||||
{{- else -}}
|
||||
rootpw --lock
|
||||
{{- end }}
|
||||
selinux --enforcing
|
||||
firewall --enabled --service=ssh
|
||||
|
||||
# --- networking (static, from NetBox) ---
|
||||
{{- $primary := .PrimaryInterface }}
|
||||
{{- range .Interfaces }}
|
||||
{{- if .IP }}
|
||||
network --bootproto=static --device={{ .MAC }} --ip={{ .IP }} --netmask={{ .Netmask }}{{ if .Gateway }} --gateway={{ .Gateway }}{{ end }}{{ range $.Nameservers }} --nameserver={{ . }}{{ end }}{{ if and $primary (eq .MAC $primary.MAC) }} --hostname={{ $.FQDN }}{{ end }} --activate --onboot=on --noipv6
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
# --- storage ---
|
||||
ignoredisk --only-use=sda
|
||||
clearpart --all --initlabel --drives=sda
|
||||
bootloader --location=mbr --boot-drive=sda
|
||||
autopart --type=lvm --nohome
|
||||
|
||||
%packages --ignoremissing
|
||||
@^minimal-environment
|
||||
openssh-server
|
||||
chrony
|
||||
git
|
||||
%end
|
||||
|
||||
%post --log=/root/bootapi-post.log
|
||||
set -x
|
||||
systemctl enable chronyd sshd
|
||||
{{ if .SSHAuthorizedKeys -}}
|
||||
install -d -m0700 /root/.ssh
|
||||
cat > /root/.ssh/authorized_keys <<'EOF'
|
||||
{{ range .SSHAuthorizedKeys }}{{ . }}
|
||||
{{ 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 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
|
||||
Done. Added a NetBox
pxe_enabledboolean 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-guardedPOST /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_enabledboolean 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-guardedPOST /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. 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.