- LVM partition layout: /, /var, /var/log, /home, /srv, swap, tmpfs /tmp plus /var/lib/longhorn for worker role (grows to fill disk) - Reprovision preserves /home, /srv, /var/lib/longhorn via %pre detection - Admin user created matching the user running the bastion script with SSH keys from authorized_keys + local pubkeys, passwordless sudo - Progress callbacks from %pre and %post to /api/progress endpoint with IP reported on completion (ssh command printed) - Installed machines boot from local disk (iPXE exit) instead of re-entering discovery mode - --role worker|infra flag (infra skips longhorn partition) - reprovision subcommand: queues install + SSH reboot into PXE - Self-cleanup: kills old bastion instances on start - Domain config (DOMAIN env, default ad.itaz.eu) - efibootmgr in %post to set local disk first in boot order - k3s prereqs: kernel modules, sysctl, firewalld disabled, chrony - VM reprovision test script (test-reprovision.sh) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
280 lines
9.5 KiB
Bash
Executable File
280 lines
9.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ─────────────────────────────────────────────────────────────
|
|
# Test: reprovision preserves /home, /srv, /var/lib/longhorn
|
|
#
|
|
# Usage: sudo bash test-reprovision.sh
|
|
# sudo bash test-reprovision.sh --skip-first-install # if disk already has a first install
|
|
# sudo bash test-reprovision.sh --cleanup # just remove the VM and disk
|
|
# ─────────────────────────────────────────────────────────────
|
|
set -euo pipefail
|
|
|
|
VM_NAME="test-bastion-ks"
|
|
DISK_PATH="/var/lib/libvirt/images/test-reprovision.qcow2"
|
|
DISK_SIZE=20 # GB
|
|
KS_PATH="/tmp/test-vm.ks"
|
|
FEDORA_MIRROR="https://download.fedoraproject.org/pub/fedora/linux/releases/43/Everything/x86_64/os/"
|
|
OVMF_CODE="/usr/share/edk2/ovmf/OVMF_CODE.fd"
|
|
OVMF_VARS="/usr/share/OVMF/OVMF_VARS.fd"
|
|
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[test]${NC} $*"; }
|
|
err() { echo -e "${RED}[test]${NC} $*" >&2; }
|
|
step() { echo -e "\n${CYAN}${BOLD}══ $* ══${NC}\n"; }
|
|
|
|
cleanup_vm() {
|
|
virsh destroy "$VM_NAME" 2>/dev/null || true
|
|
virsh undefine "$VM_NAME" --nvram 2>/dev/null || true
|
|
}
|
|
|
|
cleanup_all() {
|
|
cleanup_vm
|
|
rm -f "$DISK_PATH"
|
|
log "Cleaned up VM and disk"
|
|
}
|
|
|
|
# ── Handle args ──
|
|
SKIP_FIRST=false
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--skip-first-install) SKIP_FIRST=true ;;
|
|
--cleanup) cleanup_all; exit 0 ;;
|
|
esac
|
|
done
|
|
|
|
[[ $EUID -eq 0 ]] || { err "Must run as root"; exit 1; }
|
|
|
|
# ── Generate kickstart ──
|
|
generate_kickstart() {
|
|
cat > "$KS_PATH" << 'KSEOF'
|
|
text
|
|
reboot
|
|
lang en_GB.UTF-8
|
|
keyboard uk
|
|
timezone Europe/London --utc
|
|
network --bootproto=dhcp --activate --hostname=test-vm.ad.itaz.eu
|
|
rootpw --plaintext testpass
|
|
user --name=michal --groups=wheel
|
|
bootloader --append="console=ttyS0,115200n8"
|
|
url --mirrorlist=https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-43&arch=x86_64
|
|
%include /tmp/part.ks
|
|
|
|
%pre --log=/tmp/pre-partition.log
|
|
#!/bin/bash
|
|
set -x
|
|
VG="labvg"
|
|
DISK="vda"
|
|
|
|
REPROVISION=no
|
|
if vgs $VG &>/dev/null; then
|
|
echo "=== REPROVISION MODE ==="
|
|
REPROVISION=yes
|
|
PRESERVE_LONGHORN=no; PRESERVE_SRV=no; PRESERVE_HOME=no
|
|
lvs $VG/longhorn &>/dev/null && PRESERVE_LONGHORN=yes
|
|
lvs $VG/srv &>/dev/null && PRESERVE_SRV=yes
|
|
lvs $VG/home &>/dev/null && PRESERVE_HOME=yes
|
|
echo "Preserving: longhorn=$PRESERVE_LONGHORN srv=$PRESERVE_SRV home=$PRESERVE_HOME"
|
|
for lv in root var varlog swap; do
|
|
lvremove -f $VG/$lv 2>/dev/null || true
|
|
done
|
|
fi
|
|
|
|
if [ "$REPROVISION" = "yes" ]; then
|
|
EFI_PART=$(blkid -t TYPE=vfat -o device /dev/${DISK}* 2>/dev/null | head -1)
|
|
BOOT_PART=$(blkid -t TYPE=ext4 -o device /dev/${DISK}* 2>/dev/null | head -1)
|
|
EFI_PART=${EFI_PART:-/dev/${DISK}1}
|
|
BOOT_PART=${BOOT_PART:-/dev/${DISK}2}
|
|
echo "Reusing EFI=$EFI_PART BOOT=$BOOT_PART"
|
|
|
|
cat > /tmp/part.ks << PARTEOF
|
|
ignoredisk --only-use=$DISK
|
|
clearpart --none
|
|
part /boot/efi --onpart=$EFI_PART --fstype=efi
|
|
part /boot --onpart=$BOOT_PART --fstype=ext4
|
|
volgroup labvg --useexisting --noformat
|
|
logvol swap --vgname=labvg --name=swap --fstype=swap --size=1024
|
|
logvol / --vgname=labvg --name=root --fstype=xfs --size=4096
|
|
logvol /var --vgname=labvg --name=var --fstype=xfs --size=3072
|
|
logvol /var/log --vgname=labvg --name=varlog --fstype=xfs --size=1024
|
|
PARTEOF
|
|
if [ "$PRESERVE_HOME" = "yes" ]; then
|
|
echo "logvol /home --vgname=labvg --name=home --useexisting --noformat" >> /tmp/part.ks
|
|
else
|
|
echo "logvol /home --vgname=labvg --name=home --fstype=xfs --size=1024" >> /tmp/part.ks
|
|
fi
|
|
if [ "$PRESERVE_SRV" = "yes" ]; then
|
|
echo "logvol /srv --vgname=labvg --name=srv --useexisting --noformat" >> /tmp/part.ks
|
|
else
|
|
echo "logvol /srv --vgname=labvg --name=srv --fstype=xfs --size=1024" >> /tmp/part.ks
|
|
fi
|
|
if [ "$PRESERVE_LONGHORN" = "yes" ]; then
|
|
echo "logvol /var/lib/longhorn --vgname=labvg --name=longhorn --useexisting --noformat" >> /tmp/part.ks
|
|
fi
|
|
else
|
|
cat > /tmp/part.ks << PARTEOF
|
|
ignoredisk --only-use=$DISK
|
|
clearpart --all --initlabel --drives=$DISK
|
|
part /boot/efi --fstype=efi --size=600 --ondisk=$DISK
|
|
part /boot --fstype=ext4 --size=1024 --ondisk=$DISK
|
|
part pv.01 --size=1 --grow --ondisk=$DISK
|
|
volgroup labvg pv.01
|
|
logvol swap --vgname=labvg --name=swap --fstype=swap --size=1024
|
|
logvol / --vgname=labvg --name=root --fstype=xfs --size=4096
|
|
logvol /var --vgname=labvg --name=var --fstype=xfs --size=3072
|
|
logvol /var/log --vgname=labvg --name=varlog --fstype=xfs --size=1024
|
|
logvol /home --vgname=labvg --name=home --fstype=xfs --size=1024
|
|
logvol /srv --vgname=labvg --name=srv --fstype=xfs --size=1024
|
|
logvol /var/lib/longhorn --vgname=labvg --name=longhorn --fstype=xfs --grow --size=1
|
|
PARTEOF
|
|
fi
|
|
|
|
echo "=== Generated partition config ==="
|
|
cat /tmp/part.ks
|
|
%end
|
|
|
|
%packages
|
|
@core
|
|
openssh-server
|
|
%end
|
|
|
|
%post
|
|
echo "Installed $(date -Iseconds)" > /etc/lab-provisioned
|
|
echo "testpass" | passwd --stdin michal
|
|
%end
|
|
KSEOF
|
|
}
|
|
|
|
# ── Install helper ──
|
|
run_install() {
|
|
local label="$1"
|
|
local disk_args="$2"
|
|
|
|
log "Running virt-install ($label)..."
|
|
virt-install \
|
|
--name "$VM_NAME" \
|
|
--ram 4096 \
|
|
--vcpus 2 \
|
|
--disk "$disk_args" \
|
|
--os-variant fedora-unknown \
|
|
--network network=default \
|
|
--location "$FEDORA_MIRROR" \
|
|
--initrd-inject "$KS_PATH" \
|
|
--extra-args "inst.ks=file:///test-vm.ks console=ttyS0,115200n8 inst.text" \
|
|
--boot loader="$OVMF_CODE",loader.readonly=yes,loader.type=pflash,nvram.template="$OVMF_VARS" \
|
|
--noautoconsole \
|
|
--wait -1
|
|
|
|
log "virt-install exited — install complete"
|
|
virsh destroy "$VM_NAME" 2>/dev/null || true
|
|
}
|
|
|
|
# ── Main test flow ──
|
|
|
|
generate_kickstart
|
|
log "Kickstart generated at $KS_PATH"
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
if ! $SKIP_FIRST; then
|
|
# ── Step 1: Fresh install ──
|
|
step "Step 1/4: Fresh install"
|
|
cleanup_all
|
|
run_install "fresh" "path=$DISK_PATH,size=$DISK_SIZE,bus=virtio"
|
|
|
|
# Verify fresh install
|
|
log "Verifying fresh install..."
|
|
FILESYSTEMS=$(guestfish --ro -a "$DISK_PATH" -i list-filesystems 2>/dev/null)
|
|
for lv in root var varlog home srv longhorn swap; do
|
|
if echo "$FILESYSTEMS" | grep -q "labvg/$lv"; then
|
|
log " ✔ labvg/$lv exists"
|
|
((PASS++))
|
|
else
|
|
err " ✘ labvg/$lv MISSING"
|
|
((FAIL++))
|
|
fi
|
|
done
|
|
else
|
|
step "Skipping first install (--skip-first-install)"
|
|
[[ -f "$DISK_PATH" ]] || { err "Disk not found at $DISK_PATH"; exit 1; }
|
|
fi
|
|
|
|
# ── Step 2: Write marker files ──
|
|
step "Step 2/4: Writing marker files to preserved partitions"
|
|
guestfish -a "$DISK_PATH" -i << 'GF'
|
|
write /home/michal/PRESERVE_TEST.txt "MARKER: home partition preserved\n"
|
|
write /srv/PRESERVE_TEST.txt "MARKER: srv partition preserved\n"
|
|
write /var/lib/longhorn/PRESERVE_TEST.txt "MARKER: longhorn partition preserved\n"
|
|
write /var/SHOULD_BE_WIPED.txt "This file should NOT survive reprovision\n"
|
|
GF
|
|
log "Marker files written:"
|
|
log " /home/michal/PRESERVE_TEST.txt"
|
|
log " /srv/PRESERVE_TEST.txt"
|
|
log " /var/lib/longhorn/PRESERVE_TEST.txt"
|
|
log " /var/SHOULD_BE_WIPED.txt (should be wiped)"
|
|
|
|
# ── Step 3: Reprovision ──
|
|
step "Step 3/4: Reprovisioning (reinstall on same disk)"
|
|
cleanup_vm
|
|
run_install "reprovision" "path=$DISK_PATH,bus=virtio"
|
|
|
|
# ── Step 4: Verify ──
|
|
step "Step 4/4: Verifying preservation"
|
|
|
|
check_file() {
|
|
local path="$1" expect="$2" label="$3"
|
|
local content
|
|
content=$(guestfish --ro -a "$DISK_PATH" -i cat "$path" 2>/dev/null) || content=""
|
|
|
|
if [[ "$expect" == "exists" ]]; then
|
|
if [[ -n "$content" && "$content" == *"MARKER"* ]]; then
|
|
log " ✔ $label — PRESERVED: $(echo "$content" | head -1)"
|
|
((PASS++))
|
|
else
|
|
err " ✘ $label — LOST (file missing or empty)"
|
|
((FAIL++))
|
|
fi
|
|
elif [[ "$expect" == "gone" ]]; then
|
|
if [[ -z "$content" ]]; then
|
|
log " ✔ $label — correctly wiped"
|
|
((PASS++))
|
|
else
|
|
err " ✘ $label — should have been wiped but still exists"
|
|
((FAIL++))
|
|
fi
|
|
fi
|
|
}
|
|
|
|
check_file "/home/michal/PRESERVE_TEST.txt" "exists" "/home (preserved)"
|
|
check_file "/srv/PRESERVE_TEST.txt" "exists" "/srv (preserved)"
|
|
check_file "/var/lib/longhorn/PRESERVE_TEST.txt" "exists" "/var/lib/longhorn (preserved)"
|
|
check_file "/var/SHOULD_BE_WIPED.txt" "gone" "/var (wiped)"
|
|
|
|
# Also verify OS was actually reinstalled
|
|
PROV_DATE=$(guestfish --ro -a "$DISK_PATH" -i cat /etc/lab-provisioned 2>/dev/null || echo "")
|
|
if [[ -n "$PROV_DATE" ]]; then
|
|
log " ✔ OS reinstalled: $PROV_DATE"
|
|
((PASS++))
|
|
else
|
|
err " ✘ /etc/lab-provisioned missing — OS not installed?"
|
|
((FAIL++))
|
|
fi
|
|
|
|
# ── Summary ──
|
|
echo ""
|
|
echo -e "${BOLD}════════════════════════════════════════${NC}"
|
|
if [[ $FAIL -eq 0 ]]; then
|
|
echo -e "${GREEN}${BOLD} ALL TESTS PASSED ($PASS/$((PASS+FAIL)))${NC}"
|
|
else
|
|
echo -e "${RED}${BOLD} $FAIL TESTS FAILED ($PASS passed, $FAIL failed)${NC}"
|
|
fi
|
|
echo -e "${BOLD}════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
# ── Cleanup ──
|
|
log "Cleaning up VM (disk preserved at $DISK_PATH)"
|
|
cleanup_vm
|
|
|
|
exit $FAIL
|