diff --git a/packer/.gitignore b/packer/.gitignore new file mode 100644 index 000000000..d5637593f --- /dev/null +++ b/packer/.gitignore @@ -0,0 +1 @@ +export diff --git a/packer/README.md b/packer/README.md new file mode 100644 index 000000000..9b18d4603 --- /dev/null +++ b/packer/README.md @@ -0,0 +1,108 @@ +# Packer: Alpine 3.23 Minimal UEFI for XCP-ng + +Builds a minimal Alpine Linux 3.23.4 VM image (XVA, zstd-compressed) for use +in xcp-ng-tests. The image is UEFI-booted, has the XCP-ng guest tools +installed, and allows root SSH access using the xcp-ng CI public keys. + +## VM Specs + +| Property | Value | +|-------------|------------------------------| +| OS | Alpine Linux 3.23.4 (x86_64) | +| vCPUs | 1 | +| RAM | 256 MiB | +| Disk | 512 MiB | +| Boot | UEFI (grub + GPT) | +| Network | DHCP (eth0) | +| Format | XVA (zstd) | +| Guest tools | xe-guest-utilities (apk) | + +## Prerequisites + +### On the machine running Packer + +- [Packer](https://developer.hashicorp.com/packer) (tested with v1.x) +- The `xenserver-iso` builder plugin from + [xenserver/packer-plugin-xenserver](https://github.com/xenserver/packer-plugin-xenserver) +- Python 3 with `libarchive-c==5.3` (for zstd post-processing): + + ```sh + pip install 'libarchive-c==5.3' + ``` + +### On the XCP-ng host + +- An ISO SR where Packer can upload the Alpine ISO (the plugin uploads it + automatically if you use `iso_url`) +- A network accessible from the build machine; the default variable value is + `Pool-wide network associated with eth0` — override with `network_name` if + needed + +## Building + +```sh +cd packer/ +packer init alpine-3.23-uefi.pkr.hcl +packer build \ + -var xen_host= \ + -var xen_password= \ + alpine-3.23-uefi.pkr.hcl +``` + +Optional overrides: + +```sh + -var xen_username=root # default: root + -var network_name="" # default: Pool-wide network associated with eth0 + -var root_password= # temporary build password; default: packer +``` + +The output XVA is written to: + +``` +output-alpine-3.23-uefi/alpine-3.23-uefi.xva +``` + +After the build the post-processor rewrites it in-place with zstd compression +and sets the bridge to `xenbr0`. + +## Directory structure + +``` +packer/ +├── alpine-3.23-uefi.pkr.hcl # Packer build definition (HCL) +├── http_files/ +│ └── answers.txt # Alpine setup-alpine answerfile +└── scripts/ + └── cleanup.sh # Post-install cleanup run inside the VM +``` + +## Notes + +- The Alpine guest tools ISO `install.sh` script does not support Alpine; + `xe-guest-utilities` is installed from the Alpine community repository + instead. +- `ROOTSSHKEY` in `answers.txt` injects the xcp-ng CI public keys during + installation, so no separate provisioner step is needed for SSH keys. +- `USE_EFI=1` is passed on the kernel cmdline via `boot_command`; this makes + `setup-disk` use grub and a GPT layout automatically. +- Swap is disabled (`SWAP_SIZE=0`) to keep the image within 512 MiB. + +## Troubleshooting + +**Boot hangs / VNC never gets past the boot prompt** +: Increase `boot_wait` in the JSON (default `10s`) and ensure VNC is + reachable from the Packer host. + +**SSH timeout** +: The install takes up to ~3 minutes on a busy host. Increase + `ssh_wait_timeout` if needed. + +**Disk full during install** +: If `setup-disk` fails due to space, the disk size can be bumped to 1024 MB + by passing `-var disk_size=1024` — but note that the builder does not expose + this directly; you would need to edit the JSON. + +**xva_bridge.py fails** +: Ensure `libarchive-c==5.3` is installed. Other versions may not work due to + internal API usage. diff --git a/packer/alpine-3.23-uefi.pkr.hcl b/packer/alpine-3.23-uefi.pkr.hcl new file mode 100644 index 000000000..4e0e09100 --- /dev/null +++ b/packer/alpine-3.23-uefi.pkr.hcl @@ -0,0 +1,115 @@ +packer { + required_plugins { + xenserver = { + version = ">= v0.11.0" + source = "github.com/vatesfr/xenserver" + } + } +} + +variable "xen_host" { + type = string + description = "XCP-ng pool master hostname or IP" +} + +variable "xen_username" { + type = string + description = "XCP-ng SSH username" + default = "root" +} + +variable "xen_password" { + type = string + description = "XCP-ng SSH password" + sensitive = true +} + +variable "network_name" { + type = string + description = "Name of the XCP-ng network to attach the VM to" + default = "Pool-wide network associated with eth0" +} + +variable "sr_name" { + type = string + description = "Name of the SR to store the VM disk on (defaults to pool default)" + default = "" +} + +variable "sr_iso_name" { + type = string + description = "Name of the ISO SR to upload the Alpine ISO to (defaults to pool default)" + default = "" +} + +variable "root_password" { + type = string + description = "Temporary root password set during the build" + default = "vateslab" + sensitive = true +} + +source "xenserver-iso" "alpine" { + remote_host = var.xen_host + remote_username = var.xen_username + remote_password = var.xen_password + + vm_name = "alpine-3.23-uefi" + vm_description = "Alpine Linux 3.23.4 minimal UEFI - built with Packer" + vcpus_max = 1 + vcpus_atstartup = 1 + vm_memory = 256 + disk_size = 512 + + firmware = "uefi" + clone_template = "Other install media" + + network_names = [var.network_name] + + sr_name = var.sr_name + sr_iso_name = var.sr_iso_name + + iso_url = "https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/x86_64/alpine-standard-3.23.4-x86_64.iso" + iso_checksum = "sha256:cfef39c7954f7c4447bcb321b9f4a1cef834536a321309d2c31275d9f2475a4e" + + http_directory = "http_files" + boot_wait = "30s" + boot_command = [ + "root", + "", + "ifconfig eth0 up && udhcpc -i eth0", + # "USE-EFI=1 SWAP-SIZE=0 ERASE-DISKS=/dev/xvda setup-alpine -f http://{{.HTTPIP}}:{{.HTTPPort}}/answers.txt", + "USE-EFI=1 SWAP-SIZE=0 ERASE-DISKS=/dev/xvda setup-alpine -f https://raw.githubusercontent.com/xcp-ng/xcp-ng-tests/refs/heads/gln/packer-alpine-uefi-xcpng-loun/packer/http_files/answers.txt", + "", + "${var.root_password}", + "", + "${var.root_password}", + "", + "apk add --no-cache xe-guest-utilities", + "rc-update add xe-guest-utilities default", + # "rc-service xe-guest-utilities start", + # Remove log files + "find /var/log -type f -exec truncate -s 0 {} \\;", + # Remove temporary files + "rm -rf /tmp/* /var/tmp/*", + # Clear machine-id so each clone gets a unique one + "echo > /etc/machine-id", + # Remove history + "rm -f /root/.ash_history", + "", + ] + + communicator = "none" + ssh_username = "root" + ssh_password = var.root_password + ssh_wait_timeout = "10m" + + install_timeout = "30m" + output_directory = "export" + keep_vm = "never" + format = "xva_zstd" +} + +build { + sources = ["xenserver-iso.alpine"] +} diff --git a/packer/http_files/answers.txt b/packer/http_files/answers.txt new file mode 100644 index 000000000..6af0865f8 --- /dev/null +++ b/packer/http_files/answers.txt @@ -0,0 +1,20 @@ +KEYMAPOPTS=none +HOSTNAMEOPTS=alpine +DEVDOPTS=mdev +INTERFACESOPTS="auto lo +iface lo inet loopback +auto eth0 +iface eth0 inet dhcp + hostname alpine +" +TIMEZONEOPTS=UTC +PROXYOPTS=none +APKREPOSOPTS="-1 -c" +USEROPTS=none +SSHDOPTS=openssh +ROOTSSHKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMnN/wVdQqHA8KsndfrLS7fktH/IEgxoa533efuXR6rw XCP-ng CI +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKz9uQOoxq6Q0SQ0XTzQHhDolvuo/7EyrDZsYQbRELhcPJG8MT/o5u3HyJFhIP2+HqBSXXgmqRPJUkwz9wUwb2sUwf44qZm/pyPUWOoxyVtrDXzokU/uiaNKUMhbnfaXMz6Ogovtjua63qld2+ZRXnIgrVtYKtYBeu/qKGVSnf4FTOUKl1w3uKkr59IUwwAO8ay3wVnxXIHI/iJgq6JBgQNHbn3C/SpYU++nqL9G7dMyqGD36QPFuqH/cayL8TjNZ67TgAzsPX8OvmRSqjrv3KFbeSlpS/R4enHkSemhgfc8Z2f49tE7qxWZ6x4Uyp5E6ur37FsRf/tEtKIUJGMRXN XCP-ng CI" +NTPOPTS=none +DISKOPTS="-m sys /dev/xvda" +LBUOPTS=none +APKCACHEOPTS=none diff --git a/packer/scripts/cleanup.sh b/packer/scripts/cleanup.sh new file mode 100755 index 000000000..852e928ca --- /dev/null +++ b/packer/scripts/cleanup.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e + +# Remove apk cache +rm -rf /var/cache/apk/* + +# Remove log files +find /var/log -type f -exec truncate -s 0 {} \; + +# Remove temporary files +rm -rf /tmp/* /var/tmp/* + +# Clear machine-id so each clone gets a unique one +printf '' > /etc/machine-id 2>/dev/null || true + +# Remove history +rm -f /root/.ash_history /root/.bash_history