Rocky Linux Root on ZFS

ZFSBootMenu

ZFSBootMenu is an alternative bootloader free of such limitations and has support for boot environments. Do not follow instructions on this page if you plan to use ZBM, as the layouts are not compatible. Refer to their site for installation details.

Customization

Unless stated otherwise, it is not recommended to customize system configuration before reboot.

Only use well-tested pool features

You should only use well-tested pool features. Avoid using new features if data integrity is paramount. See, for example, this comment.

UEFI support only

Only UEFI is supported by this guide.

Preparation

  1. Disable Secure Boot. ZFS modules can not be loaded if Secure Boot is enabled.

  2. Because the kernel of latest Live CD might be incompatible with ZFS, we will use Alpine Linux Extended, which ships with ZFS by default.

    Download latest extended variant of Alpine Linux live image, verify checksum and boot from it.

    gpg --auto-key-retrieve --keyserver hkps://keyserver.ubuntu.com --verify alpine-extended-*.asc
    
    dd if=input-file of=output-file bs=1M
    
  3. Login as root user. There is no password.

  4. Configure Internet

    setup-interfaces -r
    # You must use "-r" option to start networking services properly
    # example:
    network interface: wlan0
    WiFi name:         <ssid>
    ip address:        dhcp
    <enter done to finish network config>
    manual netconfig:  n
    
  5. If you are using wireless network and it is not shown, see Alpine Linux wiki for further details. wpa_supplicant can be installed with apk add wpa_supplicant without internet connection.

  6. Configure SSH server

    setup-sshd
    # example:
    ssh server:        openssh
    allow root:        "prohibit-password" or "yes"
    ssh key:           "none" or "<public key>"
    
  7. Set root password or /root/.ssh/authorized_keys.

  8. Connect from another computer

    ssh root@192.168.1.91
    
  9. Configure NTP client for time synchronization

    setup-ntp busybox
    
  10. Set up apk-repo. A list of available mirrors is shown. Press space bar to continue

    setup-apkrepos
    
  11. Throughout this guide, we use predictable disk names generated by udev

    apk update
    apk add eudev
    setup-devd udev
    
  12. Target disk

    List available disks with

    find /dev/disk/by-id/
    

    If virtio is used as disk bus, power off the VM and set serial numbers for disk. For QEMU, use -drive format=raw,file=disk2.img,serial=AaBb. For libvirt, edit domain XML. See this page for examples.

    Declare disk array

    DISK='/dev/disk/by-id/ata-FOO /dev/disk/by-id/nvme-BAR'
    

    For single disk installation, use

    DISK='/dev/disk/by-id/disk1'
    
  13. Set a mount point

    MNT=$(mktemp -d)
    
  14. Set partition size:

    Set swap size in GB, set to 1 if you don’t want swap to take up too much space

    SWAPSIZE=4
    

    Set how much space should be left at the end of the disk, minimum 1GB

    RESERVE=1
    
  15. Install ZFS support from live media:

    apk add zfs
    
  16. Install partition tool

    apk add parted e2fsprogs cryptsetup util-linux
    

System Installation

  1. Partition the disks.

    Note: you must clear all existing partition tables and data structures from target disks.

    For flash-based storage, this can be done by the blkdiscard command below:

    partition_disk () {
     local disk="${1}"
     blkdiscard -f "${disk}" || true
    
     parted --script --align=optimal  "${disk}" -- \
     mklabel gpt \
     mkpart EFI 1MiB 4GiB \
     mkpart rpool 4GiB -$((SWAPSIZE + RESERVE))GiB \
     mkpart swap  -$((SWAPSIZE + RESERVE))GiB -"${RESERVE}"GiB \
     set 1 esp on \
    
     partprobe "${disk}"
    }
    
    for i in ${DISK}; do
       partition_disk "${i}"
    done
    
  2. Setup temporary encrypted swap for this installation only. This is useful if the available memory is small:

    for i in ${DISK}; do
       cryptsetup open --type plain --key-file /dev/random "${i}"-part3 "${i##*/}"-part3
       mkswap /dev/mapper/"${i##*/}"-part3
       swapon /dev/mapper/"${i##*/}"-part3
    done
    
  3. Load ZFS kernel module

    modprobe zfs
    
  4. Create root pool

    • Unencrypted:

      # shellcheck disable=SC2046
      zpool create \
          -o ashift=12 \
          -o autotrim=on \
          -R "${MNT}" \
          -O acltype=posixacl \
          -O canmount=off \
          -O dnodesize=auto \
          -O normalization=formD \
          -O relatime=on \
          -O xattr=sa \
          -O mountpoint=none \
          rpool \
          mirror \
         $(for i in ${DISK}; do
            printf '%s ' "${i}-part2";
           done)
      
  5. Create root system container:

    # dracut demands system root dataset to have non-legacy mountpoint
    zfs create -o canmount=noauto -o mountpoint=/ rpool/root
    

    Create system datasets, manage mountpoints with mountpoint=legacy

    zfs create -o mountpoint=legacy rpool/home
    zfs mount rpool/root
    mount -o X-mount.mkdir -t zfs rpool/home "${MNT}"/home
    
  6. Format and mount ESP. Only one of them is used as /boot, you need to set up mirroring afterwards

    for i in ${DISK}; do
     mkfs.vfat -n EFI "${i}"-part1
    done
    
    for i in ${DISK}; do
     mount -t vfat -o fmask=0077,dmask=0077,iocharset=iso8859-1,X-mount.mkdir "${i}"-part1 "${MNT}"/boot
     break
    done
    

System Configuration

  1. Download and extract minimal Rhel root filesystem:

    apk add curl
    curl --fail-early --fail -L \
    https://dl.rockylinux.org/vault/rocky/9.2/images/x86_64/Rocky-9-Container-Base-9.2-20230513.0.x86_64.tar.xz \
    -o rootfs.tar.gz
    curl --fail-early --fail -L \
    https://dl.rockylinux.org/vault/rocky/9.2/images/x86_64/Rocky-9-Container-Base-9.2-20230513.0.x86_64.tar.xz.CHECKSUM \
    -o checksum
    
    # BusyBox sha256sum treats all lines in the checksum file
    # as checksums and requires two spaces "  "
    # between filename and checksum
    
    grep 'Container-Base' checksum \
    | grep '^SHA256' \
    | sed -E 's|.*= ([a-z0-9]*)$|\1  rootfs.tar.gz|' > ./sha256checksum
    
    sha256sum -c ./sha256checksum
    
    tar x  -C "${MNT}" -af rootfs.tar.gz
    
  2. Enable community repo

    sed -i '/edge/d' /etc/apk/repositories
    sed -i -E 's/#(.*)community/\1community/' /etc/apk/repositories
    
  3. Generate fstab:

    apk add arch-install-scripts
    genfstab -t PARTUUID "${MNT}" \
    | grep -v swap \
    | sed "s|vfat.*rw|vfat rw,x-systemd.idle-timeout=1min,x-systemd.automount,noauto,nofail|" \
    > "${MNT}"/etc/fstab
    
  4. Chroot

    cp /etc/resolv.conf "${MNT}"/etc/resolv.conf
    for i in /dev /proc /sys; do mkdir -p "${MNT}"/"${i}"; mount --rbind "${i}" "${MNT}"/"${i}"; done
    chroot "${MNT}" /usr/bin/env DISK="${DISK}" bash
    
  5. Unset all shell aliases, which can interfere with installation:

    unalias -a
    
  6. Install base packages

    dnf -y install --allowerasing @core kernel-core kernel-modules
    
  7. Install ZFS packages:

    dnf install -y https://zfsonlinux.org/epel/zfs-release-2-3"$(rpm --eval "%{dist}"|| true)".noarch.rpm
    dnf config-manager --disable zfs
    dnf config-manager --enable zfs-kmod
    dnf install -y zfs zfs-dracut
    
  8. Add zfs modules to dracut:

    echo 'add_dracutmodules+=" zfs "' >> /etc/dracut.conf.d/zfs.conf
    echo 'force_drivers+=" zfs "' >> /etc/dracut.conf.d/zfs.conf
    
  9. Add other drivers to dracut:

    if grep mpt3sas /proc/modules; then
      echo 'force_drivers+=" mpt3sas "'  >> /etc/dracut.conf.d/zfs.conf
    fi
    if grep virtio_blk /proc/modules; then
      echo 'filesystems+=" virtio_blk "' >> /etc/dracut.conf.d/fs.conf
    fi
    
  10. Build initrd:

    find -D exec /lib/modules -maxdepth 1 \
    -mindepth 1 -type d \
    -exec sh -vxc \
    'if test -e "$1"/modules.dep;
       then kernel=$(basename "$1");
       dracut --verbose --force --kver "${kernel}";
     fi' sh {} \;
    
  11. For SELinux, relabel filesystem on reboot:

    fixfiles -F onboot
    
  12. Generate host id:

    zgenhostid -f -o /etc/hostid
    
  13. Install locale package, example for English locale:

    dnf install -y glibc-minimal-langpack glibc-langpack-en
    
  14. Set locale, keymap, timezone, hostname

    rm -f /etc/localtime
    systemd-firstboot \
    --force \
    --locale=en_US.UTF-8 \
    --timezone=Etc/UTC \
    --hostname=testhost \
    --keymap=us
    
  15. Set root passwd

    printf 'root:yourpassword' | chpasswd
    

Bootloader

  1. Install rEFInd boot loader:

    # from http://www.rodsbooks.com/refind/getting.html
    # use Binary Zip File option
    curl -L https://sourceforge.net/projects/refind/files/0.14.0.2/refind-bin-0.14.0.2.zip/download --output refind.zip
    
    dnf install -y unzip
    unzip refind.zip
    mkdir -p /boot/EFI/BOOT
    find ./refind-bin-0.14.0.2/ -name 'refind_x64.efi' -print0 \
    | xargs -0I{} mv {} /boot/EFI/BOOT/BOOTX64.EFI
    rm -rf refind.zip refind-bin-0.14.0.2
    
  2. Add boot entry:

    tee -a /boot/refind-linux.conf <<EOF
    "Rocky Linux" "root=ZFS=rpool/root"
    EOF
    
  3. Exit chroot

    exit
    
  4. Unmount filesystems and create initial system snapshot You can later create a boot environment from this snapshot. See Root on ZFS maintenance page.

    umount -Rl "${MNT}"
    zfs snapshot -r rpool@initial-installation
    
  5. Export all pools

    zpool export -a
    
  6. Reboot

    reboot
    

Post installaion

  1. Install package groups

    dnf group list --hidden -v       # query package groups
    dnf group install gnome-desktop
    
  2. Add new user, configure swap.

  3. Mount other EFI system partitions then set up a service for syncing their contents.