Files
bootapi/docs/template-authoring.md
T
unkin-agent a414918350
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
Fetch templates over HTTP instead of shelling out to git
The runtime image is distroless and has no git binary, so every sync
failed and bootapi silently served the stale embedded templates.

- fetch the branch tarball (<repo>/archive/<branch>.tar.gz) and extract
  it into an in-memory FS; no checkout, no writable volume
- digest the extracted tree, not the archive bytes, so a recompressed
  identical archive is not a change
- skip entries that would escape the tree
- log the source commit from Gitea's immutable Link header
2026-09-26 18:59:07 +10:00

119 lines
5.6 KiB
Markdown

# Authoring templates
bootapi ships an embedded default set and lets you override or extend it.
## Where templates live
- **Embedded defaults**: `templates/kickstart/*.ks.tmpl`,
`templates/ipxe/*.ipxe.tmpl` and `templates/catalog/*.yaml`, compiled into the
binary (`templates/embed.go`). These are the always-available startup fallback.
- **Template git repo** (preferred in prod): `BOOTAPI_TEMPLATE_GIT_URL`. bootapi
fetches the branch tarball (`<repo>/archive/<branch>.tar.gz`) over HTTP at
startup and re-fetches every `BOOTAPI_TEMPLATE_GIT_INTERVAL`
(default 3m, like argocd), atomically swapping the loaded set on change. A
parse failure keeps the **last-good** set and is only logged + counted
(`bootapi_template_sync_failures_total`), so a bad push can't take bootapi
down. If the repo is unreachable at startup, bootapi runs on the embedded
defaults. The repo is `unkin/bootapi-templates` (seeded from these embedded
files) and has its own CI validating templates + catalog.
- **Override directory**: `BOOTAPI_TEMPLATE_DIR` (a ConfigMap mount), used only
when no git URL is set. Files there override embedded ones by base name.
In all cases the embedded defaults are the base layer; the git repo / override
dir is layered on top, replacing files of the same base name and adding new ones.
## Naming
- Kickstart: `<name>.ks.tmpl` → registered as template `<name>`.
- iPXE: `<name>.ipxe.tmpl` → registered as template `<name>`.
`<name>` is what template selection matches against (platform slug, OS family,
`provision_template`, or the configured default — see
[data-model.md](data-model.md#template-selection-precedence)).
Reserved iPXE names bootapi renders directly:
- `boot` — the per-host boot script (`/ipxe/{mac}` for a known host).
- `fallback-local`, `fallback-shell` — unknown-MAC fallbacks.
## The distro catalog
`catalog/*.yaml` describes each bootable OS, so adding a distro is a YAML +
template change (and, if needed, a new artifactapi remote) — **no bootapi code
change**. One file per distro:
```yaml
name: almalinux9 # catalog key; also what provision_template matches
match:
platforms: [almalinux9] # exact NetBox platform slugs
family: almalinux # OR an OS family (matches almalinux8/9/...)
kickstart: almalinux9 # kickstart template name to render
version_default: "9" # used when the platform slug carries no version
kernel_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/vmlinuz"
initrd_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/initrd.img"
kernel_args: [inst.text, net.ifnames=0]
vars: # arbitrary templated strings -> .DistroVars.<key>
mirror: "{{.ArtifactBase}}/almalinux/{{.Version}}"
```
`kernel_url`, `initrd_url` and each `vars` value are Go templates rendered with
`{{.ArtifactBase}}` (`BOOTAPI_ARTIFACT_BASE_URL`), `{{.Version}}`, `{{.Arch}}`,
`{{.Hostname}}`, `{{.Platform}}`, `{{.OSFamily}}`. The kickstart template reads
`.DistroVars.mirror` to build its `url`/`repo` lines, so the install-tree layout
lives entirely in the catalog. bootapi derives `inst.repo` for iPXE by trimming
`/images/pxeboot/vmlinuz` off `kernel_url`.
Shipped entries: `almalinux9` (artifactapi `almalinux` remote) and `fedora`
(`fedora` remote). **debian / talos** are documented but not implemented — their
artifact shapes differ (Debian netboot `linux`+`initrd.gz` under
`dists/<rel>/main/installer-<arch>/current/images/netboot/`; Talos ships factory
`vmlinuz`+`initramfs.xz` images) so they need their own catalog fields/template
and possibly a new artifactapi remote. See the catalog README in the templates
repo for the intended path.
## Engine and functions
Standard Go `text/template`. Available funcs: `join`, `upper`, `lower`,
`default` (`{{ default "x" .Maybe }}` → `.Maybe` unless empty). The data model is
in [data-model.md](data-model.md).
Example network stanza (iterate interfaces, skip those without an IP, set the
hostname on the primary):
```gotemplate
{{- $primary := .PrimaryInterface }}
{{- range .Interfaces }}
{{- if .IP }}
network --bootproto=static --device={{ .MAC }} --ip={{ .IP }} --netmask={{ .Netmask }}{{ if .Gateway }} --gateway={{ .Gateway }}{{ end }}{{ range $.Nameservers }} --nameserver={{ . }}{{ end }}{{ if and $primary (eq .MAC $primary.MAC) }} --hostname={{ $.FQDN }}{{ end }} --activate
{{- end }}
{{- end }}
```
## What the default AlmaLinux template does (ported from Cobbler)
The literal `.ks` bodies from the old Cobbler server are not in version control
(they lived in `/var/lib/cobbler/{templates,snippets}` on the Cobbler host). The
embedded `almalinux9.ks.tmpl` reproduces the **contract** that estate relied on:
- static per-interface networking from NetBox, hostname on the primary NIC;
- `rootpw --iscrypted` from the render-time hash (Cobbler's
`default_password_crypted`), or `--lock` when unset;
- a minimal package set + `openssh-server`, `chrony`;
- a `%post` that installs the Puppet agent, points it at `puppet.query.consul` /
`puppetca.query.consul`, and enables it — handing off to the existing
`profiles::firstrun` Puppet bootstrap and autosign, exactly as the Cobbler
kickstart did.
Adjust partitioning, package sets and repos to taste; keep the puppet `%post`
handoff so a freshly-installed host still checks in and converges.
## Testing a template locally
```bash
BOOTAPI_NETBOX_URL=... BOOTAPI_NETBOX_TOKEN=... \
BOOTAPI_BASE_URL=http://localhost:8000 \
BOOTAPI_BOOT_BASE_URL=http://mirror/almalinux/9 \
BOOTAPI_TEMPLATE_DIR=./mytemplates ./bin/bootapi &
curl -s localhost:8000/ks/web01 # rendered kickstart
curl -s localhost:8000/ipxe/aa:bb:cc:00:11:22
```