80dbcdd108
The seed almalinux9 kickstart reconstructed the Cobbler contract from memory and guessed storage: it installed to sda with autopart and keyboard us. The real Cobbler server (cobbler.main.unkin.net) provisions the OptiPlex fleet from three model-specific profiles (almalinux9-dell_3050 / _3060 / _7080) that all install to NVMe with an explicit LVM layout and differ in disk selection, EFI handling and whether the VG grows. This folds that real content in, keeping one shared template driven by per-model catalog data. Changes: - Make almalinux9.ks.tmpl storage model-aware via .DistroVars.storage_mode: fixed-nvme (static nvme0n1, BIOS/MBR), auto-nvme (%pre picks the internal NVMe, UEFI-aware, --boot-drive), with vg_grow to grow the PV; generic/VM hosts keep the autopart-on-sda path. - Port the explicit LVM layout (/boot, root, swap, /home, /var/log) and the %pre NVMe picker + rootvg wipe from the real Cobbler dell templates. - Add catalog entries optiplex-3050 (fixed-nvme), optiplex-3060 (auto-nvme) and optiplex-7080 (auto-nvme + vg_grow), selected by a device's provision_template custom field set to the OptiPlex device_type slug. - Correct keyboard us -> au and add the kdump addon + kexec-tools/bind-utils to match the Cobbler profiles. - Document the per-model variants and selection in catalog/README.md. Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
199 lines
8.1 KiB
Cheetah
199 lines
8.1 KiB
Cheetah
{{- /*
|
|
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).
|
|
|
|
Storage is model-aware: the catalog entry selects a `storage_mode` (and, for
|
|
grow-to-fill, `vg_grow`) so one template serves VMs and every Dell OptiPlex
|
|
model. See the "storage" section and catalog/optiplex-*.yaml.
|
|
|
|
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='au'
|
|
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 (model-aware; selected by the distro catalog's storage_mode var) ---
|
|
# storage_mode is set per-model by the catalog entry (catalog/optiplex-*.yaml):
|
|
# "" generic/VM default -> autopart on sda (unchanged legacy path)
|
|
# "fixed-nvme" OptiPlex 3050 -> static nvme0n1, BIOS/MBR, fixed 31G VG
|
|
# "auto-nvme" OptiPlex 3060 / 7080 -> %pre picks the internal NVMe, EFI-aware
|
|
# vg_grow ("true") grows the LVM PV to fill the disk (OptiPlex 7080 only).
|
|
# Layout (explicit LVM: /boot, root, swap, /home, /var/log) mirrors the Cobbler
|
|
# profiles almalinux9-dell_3050 / _3060 / _7080. Ported from the real templates
|
|
# almalinux9_dell3050 / almalinux9_dell3060 / almalinux9_dell7080.
|
|
{{- $storage := index .DistroVars "storage_mode" }}
|
|
{{- $grow := eq (index .DistroVars "vg_grow") "true" }}
|
|
{{- if eq $storage "fixed-nvme" }}
|
|
# OptiPlex 3050: single known NVMe, legacy BIOS boot (no EFI, no boot-drive).
|
|
ignoredisk --only-use=nvme0n1
|
|
clearpart --all --initlabel --drives=nvme0n1
|
|
part /boot --fstype="xfs" --ondisk=nvme0n1 --size=1024
|
|
part pv.01 --fstype="lvmpv" --ondisk=nvme0n1 --size=31743
|
|
volgroup rootvg --pesize=4096 pv.01
|
|
logvol / --fstype="xfs" --size=10240 --name=root --vgname=rootvg
|
|
logvol swap --fstype="swap" --size=2048 --name=swap --vgname=rootvg
|
|
logvol /home --fstype="xfs" --size=9207 --name=home --vgname=rootvg
|
|
logvol /var/log --fstype="xfs" --size=10240 --name=varlog --vgname=rootvg
|
|
bootloader --location=mbr
|
|
{{- else if eq $storage "auto-nvme" }}
|
|
# OptiPlex 3060 / 7080: partition table is generated in %pre (below) into
|
|
# /tmp/bootapi-partitions and pulled in here, so the OS lands on whichever
|
|
# internal NVMe is present and the EFI partition is added only when booted UEFI.
|
|
%include /tmp/bootapi-partitions
|
|
{{- else }}
|
|
# Generic / VM default: single virtual disk, autopart.
|
|
ignoredisk --only-use=sda
|
|
clearpart --all --initlabel --drives=sda
|
|
bootloader --location=mbr --boot-drive=sda --append="crashkernel=auto"
|
|
autopart --type=lvm --nohome
|
|
{{- end }}
|
|
|
|
# kdump: reserve crash memory + install kexec-tools (Cobbler com_redhat_kdump).
|
|
%addon com_redhat_kdump --enable --reserve-mb='auto'
|
|
%end
|
|
{{- if eq $storage "auto-nvme" }}
|
|
|
|
# --- %pre: pick the internal NVMe and emit the partition table -----------------
|
|
# Ported from Cobbler almalinux9_dell3060 / almalinux9_dell7080. Anaconda runs
|
|
# %pre before partitioning, so /tmp/bootapi-partitions (pulled in by the
|
|
# %include above) is populated in time. Picks the first NVMe under 512GB, wipes
|
|
# any prior rootvg so re-provisioning is idempotent, and adds an EFI System
|
|
# Partition only when the host actually booted UEFI.
|
|
%pre --interpreter=/usr/bin/bash --log=/root/bootapi-pre.log
|
|
set -x
|
|
lsblk -d -b -n -o NAME,SIZE
|
|
|
|
# First internal NVMe under 512GB (skips large data disks and USB installers).
|
|
OSDISK=$(lsblk -d -b -n -o NAME,SIZE | awk '$1 ~ /^nvme/ && $2 < 549755813888 { print $1; exit }')
|
|
|
|
# Tear down a pre-existing rootvg so a re-install starts from a clean disk.
|
|
if vgs rootvg >/dev/null 2>&1; then
|
|
lvchange -an rootvg || true
|
|
vgchange -an rootvg || true
|
|
vgremove -y rootvg || true
|
|
for pv in $(pvs --noheadings -o pv_name,vg_name | awk '$2 == "rootvg" { print $1 }'); do
|
|
pvremove "$pv" --force --force -y || true
|
|
done
|
|
fi
|
|
|
|
wipefs -a "/dev/${OSDISK}"
|
|
dd if=/dev/zero of="/dev/${OSDISK}" bs=512 count=100
|
|
|
|
{
|
|
echo "ignoredisk --only-use=${OSDISK}"
|
|
echo "clearpart --all --initlabel --drives=${OSDISK}"
|
|
if [ -d /sys/firmware/efi ]; then
|
|
echo "part /boot/efi --fstype=vfat --ondisk=${OSDISK} --size=200"
|
|
fi
|
|
echo "part /boot --fstype=xfs --ondisk=${OSDISK} --size=1024"
|
|
echo "part pv.01 --fstype=lvmpv --ondisk=${OSDISK} --size=31743{{ if $grow }} --grow{{ end }}"
|
|
echo "volgroup rootvg --pesize=4096 pv.01"
|
|
echo "logvol / --fstype=xfs --size=10240 --name=root --vgname=rootvg"
|
|
echo "logvol swap --fstype=swap --size=2048 --name=swap --vgname=rootvg"
|
|
echo "logvol /home --fstype=xfs --size=9207 --name=home --vgname=rootvg"
|
|
echo "logvol /var/log --fstype=xfs --size=10240 --name=varlog --vgname=rootvg"
|
|
echo "bootloader --location=mbr --boot-drive=${OSDISK}"
|
|
} > /tmp/bootapi-partitions
|
|
%end
|
|
{{- end }}
|
|
|
|
# --- packages ---
|
|
%packages --ignoremissing --excludedocs
|
|
@^minimal-environment
|
|
openssh-server
|
|
chrony
|
|
kexec-tools
|
|
bind-utils
|
|
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
|