From 24fd6b3ffaf9278b1baef3026c3a7932d3cddc0a Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Tue, 28 Jul 2026 23:10:35 +1000 Subject: [PATCH] Seed bootapi templates + distro catalog Initial content for bootapi's git-synced template set: the kickstart and iPXE templates and the distro catalog (almalinux9, fedora) ported from bootapi's embedded defaults, plus CI that validates every template/catalog renders for every distro (bootapi validate) and shellchecks the %post blocks. Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv --- .pre-commit-config.yaml | 15 +++++ .woodpecker/pre-commit.yaml | 18 ++++++ .woodpecker/validate.yaml | 44 +++++++++++++ .yamllint.yaml | 8 +++ README.md | 42 ++++++++++++- catalog/README.md | 56 +++++++++++++++++ catalog/almalinux9.yaml | 18 ++++++ catalog/fedora.yaml | 16 +++++ ci/shellcheck-post.sh | 42 +++++++++++++ ipxe/boot.ipxe.tmpl | 20 ++++++ ipxe/fallback-local.ipxe.tmpl | 10 +++ ipxe/fallback-shell.ipxe.tmpl | 10 +++ kickstart/almalinux9.ks.tmpl | 113 ++++++++++++++++++++++++++++++++++ kickstart/fedora.ks.tmpl | 80 ++++++++++++++++++++++++ 14 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml create mode 100644 .woodpecker/pre-commit.yaml create mode 100644 .woodpecker/validate.yaml create mode 100644 .yamllint.yaml create mode 100644 catalog/README.md create mode 100644 catalog/almalinux9.yaml create mode 100644 catalog/fedora.yaml create mode 100755 ci/shellcheck-post.sh create mode 100644 ipxe/boot.ipxe.tmpl create mode 100644 ipxe/fallback-local.ipxe.tmpl create mode 100644 ipxe/fallback-shell.ipxe.tmpl create mode 100644 kickstart/almalinux9.ks.tmpl create mode 100644 kickstart/fedora.ks.tmpl diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..d6088b5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +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/adrienverge/yamllint + rev: v1.35.1 + hooks: + - id: yamllint + args: [-c, .yamllint.yaml] diff --git a/.woodpecker/pre-commit.yaml b/.woodpecker/pre-commit.yaml new file mode 100644 index 0000000..98ffe88 --- /dev/null +++ b/.woodpecker/pre-commit.yaml @@ -0,0 +1,18 @@ +when: + - event: [pull_request, push] + +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 diff --git a/.woodpecker/validate.yaml b/.woodpecker/validate.yaml new file mode 100644 index 0000000..4f6ff21 --- /dev/null +++ b/.woodpecker/validate.yaml @@ -0,0 +1,44 @@ +when: + - event: [pull_request, push] + +steps: + # Render every kickstart + iPXE script for every catalog distro against a + # fixture host, failing on any parse/resolve error or unresolved value. Reuses + # the real bootapi engine via its `validate` subcommand (fetched @main). + - name: validate + image: golang:1.25 + environment: + GOFLAGS: -buildvcs=false + GOPRIVATE: git.unkin.net + GONOSUMCHECK: "1" + GOSUMDB: "off" + GOPROXY: direct + commands: + - go run git.unkin.net/unkin/bootapi/cmd/bootapi@main validate . + backend_options: + kubernetes: + serviceAccountName: default + resources: + requests: + memory: 512Mi + cpu: 1 + limits: + memory: 2Gi + cpu: 2 + + # Best-effort shellcheck of the %post blocks (template actions stripped first). + - name: shellcheck + image: koalaman/shellcheck-alpine:stable + commands: + - apk add --no-cache bash + - bash ci/shellcheck-post.sh + backend_options: + kubernetes: + serviceAccountName: default + resources: + requests: + memory: 128Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..5173038 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,8 @@ +--- +extends: relaxed + +rules: + line-length: disable + document-start: disable + comments: + min-spaces-from-content: 1 diff --git a/README.md b/README.md index d69c188..bac6bff 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ # bootapi-templates -Live kickstart/iPXE templates + distro catalog for bootapi. Pulled by bootapi via git-sync; CI validates templates render for every distro. \ No newline at end of file +Live kickstart / iPXE templates and the **distro catalog** for +[bootapi](https://git.unkin.net/unkin/bootapi). bootapi git-syncs this repo +(every ~3 minutes, like argocd) and hot-swaps its template set on change, so OS +and template updates ship by merging here — no bootapi rebuild or redeploy. If +this repo is unreachable, bootapi falls back to its embedded copy of these files. + +## Layout + +``` +kickstart/.ks.tmpl # kickstart templates (Go text/template) +ipxe/.ipxe.tmpl # iPXE scripts: boot, fallback-local, fallback-shell +catalog/.yaml # distro catalog: host -> boot images + kickstart +catalog/README.md # catalog field reference + debian/talos path +ci/shellcheck-post.sh # %post shellcheck helper (used by CI) +``` + +The data model available to templates and the catalog field reference live in +the bootapi docs: [data-model](https://git.unkin.net/unkin/bootapi/src/branch/main/docs/data-model.md), +[template-authoring](https://git.unkin.net/unkin/bootapi/src/branch/main/docs/template-authoring.md), +and [catalog/README.md](catalog/README.md) here. + +## CI (required before merge) + +- **validate** — `bootapi validate .` renders every kickstart + iPXE script for + every catalog distro against a fixture host and fails on any parse/resolve + error or unresolved ``. Plus `shellcheck` (error severity) of the + `%post` blocks. +- **pre-commit** — yamllint + whitespace/EOF checks. + +Validate locally before pushing: + +```bash +go run git.unkin.net/unkin/bootapi/cmd/bootapi@main validate . +bash ci/shellcheck-post.sh # needs shellcheck +``` + +## Adding a distro + +Add `catalog/.yaml` + `kickstart/.ks.tmpl` (and, if the OS needs a +mirror bootapi can't reach, a terraform-artifactapi remote). See +[catalog/README.md](catalog/README.md) — including the debian/talos path. diff --git a/catalog/README.md b/catalog/README.md new file mode 100644 index 0000000..779d29f --- /dev/null +++ b/catalog/README.md @@ -0,0 +1,56 @@ +# Distro catalog + +One YAML file per bootable OS. bootapi selects an entry for a NetBox host +(platform slug / OS family / `provision_template` override) and uses it to build +the iPXE kernel/initrd URLs and pick the kickstart template. Adding an OS is a +YAML + template change here — **no bootapi code change**. + +## Fields + +| Field | Required | Meaning | +|-------|----------|---------| +| `name` | yes | catalog key; also what a `provision_template` override matches | +| `match.platforms` | one of platforms/family | exact NetBox platform slugs | +| `match.family` | one of platforms/family | NetBox platform family (matches any version) | +| `kickstart` | yes | kickstart template name (`kickstart/.ks.tmpl`) | +| `kernel_url` | yes | Go-template for the installer kernel URL | +| `initrd_url` | yes | Go-template for the installer initrd URL | +| `version_default` | no | version used when the platform slug carries none | +| `kernel_args` | no | extra iPXE kernel args | +| `vars` | no | named Go-template strings exposed to templates as `.DistroVars.` | + +`kernel_url`, `initrd_url` and `vars` values are rendered with: +`{{.ArtifactBase}}` (bootapi's `BOOTAPI_ARTIFACT_BASE_URL`), `{{.Version}}`, +`{{.Arch}}`, `{{.Hostname}}`, `{{.Platform}}`, `{{.OSFamily}}`. + +## Shipped entries + +- **almalinux9** — artifactapi `almalinux` remote, tree + `almalinux//BaseOS//os/`. +- **fedora** — artifactapi `fedora` remote, tree + `fedora/releases//Everything//os/`. + +## Adding another distro (the intended path) + +Add `catalog/.yaml` + `kickstart/.ks.tmpl`. If the OS lives on a +mirror bootapi can't already reach, add an artifactapi remote first +(terraform-artifactapi `config/remote_rpm/.yaml`). CI (`bootapi validate`) +will render the new distro against a fixture host and fail the PR if anything is +unresolved. + +### Debian / Talos — not implemented, different artifact shapes + +These need their own catalog fields/template because their PXE artifacts differ +from the RHEL-family `images/pxeboot/{vmlinuz,initrd.img}` layout: + +- **Debian**: netboot installer under + `dists//main/installer-/current/images/netboot/` with + `linux` + `initrd.gz`, and preseed (not kickstart). A `debian` entry would set + `kernel_url`/`initrd_url` to those paths and reference a `debian.preseed.tmpl` + template (and bootapi would serve it from `/ks` unchanged — it's just text). +- **Talos**: immutable OS booted from factory images (`vmlinuz` + `initramfs.xz` + from the Talos image factory / a mirrored remote), configured by a machine + config, not a kickstart. A `talos` entry would point `kernel_url`/`initrd_url` + at the factory artifacts and carry the config URL via `kernel_args`/`vars`. + +Both are deferred; this note records the shape so the follow-up is mechanical. diff --git a/catalog/almalinux9.yaml b/catalog/almalinux9.yaml new file mode 100644 index 0000000..25e2802 --- /dev/null +++ b/catalog/almalinux9.yaml @@ -0,0 +1,18 @@ +# Distro catalog entry: AlmaLinux 9. +# Boot images are proxied through the artifactapi "almalinux" remote. Host -> +# distro selection is NetBox-driven (platform slug almalinux9, or the almalinux +# family, or a provision_template override naming "almalinux9"). +name: almalinux9 +match: + platforms: [almalinux9] + family: almalinux +kickstart: almalinux9 +version_default: "9" +kernel_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/vmlinuz" +initrd_url: "{{.ArtifactBase}}/almalinux/{{.Version}}/BaseOS/{{.Arch}}/os/images/pxeboot/initrd.img" +kernel_args: + - inst.text + - net.ifnames=0 +vars: + # Version-level mirror base; the kickstart appends BaseOS/AppStream under it. + mirror: "{{.ArtifactBase}}/almalinux/{{.Version}}" diff --git a/catalog/fedora.yaml b/catalog/fedora.yaml new file mode 100644 index 0000000..32ce267 --- /dev/null +++ b/catalog/fedora.yaml @@ -0,0 +1,16 @@ +# Distro catalog entry: Fedora (family-level, matches any fedoraNN platform). +# Boot images are proxied through the artifactapi "fedora" remote, whose tree +# lives under releases//Everything//os/. +name: fedora +match: + family: fedora +kickstart: fedora +version_default: "41" +kernel_url: "{{.ArtifactBase}}/fedora/releases/{{.Version}}/Everything/{{.Arch}}/os/images/pxeboot/vmlinuz" +initrd_url: "{{.ArtifactBase}}/fedora/releases/{{.Version}}/Everything/{{.Arch}}/os/images/pxeboot/initrd.img" +kernel_args: + - inst.text + - net.ifnames=0 +vars: + # Install-tree root; the kickstart appends /os/ under it. + mirror: "{{.ArtifactBase}}/fedora/releases/{{.Version}}/Everything" diff --git a/ci/shellcheck-post.sh b/ci/shellcheck-post.sh new file mode 100755 index 0000000..bfbfd80 --- /dev/null +++ b/ci/shellcheck-post.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# Best-effort shellcheck of the %post ... %end blocks in the kickstart templates. +# The bodies are Go text/templates, so we first replace every {{ ... }} action +# with a shell-safe placeholder, then extract each %post block into a script and +# run shellcheck at "error" severity (template placeholders make lower-severity +# style warnings meaningless). Fails the build on any shellcheck error. +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${ROOT_DIR}" + +tmp="$(mktemp -d)" +trap 'rm -rf "${tmp}"' EXIT +rc=0 + +shopt -s nullglob +for ks in kickstart/*.ks.tmpl; do + # Split into %post-scriptlets; strip {{...}} -> "PH"; give each a shebang. + awk ' + /^%post/ { inpost=1; n++; next } + /^%end/ { inpost=0; next } + inpost { print > ("'"${tmp}"'/post-" n ".sh") } + ' "${ks}" + + for f in "${tmp}"/post-*.sh; do + [ -e "${f}" ] || continue + # Neutralize Go template actions and prepend a shebang. + sed -i 's/{{[^}]*}}/PH/g' "${f}" + printf '#!/usr/bin/env bash\n%s' "$(cat "${f}")" > "${f}.final" + echo "shellcheck (error severity): ${ks} -> $(basename "${f}")" + if ! shellcheck -S error "${f}.final"; then + rc=1 + fi + rm -f "${f}" "${f}.final" + done +done + +if [ "${rc}" -eq 0 ]; then + echo "shellcheck: no errors in %post blocks" +fi +exit "${rc}" diff --git a/ipxe/boot.ipxe.tmpl b/ipxe/boot.ipxe.tmpl new file mode 100644 index 0000000..bf94c18 --- /dev/null +++ b/ipxe/boot.ipxe.tmpl @@ -0,0 +1,20 @@ +{{- /* +iPXE boot script for a known, PXE-enabled host. Chains the OS installer +kernel+initrd (from the distro catalog) and points inst.ks= back at bootapi's +/ks/ over plain HTTP, so an installer with no internal-CA trust can fetch +it. Mirrors how Cobbler generated a per-MAC gPXE script carrying inst.ks=. + +KernelURL/InitrdURL/RepoURL come from the selected catalog entry (artifactapi +remote); KernelArgs are the catalog's extra args. KickstartURL uses +BOOTAPI_BASE_URL (http://). +*/ -}} +#!ipxe +echo bootapi: provisioning {{ .FQDN }} ({{ .Platform }}) +{{ if and .KernelURL .InitrdURL -}} +kernel {{ .KernelURL }} initrd=initrd.img{{ if .RepoURL }} inst.repo={{ .RepoURL }}{{ end }} inst.ks={{ .KickstartURL }} ip=dhcp{{ range .KernelArgs }} {{ . }}{{ end }} +initrd {{ .InitrdURL }} +boot +{{- else -}} +echo bootapi: no boot images resolved for {{ .Platform }} (no catalog entry / BOOTAPI_BOOT_BASE_URL); booting local disk +sanboot --no-describe --drive 0x80 || exit +{{- end }} diff --git a/ipxe/fallback-local.ipxe.tmpl b/ipxe/fallback-local.ipxe.tmpl new file mode 100644 index 0000000..bb5086f --- /dev/null +++ b/ipxe/fallback-local.ipxe.tmpl @@ -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 diff --git a/ipxe/fallback-shell.ipxe.tmpl b/ipxe/fallback-shell.ipxe.tmpl new file mode 100644 index 0000000..2b5d66c --- /dev/null +++ b/ipxe/fallback-shell.ipxe.tmpl @@ -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 diff --git a/kickstart/almalinux9.ks.tmpl b/kickstart/almalinux9.ks.tmpl new file mode 100644 index 0000000..3a09645 --- /dev/null +++ b/kickstart/almalinux9.ks.tmpl @@ -0,0 +1,113 @@ +{{- /* +AlmaLinux 9 kickstart, ported from the Cobbler default.ks contract. + +Rendered by bootapi from NetBox data + render-time secrets + the distro catalog. +Install source comes from the artifactapi almalinux remote (via the catalog +mirror var). The %post installs the Puppet agent and points it at the k8s +puppetserver (puppet.k8s.syd1.au.unkin.net / puppetca.k8s...), writes the +puppet-initial PUPPETCA_URL env file, then posts back to bootapi so pxe_enabled +flips off (Cobbler's netboot_enabled flow). + +Data model: see docs/data-model.md. `.RootPasswordHash` and `.ProvisionToken` +come from Vault/env at render time, never from NetBox. +*/ -}} +{{- $mirror := .DistroVars.mirror -}} +#version=RHEL9 +# Rendered by bootapi for {{ .FQDN }} (platform {{ .Platform }}, role {{ default "none" .Role }}) +text +eula --agreed +firstboot --disable +reboot + +# --- install source (artifactapi almalinux remote, from the distro catalog) --- +url --url={{ $mirror }}/BaseOS/{{ .Arch }}/os/ +repo --name=AppStream --baseurl={{ $mirror }}/AppStream/{{ .Arch }}/os/ + +# --- localization --- +keyboard --xlayouts='us' +lang en_AU.UTF-8 +timezone Australia/Sydney --utc + +# --- security --- +{{ if .RootPasswordHash -}} +rootpw --iscrypted {{ .RootPasswordHash }} +{{- else -}} +rootpw --lock +{{- end }} +selinux --enforcing +firewall --enabled --service=ssh +authselect select sssd with-mkhomedir --force + +# --- networking (static, from NetBox) --- +{{- $primary := .PrimaryInterface }} +{{- range .Interfaces }} +{{- if .IP }} +network --bootproto=static --device={{ .MAC }} --ip={{ .IP }} --netmask={{ .Netmask }}{{ if .Gateway }} --gateway={{ .Gateway }}{{ end }}{{ range $.Nameservers }} --nameserver={{ . }}{{ end }}{{ if and $primary (eq .MAC $primary.MAC) }} --hostname={{ $.FQDN }}{{ end }} --activate --onboot=on --noipv6 +{{- end }} +{{- end }} + +# --- storage --- +ignoredisk --only-use=sda +clearpart --all --initlabel --drives=sda +bootloader --location=mbr --boot-drive=sda --append="crashkernel=auto" +autopart --type=lvm --nohome + +# --- packages --- +%packages --ignoremissing --excludedocs +@^minimal-environment +openssh-server +chrony +vim-minimal +tmux +git +-iwl*-firmware +%end + +# --- bootstrap: puppet (k8s) + end-of-install callback --- +%post --log=/root/bootapi-post.log +set -x + +# chrony: keep time sane before any cert work. +systemctl enable chronyd + +{{ if .SSHAuthorizedKeys -}} +# root authorized_keys (from render-time config, not NetBox). +install -d -m0700 /root/.ssh +cat > /root/.ssh/authorized_keys <<'EOF' +{{ range .SSHAuthorizedKeys }}{{ . }} +{{ end }}EOF +chmod 0600 /root/.ssh/authorized_keys +{{- end }} + +# Install the Puppet 8 agent from the puppet platform repo. +rpm -q puppet-agent >/dev/null 2>&1 || \ + dnf install -y https://yum.puppet.com/puppet8-release-el-9.noarch.rpm +dnf install -y puppet-agent + +# Point the agent at the k8s puppetserver / CA. +PUPPET_BIN=/opt/puppetlabs/bin/puppet +"$PUPPET_BIN" config set --section main certname "{{ .FQDN }}" +"$PUPPET_BIN" config set --section main server "{{ .PuppetServer }}" +"$PUPPET_BIN" config set --section main ca_server "{{ .PuppetCAServer }}" +"$PUPPET_BIN" config set --section main report_server "{{ .PuppetServer }}" +"$PUPPET_BIN" config set --section main environment production + +# puppet-initial bootstrap unit reads PUPPETCA_URL from this EnvironmentFile. +install -d -m0755 /etc/sysconfig +cat > /etc/sysconfig/puppet-initial <<'EOF' +PUPPETCA_URL={{ .PuppetCAURL }} +EOF + +# Enable the agent; first boot triggers firstrun (autosign handles the CSR). +systemctl enable puppet + +{{ if and .ProvisionToken .CallbackURL -}} +# Tell bootapi the install is done so it clears pxe_enabled in NetBox and the +# next PXE boots local disk. Runs over plain HTTP (no internal CA trust yet); +# the token authenticates the call. Non-fatal if it fails (the local-disk +# fallback still protects a re-provisioned host on the following boot). +curl -fsS -m 15 -X POST \ + -H "Authorization: Bearer {{ .ProvisionToken }}" \ + "{{ .CallbackURL }}" || echo "bootapi: provisioned callback failed (non-fatal)" +{{- end }} +%end diff --git a/kickstart/fedora.ks.tmpl b/kickstart/fedora.ks.tmpl new file mode 100644 index 0000000..0fb9de3 --- /dev/null +++ b/kickstart/fedora.ks.tmpl @@ -0,0 +1,80 @@ +{{- /* +Fedora kickstart (family-level template: matches any "fedoraNN" platform slug +via the catalog family match). Kept close to the AlmaLinux template so the two +stay comparable; the differences are the install-tree layout (releases/.../ +Everything) and the puppet release RPM. Install source + boot images come from +the artifactapi fedora remote via the distro catalog. +*/ -}} +{{- $mirror := .DistroVars.mirror -}} +#version=F{{ default "" .OSVersion }} +# Rendered by bootapi for {{ .FQDN }} (platform {{ .Platform }}) +text +firstboot --disable +reboot + +# --- install source (artifactapi fedora remote, from the distro catalog) --- +url --url={{ $mirror }}/{{ .Arch }}/os/ + +keyboard --xlayouts='us' +lang en_AU.UTF-8 +timezone Australia/Sydney --utc + +{{ if .RootPasswordHash -}} +rootpw --iscrypted {{ .RootPasswordHash }} +{{- else -}} +rootpw --lock +{{- end }} +selinux --enforcing +firewall --enabled --service=ssh + +# --- networking (static, from NetBox) --- +{{- $primary := .PrimaryInterface }} +{{- range .Interfaces }} +{{- if .IP }} +network --bootproto=static --device={{ .MAC }} --ip={{ .IP }} --netmask={{ .Netmask }}{{ if .Gateway }} --gateway={{ .Gateway }}{{ end }}{{ range $.Nameservers }} --nameserver={{ . }}{{ end }}{{ if and $primary (eq .MAC $primary.MAC) }} --hostname={{ $.FQDN }}{{ end }} --activate --onboot=on --noipv6 +{{- end }} +{{- end }} + +# --- storage --- +ignoredisk --only-use=sda +clearpart --all --initlabel --drives=sda +bootloader --location=mbr --boot-drive=sda +autopart --type=lvm --nohome + +%packages --ignoremissing +@^minimal-environment +openssh-server +chrony +git +%end + +%post --log=/root/bootapi-post.log +set -x +systemctl enable chronyd sshd +{{ if .SSHAuthorizedKeys -}} +install -d -m0700 /root/.ssh +cat > /root/.ssh/authorized_keys <<'EOF' +{{ range .SSHAuthorizedKeys }}{{ . }} +{{ end }}EOF +chmod 0600 /root/.ssh/authorized_keys +{{- end }} +dnf install -y "https://yum.puppet.com/puppet8-release-fedora-{{ default "40" .OSVersion }}.noarch.rpm" || true +dnf install -y puppet-agent +PUPPET_BIN=/opt/puppetlabs/bin/puppet +"$PUPPET_BIN" config set --section main certname "{{ .FQDN }}" +"$PUPPET_BIN" config set --section main server "{{ .PuppetServer }}" +"$PUPPET_BIN" config set --section main ca_server "{{ .PuppetCAServer }}" + +install -d -m0755 /etc/sysconfig +cat > /etc/sysconfig/puppet-initial <<'EOF' +PUPPETCA_URL={{ .PuppetCAURL }} +EOF + +systemctl enable puppet + +{{ if and .ProvisionToken .CallbackURL -}} +curl -fsS -m 15 -X POST \ + -H "Authorization: Bearer {{ .ProvisionToken }}" \ + "{{ .CallbackURL }}" || echo "bootapi: provisioned callback failed (non-fatal)" +{{- end }} +%end