Installing Artix Linux on ZFS with LUKS encrypted boot partition

This guide will focus on Artix with runit init system on an MBR partition drive, but it should be easy to adapt to other supported init systems too.

Build Artix installation image with ZFS support

Add archzfs repo to pacman.conf

tee -a /etc/pacman.conf << 'EOF' [archzfs] SigLevel = Optional TrustAll Server = https://archzfs.com/$repo/$arch Server = https://mirror.sum7.eu/archlinux/archzfs/$repo/$arch Server = https://mirror.biocrafting.net/archlinux/archzfs/$repo/$arch Server = https://mirror.in.themindsmaze.com/archzfs/$repo/$arch EOF

Set environment variables

INST_TZ=/usr/share/zoneinfo/CET INST_HOST='put-your-hostname-here'

Choose which kernel you want to install: INST_LINVAR='linux'

List available disks with
ls -d /dev/disk/by-id/* | grep -v part

Select the target disk
DISK=/dev/disk/by-id/nvme-foo_NVMe_bar_512GB

Set a mountpoint
INST_MNT=$(mktemp -d)
or just use
INST_MNT=/mnt

To avoid name conflict when importing pools on another computer, Give them a unique suffix
INST_UUID=$(dd if=/dev/urandom of=/dev/stdout bs=1 count=100 2>/dev/null \ | tr -dc 'a-z0-9' \ | cut -c-6)

System installation

Partitioning

At this point you can overwrite the disk with random data for best privacy. Partition the disk into a boot partition and a root partition. Boot partition should be 1-2GiB in size, and you can use the rest for the root partition. Mark the boot partition as ef and the root partition as bf. From now on, I'll assume that your boot partition is ${DISK}1 or ${DISK}-part1, and the root partition is ${DISK}2 or ${DISK}-part2, depending on how you chose your disk variable.

Set the partition variables:
DISK_BOOT=${DISK}1 DISK_ROOT=${DISK}2

You need to mark the boot partition as boot and lvm (assuming the boot partition is the first partition on the disk):
parted -s $DISK set 1 boot on parted -s $DISK set 1 lvm on

Set up the boot partition

To make sure that the right kernel modules are loaded, you can run:
cryptsetup benchmark

When that completes, run the following command to create and format the LUKS partition (feel free to change the encryption parameters):
cryptsetup --verbose \ --type luks1 \ --cipher aes-xts-plain64 \ --key-size 512 \ --hash sha512 \ --iter-time 10000 \ --use-random \ --verify-passphrase \ luksFormat $DISK_BOOT

After that, open and mount the partition using the device mapper
cryptsetup luksOpen $DISK_BOOT lvm-boot

Now create a physical volume using the Logical Volume Manager (LVM) and the previously used id lvm-boot as follows:
pvcreate /dev/mapper/lvm-boot

Having the physical volume, it is possible to create a logical volume group named lvmBoot as follows:
vgcreate lvmBoot /dev/mapper/lvm-boot

And having the logical volume group, the logical volumes can be created as follows:
lvcreate --contiguous y --extents +100%FREE lvmBoot --name volBoot

Now format the boot partition:
mkfs.fat -n BOOT /dev/lvmBoot/volBoot

Set up the root partition

Create the encrypted root pool:
zpool create \ -o ashift=12 \ -O acltype=posixacl \ -O canmount=off \ -O compression=zstd \ -O dnodesize=auto \ -O normalization=formD \ -O relatime=on \ -O xattr=sa \ -O mountpoint=/ \ -R $INST_MNT \ -O encryption=aes-256-gcm \ -O keylocation=prompt \ -O keyformat=passphrase \ rpool_$INST_UUID \ $DISK_ROOT

Create container datasets:
zfs create -o canmount=off -o mountpoint=none rpool_$INST_UUID/ROOT zfs create -o canmount=off -o mountpoint=none rpool_$INST_UUID/DATA

Create root filesystem dataset:
zfs create -o mountpoint=/ -o canmount=noauto rpool_$INST_UUID/ROOT/default

Mount root filesystem dataset and boot partition:
zfs mount rpool_$INST_UUID/ROOT/default mkdir $INST_MNT/boot mount /dev/lvmBoot/volBoot $INST_MNT/boot

Create datasets to separate user data from root filesystem:
zfs create -o mountpoint=/ -o canmount=off rpool_$INST_UUID/DATA/default for i in {usr,var,var/lib}; do zfs create -o canmount=off rpool_$INST_UUID/DATA/default/$i done for i in {home,root,srv,usr/local,var/log,var/spool,var/tmp}; do zfs create -o canmount=on rpool_$INST_UUID/DATA/default/$i done chmod 750 $INST_MNT/root chmod 1777 $INST_MNT/var/tmp

Package installation

Install base packages
basestrap $INST_MNT base vim grub connman connman-runit runit elogind-runit rsm tee -a $INST_MNT/etc/pacman.conf << 'EOF' [archzfs] SigLevel = Optional TrustAll Server = https://archzfs.com/$repo/$arch Server = https://mirror.sum7.eu/archlinux/archzfs/$repo/$arch Server = https://mirror.biocrafting.net/archlinux/archzfs/$repo/$arch Server = https://mirror.in.themindsmaze.com/archzfs/$repo/$arch EOF basestrap $INST_MNT $INST_LINVAR ${INST_LINVAR}-headers basestrap $INST_MNT archzfs-dkms lvm2 cryptsetup basestrap $INST_MNT device-mapper-runit lvm2-runit cryptsetup-runit

If your computer has hardware that requires firmware to run
basestrap $INST_MNT linux-firmware

System configuration

Add boot partition to the fstab:
fstabgen -U $INST_MNT | grep "/boot" >> $INST_MNT/etc/fstab

Configure mkinitcpio:
mv $INST_MNT/etc/mkinitcpio.conf $INST_MNT/etc/mkinitcpio.conf.original tee $INST_MNT/etc/mkinitcpio.conf <<EOF HOOKS=(base udev autodetect modconf block encrypt keyboard lvm2 zfs filesystems \ fsck) EOF

Set the hostname:
echo $INST_HOST > $INST_MNT/etc/hostname

Set the time zone:
ln -sf $INST_TZ $INST_MNT/etc/localtime

Locale:
echo "en_US.UTF-8 UTF-8" >> $INST_MNT/etc/locale.gen echo "LANG=en_US.UTF-8" >> $INST_MNT/etc/locale.conf

Chroot:
artix-chroot $INST_MNT /usr/bin/env DISK=$DISK DISK_ROOT=$DISK_ROOT \ DISK_BOOT=$DISK_BOOT INST_UUID=$INST_UUID bash --login

Mount datasets on boot (sleep loop is there to prevent runit from constantly rerunning the service):
mkdir -p /etc/runit/sv/zfs-mount-all tee /etc/runit/sv/zfs-mount-all/run <<EOF #!/bin/sh zfs mount -a while true do sleep 10 done EOF chmod +x /etc/runit/sv/zfs-mount-all/run ln -s /etc/runit/sv/zfs-mount-all /etc/runit/runsvdir/default

Generate locales:
locale-gen

Generate zpool.cache:
zpool set cachefile=/etc/zfs/zpool.cache rpool_$INST_UUID

Set the root password:
passwd

Generate initramfs:
mkinitcpio -P

GRUB configuration

Change /etc/default/grub:
sed -i.bak "s|GRUB_CMDLINE_LINUX=.*|GRUB_CMDLINE_LINUX=\"cryptdevice=UUID=$(blkid -s UUID -o value $DISK_BOOT):lvm-boot loglevel=3 quiet net.ifnames=0\"|" /etc/default/grub echo 'GRUB_ENABLE_CRYPTODISK="y"' >> /etc/default/grub

If GRUB doesn't detect the root pool, you might need to do the following change:
sed -i "s|rpool=.*|rpool=\`zdb -l \${GRUB_DEVICE} \| grep -E '\\\\bname:' \| cut -d\\\' -f 2\`|" /etc/grub.d/10_linux

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/grub-probe: error: failed to get canonical path of `/dev/virtio-pci-0000:06:00.0-part3'.

The solution is:
echo 'export ZPOOL_VDEV_NAME_PATH=YES' >> /etc/profile source /etc/profile

Grub installation

Install GRUB to the disk:
grub-install $DISK

Generate GRUB config:
grub-mkconfig -o /boot/grub/grub.cfg

Start services

Start the network and device-mapper daemons on boot:
ln -s /etc/runit/sv/dmeventd /etc/runit/runsvdir/default ln -s /etc/runit/sv/connmand /etc/runit/runsvdir/default

Reboot

Exit the chroot, unmount the partitions and reboot:
exit zfs snapshot -r rpool_$INST_UUID/ROOT/default@install umount $INST_MNT/boot zpool export -a reboot