Last updated:
SquashFS images generated by genpack can be booted in two ways: on physical hardware installed to disk, and in a QEMU/KVM paravirtual environment via the vm command. In both cases, a common initramfs (dracut-genpack) constructs an overlayfs root, and genpack-init performs initial configuration based on system.ini before handing control to systemd.
This document describes the boot sequence for both methods in detail.
When installed to disk with genpack-install, the following partition layout is created:
| Partition | Filesystem | Contents |
|---|---|---|
| 1: Boot partition | FAT32 | EFI bootloader, kernel, initramfs, system.img (if < 4GiB), system.ini |
| 2: Data partition | Btrfs | overlayfs upper layer, system (if >= 4GiB) |
The boot partition size is automatically calculated from the image size. The --superfloppy option skips partition table creation and uses the entire disk as FAT32 (no data partition).
MBR or GPT is automatically selected based on disk size (MBR for <= 2TiB with 512-byte sectors, GPT otherwise).
The kernel command line specifies root=systemimg:<UUID> or root=systemimg:auto.
genpack-install builds GRUB bootloaders for each architecture and bundles them in the SquashFS image at /usr/lib/genpack-install/.
EFI bootloaders:
Built with grub-mkstandalone, each is a standalone EFI binary with grub.cfg embedded inside. No external configuration file is needed on disk.
| Binary | Target |
|---|---|
bootx64.efi | x86_64 |
bootia32.efi | i386 |
bootaa64.efi | ARM64 |
bootriscv64.efi | RISC-V 64 |
BIOS bootloader:
A combination of boot.img (MBR stage 1) and core.img (generated by grub-mkimage). The prefix (,msdos1)/boot/grub is hardcoded into core.img [10], which reads /boot/grub/grub.cfg from the boot partition. For BIOS, grub.cfg is not embedded in the binary — genpack-install places it on the boot partition during disk installation.
The grub.cfg — embedded in EFI binaries or placed on the boot partition for BIOS — performs the following:
$cmdpath (the path from which the bootloader was loaded)probe -usystem.img on the boot partitionsystem on the data partition (by label data-<UUID> → d-<UUID> → fallback to partition number)loopback command and switch root with set root=loop/boot/grub/grub.cfg, delegate to it via configfile [7]system.cfg exists on the boot partition, load it via source [8]LINUX_ARGS variable can be set to customize the kernel command linepanic=30 added by default (unless explicitly specified)console=ttyS0,115200n8r console=tty0 added (unless explicitly specified)linux /boot/kernel root=systemimg:<UUID> $LINUX_ARGS systemd.firstboot=0genpack.transient=1 to the above/boot/kernel and /boot/initramfs inside the SquashFS (already loop-mounted)If no system image is found in the above steps, a fallback path attempts to boot directly from a grub.cfg or kernel on the data partition [9].
dracut-genpack consists of two hooks.
cmdline hook (check-systemimg-root.sh):
Checks the root= parameter in the kernel command line. If it matches the root=systemimg:... format, the genpack boot sequence begins.
mount hook (mount-genpack.sh):
root=systemimg:<UUID>: Mount the partition with the specified UUIDroot=systemimg:auto: Scan all FAT partitions to find one containing system.imgfsck.fat -aw for automatic repair before mounting/run/initramfs/bootdata-<UUID> based on the boot partition's UUIDd-<UUID>, wbdata-<UUID>, or next partition number [2]fs tag) [3]genpack.transient kernel parameter can explicitly specify transient mode/run/initramfs/rw/run/initramfs/boot/system.img on the boot partition/run/initramfs/rw/system on the data partition/run/initramfs/ro/run/initramfs/ro/usr/libexec/genpack-shutdown to /run/initramfs/shutdownOnce dracut's initramfs processing completes, the root switches to $NEWROOT and /usr/bin/genpack-init starts as PID 1 (init=/usr/bin/genpack-init is added to the cmdline by the dracut module).
genpack-init is implemented in C++ + pybind11 and performs the following:
/run/initramfs/boot/system.ini (via boot partition) or /run/initramfs/rw/system.ini (via data partition)/usr/lib/genpack-init/*.py in filename orderconfigure(ini) function (timezone, locale, banner display, machine ID generation, etc.)exec /sbin/initThe vm command is a tool for directly booting genpack images with QEMU/KVM. No disk installation is required — you can specify and boot a SquashFS file directly.
The vm command uses the squashfuse library to read the kernel and initramfs directly from the /boot/ directory within the SquashFS image. There is no need to extract to disk — the files are written to file descriptors created with memfd_create and passed to QEMU.
Search order:
boot/kernel or boot/vmlinuz (fixed names)boot/kernel-* or boot/vmlinuz-* (most recent by timestamp)initramfs is similarly searched in the order: boot/initramfs, boot/initramfs.img, boot/initrd.img.
The architecture (x86_64, aarch64, riscv64, etc.) is automatically detected from the ELF header or PE header of the kernel binary, and the corresponding qemu-system-<arch> is launched.
The vm command uses QEMU's direct kernel boot (-kernel, -initrd, -append). No bootloader is involved.
Kernel command line:
root=/dev/vda ro net.ifnames=0 systemd.firstboot=0 systemd.hostname=<vmname> console=...
root=/dev/vda: The SquashFS image is provided as a virtio-blk deviceroot=fs rootfstype=virtiofs rwDisk provisioning:
| virtio device | Serial | Contents |
|---|---|---|
| vda | system | SquashFS image (read-only) |
| vdb | data | Data disk (if present) |
| vdc | swap | Swap file (if present) |
The SquashFS image is attached as a virtio-blk-pci device in read-only mode. The initramfs mount-genpack.sh references the kernel command line's root=/dev/vda (not root=block:*) and mounts /dev/vda directly as SquashFS at /run/initramfs/ro.
virtiofs mode:
The vm command also supports virtiofs. It launches virtiofsd to share a host directory with the guest, which can be used as the overlayfs upper layer. When using virtiofs, the initramfs mounts virtiofs as root based on root=fs rootfstype=virtiofs rw.
The vm command has a service mode that reads vm.ini files. The directory structure for each VM is as follows:
type=genpack (the default) in vm.ini enables direct kernel boot.
| Aspect | system.img method | Paravirtual method |
|---|---|---|
| Bootloader | GRUB (EFI/BIOS) | None (direct kernel boot) |
| Kernel location | File on boot partition | Extracted from SquashFS to memfd |
| root= parameter | systemimg:<UUID> or systemimg:auto | /dev/vda |
| SquashFS delivery | File on boot/data partition | virtio-blk device |
| Data persistence | Btrfs partition | Data disk file or virtiofs |
| Transient mode | genpack.transient kernel parameter | Automatic when no data disk specified |
| system.ini | On FAT32 partition | Via virtiofs or fw_cfg |
| initramfs behavior | Common (dracut-genpack) | Common (dracut-genpack) |
| genpack-init behavior | Common | Common |
Shutdown of a genpack image follows the normal systemd shutdown process, after which control returns to dracut's initramfs and /run/initramfs/shutdown (genpack-shutdown) executes. genpack-shutdown performs the following:
/oldroot in reverse order/run/initramfs/rw (data partition) and /run/initramfs/boot (boot partition)boottime.txt on the boot partition [1]reboot(2) or poweroffd-<UUID> is the current canonical label format (shortened due to Btrfs label length limitations). data-<UUID> and wbdata-<UUID> are retained for backward compatibility with the Walbrix era (genpack's predecessor). ↩/usr timestamp to determine whether ld.so.cache needs to be regenerated. In overlayfs, the upper layer's presence masks the lower layer's timestamps, so the lower layer's /usr timestamp must be explicitly propagated to the upper layer. ↩rw/rw/root. This was simplified to rw/root, but the initramfs continues to recognize the old path rw/rw/root for backward compatibility with existing deployments. ↩BOOT_PARTITION and BOOT_PARTITION_UUID are exported, so the delegated grub.cfg can reference boot partition information. ↩This document was created based on snapshots of the following repositories: