From 80dbcdd1080a91bc6995424620b849739fcb0a55 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Wed, 29 Jul 2026 00:48:08 +1000 Subject: [PATCH] Add per-model OptiPlex kickstart variants from live Cobbler 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 --- catalog/README.md | 33 ++++++++++++- catalog/optiplex-3050.yaml | 23 ++++++++++ catalog/optiplex-3060.yaml | 21 +++++++++ catalog/optiplex-7080.yaml | 22 +++++++++ kickstart/almalinux9.ks.tmpl | 89 +++++++++++++++++++++++++++++++++++- 5 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 catalog/optiplex-3050.yaml create mode 100644 catalog/optiplex-3060.yaml create mode 100644 catalog/optiplex-7080.yaml diff --git a/catalog/README.md b/catalog/README.md index 779d29f..dd9b796 100644 --- a/catalog/README.md +++ b/catalog/README.md @@ -26,9 +26,40 @@ YAML + template change here — **no bootapi code change**. ## Shipped entries - **almalinux9** — artifactapi `almalinux` remote, tree - `almalinux//BaseOS//os/`. + `almalinux//BaseOS//os/`. Generic / VM default (autopart on `sda`). - **fedora** — artifactapi `fedora` remote, tree `fedora/releases//Everything//os/`. +- **optiplex-3050 / optiplex-3060 / optiplex-7080** — AlmaLinux 9 on the Dell + OptiPlex fleet. Same install tree and `almalinux9` kickstart template, but + each passes a `storage_mode` (and, for the 7080, `vg_grow`) var so the one + template lays disks out per model. Ported from the Cobbler profiles + `almalinux9-dell_3050 / _3060 / _7080`. See "Per-model variants" below. + +## Per-model variants (Dell OptiPlex) + +Different OptiPlex models need slightly different storage handling, so each is +its own catalog entry selecting the shared `almalinux9` kickstart with a +model-specific `storage_mode` var: + +| Entry | `storage_mode` | Disk selection | Firmware | LVM PV | +|-------|----------------|----------------|----------|--------| +| `optiplex-3050` | `fixed-nvme` | static `nvme0n1` | legacy BIOS/MBR | fixed ~31G | +| `optiplex-3060` | `auto-nvme` | `%pre` picks first NVMe <512G | UEFI-aware (EFI part if booted UEFI) | fixed ~31G | +| `optiplex-7080` | `auto-nvme` + `vg_grow: "true"` | `%pre` picks first NVMe <512G | UEFI-aware | grows to fill disk | + +The kickstart (`kickstart/almalinux9.ks.tmpl`) reads `.DistroVars.storage_mode` +/ `.DistroVars.vg_grow` and renders the matching storage stanza; any entry +without those vars (e.g. plain `almalinux9`, `fedora`) gets the generic +autopart-on-`sda` path. + +**Model selection (NetBox-driven, no code change):** the entry `name` is the +OptiPlex **device_type slug**. Set the device's `provision_template` custom +field to that slug (`optiplex-3050` / `-3060` / `-7080`) and bootapi's catalog +`Select` picks the matching entry (override precedence beats platform/family). +The `match.platforms` slug on each entry is the device_type slug too — not a +real NetBox platform slug — so these entries are *only* reachable via the +override and never hijack a plain `almalinux9` host. To add another model, copy +one of these files, change the `name`/slug and `storage_mode`. ## Adding another distro (the intended path) diff --git a/catalog/optiplex-3050.yaml b/catalog/optiplex-3050.yaml new file mode 100644 index 0000000..a3cf9c6 --- /dev/null +++ b/catalog/optiplex-3050.yaml @@ -0,0 +1,23 @@ +# Distro catalog entry: AlmaLinux 9 on the Dell OptiPlex 3050. +# +# Same install tree as the generic almalinux9 entry, but with the storage layout +# the Cobbler profile almalinux9-dell_3050 used: a single known NVMe (nvme0n1), +# legacy BIOS/MBR boot, and a fixed ~31G LVM (no grow). Model selection is +# NetBox-driven: set the device's `provision_template` custom field to this +# entry's name (the OptiPlex device_type slug "optiplex-3050"). Because the +# match slug below is the device_type -- not a real platform slug -- this entry +# is never auto-selected for a plain almalinux9 host; only the override picks it. +name: optiplex-3050 +match: + platforms: [optiplex-3050] +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: + mirror: "{{.ArtifactBase}}/almalinux/{{.Version}}" + # storage_mode drives the model-specific storage stanza in almalinux9.ks.tmpl. + storage_mode: fixed-nvme diff --git a/catalog/optiplex-3060.yaml b/catalog/optiplex-3060.yaml new file mode 100644 index 0000000..0a8ad2b --- /dev/null +++ b/catalog/optiplex-3060.yaml @@ -0,0 +1,21 @@ +# Distro catalog entry: AlmaLinux 9 on the Dell OptiPlex 3060. +# +# Same install tree as the generic almalinux9 entry, with the storage behaviour +# of the Cobbler profile almalinux9-dell_3060: a %pre script auto-selects the +# internal NVMe (first under 512G), is UEFI-aware (adds an EFI System Partition +# only when booted UEFI), sets bootloader --boot-drive, and uses a fixed ~31G +# LVM (no grow). Select it by setting the device's `provision_template` custom +# field to "optiplex-3060" (the OptiPlex device_type slug). +name: optiplex-3060 +match: + platforms: [optiplex-3060] +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: + mirror: "{{.ArtifactBase}}/almalinux/{{.Version}}" + storage_mode: auto-nvme diff --git a/catalog/optiplex-7080.yaml b/catalog/optiplex-7080.yaml new file mode 100644 index 0000000..0e3ee66 --- /dev/null +++ b/catalog/optiplex-7080.yaml @@ -0,0 +1,22 @@ +# Distro catalog entry: AlmaLinux 9 on the Dell OptiPlex 7080. +# +# Identical to optiplex-3060 (auto-NVMe %pre, UEFI-aware, --boot-drive) EXCEPT +# the LVM PV grows to fill the disk -- the one real delta between the Cobbler +# profiles almalinux9-dell_3060 and almalinux9-dell_7080 (the 7080s ship larger +# NVMes and are meant to use the whole disk). vg_grow=true adds `--grow` to the +# pv partition. Select it via the device's `provision_template` custom field set +# to "optiplex-7080" (the OptiPlex device_type slug). +name: optiplex-7080 +match: + platforms: [optiplex-7080] +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: + mirror: "{{.ArtifactBase}}/almalinux/{{.Version}}" + storage_mode: auto-nvme + vg_grow: "true" diff --git a/kickstart/almalinux9.ks.tmpl b/kickstart/almalinux9.ks.tmpl index 3a09645..1316654 100644 --- a/kickstart/almalinux9.ks.tmpl +++ b/kickstart/almalinux9.ks.tmpl @@ -8,6 +8,10 @@ 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. */ -}} @@ -24,7 +28,7 @@ url --url={{ $mirror }}/BaseOS/{{ .Arch }}/os/ repo --name=AppStream --baseurl={{ $mirror }}/AppStream/{{ .Arch }}/os/ # --- localization --- -keyboard --xlayouts='us' +keyboard --xlayouts='au' lang en_AU.UTF-8 timezone Australia/Sydney --utc @@ -46,17 +50,98 @@ network --bootproto=static --device={{ .MAC }} --ip={{ .IP }} --netmask={{ .Netm {{- end }} {{- end }} -# --- storage --- +# --- 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