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,80 @@
|
||||
|
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.
|
||||
| `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.
|
||||
Unknown MAC → iPXE gets a **safe fallback** (local-disk boot, HTTP 200), never 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.
|
||||
404. Unknown kickstart host → **404** (fail loud once installing). Full rationale
|
||||
|
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.
|
||||
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.
|
||||
## 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
|
||||
|
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 (+ TTL cache), behind a Resolver 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/render/ text/template engine, selection, embedded-defaults 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/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 default 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/ 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,76 @@
|
||||
// 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"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/config"
|
||||
"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() {
|
||||
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")
|
||||
}
|
||||
|
||||
engine, err := render.NewEngine(templates.FS, cfg.TemplateDir, render.RenderConfig{
|
||||
PuppetServer: cfg.PuppetServer,
|
||||
PuppetCAServer: cfg.PuppetCAServer,
|
||||
BaseURL: cfg.BaseURL,
|
||||
BootBaseURL: cfg.BootBaseURL,
|
||||
DefaultDomain: cfg.Domain,
|
||||
DefaultNS: cfg.Nameservers,
|
||||
RootPasswordHash: cfg.RootPasswordHash,
|
||||
SSHAuthorizedKeys: cfg.SSHAuthorizedKeys,
|
||||
DefaultTemplate: cfg.DefaultTemplate,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("load templates", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
nb := netbox.New(netbox.Options{
|
||||
BaseURL: cfg.NetBoxURL,
|
||||
Token: cfg.NetBoxToken,
|
||||
Timeout: cfg.NetBoxTimeout,
|
||||
Insecure: cfg.NetBoxInsecure,
|
||||
})
|
||||
cache := netbox.NewCache(nb, cfg.CacheTTL)
|
||||
|
||||
srv := server.New(server.Options{
|
||||
Resolver: cache,
|
||||
Engine: engine,
|
||||
Cache: cache,
|
||||
UnknownMACFallback: cfg.UnknownMACFallback,
|
||||
})
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
if err := srv.ListenAndServe(ctx, cfg.ListenAddr); err != nil {
|
||||
slog.Error("server", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
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 style, same as
|
||||
|
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.
|
||||
# encapi). In Kubernetes these come from the Deployment env + a Vault-sourced
|
||||
|
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.
|
||||
# Secret (see docs/deployment.md). Locally, `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 ---
|
||||
|
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.
|
||||
|
||||
|
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 file mounted from Vault:
|
||||
|
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 ---
|
||||
|
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.
|
||||
# Short by design: a re-provisioned host must pick up NetBox changes on its next
|
||||
|
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.
|
||||
# boot. Set 0 to disable.
|
||||
|
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 ---
|
||||
|
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 override directory (a ConfigMap mount in k8s); files here win over
|
||||
|
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 embedded defaults. Leave empty to use only the built-in templates.
|
||||
|
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
|
||||
|
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.
|
||||
# Template used when NetBox provides no platform/role/override selection 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.
|
||||
BOOTAPI_DEFAULT_TEMPLATE=almalinux9
|
||||
|
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.
|
||||
# --- 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 externally-reachable base URL (goes into the iPXE inst.ks=).
|
||||
|
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
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 URL of the OS install trees (kernel/initrd + inst.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_BOOT_BASE_URL=http://mirror.k8s.syd1.au.unkin.net/almalinux/9
|
||||
|
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.
|
||||
|
||||
|
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 (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.query.consul
|
||||
|
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.
|
||||
BOOTAPI_PUPPET_CA_SERVER=puppetca.query.consul
|
||||
|
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 does not record them 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.
|
||||
BOOTAPI_NAMESERVERS=198.18.19.19
|
||||
|
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.
|
||||
|
||||
|
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,77 @@
|
||||
# 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.query.consul` |
|
||||
| `.PuppetCAServer` | string | `BOOTAPI_PUPPET_CA_SERVER` | default `puppetca.query.consul` |
|
||||
| `.BaseURL` | string | `BOOTAPI_BASE_URL` | bootapi's own URL |
|
||||
| `.BootBaseURL` | string | `BOOTAPI_BOOT_BASE_URL` | OS install-tree base |
|
||||
| `.KickstartURL` | string | derived | `BaseURL/ks/Hostname` |
|
||||
| `.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 | `BootBaseURL/images/pxeboot/vmlinuz` (empty if `BootBaseURL` unset) |
|
||||
| `.InitrdURL` | string | `BootBaseURL/images/pxeboot/initrd.img` |
|
||||
|
||||
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 | Effect |
|
||||
|--------------|----|--------|
|
||||
| `domain` | device | DNS domain; overrides `BOOTAPI_DOMAIN` |
|
||||
| `gateway` | device / IP address | default gateway (IP-level wins) |
|
||||
| `nameservers` | device | comma-separated resolvers; overrides `BOOTAPI_NAMESERVERS` |
|
||||
| `provision_template` | device | force a specific template name (see below) |
|
||||
|
||||
## Template selection precedence
|
||||
|
||||
`SelectKickstart` picks the first template name that exists, in order:
|
||||
|
||||
1. `provision_template` custom field (exact template name)
|
||||
2. `.Platform` slug (e.g. `almalinux9`)
|
||||
3. `.OSFamily` (e.g. `almalinux`, or `fedora`)
|
||||
4. `BOOTAPI_DEFAULT_TEMPLATE` (default `almalinux9`)
|
||||
@@ -0,0 +1,79 @@
|
||||
# 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, read-only** NetBox API token for bootapi
|
||||
(create a `bootapi` NetBox user/token via terraform-netbox rather than
|
||||
reusing the seeded superuser token at
|
||||
`kv/kubernetes/namespace/netbox/default/netbox-superuser`).
|
||||
- `root_password_hash` — crypt(3) hash for the installed root account
|
||||
(the successor to Cobbler's eyaml `default_password_crypted`).
|
||||
- `ssh_authorized_keys` — optional, newline-separated.
|
||||
4. **ConfigMap** `bootapi-templates` (optional) — override `*.ks.tmpl` /
|
||||
`*.ipxe.tmpl`, mounted at `BOOTAPI_TEMPLATE_DIR=/etc/bootapi/templates`. Omit
|
||||
to use the embedded defaults. Annotate the Deployment with
|
||||
`reloader.stakater.com/auto: "true"` so template edits roll the pods.
|
||||
5. **Deployment** — image above, env from `config.example.env`, secret keys wired
|
||||
as `BOOTAPI_NETBOX_TOKEN_FILE`/`BOOTAPI_ROOT_PASSWORD_HASH_FILE` (mount the
|
||||
Secret) or `...FROM secretKeyRef`. Least-privilege securityContext
|
||||
(`runAsNonRoot`, `drop: [all]`). Baseline resources: requests `512Mi`/`1`,
|
||||
limits `2Gi`/`2` cpu.
|
||||
6. **Service** `bootapi` (ClusterIP, port 80 → 8000) plus a **LoadBalancer** (or
|
||||
Gateway HTTPRoute) reachable by PXE clients at a stable address/hostname —
|
||||
this is what DHCP points at. Reuse the Vault-issued TLS the Cobbler vhost used
|
||||
if you terminate TLS at a gateway; note that iPXE fetches are plain HTTP, so a
|
||||
plain HTTP listener on the PXE VLAN is required either way.
|
||||
7. Register in `argocd/applicationsets/platform.yaml` (`apps/overlays/*/bootapi`)
|
||||
and the platform AppProject destinations.
|
||||
|
||||
### Cross-repo dependencies (per estate conventions)
|
||||
|
||||
- **argocd-apps**: add a `serviceaccount_*` under `apps/base/woodpecker/` if the
|
||||
bootapi pipelines need a dedicated SA (they use `default` today).
|
||||
- **terraform-vault**: add the k8s auth role + kv policy granting the `bootapi`
|
||||
namespace read on `kv/kubernetes/namespace/bootapi/default/*`.
|
||||
- **terraform-netbox**: create the read-only `bootapi` NetBox token and seed it
|
||||
(plus `root_password_hash`) into the Vault kv path above.
|
||||
|
||||
## 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,68 @@
|
||||
|
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.
|
||||
bootapi speaks plain HTTP. It is fronted by the same Vault-issued TLS the Cobbler
|
||||
|
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.
|
||||
server used; the booting firmware reaches it at the DHCP `next-server` (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)).
|
||||
|
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 /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=<BASE_URL>/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 /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.
|
||||
```
|
||||
|
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>` 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.
|
||||
served a per-system script carrying `inst.ks=`.
|
||||
|
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}` (some firmware finds this shape easier to template). |
|
||||
|
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.
|
||||
| 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 (a NetBox outage still lets iPXE serve the safe 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.
|
||||
| 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}`), which bootapi resolves via 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 /api/dcim/interfaces/?mac_address=<mac>` → device → primary IP, platform,
|
||||
|
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.
|
||||
role, interfaces. `/ks/{ident}` additionally accepts a **hostname** (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.
|
||||
device name), for hand-testing and for installers that template the hostname
|
||||
|
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.
|
||||
into the kickstart 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.
|
||||
## Error behavior (important, and 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 two 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 or
|
||||
|
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 chain simply errors. An unknown MAC — or *any* NetBox error — returns
|
||||
|
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 200 with 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: a machine that PXE-booted
|
||||
|
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.
|
||||
by accident (or a NetBox blip) just boots its installed OS; a genuinely new
|
||||
|
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 loops back to PXE next time, by which point NetBox should know it. We
|
||||
|
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.
|
||||
deliberately do **not** 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.
|
||||
that could wipe a production box.
|
||||
|
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`: drop to an interactive iPXE shell so an operator racking a new box
|
||||
|
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.
|
||||
can read `${net0/mac}` and register it. Opt-in; unsafe as a default because
|
||||
|
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.
|
||||
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 (and 502 on a NetBox 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.
|
||||
By the time Anaconda fetches the kickstart it has already committed to
|
||||
|
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.
|
||||
installing; a clear failure is safer than serving an empty or 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.
|
||||
## 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.
|
||||
- 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,49 @@
|
||||
# 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` |
|
||||
| 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.
|
||||
|
||||
## 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,71 @@
|
||||
# Authoring templates
|
||||
|
||||
bootapi ships an embedded default set and lets you override or extend it.
|
||||
|
||||
## Where templates live
|
||||
|
||||
- **Embedded defaults**: `templates/kickstart/*.ks.tmpl` and
|
||||
`templates/ipxe/*.ipxe.tmpl`, compiled into the binary (`templates/embed.go`).
|
||||
- **Overrides**: any directory pointed to by `BOOTAPI_TEMPLATE_DIR`. Files there
|
||||
with the same base name **replace** the embedded one; new names **add** to the
|
||||
set. In Kubernetes this is a ConfigMap mount (see [deployment.md](deployment.md)).
|
||||
|
||||
## 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.
|
||||
|
||||
## 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,20 @@
|
||||
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
|
||||
)
|
||||
|
||||
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/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,36 @@
|
||||
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/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/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/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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 HTTP bind address, e.g. ":8000".
|
||||
ListenAddr string
|
||||
|
||||
// NetBoxURL is the base URL of the NetBox API,
|
||||
// e.g. "https://netbox.k8s.syd1.au.unkin.net".
|
||||
NetBoxURL string
|
||||
// NetBoxToken is the NetBox API token. Prefer NetBoxTokenFile in k8s.
|
||||
NetBoxToken string
|
||||
// NetBoxTimeout bounds each NetBox HTTP request.
|
||||
NetBoxTimeout time.Duration
|
||||
// NetBoxInsecure disables TLS verification against NetBox (dev only).
|
||||
NetBoxInsecure bool
|
||||
|
||||
// CacheTTL is how long a resolved host is cached in memory. Short by
|
||||
// design: NetBox is the source of truth and a machine's provisioning data
|
||||
// can change between boots.
|
||||
CacheTTL time.Duration
|
||||
|
||||
// TemplateDir, when set, is a directory of override templates layered on
|
||||
// top of the embedded defaults (a Kubernetes ConfigMap mount in prod).
|
||||
TemplateDir string
|
||||
// DefaultTemplate is the kickstart template used when NetBox provides no
|
||||
// platform/role/override selection key.
|
||||
DefaultTemplate string
|
||||
|
||||
// BaseURL is bootapi's own externally-reachable base URL, baked into the
|
||||
// iPXE script's inst.ks= and repo URLs so a booting host calls back here.
|
||||
// e.g. "http://bootapi.k8s.syd1.au.unkin.net".
|
||||
BaseURL string
|
||||
|
||||
// BootBaseURL is the base URL of the OS install trees (kernel/initrd +
|
||||
// inst.repo), e.g. "http://mirror.k8s.syd1.au.unkin.net/almalinux".
|
||||
BootBaseURL string
|
||||
|
||||
// PuppetServer / PuppetCAServer are baked into kickstart %post so the
|
||||
// freshly-installed host checks in to the right place.
|
||||
PuppetServer string
|
||||
PuppetCAServer string
|
||||
|
||||
// Domain is the default DNS domain applied when NetBox does not record one
|
||||
// for a device.
|
||||
Domain string
|
||||
|
||||
// Nameservers is the default resolver list applied when NetBox records
|
||||
// none for a device.
|
||||
Nameservers []string
|
||||
|
||||
// RootPasswordHash is a crypt(3) hash injected into kickstarts at render
|
||||
// time (sourced from Vault in k8s). Empty locks the root account.
|
||||
RootPasswordHash string
|
||||
// SSHAuthorizedKeys are public keys installed for root at render time.
|
||||
SSHAuthorizedKeys []string
|
||||
|
||||
// UnknownMACFallback selects what the iPXE endpoint returns for a MAC that
|
||||
// NetBox does not know: "local" (chain to local disk, the safe default) or
|
||||
// "shell" (drop to an iPXE shell for debugging). See docs/endpoints.md.
|
||||
UnknownMACFallback string
|
||||
}
|
||||
|
||||
// Load reads configuration from the environment, applying defaults, and reads a
|
||||
// token file when BOOTAPI_NETBOX_TOKEN_FILE is set (Vault-mounted secret).
|
||||
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)
|
||||
}
|
||||
|
||||
token := os.Getenv("BOOTAPI_NETBOX_TOKEN")
|
||||
if tf := os.Getenv("BOOTAPI_NETBOX_TOKEN_FILE"); tf != "" {
|
||||
b, err := os.ReadFile(tf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read BOOTAPI_NETBOX_TOKEN_FILE %q: %w", tf, err)
|
||||
}
|
||||
token = strings.TrimSpace(string(b))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
rootHash := os.Getenv("BOOTAPI_ROOT_PASSWORD_HASH")
|
||||
if rf := os.Getenv("BOOTAPI_ROOT_PASSWORD_HASH_FILE"); rf != "" {
|
||||
b, err := os.ReadFile(rf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read BOOTAPI_ROOT_PASSWORD_HASH_FILE %q: %w", rf, err)
|
||||
}
|
||||
rootHash = strings.TrimSpace(string(b))
|
||||
}
|
||||
|
||||
return &Config{
|
||||
ListenAddr: getenv("BOOTAPI_LISTEN_ADDR", ":8000"),
|
||||
NetBoxURL: strings.TrimRight(os.Getenv("BOOTAPI_NETBOX_URL"), "/"),
|
||||
NetBoxToken: token,
|
||||
NetBoxTimeout: nbTimeout,
|
||||
NetBoxInsecure: getenv("BOOTAPI_NETBOX_INSECURE", "false") == "true",
|
||||
CacheTTL: cacheTTL,
|
||||
TemplateDir: os.Getenv("BOOTAPI_TEMPLATE_DIR"),
|
||||
DefaultTemplate: getenv("BOOTAPI_DEFAULT_TEMPLATE", "almalinux9"),
|
||||
BaseURL: strings.TrimRight(os.Getenv("BOOTAPI_BASE_URL"), "/"),
|
||||
BootBaseURL: strings.TrimRight(os.Getenv("BOOTAPI_BOOT_BASE_URL"), "/"),
|
||||
PuppetServer: getenv("BOOTAPI_PUPPET_SERVER", "puppet.query.consul"),
|
||||
PuppetCAServer: getenv("BOOTAPI_PUPPET_CA_SERVER", "puppetca.query.consul"),
|
||||
Domain: getenv("BOOTAPI_DOMAIN", "main.unkin.net"),
|
||||
Nameservers: splitList(os.Getenv("BOOTAPI_NAMESERVERS")),
|
||||
RootPasswordHash: rootHash,
|
||||
SSHAuthorizedKeys: splitLines(os.Getenv("BOOTAPI_SSH_AUTHORIZED_KEYS")),
|
||||
UnknownMACFallback: fallback,
|
||||
}, 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,94 @@
|
||||
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.query.consul" || c.PuppetCAServer != "puppetca.query.consul" {
|
||||
t.Errorf("puppet servers = %q / %q", c.PuppetServer, c.PuppetCAServer)
|
||||
}
|
||||
if c.UnknownMACFallback != "local" {
|
||||
t.Errorf("UnknownMACFallback = %q", c.UnknownMACFallback)
|
||||
}
|
||||
}
|
||||
|
||||
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,100 @@
|
||||
// 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 {
|
||||
// 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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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,89 @@
|
||||
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 Resolver
|
||||
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 Resolver, ttl time.Duration) *Cache {
|
||||
return &Cache{
|
||||
inner: inner,
|
||||
ttl: ttl,
|
||||
now: time.Now,
|
||||
entries: map[string]cacheEntry{},
|
||||
}
|
||||
}
|
||||
|
||||
// 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,87 @@
|
||||
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
|
||||
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 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 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,423 @@
|
||||
// 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 (
|
||||
"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)
|
||||
}
|
||||
|
||||
// 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{
|
||||
Hostname: dev.Name,
|
||||
Domain: domain,
|
||||
Custom: cf,
|
||||
Nameservers: cfStringList(cf, "nameservers"),
|
||||
TemplateOverride: cfString(cf, "provision_template"),
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
}
|
||||
|
||||
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,220 @@
|
||||
package netbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 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.
|
||||
mux.HandleFunc("/api/dcim/devices/", func(w http.ResponseWriter, r *http.Request) {
|
||||
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}}`)
|
||||
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 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 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,268 @@
|
||||
// Package render turns a resolved model.Host into a kickstart file or an iPXE
|
||||
// boot script using Go text/template. Templates come from an embedded default
|
||||
// set (ported from Cobbler's kickstarts) optionally layered with an override
|
||||
// directory (a Kubernetes ConfigMap mount in production).
|
||||
package render
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"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
|
||||
BaseURL string // bootapi's own base URL
|
||||
BootBaseURL string // OS install-tree base URL
|
||||
KickstartURL string // absolute URL a booting host fetches its KS from
|
||||
|
||||
// --- 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
|
||||
BaseURL string
|
||||
BootBaseURL string
|
||||
DefaultDomain string
|
||||
DefaultNS []string
|
||||
RootPasswordHash string
|
||||
SSHAuthorizedKeys []string
|
||||
DefaultTemplate string
|
||||
}
|
||||
|
||||
// Engine holds parsed templates and render-time defaults.
|
||||
type Engine struct {
|
||||
ks *template.Template // kickstart templates, named "<key>"
|
||||
ipxe *template.Template // ipxe templates, named "<key>"
|
||||
cfg RenderConfig
|
||||
ksSet map[string]bool // which kickstart template names exist
|
||||
}
|
||||
|
||||
const (
|
||||
ksExt = ".ks.tmpl"
|
||||
ipxeExt = ".ipxe.tmpl"
|
||||
)
|
||||
|
||||
// NewEngine parses the embedded defaults, then overlays overrideDir when
|
||||
// non-empty (files there win over embedded ones of the same name).
|
||||
func NewEngine(embedded fs.FS, overrideDir string, cfg RenderConfig) (*Engine, error) {
|
||||
funcs := funcMap()
|
||||
ks := template.New("kickstart").Funcs(funcs)
|
||||
ipxe := template.New("ipxe").Funcs(funcs)
|
||||
set := map[string]bool{}
|
||||
|
||||
if err := parseTree(ks, ipxe, set, embedded, ".", true); err != nil {
|
||||
return nil, fmt.Errorf("parse embedded templates: %w", err)
|
||||
}
|
||||
if overrideDir != "" {
|
||||
if err := parseTree(ks, ipxe, set, os.DirFS(overrideDir), ".", false); err != nil {
|
||||
return nil, fmt.Errorf("parse override templates in %q: %w", overrideDir, err)
|
||||
}
|
||||
}
|
||||
return &Engine{ks: ks, ipxe: ipxe, cfg: cfg, ksSet: set}, nil
|
||||
}
|
||||
|
||||
// parseTree walks fsys under root, registering *.ks.tmpl into ks and
|
||||
// *.ipxe.tmpl into ipxe under their base name (extension stripped).
|
||||
func parseTree(ks, ipxe *template.Template, set map[string]bool, fsys fs.FS, root string, mustExist bool) error {
|
||||
walked := false
|
||||
err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
walked = true
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
}
|
||||
set[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)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mustExist && !walked {
|
||||
return fmt.Errorf("no templates found under %q", root)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectKickstart returns the template name chosen for host, following the
|
||||
// documented precedence: custom-field override → platform slug → OS family →
|
||||
// configured default. It reports whether a concrete template was found.
|
||||
func (e *Engine) SelectKickstart(h *model.Host) (string, bool) {
|
||||
for _, cand := range []string{h.TemplateOverride, h.Platform, h.OSFamily, e.cfg.DefaultTemplate} {
|
||||
if cand != "" && e.ksSet[cand] {
|
||||
return cand, true
|
||||
}
|
||||
}
|
||||
return e.cfg.DefaultTemplate, e.ksSet[e.cfg.DefaultTemplate]
|
||||
}
|
||||
|
||||
// dataFor builds the flat Data view for a host, merging render-time config.
|
||||
func (e *Engine) dataFor(h *model.Host) Data {
|
||||
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 = strings.TrimRight(e.cfg.BaseURL, "/") + "/ks/" + 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,
|
||||
BaseURL: e.cfg.BaseURL,
|
||||
BootBaseURL: e.cfg.BootBaseURL,
|
||||
KickstartURL: ksURL,
|
||||
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) {
|
||||
name, ok := e.SelectKickstart(h)
|
||||
if !ok {
|
||||
return nil, name, fmt.Errorf("no kickstart template for host %q (tried override/platform/family/default %q)", h.Hostname, name)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := e.ks.ExecuteTemplate(&buf, name, e.dataFor(h)); 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; empty when BootBaseURL
|
||||
// is unset, in which case the template should fall back to a static path.
|
||||
KernelURL string
|
||||
InitrdURL string
|
||||
}
|
||||
|
||||
// RenderIPXE renders the "boot" iPXE script that chains kernel+initrd with
|
||||
// inst.ks= pointing back at bootapi.
|
||||
func (e *Engine) RenderIPXE(h *model.Host) ([]byte, error) {
|
||||
d := e.dataFor(h)
|
||||
id := IPXEData{Data: d}
|
||||
if d.BootBaseURL != "" {
|
||||
tree := strings.TrimRight(d.BootBaseURL, "/")
|
||||
id.KernelURL = tree + "/images/pxeboot/vmlinuz"
|
||||
id.InitrdURL = tree + "/images/pxeboot/initrd.img"
|
||||
}
|
||||
return e.execIPXE("boot", id)
|
||||
}
|
||||
|
||||
// RenderFallback renders a fallback iPXE script ("local" or "shell") for an
|
||||
// unknown MAC. See docs/endpoints.md for the safety rationale.
|
||||
func (e *Engine) RenderFallback(kind string) ([]byte, error) {
|
||||
name := "fallback-" + kind
|
||||
return e.execIPXE(name, IPXEData{})
|
||||
}
|
||||
|
||||
func (e *Engine) execIPXE(name string, d IPXEData) ([]byte, error) {
|
||||
if e.ipxe.Lookup(name) == nil {
|
||||
return nil, fmt.Errorf("no iPXE template %q", name)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := e.ipxe.ExecuteTemplate(&buf, name, d); err != nil {
|
||||
return nil, fmt.Errorf("render ipxe %q: %w", name, err)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
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,161 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.unkin.net/unkin/bootapi/internal/model"
|
||||
"git.unkin.net/unkin/bootapi/templates"
|
||||
)
|
||||
|
||||
func testEngine(t *testing.T, override string) *Engine {
|
||||
t.Helper()
|
||||
e, err := NewEngine(templates.FS, override, RenderConfig{
|
||||
PuppetServer: "puppet.query.consul",
|
||||
PuppetCAServer: "puppetca.query.consul",
|
||||
BaseURL: "http://bootapi.example.net",
|
||||
BootBaseURL: "http://mirror.example.net/almalinux/9",
|
||||
DefaultDomain: "main.unkin.net",
|
||||
DefaultNS: []string{"10.0.0.1"},
|
||||
RootPasswordHash: "$6$rounds=4096$abc$deadbeef",
|
||||
SSHAuthorizedKeys: []string{"ssh-ed25519 AAAAC3xxx root@ops"},
|
||||
DefaultTemplate: "almalinux9",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewEngine: %v", err)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func 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 -> must be skipped in network stanza
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderKickstartAlma(t *testing.T) {
|
||||
e := testEngine(t, "")
|
||||
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")
|
||||
// The primary interface must produce a full static network line incl hostname.
|
||||
mustContain(t, ks, "network --bootproto=static --device=aa:bb:cc:00:11:22 --ip=10.0.1.20 --netmask=255.255.255.0 --gateway=10.0.1.254 --nameserver=10.0.0.1 --hostname=web01.syd1.au.unkin.net")
|
||||
mustContain(t, ks, `"$PUPPET_BIN" config set --section main server "puppet.query.consul"`)
|
||||
mustContain(t, ks, `config set --section main ca_server "puppetca.query.consul"`)
|
||||
mustContain(t, ks, "url --url=http://mirror.example.net/almalinux/9/BaseOS/x86_64/os/")
|
||||
mustContain(t, ks, "ssh-ed25519 AAAAC3xxx root@ops")
|
||||
mustContain(t, ks, "dnf install -y puppet-agent")
|
||||
mustContain(t, ks, "%packages")
|
||||
mustContain(t, ks, "%post")
|
||||
|
||||
// eth1 has no IP, so it must NOT appear as a network device line.
|
||||
if strings.Contains(ks, "--device=aa:bb:cc:00:11:33") {
|
||||
t.Error("interface without an IP leaked into a network stanza")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderKickstartLockedRoot(t *testing.T) {
|
||||
// With no root hash configured, the account must be locked, not blank.
|
||||
e, err := NewEngine(templates.FS, "", RenderConfig{DefaultTemplate: "almalinux9", BootBaseURL: "http://m/9"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, _, err := e.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, "")
|
||||
cases := []struct {
|
||||
host *model.Host
|
||||
want string
|
||||
}{
|
||||
{&model.Host{TemplateOverride: "fedora", Platform: "almalinux9"}, "fedora"}, // override wins
|
||||
{&model.Host{Platform: "almalinux9"}, "almalinux9"}, // platform
|
||||
{&model.Host{Platform: "fedora42", OSFamily: "fedora"}, "fedora"}, // family fallback
|
||||
{&model.Host{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 TestRenderIPXE(t *testing.T) {
|
||||
e := testEngine(t, "")
|
||||
out, err := e.RenderIPXE(almaHost())
|
||||
if err != nil {
|
||||
t.Fatalf("RenderIPXE: %v", err)
|
||||
}
|
||||
s := string(out)
|
||||
mustContain(t, s, "#!ipxe")
|
||||
mustContain(t, s, "kernel http://mirror.example.net/almalinux/9/images/pxeboot/vmlinuz")
|
||||
mustContain(t, s, "inst.ks=http://bootapi.example.net/ks/web01")
|
||||
mustContain(t, s, "initrd http://mirror.example.net/almalinux/9/images/pxeboot/initrd.img")
|
||||
}
|
||||
|
||||
func TestRenderFallback(t *testing.T) {
|
||||
e := testEngine(t, "")
|
||||
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, 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,82 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func newMetrics(cache cacheStats) *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"}),
|
||||
}
|
||||
reg.MustRegister(m.httpRequests, m.renders, m.netboxLookups, m.netboxDuration)
|
||||
if cache != nil {
|
||||
reg.MustRegister(newCacheCollector(cache))
|
||||
}
|
||||
reg.MustRegister(
|
||||
collectors.NewGoCollector(),
|
||||
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
||||
)
|
||||
return m
|
||||
}
|
||||
|
||||
// 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,278 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"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 resolver and template engine into HTTP handlers.
|
||||
type Server struct {
|
||||
resolver netbox.Resolver
|
||||
engine *render.Engine
|
||||
metrics *metrics
|
||||
// fallback is the unknown-MAC iPXE behavior: "local" (safe default) or
|
||||
// "shell" (debug).
|
||||
fallback string
|
||||
}
|
||||
|
||||
// Options configures a Server.
|
||||
type Options struct {
|
||||
Resolver netbox.Resolver
|
||||
Engine *render.Engine
|
||||
// Cache, when non-nil, has its hit/miss counters published as metrics.
|
||||
Cache cacheStats
|
||||
UnknownMACFallback string
|
||||
}
|
||||
|
||||
// New builds a Server.
|
||||
func New(o Options) *Server {
|
||||
fb := o.UnknownMACFallback
|
||||
if fb == "" {
|
||||
fb = "local"
|
||||
}
|
||||
return &Server{
|
||||
resolver: o.Resolver,
|
||||
engine: o.Engine,
|
||||
metrics: newMetrics(o.Cache),
|
||||
fallback: fb,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
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
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
// lookup resolves a host by field ("mac" or "name"), recording metrics.
|
||||
func (s *Server) lookup(ctx context.Context, field, value string) (*model.Host, error) {
|
||||
start := time.Now()
|
||||
var host *model.Host
|
||||
var err error
|
||||
if field == "mac" {
|
||||
host, err = s.resolver.HostByMAC(ctx, value)
|
||||
} else {
|
||||
host, err = s.resolver.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 HTTP server until ctx is cancelled.
|
||||
func (s *Server) ListenAndServe(ctx context.Context, addr string) error {
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: s.Router(),
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
_ = srv.Shutdown(shutdownCtx)
|
||||
}()
|
||||
slog.Info("bootapi listening", "addr", addr)
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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,200 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// fakeResolver is a canned netbox.Resolver for handler tests.
|
||||
type fakeResolver struct {
|
||||
byMAC map[string]*model.Host
|
||||
byName map[string]*model.Host
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeResolver) 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 *fakeResolver) 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 testHost() *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",
|
||||
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, res netbox.Resolver, fallback string) *Server {
|
||||
t.Helper()
|
||||
eng, err := render.NewEngine(templates.FS, "", render.RenderConfig{
|
||||
PuppetServer: "puppet.query.consul", PuppetCAServer: "puppetca.query.consul",
|
||||
BaseURL: "http://bootapi.example.net", BootBaseURL: "http://mirror.example.net/almalinux/9",
|
||||
DefaultDomain: "main.unkin.net", DefaultTemplate: "almalinux9",
|
||||
RootPasswordHash: "$6$abc$def",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return New(Options{Resolver: res, Engine: eng, UnknownMACFallback: fallback})
|
||||
}
|
||||
|
||||
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 TestIPXEKnownMAC(t *testing.T) {
|
||||
res := &fakeResolver{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, &fakeResolver{}, "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, &fakeResolver{}, "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 := &fakeResolver{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 := &fakeResolver{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 := &fakeResolver{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, &fakeResolver{}, "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, &fakeResolver{}, "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 := &fakeResolver{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 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,11 @@
|
||||
// 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 and ipxe/*.ipxe.tmpl.
|
||||
//
|
||||
//go:embed kickstart ipxe
|
||||
var FS embed.FS
|
||||
@@ -0,0 +1,20 @@
|
||||
{{- /*
|
||||
iPXE boot script for a known host. Chains the OS installer kernel+initrd and
|
||||
points inst.ks= back at bootapi's /ks/<hostname> endpoint, mirroring how Cobbler
|
||||
generated a per-MAC gPXE script that carried inst.ks=.
|
||||
|
||||
Requires BOOTAPI_BOOT_BASE_URL (KernelURL/InitrdURL) and BOOTAPI_BASE_URL
|
||||
(KickstartURL) to be configured.
|
||||
*/ -}}
|
||||
#!ipxe
|
||||
echo bootapi: provisioning {{ .FQDN }} ({{ .Platform }})
|
||||
{{ if and .KernelURL .InitrdURL -}}
|
||||
kernel {{ .KernelURL }} initrd=initrd.img inst.repo={{ .BootBaseURL }} inst.ks={{ .KickstartURL }} inst.text ip=dhcp net.ifnames=0
|
||||
initrd {{ .InitrdURL }}
|
||||
boot
|
||||
{{- else -}}
|
||||
echo bootapi: BOOTAPI_BOOT_BASE_URL not configured; cannot build a boot line
|
||||
echo Falling back to local disk in 5s
|
||||
sleep 5
|
||||
exit
|
||||
{{- 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,96 @@
|
||||
{{- /*
|
||||
AlmaLinux 9 kickstart, ported from the Cobbler default.ks contract.
|
||||
|
||||
Rendered by bootapi from NetBox data + render-time secrets. The %post hands off
|
||||
to the existing Puppet firstrun bootstrap: it installs the agent, points it at
|
||||
the Consul-discovered puppet servers, and triggers the first run. Autosign
|
||||
(*.main.unkin.net + the PXE subnets) and the `profiles::firstrun` class do the
|
||||
rest, exactly as they did under Cobbler.
|
||||
|
||||
Data model: see docs/data-model.md. `.RootPasswordHash` comes from Vault at
|
||||
render time, never from NetBox.
|
||||
*/ -}}
|
||||
#version=RHEL9
|
||||
# Rendered by bootapi for {{ .FQDN }} (platform {{ .Platform }}, role {{ default "none" .Role }})
|
||||
text
|
||||
eula --agreed
|
||||
firstboot --disable
|
||||
reboot
|
||||
|
||||
# --- install source (served by bootapi's configured mirror) ---
|
||||
url --url={{ .BootBaseURL }}/BaseOS/{{ .Arch }}/os/
|
||||
repo --name=AppStream --baseurl={{ .BootBaseURL }}/AppStream/{{ .Arch }}/os/
|
||||
|
||||
# --- 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: hand off to Puppet firstrun ---
|
||||
%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 Consul-discovered servers (matches the pre-bootapi
|
||||
# Cobbler kickstart + hieradata/roles/infra/puppet).
|
||||
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
|
||||
|
||||
# Enable the agent; the first boot triggers firstrun (autosign handles the CSR).
|
||||
systemctl enable puppet
|
||||
%end
|
||||
@@ -0,0 +1,66 @@
|
||||
{{- /*
|
||||
Fedora kickstart (family-level template: matches any "fedoraNN" platform slug
|
||||
via the OS-family selection fallback). Kept close to the AlmaLinux template so
|
||||
the two stay comparable; the differences are the install tree layout and that
|
||||
Fedora ships a recent-enough dnf/agent story out of the box.
|
||||
*/ -}}
|
||||
#version=F{{ default "" .OSVersion }}
|
||||
# Rendered by bootapi for {{ .FQDN }} (platform {{ .Platform }})
|
||||
text
|
||||
firstboot --disable
|
||||
reboot
|
||||
|
||||
# --- install source ---
|
||||
url --url={{ .BootBaseURL }}/releases/{{ default "rawhide" .OSVersion }}/Everything/{{ .Arch }}/os/
|
||||
|
||||
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 }}"
|
||||
systemctl enable puppet
|
||||
%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.