ops(pfi-pve): PVE 9 prep done (8.4.21, grub-efi-amd64 on the UEFI/LVM box, microcode, NIC pins); upgrade plan + playbooks

This commit is contained in:
vh
2026-10-04 06:11:13 -07:00
parent a4b7626a13
commit 075ba0930e
4 changed files with 416 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
# pfi-pve: no-downtime prep for the PVE 8 → 9 upgrade (Prime 2026-10-04: "drive the ana pve upgrade to proxmox 9").
# scripts/elway infra-ops@10.250.250.31 --playbook playbooks/pfi-pve-pve9-prep.yaml
# Plan + rationale: docs/runbooks/pfi-pve-pve9-upgrade-plan.md. Template: playbooks/esh-nas-pve-pve9-prep.yaml.
# Nothing here reboots or touches a guest. The new kernel, microcode, bootloader and NIC pins all take effect at the
# NEXT boot, which is the planned reboot test on PVE 8, BEFORE any repo switch to trixie.
# - latest 8.4 packages (it is on 8.3.5 / kernel 6.8.12-8; pve8to9 only exists from 8.4);
# - non-free-firmware + intel-microcode;
# - grub-pc → grub-efi-amd64. The box boots UEFI from \EFI\proxmox\grubx64.efi, but the BIOS meta package is the
# one installed, so that EFI binary has not been touched since the 2023-03-30 (PVE 7.4 / bullseye) install. The
# PVE 8→9 wiki's "UEFI systems with root on LVM may fail to boot" fix is exactly this package. The 2023 binary is
# copied to EFI/proxmox-2023/ as a last resort only: once grub-install replaces /boot/grub/x86_64-efi, an old core
# loading new modules can fail (the grub_is_lockdown class), so the real fallback is the tar of /boot/grub;
# - pin every NIC name by MAC. vmbr0 has four ports and only eno8303 has carrier; the two bnxt ports carry an
# `np0/np1` phys_port_name suffix, the class of name the wiki says a new kernel can change.
vars:
stage_dir: /root/pve9-prep-20261004
steps:
- name: back up /etc, the package list and the 2023 EFI binaries
sudo: true
shell: |
set -e
mkdir -p {{ stage_dir }}
tar czf {{ stage_dir }}/etc-before.tar.gz -C / etc 2>/dev/null
dpkg -l > {{ stage_dir }}/dpkg-before.txt
pveversion -v > {{ stage_dir }}/pveversion-before.txt 2>&1
cp -p /etc/network/interfaces {{ stage_dir }}/interfaces.before
cp -p /etc/apt/sources.list {{ stage_dir }}/sources.list.before
cp -a /boot/efi/EFI {{ stage_dir }}/EFI-2023
cp -a /boot/grub {{ stage_dir }}/boot-grub-before
ls -la {{ stage_dir }}
creates: "{{ stage_dir }}/etc-before.tar.gz"
- name: keep the 2023 GRUB bootable from the ESP (EFI/proxmox-2023/)
sudo: true
shell: |
set -e
mkdir -p /boot/efi/EFI/proxmox-2023
cp -p /boot/efi/EFI/proxmox/grubx64.efi /boot/efi/EFI/proxmox-2023/grubx64.efi
md5sum /boot/efi/EFI/proxmox-2023/grubx64.efi
creates: /boot/efi/EFI/proxmox-2023/grubx64.efi
- name: add non-free-firmware to the Debian bookworm lines (for intel-microcode)
sudo: true
shell: sed -i -E '/^deb .*debian(\.org)?(\/debian)? bookworm/{/non-free-firmware/!s/$/ non-free-firmware/}' /etc/apt/sources.list
when: "grep -E '^deb .*debian(\\.org)?(/debian)? bookworm' /etc/apt/sources.list | grep -qv non-free-firmware"
- name: apt update (must be clean)
sudo: true
shell: |
! apt-get update -q 2>&1 | grep -E '^(E|Err):'
changed_when: "false"
- name: latest 8.4 packages (keep local configs)
sudo: true
shell: |
set -o pipefail
DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y -q \
-o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold 2>&1 | tail -8
pveversion | cat
- name: intel-microcode
sudo: true
shell: DEBIAN_FRONTEND=noninteractive apt-get install -y -q intel-microcode 2>&1 | tail -3
when: "! dpkg-query -W -f='${Status}' intel-microcode 2>/dev/null | grep -q 'install ok installed'"
- name: grub-pc → grub-efi-amd64, with the removable-path copy (EFI/BOOT/BOOTX64.EFI) kept in step
sudo: true
shell: |
set -o pipefail
echo 'grub-efi-amd64 grub2/force_efi_extra_removable boolean true' | debconf-set-selections -v -u
DEBIAN_FRONTEND=noninteractive apt-get install -y -q --no-install-recommends grub-efi-amd64 2>&1 | tail -8
when: "! dpkg-query -W -f='${Status}' grub-efi-amd64 2>/dev/null | grep -q 'install ok installed'"
- name: make sure the ESP binary was actually rewritten (grub-install explicitly if the postinst skipped it)
sudo: true
shell: |
set -e
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=proxmox --force-extra-removable 2>&1 | tail -3
update-grub 2>&1 | tail -3
when: "test \"$(md5sum < /boot/efi/EFI/proxmox/grubx64.efi | cut -d' ' -f1)\" = a4a8d8d1e75eb2dad19c22a8c1065c4c"
- name: pin every NIC name to its MAC (systemd .link; takes effect next boot)
sudo: true
shell: |
set -e
pin() { # name mac
cat > /etc/systemd/network/10-pin-$1.link <<EOF
# Pin $1 by MAC so the PVE 9 / Debian 13 udev cannot rename it.
# See eshpfi playbooks/pfi-pve-pve9-prep.yaml
[Match]
MACAddress=$2
Type=ether
[Link]
Name=$1
EOF
}
pin eno8303 cc:48:3a:1b:c3:d0
pin eno8403 cc:48:3a:1b:c3:d1
pin eno12399np0 00:62:0b:c6:ca:f0
pin eno12409np1 00:62:0b:c6:ca:f1
ls -la /etc/systemd/network/
when: "! test -f /etc/systemd/network/10-pin-eno8303.link"
- name: rebuild the initramfs of the running and the newest kernel so the .link files apply in early boot
sudo: true
shell: |
set -e
new=$(ls /boot/vmlinuz-* | sed 's#/boot/vmlinuz-##' | sort -V | tail -1)
for k in $(uname -r) $new; do update-initramfs -u -k $k 2>&1 | tail -1; done
when: "! lsinitramfs /boot/initrd.img-$(ls /boot/vmlinuz-* | sed 's#/boot/vmlinuz-##' | sort -V | tail -1) | grep -q 10-pin-eno8303.link"
verify:
- name: interfaces file still parses (no live reload)
sudo: true
shell: ifreload -a -s
changed_when: "false"
- name: newest kernel's initramfs carries the uplink pin
sudo: true
shell: lsinitramfs /boot/initrd.img-$(ls /boot/vmlinuz-* | sed 's#/boot/vmlinuz-##' | sort -V | tail -1) | grep -q 10-pin-eno8303.link
changed_when: "false"
- name: grub-efi-amd64 in, grub-pc out, intel-microcode in
sudo: true
shell: |
dpkg-query -W -f='${Status}' grub-efi-amd64 | grep -q 'install ok installed' &&
! dpkg-query -W -f='${Status}' grub-pc 2>/dev/null | grep -q 'install ok installed' &&
dpkg-query -W -f='${Status}' intel-microcode | grep -q 'install ok installed'
changed_when: "false"
- name: ESP carries a NEW grubx64.efi, the removable copy matches it, the 2023 copy is kept
sudo: true
shell: |
set -e
a=$(md5sum < /boot/efi/EFI/proxmox/grubx64.efi | cut -d' ' -f1)
b=$(md5sum < /boot/efi/EFI/BOOT/BOOTX64.EFI | cut -d' ' -f1)
test "$a" != a4a8d8d1e75eb2dad19c22a8c1065c4c
test "$a" = "$b"
test "$(md5sum < /boot/efi/EFI/proxmox-2023/grubx64.efi | cut -d' ' -f1)" = a4a8d8d1e75eb2dad19c22a8c1065c4c
ls -la --time-style=long-iso /boot/efi/EFI/proxmox/ /boot/efi/EFI/BOOT/
changed_when: "false"
- name: firmware boots the proxmox entry first
sudo: true
shell: |
efibootmgr -v | grep -E '^BootOrder|proxmox'
first=$(efibootmgr | sed -n 's/^BootOrder: \([0-9A-F]*\).*/\1/p')
efibootmgr -v | grep -E "^Boot${first}\*? " | grep -qi 'EFI\\proxmox\\grubx64.efi'
changed_when: "false"
- name: pve8to9 --full summary
sudo: true
shell: pve8to9 --full 2>&1 | grep -E '^(FAIL|WARN):|^= SUMMARY|TOTAL|PASSED|SKIPPED|WARNINGS|FAILURES' | cat
changed_when: "false"
+64
View File
@@ -0,0 +1,64 @@
# pfi-pve: after the PVE 9 dist-upgrade, BEFORE the reboot. One fix-up step, the rest is read-only.
# scripts/elway infra-ops@10.250.250.31 --playbook playbooks/pfi-pve-pve9-prereboot.yaml
# Every verify here is something that, if wrong, makes the reboot a full Anaheim outage with no path back in except
# the FortiGate WAN-SSH break-glass (docs/runbooks/pfi-pve-pve9-upgrade-plan.md § Break-glass).
steps:
- name: the upgrade ships pve-enterprise.sources enabled; turn it off (no subscription)
sudo: true
shell: |
f=/etc/apt/sources.list.d/pve-enterprise.sources
if grep -q '^Enabled:' $f; then sed -i 's/^Enabled:.*/Enabled: false/' $f; else echo 'Enabled: false' >> $f; fi
cat $f
when: "test -f /etc/apt/sources.list.d/pve-enterprise.sources && ! grep -q '^Enabled: *false' /etc/apt/sources.list.d/pve-enterprise.sources"
verify:
- name: the detached dist-upgrade finished with EXIT 0
sudo: true
shell: tail -1 /root/pve9-upgrade.log | grep -qx 'EXIT 0'
changed_when: "false"
- name: dpkg is clean and nothing is left for apt -f
sudo: true
shell: |
test -z "$(dpkg --audit 2>&1)"
test "$(apt-get -s -f install 2>/dev/null | grep -cE '^(Inst|Conf) ')" -eq 0
changed_when: "false"
- name: PVE 9 userland
sudo: true
shell: pveversion | grep -E '^pve-manager/9\.'
changed_when: "false"
- name: newest kernel has an initramfs carrying the NIC pins and dm-snapshot (root has an LVM snapshot)
sudo: true
shell: |
set -e
new=$(ls /boot/vmlinuz-* | sed 's#/boot/vmlinuz-##' | sort -V | tail -1)
echo "newest kernel: $new"
lsinitramfs /boot/initrd.img-$new > /tmp/pve9-initrd.lst
grep -q 10-pin-eno8303.link /tmp/pve9-initrd.lst
grep -q 10-pin-eno12399np0.link /tmp/pve9-initrd.lst
grep -q 'dm-snapshot.ko' /tmp/pve9-initrd.lst
grep -q '/bnxt_en.ko' /tmp/pve9-initrd.lst || echo " (bnxt_en not in initramfs; loads from root, fine)"
grep -q "vmlinuz-$new" /boot/grub/grub.cfg
changed_when: "false"
- name: ESP rewritten by the trixie GRUB; removable copy matches; firmware boots proxmox first
sudo: true
shell: |
set -e
ls -la --time-style=long-iso /boot/efi/EFI/proxmox/ /boot/efi/EFI/BOOT/
test "$(md5sum < /boot/efi/EFI/proxmox/grubx64.efi)" = "$(md5sum < /boot/efi/EFI/BOOT/BOOTX64.EFI)"
strings /boot/efi/EFI/proxmox/grubx64.efi | grep -m1 -E '^2\.1[2-9]' || strings /boot/efi/EFI/proxmox/grubx64.efi | grep -m1 -i 'grub 2\.1'
first=$(efibootmgr | sed -n 's/^BootOrder: \([0-9A-F]*\).*/\1/p')
efibootmgr -v | grep -E "^Boot${first}\*? " | grep -qi 'EFI\\proxmox\\grubx64.efi'
changed_when: "false"
- name: networking will come back (dhclient for vmbr0.250, interfaces parse, sysctl.conf has nothing to lose)
sudo: true
shell: |
set -e
dpkg-query -W -f='${Status}' isc-dhcp-client | grep -q 'install ok installed' || dpkg-query -W -f='${Status}' dhcpcd-base | grep -q 'install ok installed'
dpkg-query -W -f='${Status}' ifupdown2 | grep -q 'install ok installed'
ifreload -a -s
test -z "$(grep -vE '^\s*(#|$)' /etc/sysctl.conf 2>/dev/null)"
changed_when: "false"
- name: pve8to9 --full (post-upgrade checks)
sudo: true
shell: pve8to9 --full 2>&1 | grep -E '^(FAIL|WARN):|TOTAL|PASSED|SKIPPED|WARNINGS|FAILURES' | cat
changed_when: "false"
+101
View File
@@ -0,0 +1,101 @@
# pfi-pve: PVE 8.4 → 9 upgrade, the package half (Prime go 2026-10-04). Does NOT reboot.
# scripts/elway infra-ops@10.250.250.31 --playbook playbooks/pfi-pve-pve9-upgrade.yaml
# Prereq: playbooks/pfi-pve-pve9-prep.yaml, then the PVE 8 reboot test (it validates the new GRUB, NIC pins and kernel
# before trixie lands; see docs/runbooks/pfi-pve-pve9-upgrade-plan.md). Template: playbooks/esh-nas-pve-pve9-upgrade.yaml.
# The dist-upgrade runs DETACHED (systemd-run → /root/pve9-upgrade.log), so a dropped ssh cannot kill it mid-dpkg.
# Poll: sudo tail /root/pve9-upgrade.log ; systemctl status pve9-dist-upgrade
# Then: playbooks/pfi-pve-pve9-prereboot.yaml, then the reboot.
# Rollback: `lvconvert --merge pve/root-pre-pve9` and reboot (the merge runs at the next activation of pve/root);
# from a rescue boot if 9 does not boot. The snapshot covers / and /boot (one LV), not the ESP or guest storage.
vars:
stage_dir: /root/pve9-prep-20261004
steps:
- name: GUARD — pve8to9 --full reports no FAIL
sudo: true
shell: |
out=$(pve8to9 --full 2>&1)
echo "$out" | grep -E '^(FAIL|WARN):' | cat
! echo "$out" | grep -q '^FAIL:'
changed_when: "false"
- name: GUARD — prep is in, and the box has booted the newest 6.8 kernel since (the PVE 8 reboot test)
sudo: true
shell: |
set -e
dpkg-query -W -f='${Status}' grub-efi-amd64 | grep -q 'install ok installed'
test -f /etc/systemd/network/10-pin-eno8303.link
new=$(ls /boot/vmlinuz-* | sed 's#/boot/vmlinuz-##' | sort -V | tail -1)
echo "running $(uname -r), newest $new"
test "$(uname -r)" = "$new"
changed_when: "false"
- name: GUARD — the VG has room for the root snapshot and dm-snapshot is in the running kernel's initramfs
sudo: true
shell: |
set -e
free=$(vgs --noheadings --units g --nosuffix -o vg_free pve | tr -d ' ' | cut -d. -f1)
echo "pve VG free: ${free}G"
test "$free" -ge 15
lsinitramfs /boot/initrd.img-$(uname -r) | grep -q 'dm-snapshot.ko'
changed_when: "false"
- name: root snapshot for rollback (pve/root-pre-pve9, 15G COW)
sudo: true
shell: lvcreate -s -L 15G -n root-pre-pve9 pve/root
when: "! lvs pve/root-pre-pve9 >/dev/null 2>&1"
- name: switch apt sources bookworm → trixie (Debian lines rewritten; PVE repo moved to deb822 proxmox.sources)
sudo: true
shell: |
set -e
cp -p /etc/apt/sources.list {{ stage_dir }}/sources.list.bookworm
cat > /etc/apt/sources.list <<'EOF'
deb http://ftp.us.debian.org/debian trixie main contrib non-free-firmware
deb http://ftp.us.debian.org/debian trixie-updates main contrib non-free-firmware
deb http://security.debian.org/debian-security trixie-security main contrib non-free-firmware
EOF
cat > /etc/apt/sources.list.d/proxmox.sources <<'EOF'
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
for f in /etc/apt/sources.list.d/pve-enterprise.list /etc/apt/sources.list.d/pve-enterprise.list.dpkg-old; do
if [ -e "$f" ]; then mv "$f" {{ stage_dir }}/; fi
done
cat /etc/apt/sources.list; ls /etc/apt/sources.list.d/
when: "grep -q bookworm /etc/apt/sources.list"
- name: apt update on trixie (must be clean)
sudo: true
shell: |
! apt-get update -q 2>&1 | grep -E '^(E|Err):'
changed_when: "false"
- name: GUARD — read the plan; refuse if it would remove anything this host cannot boot or network without
sudo: true
shell: |
apt-get -s dist-upgrade > {{ stage_dir }}/dist-upgrade-plan.txt 2>&1
grep -cE '^Inst ' {{ stage_dir }}/dist-upgrade-plan.txt | sed 's/^/ installs\/upgrades: /'
grep -E '^Remv ' {{ stage_dir }}/dist-upgrade-plan.txt | sed 's/^/ /' || echo " no removals"
! grep -E '^Remv (proxmox-ve|pve-manager|grub-efi-amd64|grub-efi-amd64-bin|ifupdown2|isc-dhcp-client|zfsutils-linux|lvm2|openssh-server|qemu-server|pve-container) ' {{ stage_dir }}/dist-upgrade-plan.txt
changed_when: "false"
- name: start the dist-upgrade DETACHED (log /root/pve9-upgrade.log)
sudo: true
shell: |
systemd-run --unit=pve9-dist-upgrade --setenv=DEBIAN_FRONTEND=noninteractive \
/bin/sh -c 'apt-get dist-upgrade -y -q -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold > /root/pve9-upgrade.log 2>&1; echo "EXIT $?" >> /root/pve9-upgrade.log'
when: "! test -f /root/pve9-upgrade.log"
verify:
- name: root snapshot exists
sudo: true
shell: lvs pve/root-pre-pve9
changed_when: "false"
- name: sources are trixie
sudo: true
shell: "! grep -rq bookworm /etc/apt/sources.list /etc/apt/sources.list.d/"
changed_when: "false"