274c480b09
bootapi replaces Cobbler's PXE/kickstart side. It resolves a PXE-booting host
from NetBox (by MAC or hostname), renders an iPXE boot script and a kickstart
from Go text/templates, and serves them over HTTP. The ENC half already moved to
encapi; this covers the provisioning/boot half.
What's here:
- cmd/bootapi + internal/{config,model,netbox,render,server}; embedded default
templates under templates/ (AlmaLinux 9 + Fedora kickstarts, iPXE boot +
unknown-MAC fallbacks) ported from Cobbler's boot/bootstrap contract.
- NetBox client (v4.x API) behind a Resolver interface with a short-TTL cache;
tested against httptest fixtures using real NetBox JSON shapes.
- chi HTTP server: /ipxe/{mac}, /boot/ipxe?mac=, /ks/{ident}, healthz/readyz,
Prometheus /metrics. Unknown MAC -> safe fallback iPXE (200), unknown KS -> 404.
- Secrets (root pw hash, ssh keys) injected at render time from env/Vault, never
NetBox. Config is env-based per estate convention.
- Makefile (build/test/lint/docker + patch/minor/major), Dockerfile (distroless),
.woodpecker (pre-commit, golangci-lint v2 + go test -race, docker build on PR;
image push + Gitea binary release on v* tag), docs/ and example config.
go build/vet clean, go test -race green, golangci-lint v2 clean, pre-commit clean.
Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
101 lines
3.7 KiB
Go
101 lines
3.7 KiB
Go
// 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
|
|
}
|