# Mkosi for Arch Boxes

Deliverables

  • vagrant blogpost
  • building in CI
  • testing mkosi build images! (in CI)
  • reproducible images
  • talk!

Questions

  • how do we make btrfs partition of 40GB for basic
  • how do I add a user with sudo? => https://github.com/DaanDeMeyer/mkosi/blob/main/mkosi/resources/mkosi.md#frequently-asked-questions-faq
  • how do I cache the package manager step
  • how do I create a qcow2 after the final step? => mkosi.postoutput
  • how do we make profiles?
    • profile for basic
    • profile for cloud-init
    • profile for vagrant
    • profile for vagrant-libvirt
  • do we need to remove /etc/machine-id?
  • get mkswapfile / btrfs swap working by default => create it on first boot?!
  • How do we apply sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"rootflags=compress-force=zstd\"/' "${MOUNT}/etc/default/grub"
  • how do we apply, this is apparently what all cloud images do (cloud-init image) echo 'GRUB_TERMINAL="serial console"' >>"${MOUNT}/etc/default/grub" echo 'GRUB_SERIAL_COMMAND="serial --speed=115200"' >>"${MOUNT}/etc/default/grub"
  • systemd-firstboot queries with timezone, this is unwanted!!!

Resources

  • https://btrfs.readthedocs.io/en/latest/Swapfile.html
  • https://swsnr.de/archlinux-rescue-image-with-mkosi/
  • https://noise.getoto.net/2024/01/10/a-re-introduction-to-mkosi-a-tool-for-generating-os-images/

Requirements

  • mkosi
  • systemd-ukify
  • grub
  • btrfs-progs

Creation

cd projects/arch-boxes/cloud-init-image
mkdir mkosi.{cache,output}
mkosi build

Reproducible

  • SourceDateEpoch=
  • Seed=
  • Pin mirrors to a fixed set. (OR introduce .BUILDINFO, harder imo)
  • FS allocation might be unreproducible

Some documentation: https://reproducible-builds.org/docs/system-images/ mkosi reproducible: https://github.com/edgelesssys/reproducible-mkosi

Problems

  • image too big for diffoscope
  • how to diff an EFI executable, not supported by diffoscope it seems https://salsa.debian.org/reproducible-builds/diffoscope/-/issues/181
  • split up the problem in smaller steps
    • is btrfs swapfile repro
    • is mkfs.btrfs repro
    • what about ext4

btrfs

mkfs.btrfs is not reproducible.

qemu-img create -f raw test1.img 2G
qemu-img create -f raw test2.img 2G

0414cac598ebfa08e8e9c6d2544aa414385b9985c5d67d7a8746aa64324c715fa96ff63351016d30dd2b89276252c121c71619f15496b5ca95785d0b25fe4dfd  test2.img
0414cac598ebfa08e8e9c6d2544aa414385b9985c5d67d7a8746aa64324c715fa96ff63351016d30dd2b89276252c121c71619f15496b5ca95785d0b25fe4dfd  test1.img

mkfs.btrfs -f -L arch -U 58c7d0b2-d224-4834-a16f-e036322e88f7 test1.img
mkfs.btrfs -f -L arch -U 58c7d0b2-d224-4834-a16f-e036322e88f7 test2.img

Building in container in CI

  • Can we do this by default?
  • Try out a gitlab action

CI testing

  • Can we boot tests in CI
  • Can we boot test in CI? Check if swap is enabled etc.? Systemd units run? systemctl --failed is green?
  • Use mkosi ssh?

Grub

How do we apply, this is apparently what all cloud images do (cloud-init image) echo 'GRUB_TERMINAL="serial console"' >>"${MOUNT}/etc/default/grub" echo 'GRUB_SERIAL_COMMAND="serial --speed=115200"' >>"${MOUNT}/etc/default/grub"

This is for hosting companies like hetzner if they don't offer a VNC view.

[root@archlinux ~]# grep serial /boot/grub/grub.cfg
serial --speed=115200
terminal_input serial console
terminal_output serial console
[root@archlinux ~]# cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-linux root=UUID=d7d6a869-2230-43de-b3c4-23cb261ac1bd rw net.ifnames=0 console=ttyS0,115200 rootflags=compress-force=zstd console=tty0 console=ttyS0,115200

Current mkosi image

net.ifnames=0 console=tty0 console=ttyS0,115200
  1. Vendor grub.cfg - meh
  2. provide default grub.cfg which mkosi can append too
  3. Somehow sed it in an mkosi script?

Editing /etc/default/grub in mkosi.finalize does not seem to work, which runs before grub

Mkosi issues

Should mkosi qemu read BiosBootLoader options and then use mkosi --qemu-firmware bios qemu

  • How to exit QEMU? Maybe add ctrl+] support like nspawn? => ctrl+a x in QEMU

modifications in mkosi qemu stay, seems somewhat unexpected

Vagrant images

  • What is vagrant?
  • What does it support?
  • What is its image format?
  • How do we build this?
  • How to make this in mkosi?

Intro

Vagrant was a popular way to setup a development virtual machine for projects, all you had to do is provide a Vagrantfile and vagrant up would setup a development VM and for example mount your code into the VM via sshfs.

The image you use in your Vagrantfile are called boxes in Vagrant's terminology and are hosted by the Vagrant project.

Vagrant also supports the concept of providers, these are the backends on which the box would run, the default being virtualbox, alternatives are hyperv, libvirt and more.

Format

Usually the vagrant boxes are made using packer, but other tools such as kiwi, osbuild (only libvirt provider) also support creating these boxes. The alternative tools usually only support a subset of the providers packer supports. In the Linux community virtualbox and libvirt are usually the most popular ones. At least this is what Arch Linux supports.

The base box format is described in a very short summary what needs to be in the image:

  • Package manager
  • SSH server enabled and running
  • A vagrant user with SSH pubkey and password-less sudo
  • Enough disk space to do interesting things
  • Provider specific tools (for example virtualbox guest tools)

The image format is provider dependant and is described for Virtualbox and libvirt.

Mkosi

Libvirt

local virtual_size
virtual_size="$(grep -o "^[0-9]*" <<<"${DISK_SIZE}")"
echo '{"format":"qcow2","provider":"libvirt","virtual_size":'"${virtual_size}"'}' >metadata.json
qemu-img convert -f raw -O qcow2 "${1}" box.img
rm "${1}"

tar -czf "${2}" Vagrantfile metadata.json box.img

Presentation

Reproduciblity

Arch Linux is working on making packages 100% reproducible, currently we are ~ 90% reproducible but so far we haven't looked at reproducing our produced images.

The most common reproducibility issues are timestamps, hostnames, and other versioned identifiers (kernel, etc.), locale and build paths can also be recorded in the resulting artificat twarting reproducibility.

What does this mean for making an reproducible images?

  • Our minimal container image is already reproducible
  • Installed packages need to reproducible
  • Kernel notably is not reproducible

The goal is to allow others to reproduce the arch image bit by bit so we have to reduce any variation for this we can:

  • Pin the package repository of the build image to a fixed date (Arch Linux has support for this using the Archive)
  • Build an image with the same package set and compare

Problems:

  • The created cloud image is big!
  • Diffoscope simply OOM's
  • We need to reduce the problem scope and tackle things one by one

Unreproducibility

  • The generated UKI is not reproducible, (a seperate build artifact)
  • Diffing images is hard, so we work on a directory first (supported output type)
mkosi -d arch -p systemd --format directory -o foo -m https://archive.archlinux.org/repos/2024/06/30/
mkosi -d arch -p systemd --format directory -o bar -m https://archive.archlinux.org/repos/2024/06/30/
sudo diffoscope foo bar --html-dir output
xdg-open output/index.html

Timestamps, timestamps everywhere

export SOURCE_DATE_EPOCH=1662046009
mkosi -d arch -p systemd --format directory --source-date-epoch $SOURCE_DATE_EPOCH -o foo -m https://archive.archlinux.org/repos/2024/06/30/
mkosi -d arch -p systemd --format directory --source-date-epoch $SOURCE_DATE_EPOCH -o bar -m https://archive.archlinux.org/repos/2024/06/30/

This is a lot better!

  • Look at what mkosi does

Now the leftover issues are:

  • var/cache/ldconfig/aux-cache - we can opt to remove this post creation (post install script)
  • var/lib/pacman/local/*
20	%INSTALLDATE%	20	%INSTALLDATE%
21	1722251133	21	1722251028

https://gitlab.archlinux.org/pacman/pacman/-/merge_requests/213

lib/libalpm/add.c

	/* make an install date (in UTC) */
	newpkg->installdate = time(NULL);
sed -i -e '/INSTALLDATE/{n;s/.*/0/}' "${BUILDROOT}"/var/lib/pacman/local/*/desc || true

Now with a cloud image:

mkosi -d arch -p systemd -p linux -p base -p grub -p openssh -p sudo -p reflector -p btrfs-progs -p udev --source-date-epoch 1662046009 --format directory -o bar -m https://archive.archlinux.org/repos/2024/06/30/

This OOM's. on foo/boot arch-6.9.7-arch1-1.efi:

https://salsa.debian.org/reproducible-builds/diffoscope/-/issues/181

Directory containing the UKI

Also unreproducible: bar/efi/loader/random-seed comes from bootctl?

https://www.freedesktop.org/software/systemd/man/latest/bootctl.html#random-seed https://systemd.io/RANDOM_SEEDS/

UKI tools:

  • llvm-objdump
  • pe-inspect (virt-firmware)
  • ukify inspect

Different sector: .initrd

.initrd:
  size: 202577872 bytes
  sha256: 04a3e836f5120c1991dda74285293fe9f7badf2f40602eb4587a9a2b37e8cadf
.initrd:
  size: 202577856 bytes
  sha256: b6cc6d6b62c0bd0e4d108a550bb8b59693fa79184987990d708348b40a316cd9

different:

  • var/cache/ldconfig
dump2efs test1.img > test1.txt
dump2efs test2.img > test1.txt
diffoscope test1.txt test2.txt
fallocate -l0.1G test1.img
mkfs.ext4 -U c1b9d5a2-f162-11cf-9ece-0020afc76f16 -E hash_seed=a24031c1-fc68-453d-80fa-00ad057a5780  test1.img

mkosi.conf.d/30-debian-ubuntu/mkosi.conf.d/20-ext4-orphan-file.conf:Environment=SYSTEMD_REPART_MKFS_OPTIONS_EXT4="-O ^orphan_file"

Environment=SYSTEMD_REPART_MKFS_OPTIONS_EXT4="-E hash_seed=a24031c1-fc68-453d-80fa-00ad057a5780"

To Do mkosi this!

mkosi --debug -d arch -p systemd --seed 0e9a6fe0-68f6-408c-bbeb-136054d20445 --source-date-epoch 1662046009 -m https://archive.archlinux.org/repos/2024/06/30/ --force -o foo --env-file env  --remove-files var/cache/ldconfig/aux-cache --environment=SYSTEMD_LOG_LEVEL=debug

env

SYSTEMD_REPART_MKFS_OPTIONS_EXT4=-E hash_seed=a24031c1-fc68-453d-80fa-00ad057a5780 -U 6de632fe-7638-44c4-917c-ecf4170af3b4
mkosi --debug -d arch -p systemd -p udev  --seed 0e9a6fe0-68f6-408c-bbeb-136054d20445 --source-date-epoch 1662046009 -m https://archive.archlinux.org/repos/2024/06/30/ --force -o bar --env-file env --remove-files var/cache/ldconfig/aux-cache --environment=SYSTEMD_LOG_LEVEL=debug

But adding -p linux fails, this probably installs a boot loader which is the issue?

The issue is likely making it bootable

mkosi --debug -d arch -p systemd -p udev -p linux --seed 0e9a6fe0-68f6-408c-bbeb-136054d20445 --source-date-epoch 1662046009 -m https://archive.archlinux.org/repos/2024/06/30/ --bootable false --force -o bar --env-file env --remove-files var/cache/ldconfig/aux-cache --environment=SYSTEMD_LOG_LEVEL=debug

vfat reproducible issues:

cfdisk foo.raw
cfdisk bar.raw

Show the same meta-data

Mounting with losetup shows:

losetup --find --show -P bar.raw

Mounting and diffoscope shows difference in

sudo mount /dev/loop1p1 -o ro /tmp/foo
sudo mount /dev/loop2p1 -o ro /tmp/bar

EFI directory and mtimes, they are recent. Unsure if due to mounting +Modify: 2024-08-24 14:23:34.000000000 +0000

/mnt/disk/EFI
[jelle@natrium][~/projects/arch-mkosi-boxes/reproducible/mkosi.output]%mkfs.vfat -i e7b87c0d -n ESP -F 32 -S 4096 test1.img
mkfs.fat 4.2 (2021-01-31)
[jelle@natrium][~/projects/arch-mkosi-boxes/reproducible/mkosi.output]%mkfs.vfat -i e7b87c0d -n ESP -F 32 -S 4096 test2.img
mkfs.fat 4.2 (2021-01-31)
[jelle@natrium][~/projects/arch-mkosi-boxes/reproducible/mkosi.output]%md5sum test1.img test2.img
9d95cb024f4a79a76345de000568dd0e  test1.img
2ccc5c8845667d6afe943c6ce1c6bd40  test2.img

Needs: https://github.com/dosfstools/dosfstools/commit/8da7bc93315cb0c32ad868f17808468b81fa76ec

But after that we are still not reproducible....

But when mounting and diffing the FS it is. So must be some other meta-data

Useful tools:

sfdisk -J foo.raw

0022000E 56 43
00220016 56 43
0022002E 56 43
00220036 56 43
0022004E 56 43
00220056 56 43
0022100E 56 43
00221016 56 43
0022102E 56 43
00221036 56 43
0D07500E 56 43
0D075016 56 43
0D07502E 56 43
0D075036 56 43

So systemd-repart implements creating fat partitions by:

  • mkfs.vfat
  • mcopy (mtools) the stuff into the fat partition.

Executing mkfs command: mkfs.vfat -i e7b87c0d -n ESP -F 32 /var/tmp/repart-OWnjo5 -S 4096 Executing mkfs command: mkfs.vfat -i e7b87c0d -n ESP -F 32 /var/tmp/repart-TA8zUX -S 4096

dd if=bar.raw of=bar-boot.raw bs=512 skip=2048 count=1048576 status=progress 

Reproducible if I copy out one file, so time to:

  • Find the list of files copied either debug systemd-repart build or grab from mkosi
  • Make a reproducer with mcopy / mkfs.vfat

systemd-repart takes CopyFiles, copies the source to a tmpdir var_tmp_dir and then passes this over to mcopy

[Partition]
Type=esp
Format=vfat
# CopyFiles=/boot:/
CopyFiles=/efi:/
SizeMinBytes={"1G" if bios else "512M"}
SizeMaxBytes={"1G" if bios else "512M"}
‣  Normalizing modification times of /boot
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/boot/loader
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/boot/EFI
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/boot/loader/entries.srel
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/boot/loader/entries
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/boot/EFI/Linux
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/boot/EFI/Linux/arch-6.9.7-arch1-1.efi
‣  Normalizing modification times of /efi
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/EFI
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/loader
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/EFI/systemd
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/EFI/BOOT
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/loader/loader.conf
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/EFI/systemd/systemd-bootia32.efi
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/EFI/systemd/systemd-bootx64.efi
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/EFI/BOOT/BOOTIA32.EFI
‣ Normalize mtime of /home/jelle/.cache/mkosi/mkosi-workspace-9gik1art/root/efi/EFI/BOOT/BOOTX64.EFI

Filesystems reproducible

  • vfat - with patch
  • ext4 - yes
  • xfs
  • btrfs

FAT

fallocate -l512M test1.img
fallocate -l512M test2.img
mkfs.vfat -i e7b87c0d -n ESP -F 32 -S 4096 test1.img
mkfs.vfat -i e7b87c0d -n ESP -F 32 -S 4096 test2.img
TZ=UTC mcopy -p -s -Q -m -i test1.img boot efi :: && echo 1
TZ=UTC mcopy -p -s -Q -m -i test2.img boot efi :: && echo 1

mcopy -s -p -Q -m -i /var/tmp/repart-kAScbM /var/tmp/.#repartafc0df669fc911a5/EFI /var/tmp/.#repartafc0df669fc911a5/loader ::

/var/tmp/.#repart096a80760fee7737/EFI
total 0
drwx------ 1 root root 46 Jan  1  1970 BOOT
drwx------ 1 root root 44 Jan  1  1970 Linux
drwx------ 1 root root 78 Jan  1  1970 systemd
/var/tmp/.#repart096a80760fee7737/loader
total 8.0K
drwx------ 1 root root  0 Jan  1  1970 entries
-rw------- 1 root root  6 Jan  1  1970 entries.srel
-rw-r----- 1 root root 30 Jan  1  1970 loader.conf
drwx------ 1 root root 32 Aug 29 19:59 EFI
drwx------ 1 root root 60 Aug 29 19:59 loader
total 0
drwx------ 1 root root 32 Aug 29 20:02 EFI
drwx------ 1 root root 60 Aug 29 20:02 loader

mcopy -i option not documented

There is an mtools mailing list at info-mtools@gnu.org. Please send all bug reports to this list. Note: You must be subscribed to the list in order to be able to send comments.

BTRFS

if (blk == MKFS_FS_TREE) {
	time_t now = time(NULL);
static int make_root_dir(struct btrfs_trans_handle *trans,
		struct btrfs_root *root)
{
	struct btrfs_key location;
	int ret;

	ret = btrfs_make_root_dir(trans, root->fs_info->tree_root,
			      BTRFS_ROOT_TREE_DIR_OBJECTID);

From: 3362

To: 3300

erofs

Apparently can be made reproducible.

F2FS

fallocate -l 5M test1.img
fallocate -l 5M test2.img
mkfs.f2fs -U 588114f7-e142-40a1-8b99-30db4519183e -f  -r test2.img
[jelle@natrium][~/Downloads/tmp]%~/Downloads/bindiff.sh test1.img test2.img
01000020 56 59
01000028 56 59
01000030 56 59
 1000000 41ed 0000 03e8 0000 03e8 0000 0002 0000
 1000010 1000 0000 0000 0000 0002 0000 0000 0000
-1000020 3b39 66ca 0000 0000 3b39 66ca 0000 0000
-1000030 3b39 66ca 0000 0000 0000 0000 0000 0000
+1000020 3b3b 66ca 0000 0000 3b3b 66ca 0000 0000
+1000030 3b3b 66ca 0000 0000 0000 0000 0000 0000
 1000040 0000 0000 0000 0000 0001 0000 0000 0000
 1000050 0000 0000 0000 0000 0000 0000 0000 0000
-01000020  39 3b ca 66 00 00 00 00  39 3b ca 66 00 00 00 00  |9;.f....9;.f....|
-01000030  39 3b ca 66 00 00 00 00  00 00 00 00 00 00 00 00  |9;.f............|
+01000020  3b 3b ca 66 00 00 00 00  3b 3b ca 66 00 00 00 00  |;;.f....;;.f....|
+01000030  3b 3b ca 66 00 00 00 00  00 00 00 00 00 00 00 00  |;;.f............|

-T 0

Can be made reproducible with:

mkfs.f2fs -T0 -r

 -r set checkpointing seed (srand()) to 0
 -T timestamps

archiso

  • 32 bit EFI
  • 32 bit EFI eltorito
  • 64 bit EFI
  • 64 bit EFI eltorito
  • BIOS boot
  • BIOS boot el torito