build-xoa-hl
Packer pipeline that builds the XOA-hl VM appliance on XCP-ng and produces the XVA image XO Lite CE deploys.
Repository: Vagrantin/build-xoa-hl · Language: Bash / Packer JSON / Kickstart · License: AGPL-3.0
Purpose
This repo builds the XOA HomeLab Edition VM appliance: an AlmaLinux 9
VM, installed and provisioned by Packer on a real XCP-ng host, with
the xoa-hl RPM inside. The output is a compressed XVA
image, the artifact the XO Lite CE deploy button imports.
The appliance is generic at build time: it carries two one-shot first-boot services that read provisioning data (network, admin credentials) from XenStore when XO Lite deploys it, so a single image works for every user.
Repo structure
build-xoa-hl/
├── build.config.sample ← infrastructure config template (copy → build.config)
├── scripts/
│ ├── setup-xoa-builder.sh ← build entry point: generates Kickstart + Packer JSON, runs build
│ ├── xoa-first-boot.sh ← in-VM phase 1: XenStore → network + env file
│ └── xoa-credentials.sh ← in-VM phase 2: sets XO admin credentials via xo-cli
├── systemd/
│ ├── xoa-first-boot.service
│ └── xoa-credentials.service
├── bin/ ← vendored VMware VDDK tarball (V2V support)
└── artefact/ ← build/debug artefacts: logs, installed-RPM list, memoPrerequisites
- A Linux build machine with
aptand sudo (developed on Linux Mint). The setup script installs its own dependencies: Packer (HashiCorp apt repo), theddelnano/xenserverPacker plugin,wget,curl,jq,ufw. - A reachable XCP-ng host with root credentials, a
Local storageSR, and a named VM network. - A published
xoa-hlrelease on GitHub (the latest release’s RPM is resolved automatically). build.config, copied frombuild.config.sampleand filled in: XCP-ng host IP/credentials, network name, VM name and root password, AlmaLinux ISO URL, and thexe-guest-utilitiesRPM URLs.
build.config contains plaintext credentials (XCP-ng root password,
VM root password). Never commit it, only build.config.sample belongs
in git.Build entry point, scripts/setup-xoa-builder.sh
Runs on the build machine and generates everything Packer needs:
- Load
build.config(falls back to built-in defaults if absent). - Install prerequisites, apt packages, HashiCorp Packer, and the
ddelnano/xenserverPacker plugin. - Open ports 8000–9000/tcp (ufw), Packer serves the Kickstart file to the VM over its built-in HTTP server on a port in that range.
- Resolve the AlmaLinux ISO checksum, parses the mirror’s
BSD-style
CHECKSUMfile, falling back to GNU-styleSHA256SUMS; fails the build if no valid SHA256 is found (unless pinned inbuild.config). - Resolve the latest xoa-hl RPM URL, scans
api.github.com/repos/Vagrantin/xoa-hl/releasesfor the newest release carrying a.rpmasset. Notreleases/latest: that can still land on one of the pre-#22xoa-image-*releases, which ship only an XVA. - Generate
inst.ks, the Kickstart answer file: DHCP oneth0, EXT4 partitioning (no LVM), SELinux and firewall disabled, minimal package set;%postenables sshd/chrony, installsepel-release,wget,nc,vim, and creates thexouser (inwheel). - Generate
almalinux-build.json, the Packer template (see below). - Run the build,
packer validatethenPACKER_LOG=1 packer build almalinux-build.json.
Packer template, almalinux-build.json
Single xenserver-iso builder: Packer uploads the AlmaLinux ISO to the
XCP-ng host, boots a VM (2 GB RAM, 10 GB disk) with
inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/inst.ks on the kernel
command line, waits for SSH, then runs the provisioners:
dnf update -y.- Install xe-guest-utilities + xe-guest-utilities-xenstore
(RPM URLs from
build.config), required for XenStore access at first boot. - Install Node.js 24 (NodeSource).
- Install the xoa-hl RPM, this pulls in the whole XOA-hl stack
(see
xoa-hl: the RPM ships the pre-built Xen Orchestra under/opt/xo, and its%postenablesredis+xo-server). - Upload
xoa-first-boot.sh/xoa-credentials.shto/root/and the two systemd units to/etc/systemd/system/, then enable both units. - Slim the image, remove wifi firmware, firewalld, sssd,
NetworkManager extras, rsyslog, docs/man/info pages, and non-English
locales;
dnf autoremove+clean all. - Strip identity, blank
/etc/machine-idso each deployed VM regenerates its own.
Key builder settings: format: xva_compressed (the XVA output),
keep_vm: never (the built VM is deleted from the XCP-ng host),
and vm_name (XOA-hl), which Packer bakes into the XVA as the VM’s
name-label. That label is what XO Lite shows once the appliance is
deployed, and it also names the output file, <vm_name>.xva.
First-boot self-configuration
Two one-shot services are baked into the image; XO Lite writes the
provisioning data into XenStore (/local/domain/<domid>/vm-data/*) when
it deploys the appliance.
Phase 1, xoa-first-boot.service
Runs before the network comes up (Before=network.target, gated by
ConditionPathExists=!/var/lib/xoa-first-boot.done). The script:
- reads the
vm-datakeys viaxenstore-read:ip,netmask,gateway,dns,ntp-servers,system-account-xoa-password, and theadmin-accountJSON blob (email + password); - persists them to
/etc/xoa-first-boot.env(mode 600); - writes a NetworkManager keyfile
(
/etc/NetworkManager/system-connections/xoa-provisioned.nmconnection) , static IP if provided, DHCP otherwise; - logs verbosely to
/var/log/xoa-first-boot.logfor field diagnostics.
Phase 2, xoa-credentials.service
Runs once after network-online.target and xo-server.service,
gated by !/var/lib/xoa-credentials.done. The script:
- waits up to 3 minutes for xo-server on port 443;
- sets the
xosystem user’s SSH password from the provisioned value; - registers
xo-cliagainstwss://127.0.0.1using the bootstrap credentials, then callsuser.changePasswordanduser.setto apply the provisioned admin email + password; - self-destructs on exit (trap): writes the done flag, disables and removes both units and scripts, and deletes the secrets env file.
admin@admin.net / admin,
change them via the XO web UI after deployment.Outputs
- The compressed XVA image in
output-xva/inside the build directory on the build machine. - The built VM itself, kept on the XCP-ng host (
keep_vm: always). - In the automated pipeline, a GitHub Release on this repository
(
Vagrantin/build-xoa-hl) taggedxoa-image-<date>-<sha7>carrying theXOA-hl.xvaasset, the artefact XO Lite’s deploy button resolves.
Automated builds, the orchestrator
In the daily pipeline, setup-xoa-builder.sh is replaced by the
xoa-vm-agent crate in
buildorchestration.
It performs the same steps programmatically, and in addition:
- Skips the build when the repo HEAD already matches the last built SHA.
- First triggers the
build-xoa.ymlworkflow inVagrantin/xoa-hlviaworkflow_dispatchand waits for the RPM release. - Runs
packer validate+packer buildwith generatedinst.ks/almalinux-build.json. - Publishes the XVA as a GitHub Release tagged
xoa-image-<date>-<sha7>onVagrantin/build-xoa-hl, this repo (<sha7>is thexoa-hlcommit the image was built from, that commit lives in the other repo, so the tag is created onmainand the source commit is recorded in the release body). Image releases are recorded in the Release Matrix.
Vagrantin/xoa-hl until
#22. Those releases stay
where they are so already-shipped ISOs keep resolving them, which is why
tooling scanning xoa-hl for the RPM must still skip xoa-image-* tags.Contributing
To report a problem or suggest a change, open an issue on Vagrantin/xcp-hl.