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
Table of contents
- Purpose
- Repo structure
- Prerequisites
- Build entry point, scripts/setup-xoa-builder.sh
- Packer template, almalinux-build.json
- First-boot self-configuration
- Outputs
- Automated builds, the orchestrator
- Contributing
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, memo
Prerequisites
- 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://:/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’s%postdownloads the release tarball into/opt/xoand enablesredis+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: always (the built VM stays on the XCP-ng host for inspection).
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.
If provisioning data is missing or phase 2 fails, the appliance keeps the bootstrap defaults 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-almalinux.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.
Images were published on 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
A contributing model is not formally design yet, for now create an issue at the XCP-HL repository