# Authoring templates bootapi ships an embedded default set and lets you override or extend it. ## Where templates live - **Embedded defaults**: `templates/kickstart/*.ks.tmpl`, `templates/ipxe/*.ipxe.tmpl` and `templates/catalog/*.yaml`, compiled into the binary (`templates/embed.go`). These are the always-available startup fallback. - **Template git repo** (preferred in prod): `BOOTAPI_TEMPLATE_GIT_URL`. bootapi clones it at startup and re-pulls every `BOOTAPI_TEMPLATE_GIT_INTERVAL` (default 3m, like argocd), atomically swapping the loaded set on change. A parse failure keeps the **last-good** set and is only logged + counted (`bootapi_template_sync_failures_total`), so a bad push can't take bootapi down. If the repo is unreachable at startup, bootapi runs on the embedded defaults. The repo is `unkin/bootapi-templates` (seeded from these embedded files) and has its own CI validating templates + catalog. - **Override directory**: `BOOTAPI_TEMPLATE_DIR` (a ConfigMap mount), used only when no git URL is set. Files there override embedded ones by base name. In all cases the embedded defaults are the base layer; the git repo / override dir is layered on top, replacing files of the same base name and adding new ones. ## Naming - Kickstart: `.ks.tmpl` → registered as template ``. - iPXE: `.ipxe.tmpl` → registered as template ``. `` 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. 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//main/installer-/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 ```