Creating a configuration

The following illustrates how to create a custom build configuration and guides through the available configuration directives with examples.

The directives are further described in the build configuration reference.

Custom build configuration

Assuming a FreeBSD target-vm is to be created, the following steps will allow bootstrapping the guest and allow for arbitrary command executing for e.g. installing addtional packages.

  • Create a new directory for the to be built virtual machine and create a build configuration file:
mkdir -p my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13
cd my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13
touch build.xml
  • For this howto, also copy the files directory from the freebsd-13.0 configuration in the example repository. It includes a bootloader and a firstrun script for the target-vm later used here. Copy it from the vm-builder repository path:
    • at examples/vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files
    • to your my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13 directory.
  • In the beginning, the file build.xml will have the following contents:
<config>
</config>
  • Since we want to build a virtual machine, we need to specify it's architecture, name and version. The first in this example being x86_64, the name would be freebsd-13.0 and for the version we may use todays date 2024-04-13. The updated configuration file looks like the following now:
<config>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
</config>
  • The target-vm is not yet formated, bootable and has no files on it's virtual hard disk. Doing these boostrapping steps is done by a build-vm. A reference build-vm is provided by the vm-builder project. Importing and building it is described in the getting started page. We can use it by adding it with the build-vm directive:
<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
</config>
  • In order to make use of the build-vm, copy it from the example repository:
    • at examples/vm-repo/configs/x86_64/linux-busybox
    • to your repository at my-vm-repo/configs/x86_64/linux-busybox

Creating a disk

The build configuration in it's current state does execute any jobs on the target-vms image. Providing a list of jobs will allow us to manipulate it. If no image file is present, we start by creating it with the create-disk job. Specifying the size attribute is required. This creates a raw disk image with a size of 4G:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
  </jobs>
</config>

Partition table

Having a disk file, we can now give it a partion label. The type is required. Here we set it to msdos:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
  </jobs>
</config>

Partitioning

Having a partition table at hand, we may now add a primary partition starting at an offset of 1MB and a size of 2500MB:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
  </jobs>
</config>

Formatting

After adding a partition, we may now format it with FreeBSDs native ufs file system:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
  </jobs>
</config>

Installing a bootloader

Let us install the GRUB2 bootloader and copy the master boot record data to the disk, to make it bootable. The files boot.img and core.img are binary files present in the files directory we copied at the start of the howto:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
      <dd file="boot.img" bs="446" count="1"/>
      <dd file="core.img" bs="512" seek="1"/>
  </jobs>
</config>

Copying files

The provided GRUB2 bootloader binary will search for it's configuration at /boot/grub/grub.cfg in the first partition. Let us copy that file onto the disk. You may find that file in the files directory as well:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
      <dd file="boot.img" bs="446" count="1"/>
      <dd file="core.img" bs="512" seek="1"/>
      <copy-in partition="1" source="grub.cfg" target="/boot/grub/grub.cfg"/>
  </jobs>
</config>

Downloading files

The current configuration will make the bootloader complain, that no kernel could be found. It's time to populate the file system a kernel and the base files. For convenience the vm-builder can download those files with the fetch job and compare their hash to avoid file corruptions. If the files already exist at the specified path in the files directory, they will be used instead. In the next step, we will extract those files:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
      <dd file="boot.img" bs="446" count="1"/>
      <dd file="core.img" bs="512" seek="1"/>
      <copy-in partition="1" source="grub.cfg" target="/boot/grub/grub.cfg"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/kernel.txz" path="kernel.txz" retry="3" hash="d08c54610a8ed40de103accd6171fc8abc59e0594d4e8bb8ecf8f8cf2fc8feb55422a1ee58996b6e42364140fa8bc8505d42758579da064018e98330209fc35c" hash-algorithm="sha512"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/base.txz" path="base.txz" retry="3" hash="faae230c12d8028c050de551656bb86435b5414aa605c20aaff3ac149816a3d711bbdbe867823f9ae6cb02bb56d9092a92f5913c1d07aa3ba86ef6484ce0e417" hash-algorithm="sha512"/>
  </jobs>
</config>

Extracting files

The downloaded archive are then extracted to the disk using the tar-in job. The build-vm does not have any knowledge on how and where the target-vm would mount it's partitions. You thus have to specify the target partition for archive extraction:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
      <dd file="boot.img" bs="446" count="1"/>
      <dd file="core.img" bs="512" seek="1"/>
      <copy-in partition="1" source="grub.cfg" target="/boot/grub/grub.cfg"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/kernel.txz" path="kernel.txz" retry="3" hash="d08c54610a8ed40de103accd6171fc8abc59e0594d4e8bb8ecf8f8cf2fc8feb55422a1ee58996b6e42364140fa8bc8505d42758579da064018e98330209fc35c" hash-algorithm="sha512"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/base.txz" path="base.txz" retry="3" hash="faae230c12d8028c050de551656bb86435b5414aa605c20aaff3ac149816a3d711bbdbe867823f9ae6cb02bb56d9092a92f5913c1d07aa3ba86ef6484ce0e417" hash-algorithm="sha512"/>
      <tar-in partition="1" source="kernel.txz" target="/"/>
      <tar-in partition="1" source="base.txz" target="/"/>
  </jobs>
</config>

Network configuration

The copy-in job let's us copy files and directories to the target-vm. We will use this, to copy the interfaces and resolv.conf files, which FreeBSD uses for configuring network related options.

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
      <dd file="boot.img" bs="446" count="1"/>
      <dd file="core.img" bs="512" seek="1"/>
      <copy-in partition="1" source="grub.cfg" target="/boot/grub/grub.cfg"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/kernel.txz" path="kernel.txz" retry="3" hash="d08c54610a8ed40de103accd6171fc8abc59e0594d4e8bb8ecf8f8cf2fc8feb55422a1ee58996b6e42364140fa8bc8505d42758579da064018e98330209fc35c" hash-algorithm="sha512"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/base.txz" path="base.txz" retry="3" hash="faae230c12d8028c050de551656bb86435b5414aa605c20aaff3ac149816a3d711bbdbe867823f9ae6cb02bb56d9092a92f5913c1d07aa3ba86ef6484ce0e417" hash-algorithm="sha512"/>
      <tar-in partition="1" source="kernel.txz" target="/"/>
      <tar-in partition="1" source="base.txz" target="/"/>
      <copy-in partition="1" source="interfaces" target="/etc/network/interfaces"/>
      <copy-in partition="1" source="resolv.conf" target="/etc/resolv.conf"/>
  </jobs>
</config>

Mount configuration

The FreeBSD kernel will look for a file at /etc/fstab for information on how to mount the partition the created earlier. We copy it into the disk image as well:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
      <dd file="boot.img" bs="446" count="1"/>
      <dd file="core.img" bs="512" seek="1"/>
      <copy-in partition="1" source="grub.cfg" target="/boot/grub/grub.cfg"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/kernel.txz" path="kernel.txz" retry="3" hash="d08c54610a8ed40de103accd6171fc8abc59e0594d4e8bb8ecf8f8cf2fc8feb55422a1ee58996b6e42364140fa8bc8505d42758579da064018e98330209fc35c" hash-algorithm="sha512"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/base.txz" path="base.txz" retry="3" hash="faae230c12d8028c050de551656bb86435b5414aa605c20aaff3ac149816a3d711bbdbe867823f9ae6cb02bb56d9092a92f5913c1d07aa3ba86ef6484ce0e417" hash-algorithm="sha512"/>
      <tar-in partition="1" source="kernel.txz" target="/"/>
      <tar-in partition="1" source="base.txz" target="/"/>
      <copy-in partition="1" source="interfaces" target="/etc/network/interfaces"/>
      <copy-in partition="1" source="resolv.conf" target="/etc/resolv.conf"/>
      <copy-in partition="1" source="fstab" target="/etc/fstab"/>
  </jobs>
</config>

Firstrun script

Now the target-vm is ready to boot! Though we still can not use the run job, to execute arbitrary commands. For that, a firstrun script is provided, which reads the commands provided by us and executes them. Copying it to this specific location, makes FreeBSD run in on every boot:

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
      <dd file="boot.img" bs="446" count="1"/>
      <dd file="core.img" bs="512" seek="1"/>
      <copy-in partition="1" source="grub.cfg" target="/boot/grub/grub.cfg"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/kernel.txz" path="kernel.txz" retry="3" hash="d08c54610a8ed40de103accd6171fc8abc59e0594d4e8bb8ecf8f8cf2fc8feb55422a1ee58996b6e42364140fa8bc8505d42758579da064018e98330209fc35c" hash-algorithm="sha512"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/base.txz" path="base.txz" retry="3" hash="faae230c12d8028c050de551656bb86435b5414aa605c20aaff3ac149816a3d711bbdbe867823f9ae6cb02bb56d9092a92f5913c1d07aa3ba86ef6484ce0e417" hash-algorithm="sha512"/>
      <tar-in partition="1" source="kernel.txz" target="/"/>
      <tar-in partition="1" source="base.txz" target="/"/>
      <copy-in partition="1" source="interfaces" target="/etc/network/interfaces"/>
      <copy-in partition="1" source="resolv.conf" target="/etc/resolv.conf"/>
      <copy-in partition="1" source="fstab" target="/etc/fstab"/>
      <copy-in partition="1" source="firstrun" target="/usr/local/etc/rc.d/firstrun.sh"/>
  </jobs>
</config>

Arbitray commands

The last job for executing custom commands, namely the run job is usable now. It furthermore allows for enabling or disabling network access, specifying whether to use virtio virtual hardware or legacy virtual hardware, and the amount of ram. For the sake of checking, if the firstrun script works as expected, we use a command to print out the installed operating systems version

<config>
  <build-vm arch="x86_64" name="linux-busybox" version="2024-05-23"/>
  <target-vm arch="x86_64" name="freebsd-13.0" version="2024-04-13"/>
  <jobs>
      <create-disk size="4G"/>
      <label type="msdos"/>
      <partition type="primary" start="1MB" size="2500MB"/>
      <format partition="1" type="ufs2" label="" uuid=""/>
      <dd file="boot.img" bs="446" count="1"/>
      <dd file="core.img" bs="512" seek="1"/>
      <copy-in partition="1" source="grub.cfg" target="/boot/grub/grub.cfg"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/kernel.txz" path="kernel.txz" retry="3" hash="d08c54610a8ed40de103accd6171fc8abc59e0594d4e8bb8ecf8f8cf2fc8feb55422a1ee58996b6e42364140fa8bc8505d42758579da064018e98330209fc35c" hash-algorithm="sha512"/>
      <fetch url="https://download.freebsd.org/ftp/releases/amd64/13.3-RELEASE/base.txz" path="base.txz" retry="3" hash="faae230c12d8028c050de551656bb86435b5414aa605c20aaff3ac149816a3d711bbdbe867823f9ae6cb02bb56d9092a92f5913c1d07aa3ba86ef6484ce0e417" hash-algorithm="sha512"/>
      <tar-in partition="1" source="kernel.txz" target="/"/>
      <tar-in partition="1" source="base.txz" target="/"/>
      <copy-in partition="1" source="interfaces" target="/etc/network/interfaces"/>
      <copy-in partition="1" source="resolv.conf" target="/etc/resolv.conf"/>
      <copy-in partition="1" source="fstab" target="/etc/fstab"/>
      <copy-in partition="1" source="firstrun" target="/usr/local/etc/rc.d/firstrun.sh"/>
      <run virtio="yes" network="yes" ram="1G" file="kernel.txz" command="uname -a"/>
  </jobs>
</config>

Importing and building

Now we will import this configuration file by specifying it's repositories directory as the path:

vm-builder import --path my-vm-repo

This validates and copies the contents of the my-vm-repo directory to the path ~/.uvm/repos:

~/.uvm/repos/my-vm-repo
~/.uvm/repos/my-vm-repo/configs
~/.uvm/repos/my-vm-repo/configs/x86_64
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files/resolv.conf
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files/interfaces
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files/grub.cfg
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files/boot.img
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files/core.img
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files/fstab
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/files/firstrun
~/.uvm/repos/my-vm-repo/configs/x86_64/freebsd-13.0/2024-04-13/build.xml

After the build configuration is imported, we may build it:

vm-builder build --repo my-vm-repo --arch x86_64 --name freebsd-13.0 --version 2024-04-13

At the end of the build process, the path to the virtual machine image is printed:

...
The image can be found at:
        /home/user/.uvm/images/my-vm-repo/x86_64/freebsd-13.0/2024-04-13/disk0.img
Run it with:
        qemu-system-x86_64 -bios /usr/share/seabios/bios.bin -enable-kvm -cpu host -m 1G -smp 2 -drive file=/home/user/.uvm/images/my-vm-repo/x86_64/freebsd-13.0/2024-04-13/disk0.img,format=raw,if=virtio
Cleaning up build directory at: /home/user/.uvm/images/my-vm-repo/x86_64/freebsd-13.0/2024-04-13/build