Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export
108 changes: 108 additions & 0 deletions packer/README.md
Original file line number Diff line number Diff line change
@@ -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=<XCP-ng IP or hostname> \
-var xen_password=<XCP-ng root password> \
alpine-3.23-uefi.pkr.hcl
```

Optional overrides:

```sh
-var xen_username=root # default: root
-var network_name="<name>" # default: Pool-wide network associated with eth0
-var root_password=<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.
115 changes: 115 additions & 0 deletions packer/alpine-3.23-uefi.pkr.hcl
Original file line number Diff line number Diff line change
@@ -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<enter>",
"<wait3>",
"ifconfig eth0 up && udhcpc -i eth0<enter><wait5>",
# "USE<leftShiftOn>-<leftShiftOff>EFI=1 SWAP<leftShiftOn>-<leftShiftOff>SIZE=0 ERASE<leftShiftOn>-<leftShiftOff>DISKS=/dev/xvda setup-alpine -f http://{{.HTTPIP}}:{{.HTTPPort}}/answers.txt<enter>",
"USE<leftShiftOn>-<leftShiftOff>EFI=1 SWAP<leftShiftOn>-<leftShiftOff>SIZE=0 ERASE<leftShiftOn>-<leftShiftOff>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<enter>",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value of -f flag should be generic, isn't it?

What do you think of using an hcl variable to enable configuration here?

"<wait10>",
"${var.root_password}<enter>",
"<wait>",
"${var.root_password}<enter>",
"<wait60>",
"apk add --no-cache xe-guest-utilities<enter>",
"rc-update add xe-guest-utilities default<enter>",
# "rc-service xe-guest-utilities start<enter>",
# Remove log files
"find /var/log -type f -exec truncate -s 0 {} \\;<enter>",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those commands look similar to what I can see in cleanup.sh. Is it normal?

Btw, I don't see any occurrence of calling the cleanup script. Is there an implicit mechanism of Packer?

# Remove temporary files
"rm -rf /tmp/* /var/tmp/*<enter>",
# Clear machine-id so each clone gets a unique one
"echo > /etc/machine-id<enter>",
# Remove history
"rm -f /root/.ash_history<enter>",
"<wait3>",
]

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"]
}
20 changes: 20 additions & 0 deletions packer/http_files/answers.txt
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions packer/scripts/cleanup.sh
Original file line number Diff line number Diff line change
@@ -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
Loading