This is a Packer + QEMU infrastructure project that builds FreeBSD VM disk images (qcow2) for the cross-platform-actions/action GitHub Action. The codebase consists of shell scripts, HashiCorp Configuration Language (HCL) files, and CI YAML. There is no application-level code (no TypeScript, JavaScript, Python, etc.).
Supported architectures: x86-64, arm64
Supported FreeBSD versions: 12.2, 12.4, 13.0–13.5, 14.0–14.4, 15.0
./build.sh <version> <architecture>
# Examples:
./build.sh 14.4 arm64
./build.sh 15.0 x86-64This runs packer init . then packer build with layered variable files:
var_files/common.pkrvars.hcl(shared: memory, CPUs, disk, user config)var_files/<architecture>.pkrvars.hcl(arch-specific: QEMU binary, CPU type, firmware)var_files/<version>/<architecture>.pkrvars.hcl(version-specific: ISO checksum)
Output: compressed qcow2 image in output/.
packer validate -var-file=var_files/common.pkrvars.hcl \
-var-file=var_files/x86-64.pkrvars.hcl \
-var-file=var_files/14.4/x86-64.pkrvars.hcl \
freebsd.pkr.hclpacker fmt freebsd.pkr.hcl
packer fmt var_files/There is no unit test framework. Testing is integration-only via GitHub Actions CI.
The CI pipeline (.github/workflows/build.yml):
- Builds the image with Packer
- Serves it via a local HTTP server
- Boots it using
cross-platform-actions/action@master - Runs shell assertions inside the VM verifying OS name, version, architecture, working directory, hostname, and file synchronization
There is no way to run the full test suite locally. You can only build an image and manually boot it with QEMU.
CI matrix: 13 versions x 2 architectures = ~25 jobs.
No linting tools are configured. There is no shellcheck, no editorconfig, no pre-commit hooks. Maintain consistency manually by following the patterns below.
- Provisioning scripts (run inside the VM):
#!/bin/shwithset -exu - Build entrypoint:
#!/usr/bin/env shwithset -eux - Always enable strict mode:
set -exuorset -eux
- Functions:
snake_case(e.g.,configure_boot_flags,install_extra_packages) - Environment/exported variables:
UPPER_SNAKE_CASE(e.g.,OS_VERSION,ABI_VERSION) - Local variables:
lower_snake_case, declared withlocalkeywordlocal rc_dir="/etc/rc.d" local device_version="$1"
Always double-quote variable expansions:
echo "$OS_VERSION"
if [ -e "$device" ]; then- Rely on
set -efor automatic exit on error - Use explicit checks with error messages to stderr for critical failures:
if [ ! -e /dev/vtbd0 ] && [ ! -e /dev/ada0 ]; then echo "ERROR: There is no disk available for installation" >&2 exit 1 fi
- Suppress expected failures with
|| :(e.g.,dd if=/dev/zero of=/EMPTY bs=1M || :)
Organize scripts as function definitions at the top, followed by sequential calls at the bottom:
#!/bin/sh
set -exu
configure_thing() {
# ...
}
install_packages() {
# ...
}
# Main execution
configure_thing
install_packagesUse heredocs for multi-line file content:
cat <<EOF >> /boot/loader.conf
autoboot_delay="-1"
beastie_disable="YES"
EOF- Variable naming:
snake_case(e.g.,os_version,image_architecture) - Every variable should have
type,description, anddefaultwhere appropriate - String interpolation:
${var.name}and${local.name} - Comments:
//for single-line comments - File organization: Variables at top, locals next, source block, then build block
- Standard GitHub Actions conventions
- Use matrix strategy for version/architecture combinations
- Multi-line shell commands use
|block scalar
var_files/
├── common.pkrvars.hcl # Shared: memory, CPUs, disk size, user
├── arm64.pkrvars.hcl # Arch: QEMU binary, CPU type, firmware
├── x86-64.pkrvars.hcl # Arch: QEMU binary, CPU type
└── <version>/
├── arm64.pkrvars.hcl # Version+arch: ISO checksum only
└── x86-64.pkrvars.hcl # Version+arch: ISO checksum only
resources/installerconfig- FreeBSD bsdinstall automation (partitioning, network, SSH)resources/provision.sh- Main setup (user creation, boot config, packages, sudo)resources/custom.sh- Empty customization hook for downstream imagesresources/cleanup.sh- Minimize disk (clear caches, zero free space)
- Tags matching
v*trigger draft GitHub Releases with qcow2 artifacts - One artifact per version/architecture combination
- Create
var_files/<version>/arm64.pkrvars.hclwith the ISO checksum - Create
var_files/<version>/x86-64.pkrvars.hclwith the ISO checksum - Add the version to the CI matrix in
.github/workflows/build.yml - Verify the ISO URL pattern still works (defined in
freebsd.pkr.hcllocals)
- ISO download can be slow;
PACKER_GETTER_READ_TIMEOUT=60mis set inbuild.sh - arm64 builds require EFI firmware (path configured in
var_files/arm64.pkrvars.hcl) - arm64 var file overrides memory to 3072MB (from common's 4096MB)
- Version-specific var files contain only the checksum; all other config is inherited
- The
installerconfigscript auto-detects the disk device (vtbd0orada0)