Apply workarounds

Currently GRUB has multiple compatibility problems with ZFS, especially with regards to newer ZFS features. Workarounds have to be applied.

  1. grub2-probe fails to get canonical path

    When persistent device names /dev/disk/by-id/* are used with ZFS, GRUB will fail to resolve the path of the boot pool device. Error:

    # /usr/bin/grub2-probe: error: failed to get canonical path of `/dev/virtio-pci-0000:06:00.0-part3'.


    echo 'export ZPOOL_VDEV_NAME_PATH=YES' >> /etc/profile.d/
    source /etc/profile.d/
  2. Pool name missing

    See this bug report. Root pool name is missing from root=ZFS=rpool_$INST_UUID/ROOT/default kernel cmdline in generated grub.cfg file.

    A workaround is to replace the pool name detection with zdb command:

    sed -i "s|rpool=.*|rpool=\`zdb -l \${GRUB_DEVICE} \| grep -E '[[:blank:]]name' \| cut -d\\\' -f 2\`|"  /etc/grub.d/10_linux

Install GRUB

  1. If using virtio disk, add driver to initrd:

    echo 'filesystems+=" virtio_blk "' >> /etc/dracut.conf.d/fs.conf
  2. Generate initrd:

    rm -f /etc/zfs/zpool.cache
    touch /etc/zfs/zpool.cache
    chmod a-w /etc/zfs/zpool.cache
    chattr +i /etc/zfs/zpool.cache
    for directory in /lib/modules/*; do
      kernel_version=$(basename $directory)
      dracut --force --kver $kernel_version
  3. Load ZFS modules and disable BLS:

    echo 'GRUB_ENABLE_BLSCFG=false' >> /etc/default/grub
  4. Create GRUB boot directory, in ESP and boot pool:

    mkdir -p /boot/efi/EFI/rocky        # EFI GRUB dir
    mkdir -p /boot/efi/EFI/rocky/grub2  # legacy GRUB dir
    mkdir -p /boot/grub2

    Boot environment-specific configuration (kernel, etc) is stored in /boot/grub2/grub.cfg, enabling rollback.

  5. When in doubt, install both legacy boot and EFI.

  6. If using legacy booting, install GRUB to every disk:

    for i in ${DISK}; do
     grub2-install --boot-directory /boot/efi/EFI/rocky --target=i386-pc $i
  7. If using EFI:

    for i in ${DISK}; do
     efibootmgr -cgp 1 -l "\EFI\rocky\shimx64.efi" \
     -L "rocky-${i##*/}" -d ${i}
    cp -r /usr/lib/grub/x86_64-efi/ /boot/efi/EFI/rocky
  8. Generate GRUB Menu:

    Apply workaround:

    tee /etc/grub.d/09_fix_root_on_zfs <<EOF
    echo 'insmod zfs'
    echo 'set root=(hd0,gpt2)'
    chmod +x /etc/grub.d/09_fix_root_on_zfs

    Generate menu:

    grub2-mkconfig -o /boot/efi/EFI/rocky/grub.cfg
    cp /boot/efi/EFI/rocky/grub.cfg /boot/efi/EFI/rocky/grub2/grub.cfg
    cp /boot/efi/EFI/rocky/grub.cfg /boot/grub2/grub.cfg

    The following errors may be safely ignored:

    • device-mapper: reload ioctl on osprober-linux-sda2 (253:0) failed: Device or resource busy This is caused by os-prober probing OS on the partitions used by ZFS, harmless but os-prober can be disabled by:

      echo GRUB_DISABLE_OS_PROBER=true >> /etc/default/grub
    • /usr/sbin/grub2-probe: error: ../grub-core/kern/fs.c:120:unknown filesystem. This is fixed by /etc/grub.d/09_fix_root_on_zfs

  9. For both legacy and EFI booting: mirror ESP content:

    ESP_MIRROR=$(mktemp -d)
    unalias -a
    cp -r /boot/efi/EFI $ESP_MIRROR
    for i in /boot/efis/*; do
     cp -r $ESP_MIRROR/EFI $i
  10. Automatically regenerate GRUB menu on kernel update:

    tee /etc/dnf/plugins/post-transaction-actions.d/00-update-grub-menu-for-kernel.action <<EOF >/dev/null
    # kernel-core package contains vmlinuz and initramfs
    # change package name if non-standard kernel is used
    tee /usr/local/sbin/ <<-'EOF' >/dev/null
    export PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    source /etc/os-release
    grub2-mkconfig -o /boot/efi/EFI/${ID}/grub.cfg
    cp /boot/efi/EFI/${ID}/grub.cfg /boot/efi/EFI/${ID}/grub2/grub.cfg
    cp /boot/efi/EFI/${ID}/grub.cfg /boot/grub2/grub.cfg
    ESP_MIRROR=$(mktemp -d)
    cp -r /boot/efi/EFI $ESP_MIRROR
    for i in /boot/efis/*; do
     cp -r $ESP_MIRROR/EFI $i
    rm -rf $ESP_MIRROR
    chmod +x /usr/local/sbin/
  11. Notes for GRUB on RHEL

    To support Secure Boot, GRUB has been heavily modified by Fedora, namely:

    • grub2-install is disabled for UEFI

    • Only a static, signed version of bootloader is copied to EFI system partition

    • This signed bootloader does not have built-in support for either ZFS or LUKS containers

    • This signed bootloader only loads configuration from /boot/efi/EFI/fedora/grub.cfg

    Unrelated to Secure Boot, GRUB has also been modified to provide optional support for systemd bootloader specification (bls). Currently blscfg.mod is incompatible with root on ZFS.

    As bls is disabled, you will need to regenerate GRUB menu after each kernel upgrade. Or else the new kernel will not be recognized and system will boot the old kernel on reboot.

    Also see Fedora docs for GRUB.

Finish Installation

  1. Exit chroot:

  2. Take a snapshot of the clean installation for future use:

    zfs snapshot -r rpool_$INST_UUID/$INST_ID@install
    zfs snapshot -r bpool_$INST_UUID/$INST_ID@install
  3. Unmount EFI system partition:

    umount /mnt/boot/efi
    umount /mnt/boot/efis/*
  4. Export pools:

    zpool export bpool_$INST_UUID
    zpool export rpool_$INST_UUID
  5. Reboot:


Post installaion

  1. If you have other data pools, generate list of datasets for zfs-mount-generator to mount them at boot:

    DATA_POOL='tank0 tank1'
    # tab-separated zfs properties
    # see /etc/zfs/zed.d/
    export \
    for i in $DATA_POOL; do
    zfs list -H -t filesystem -o $PROPS -r $i > /etc/zfs/zfs-list.cache/$i
  2. After reboot, consider adding a normal user:

    zfs create $(df --output=source /home | tail -n +2)/${myUser}
    useradd -MUd /home/${myUser} -c 'My Name' ${myUser}
    zfs allow -u ${myUser} mount,snapshot,destroy $(df --output=source /home | tail -n +2)/${myUser}
    chown -R ${myUser}:${myUser} /home/${myUser}
    chmod 700 /home/${myUser}
    restorecon /home/${myUser}
    passwd ${myUser}

    Set up cron job to snapshot user home everyday:

    dnf install cronie
    systemctl enable --now crond
    crontab -eu ${myUser}
    #@daily zfs snap $(df --output=source /home/${myUser} | tail -n +2)@$(dd if=/dev/urandom of=/dev/stdout bs=1 count=100 2>/dev/null |tr -dc 'a-z0-9' | cut -c-6)
    zfs list -t snapshot -S creation $(df --output=source /home/${myUser} | tail -n +2)

    Install package groups:

    dnf group list --hidden -v       # query package groups
    dnf group install 'Virtualization Host'