Installing Void Linux with encrypted Root on ZFS

Published on 03-01-2024 ( Updated 20-04-2024 )

This guide is how I install Void Linux onto a single disk with with encrypted Root on ZFS and a seperate encrypted swap partition.

It assumes the following:

Your system uses UEFI to boot

Your system is x86_64

You will use glibc as your system libc.

You’re mildly comfortable with ZFS, EFI and discovering system facts on your own (lsblk, dmesg, gdisk, …)

ZFSBootMenu does not require glibc and is not restricted to x86_64. If you are comfortable installing Void Linux on other architectures or with the musl libc, you can adapt the instructions here to your desired configuration.

Please note that I have used information from the sources listed below:

ZFS BootMenu Docs https://docs.zfsbootmenu.org/en/v2.3.x/guides/void-linux/uefi.html

Void Linux ZFS Docs https://docs.voidlinux.org/installation/guides/zfs.html

Chain’s computar projects
https://forum.level1techs.com/t/chains-computar-projects/190001

Daniel Wayne Armstrong Blog
https://www.dwarmstrong.org/encrypt-swap/

To start

Download the latest hrmpf image from:
https://github.com/leahneukirchen/hrmpf

Write it to USB drive and boot your system in EFI mode.

Confirm EFI support:

# dmesg | grep -i efivars
[    0.301784] Registered efivars operations

Configure Live Environment

Source /etc/os-release

The file /etc/os-release defines variables that describe the running distribution. In particular, the $ID variable defined within can be used as a short name for the filesystem that will hold this installation.

source /etc/os-release
export ID

Generate /etc/hostid

zgenhostid -f 0x00bab10c

Disk preparation

Verify your target disk devices with lsblk. /dev/sda, /dev/sdb and /dev/nvme0n1.
On my laptop I,ll be using a single drive on /dev/nvme0n1 with three paritions as follows:

1 - EFI boot 2 - Swap ( I’m using 64Gb but you can adjust to suit your needs or just not create swap and number the zpool parition 2 ) 3 - Zpool ( This partition uses the remaining disk space )

Note you will need to adjust below when wiping partitions if you use seperate disks.

Wipe partitions

zpool labelclear -f "/dev/nvme0n1"

wipefs -a "/dev/nvme0n1"

sgdisk --zap-all "/dev/nvme0n1"

Create EFI boot partition

sgdisk -n "1:1m:+512m" -t "1:ef00" "/dev/nvme0n1"

Create swap partition

sgdisk -n "2::64Gb" -t "2:8200" "/dev/nvme0n1" -c "2:cryptswap" "/dev/nvme0n1"

Create zpool partition

sgdisk -n "3:0:-10m" -t "3:bf00" "/dev/nvme0n1"

ZFS pool creation

Store the pool passphrase in a key file

echo 'SomeKeyphrase' > /etc/zfs/zroot.key
chmod 000 /etc/zfs/zroot.key

Create the encrypted zpool

zpool create -f -o ashift=12 \
 -O compression=lz4 \
 -O acltype=posixacl \
 -O xattr=sa \
 -O relatime=on \
 -O encryption=aes-256-gcm \
 -O keylocation=file:///etc/zfs/zroot.key \
 -O keyformat=passphrase \
 -o autotrim=on \
 -m none zroot "/dev/disk/by-id/wwn-0x5000c500deadbeef-part3"

Adjust the pool (-o) and filesystem (-O) options as desired, and replace the partition identifier wwn-0x5000c500deadbeef-part3 with that of the actual partition to be used. You can find this out by typing ls /dev/disk/by-id/ and they will be listed in the output.

When adding disks or partitions to ZFS pools, it is generally advisable to refer to them by the symbolic links created in /dev/disk/by-id or (on UEFI systems) /dev/disk/by-partuuid so that ZFS will identify the right partitions even if disk naming should change at some point. Using traditional device nodes like /dev/sda3 may cause intermittent import failures. ## Create initial file systems

zfs create -o mountpoint=none zroot/ROOT
zfs create -o mountpoint=/ -o canmount=noauto zroot/ROOT/${ID}
zfs create -o mountpoint=/home zroot/home

zpool set bootfs=zroot/ROOT/${ID} zroot

Note It is important to set the property canmount=noauto on any file systems with mountpoint=/ (that is, on any additional boot environments you create). Without this property, the OS will attempt to automount all ZFS file systems and fail when multiple file systems attempt to mount at /; this will prevent your system from booting. Automatic mounting of / is not required because the root file system is explicitly mounted in the boot process. Also note that, unlike many ZFS properties, canmount is not inheritable. Therefore, setting canmount=noauto on zroot/ROOT is not sufficient, as any subsequent boot environments you create will default to canmount=on. It is necessary to explicitly set the canmount=noauto on every boot environment you create.

Export, then re-import with a temporary mountpoint of /mnt

zpool export zroot
zpool import -N -R /mnt zroot
zfs load-key -L prompt zroot
zfs mount zroot/ROOT/${ID}
zfs mount zroot/home

Verify that everything is mounted correctly

# mount | grep mnt
zroot/ROOT/void on /mnt type zfs (rw,relatime,xattr,posixacl)
zroot/home on /mnt/home type zfs (rw,relatime,xattr,posixacl)
udevadm trigger

Install Void

Adjust the mirror, libc, and package selection as you see fit.

XBPS_ARCH=x86_64 xbps-install \
  -S -R https://mirrors.servercentral.com/voidlinux/current \
  -r /mnt base-system

Copy our files into the new install

cp /etc/hostid /mnt/etc
mkdir /mnt/etc/zfs
cp /etc/zfs/zroot.key /mnt/etc/zfs

Chroot into the new OS

xchroot /mnt

Basic Void configuration

Set the keymap, timezone and hardware clock

cat << EOF >> /etc/rc.conf
KEYMAP="us"
HARDWARECLOCK="UTC"
TIMEZONE="Europe/London"
EOF

Configure your glibc locale

cat << EOF >> /etc/default/libc-locales
en_US.UTF-8 UTF-8
en_US ISO-8859-1
EOF
xbps-reconfigure -f glibc-locales

Set a root password

passwd

ZFS Configuration

Configure Dracut to load ZFS support

cat << EOF > /etc/dracut.conf.d/zol.conf
nofsck="yes"
add_dracutmodules+=" zfs "
omit_dracutmodules+=" btrfs resume "
install_items+=" /etc/zfs/zroot.key "
EOF

Install ZFS and Cryptsetup

xbps-install -S zfs cryptsetup

To quickly discover and import pools on boot, we need to set a pool cachefile

zpool set cachefile=/etc/zfs/zpool.cache zroot

Install and configure ZFSBootMenu

Set ZFSBootMenu properties on datasets

Assign command-line arguments to be used when booting the final kernel. Because ZFS properties are inherited, assign the common properties to the ROOT dataset so all children will inherit common arguments by default.

zfs set org.zfsbootmenu:commandline="quiet loglevel=4" zroot/ROOT

Setup key caching in ZFSBootMenu.

zfs set org.zfsbootmenu:keysource="zroot/ROOT/${ID}" zroot

Create vfat and encrypted swap filesystems

mkfs.vfat -F32 /dev/nvme0n1p1
cat << EOF >> /etc/crypttab
cryptswap /dev/disk/by-partlabel/cryptswap /dev/urandom swap,offset=2048,cipher=aes-xts-plain64,size=512
EOF

Create fstab entries and mount the efi partition

cat << EOF >> /etc/fstab
$( blkid | grep /dev/nvme0n1p1 | cut -d ' ' -f 2 ) /boot/efi vfat defaults 0 0
/dev/mapper/cryptswap none swap defaults 0 0
EOF
mkdir -p /boot/efi
mount /boot/efi

Install ZFSBootMenu

xbps-install -S zfsbootmenu systemd-boot-efistub

Configure generate-zbm(5) by ensuring that the following keys appear in /etc/zfsbootmenu/config.yaml:

Global:
  ManageImages: true
  BootMountPoint: /boot/efi
Components:
   Enabled: false
EFI:
  ImageDir: /boot/efi/EFI/zbm
  Versions: false
  Enabled: true
Kernel:
  CommandLine: quiet loglevel=0 

My CommandLine: will be as follows as I don’t want ZFSBootMenu automatically resizing the fonts according to the display size.

CommandLine: quiet loglevel=0 zbm.skip_hooks=20-console-autosize.sh

Create a ZFSBootMenu image:

generate-zbm

Configure EFI boot entries

xbps-install efibootmgr
efibootmgr -c -d "/dev/nvme0n1" -p "1" \
  -L "ZFSBootMenu (Backup)" \
  -l '\EFI\ZBM\VMLINUZ-BACKUP.EFI'

efibootmgr -c -d "/dev/nvme0n1" -p "1" \
  -L "ZFSBootMenu" \
  -l '\EFI\ZBM\VMLINUZ.EFI'

Prepare for first boot

Exit the chroot, unmount everything

exit
umount -n -R /mnt

Export the zpool and reboot

zpool export zroot
reboot