Last updated:

Boot Sequence of genpack Images

Overview

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.

system.img Method (Disk Install)

Disk Layout

When installed to disk with genpack-install, the following partition layout is created:

PartitionFilesystemContents
1: Boot partitionFAT32EFI bootloader, kernel, initramfs, system.img (if < 4GiB), system.ini
2: Data partitionBtrfsoverlayfs 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).

Boot Sequence

UEFI/BIOS GRUB Linux kernel initramfs (dracut-genpack) overlayfs root construction genpack-init (PID 1) Python plugins exec /sbin/init (systemd)

The kernel command line specifies root=systemimg:<UUID> or root=systemimg:auto.

Bootloader

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.

BinaryTarget
bootx64.efix86_64
bootia32.efii386
bootaa64.efiARM64
bootriscv64.efiRISC-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.

grub.cfg Processing Flow

The grub.cfg — embedded in EFI binaries or placed on the boot partition for BIOS — performs the following:

  1. Serial console initialization
    • Attempts COM0 at 115200 baud
    • On success, both serial and console are configured as input/output terminals
  2. Boot partition identification
    • Determines the boot partition from the GRUB variable $cmdpath (the path from which the bootloader was loaded)
    • Obtains its UUID via probe -u
  3. System image detection
    • Search for system.img on the boot partition
    • If not found, search for system on the data partition (by label data-<UUID>d-<UUID> → fallback to partition number)
  4. SquashFS mount and kernel detection
    • Loop-mount SquashFS via the loopback command and switch root with set root=loop
    • If the image contains /boot/grub/grub.cfg, delegate to it via configfile [7]
    • If no such file exists, continue with the following steps
  5. Timeout determination
    • boottime.txt persists (evidence of previous unclean shutdown [1]) → 10 seconds [6]
    • Normal → 1 second
  6. Custom configuration loading
    • If system.cfg exists on the boot partition, load it via source [8]
    • The LINUX_ARGS variable can be set to customize the kernel command line
  7. Kernel command line construction
    • panic=30 added by default (unless explicitly specified)
    • On x86, console=ttyS0,115200n8r console=tty0 added (unless explicitly specified)
  8. Menu entries
    • Normal mode: linux /boot/kernel root=systemimg:<UUID> $LINUX_ARGS systemd.firstboot=0
    • Transient mode: adds genpack.transient=1 to the above
    • Kernel and initramfs loaded from /boot/kernel and /boot/initramfs inside the SquashFS (already loop-mounted)
    • Additional menu entries shown if MemTest86 is available

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].

initramfs Processing (dracut-genpack)

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):

  1. Boot partition detection and mount
    • root=systemimg:<UUID>: Mount the partition with the specified UUID
    • root=systemimg:auto: Scan all FAT partitions to find one containing system.img
    • For FAT, run fsck.fat -aw for automatic repair before mounting
    • Mount point: /run/initramfs/boot
  2. Data partition detection and mount
    • Search by label data-<UUID> based on the boot partition's UUID
    • Fallback: d-<UUID>, wbdata-<UUID>, or next partition number [2]
    • If not found, attempt virtiofs (fs tag) [3]
    • If still unmountable, fall back to tmpfs (transient mode)
    • genpack.transient kernel parameter can explicitly specify transient mode
    • Mount point: /run/initramfs/rw
  3. SquashFS image mount
    • Search for /run/initramfs/boot/system.img on the boot partition
    • If not found, use /run/initramfs/rw/system on the data partition
    • Mount read-only at /run/initramfs/ro
  4. overlayfs construction
    • lowerdir: /run/initramfs/ro (SquashFS, read-only)
    • upperdir: /run/initramfs/rw/root (Btrfs or tmpfs) [5]
    • workdir: /run/initramfs/rw/work
    • Mount overlay at $NEWROOT
    • Synchronize lower layer's /usr timestamp to the upper layer [4]
  5. Shutdown program placement
    • Copy /run/initramfs/ro/usr/libexec/genpack-shutdown to /run/initramfs/shutdown
    • Used for safely unmounting overlayfs and SquashFS during shutdown

genpack-init

Once 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:

  1. Load /run/initramfs/boot/system.ini (via boot partition) or /run/initramfs/rw/system.ini (via data partition)
  2. Load all Python modules in /usr/lib/genpack-init/*.py in filename order
  3. Execute each module's configure(ini) function (timezone, locale, banner display, machine ID generation, etc.)
  4. Hand control to systemd via exec /sbin/init

Paravirtual Method (vm command)

vm Command Overview

The 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.

Boot Sequence

vm run system.squashfs Extract kernel from SquashFS (memfd) qemu-system-<arch> initramfs (dracut-genpack) overlayfs root construction genpack-init (PID 1) exec /sbin/init (systemd)

Kernel and initramfs Extraction

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:

  1. boot/kernel or boot/vmlinuz (fixed names)
  2. 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.

QEMU Launch Configuration

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=...

Disk provisioning:

virtio deviceSerialContents
vdasystemSquashFS image (read-only)
vdbdataData disk (if present)
vdcswapSwap 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.

vm Service Mode

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.

Comparison with the system.img Method

Aspectsystem.img methodParavirtual method
BootloaderGRUB (EFI/BIOS)None (direct kernel boot)
Kernel locationFile on boot partitionExtracted from SquashFS to memfd
root= parametersystemimg:<UUID> or systemimg:auto/dev/vda
SquashFS deliveryFile on boot/data partitionvirtio-blk device
Data persistenceBtrfs partitionData disk file or virtiofs
Transient modegenpack.transient kernel parameterAutomatic when no data disk specified
system.iniOn FAT32 partitionVia virtiofs or fw_cfg
initramfs behaviorCommon (dracut-genpack)Common (dracut-genpack)
genpack-init behaviorCommonCommon

Shutdown

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:

  1. Unmount all mount points under /oldroot in reverse order
  2. Safely move and unmount /run/initramfs/rw (data partition) and /run/initramfs/boot (boot partition)
  3. Delete boottime.txt on the boot partition [1]
  4. Execute reboot(2) or poweroff
  1. boottime.txt is created at boot time. Since it is deleted during a clean shutdown, its presence at the next boot serves as evidence of an unclean shutdown (crash or power loss).
  2. d-<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).
  3. The systemimg method is not exclusive to physical hardware — it can also be used when running directly on QEMU without the vm frontend. This fallback allows baremetal profile images to boot in paravirtual environments as well. This is also why the baremetal profile exists as a specialization of systemimg.
  4. systemd uses the /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.
  5. In older versions, the upperdir used the redundant path 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.
  6. Extending the timeout after an unclean shutdown gives the operator time to select transient mode or MemTest86. Since boottime.txt is deleted during a clean shutdown, normal boots use a short 1-second timeout for automatic boot.
  7. This mechanism allows image-specific boot customization, such as special kernel parameters or splash screen display. BOOT_PARTITION and BOOT_PARTITION_UUID are exported, so the delegated grub.cfg can reference boot partition information.
  8. Since GRUB cannot natively parse INI file format, settings needed at the bootloader stage are placed in the GRUB-script-format system.cfg, while system settings needed at the genpack-init stage use the INI-format system.ini — a two-layer structure born of necessity.
  9. An undocumented feature for coexistence with non-genpack systems. If the data partition contains its own grub.cfg or kernel/initramfs, a boot from there is attempted. Success is not guaranteed.
  10. BIOS boot assumes an MBR partition layout. For GPT disks, EFI boot is used instead.

Source References

This document was created based on snapshots of the following repositories:

Update History