Install Arch Linux on ZFS on LUKS

The goal of this guide is to show the necessary steps to install Arch Linux using the ZFS file system on top of an encrypted LUKS container.

Prerequisites

Before the installation on the target machine can take place, some prerequisites need to be established.

Custom Install ISO

In order to be able to create and mount volumes with ZFS, a custom installation medium (ISO) is required, since the official image and repositories do not contain the necessary packages.

The complete instructions can be found in the official Arch Linux wiki, but these are the main steps.

Installation of Tooling

sudo pacman -S archiso git

Copy Profile (Template)

The archiso package installed above provides a template (Arch Linux calls this a profile) which will later be turned into an ISO image that can be modified to create a custom installation medium.

cd ~/
cp -r /usr/share/archiso/configs/releng/ archlive

Build ZFS Packages

Since the requried packages are not (yet) available from the official repositories, the AUR packages needs to be built manually. Afterwards, they will be copied to the ISO template in a future step.

cd ~/temp
git clone https://aur.archlinux.org/zfs-utils.git
git clone https://aur.archlinux.org/zfs-dkms.git
cd ~/temp/zfs-utils
makepkg -s
cd ~/temp/zfs-dkms
makepkg -s

Modify pacman.conf

In order for pacman to find the just compiled AUR packages, a custom repository that is stored locally needs to be created.

cd ~/temp
cp zfs-utils/zfs-utils*.pkg.tar.zst .
cp zfs-dkms/zfs-dkms*.pkg.tar.zst .
repo-add custom.db.tar.gz zfs-utils*.pkg.tar.zst zfs-dkms*.pkg.tar.zst

The custom repository now needs to be referenced in the template's pacman.conf.

cat >> ~/archlive/pacman.conf << EOF
[custom]
SigLevel = Optional TrustAll
Server = file://$HOME/temp
EOF

Add AUR Packages to List of Packages

During the build of the installation ISO, pacman will install a list of packages to the built image. This list is stored in the template's package.x86_64 file. To this file the two manually built packages need to be added.

cat >> packages.x86_64 << EOF
zfs-utils
zfs-dkms
linux-headers
EOF

Note: linux-headers is required for using dkms.

Build the Installation ISO Image

Finally the installation ISO image can be built using the following command. This might take some time.

cd ~/archlive
sudo mkarchiso -v .

After the image has been built, the ISO image can be found in the ~/archlive/out directory.

Flash the ISO to a Thumbdrive

This is the same procedure like with a standard Arch install ISO.

sudo dd if=~/archlive/iso/archlinux-*.iso \
	of=/dev/sdx bs=8M oflag=sync status=progress

After the image has been flashed, the target machine can be booted from the thumbdrive.

System Installation

Most of the installation is the same like a standard Arch Linux install, so this section will focus only a few steps.

Partitioning

The goal is create a partitioning that looks something like this:

PARTITION           TYPE   MOUNT   COMMENT
sda                 disk           GPT schema
├─sda1              part   /boot   Type: EFI, FAT32 EFI partition, Size: 100M
└─sda2              part           Type: Linux Filesystem
  └─cryptoroot      crypt          LUKS container, GPT schema
    ├─cryptoroot1   part   [SWAP]  Type: Linux Swap, Simple swap partition (mkswap)
    └─cryptoroot2   part           Type: Solaris Root, Partition that holds ZFS pool

Setting up Partitions

Formatting the EFI Boot Volume

mkfs.fat -F32 /dev/sda1

Creating the LUKS Container

cryptsetup luksFormat /dev/sda2
cryptsetup open /dev/sda2 cryptoroot

Partitioning Inside LUKS Container

Two partitions needs to be created, a smaller swap partition (preferrably slightly larger than the system's RAM, so hibernation will work) and a partition that fills the rest of the container.

Setting up ZFS

First a cache file for the zpool needs to be created.

touch /etc/zfs/zpool.cache

Creating ZPool

Next, the pool can be created on the second partition inside the LUKS container.

zpool create \
	-o cachefile=/etc/zfs/zpool.cache \
	-o ashift=12 \
	-m none \
	-R /mnt \
	zroot \
	/dev/mapper/cryptoroot2

Creating Datasets Inside ZPool

After the pool has been created the datasets that hold the actual files can be created.

zfs create -o mountpoint=none -o compression=lz4 zroot/ROOT
zfs create -o mountpoint=/ zroot/ROOT/default
zfs create -o mountpoint=/home zroot/home
zfs create -o mountpoint=/root zroot/home/root

zpool set bootfs=zroot zroot

Afterwards, the pool needs to be reimported.

zpool export zroot
zpool import -R /mnt zroot

Mounting Boot Partition

Importing the ZFS pool already took care of mounting all the datasets, however the boot partition needs to be mounted manually as it is stored outside of the pool.

mkdir /mnt/boot
mount /dev/sda1 /mnt/boot

Installation of Base System

This step is again very similar to a standard Arch Linux install, so these are the basic steps.

pacstrap -i /mnt base base-devel linux linux-firmware linux-headers bash vim \
	grub efibootmgr git sudo mkinitcpio parted networkmanager wpa_supplicant \
	# etc.

Generate fstab Entries

genfstab -U -p /mnt | grep boot >> /mnt/etc/fstab
genfstab -U -p /mnt | grep swap >> /mnt/etc/fstab

Chroot Into new Installation

arch-chroot /mnt /bin/bash

Create Personal User

useradd john
mkdir /home/john
chown john:john /home/john

To make sure the personal user is able to administrate the system, they need to be added to the wheel group and the group needs to be added to the whitelisted groups.

usermod -aG wheel john
sed -i '/%wheel ALL=(ALL) ALL/ s/# //' /etc/sudoers

Install AUR Package Manager

In order to install the two AUR packages and keep them up-to-date, an AUR package manager needs to be installed. The current most-popular choice is yay.

As the personal user:

git clone https://aur.archlinux.org/yay.git
cd yay
makepkg -si

Install ZFS Packages From AUR

After the AUR package manager has been install, the two ZFS packages can be installed with it.

yay -S zfs-utils zfs-dkms

Enable ZFS Services

Using ZFS requires little administrative effort, as most things are handled by systemd services. These need to be enabled now.

systemctl enable zfs.target
systemctl enable zfs-import-cache
systemctl enable zfs-mount
systemctl enable zfs-import.target

Create Bootloader Hook

Since the bootloader only probes once for partitions (before the LUKS container has been opened) a hook needs to be created that probes for partitions again after the container has been opened.

cat > /etc/initcpio/install/load_part << EOFHOOK
#!/bin/bash

build() {
        add_binary 'partprobe'
        add_runscript
}

help() {
        cat << HELPEOF
Probes mapped LUKS container for partitions.
HELPEOF
}
EOFHOOK
cat > /etc/initcpio/hooks/load_part << EOFHOOK
run_hook() {
        partprobe /dev/mapper/cryptoroot
}
EOFHOOK

Set up mkinitcpio

The HOOKS value in the /etc/mkinitcpio.conf file needs to be updated to contain the encrypt hook after the keyboard hook. After which the custom load_part hook created above can be placed, followed by zfs and filesystems. The final line should look similar to the following.

HOOKS="base udev autodetect modconf block keyboard encrypt load_part resume zfs filesystems"

After the file has been updated, mkinitcpio needs to be run to recreate the initramfs.

mkinitcpio -p linux

Install Bootloader

The setup of GRUB is very similar to a standard installation. In order for the LUKS container to be decrypted and the ZFS pool to be imported on boot, the kernel commandline needs to be changed to contain the following.

cryptdevice=/dev/disk/by-uuid/<uuid>:cryptoroot zfs=zroot/ROOT/default rw resume=UUID=<swap UUID>

The UUIDs can be retrieved using blkid. The first UUID is the id of /dev/sda2 while the swap UUID is the ID of /dev/mapper/cryptoroot1.

Afterwards the bootloader can be installed with the following commands.

grub-install --target=x86_64-efi --efi-directory=/boot
ZPOOL_VDEV_NAME_PATH=1 grub-mkconfig -o /boot/grub/grub.cfg