2024-05-05
Dieses Jahr wollte ich mich endlich mit ZFS
beschäftigen, jedoch merkte ich das ist nicht so einfach wie gedacht.
ZFS ist ein Copy-on-Write Dateisystem, welches von Sun Microsystems entwickelt wurde und bietet Snapshots an. ZFS
eignet sich besonders auf Server Strukturen zu fahren, da dort die Stärken besser und mehr genutzt werden.
Für den privaten Gebrauch auf dem eigenen Linux Desktop/Laptop ist die Einrichtung von Btrfs
eine gute Alternative. Besonders da dieses Dateisystem im Linux Kernel integriert ist und ein vergleichbares Feature-Set bietet.
Das große Problem bei der Einrichtung von ZFS
ist die CDDL Lizenz, welche nicht mit der GPL kompatibel ist. Somit kann ZFS
nicht teil des Linux Kernels werden und kann lediglich als Modul nachgeladen werden.
Unter Arch Linux muss hierzu ein eigenes Arch ISO erstellt werden, im welchen das Modul und benötigten Pakete bereits vorhanden sind, oder es wird ein Bash Script von eoli3n genutzt.
Ich möchte mit diesem Blogartikel meine Vorgehensweise niederschreiben, Arch Linux auf ZFS zu installieren, welcher in einem LUKS Container verschlüsselt liegt und mittels systemd-boot gestartet wird.
Diese Anleitung wurde am 05.05.2024 geschrieben und wurde mit folgenden Versionen getestet:
Ladet ein Arch Linux ISO herunter und packt dieses auf einen USB Stick:
$ curl https://mirror.selfnet.de/archlinux/iso/2024.05.01/archlinux-2024.05.01-x86_64.iso -o /path/to/download/archlinux.iso
Beim Schreiben auf den USB-Stick müsst ihr /dev/sda
, /dev/sdc
, usw.
$ sudo dd if=/path/to/archlinux.iso of=/dev/<USBDEVICE> bs=1M
$ sync
Bootet das Arch Linux Live Medium und verbindet euch ins Internet. Am einfachsten ist dies via Ethernet. Falls ihr keine Konnektivität via Kabel umsetzen könnt, könnt ihr iwctl
benutzen.
$ iwctl
NetworkConfigurationEnabled: enabled
StateDirectory: /var/lib/iwd
Version: 2.17
[iwd]# station wlan0 scan
[iwd]# station wlan0 get-networks
Available networks
--------------------------------------------------------------------------------
Network name Security Signal
--------------------------------------------------------------------------------
SSID psk ****
[iwd]# station wlan0 connect SSID
Nun portionieren wir unser Speichermedium, auf welchen wir Arch Linux installieren wollen. In meinem Setup habe ich eine Partition für:
Aus historisch gewachsenen Gründen benutze ich fdisk
hierfür. Mit der Taste n
wird eine neue Partition angelegt. Diese soll an erster Stelle stehen und den ersten verfügbaren Sektor benutzen.
Mit +1G
geben wir die Größe der Partition an.
Dies machen wir ebenso mit der zweiten Partition. Sprich, wir erstellen mit n
eine neue Partition, benutzten den ersten verfügbaren Sektor des Speichermediums und geben die Größe vom Swap
an, bei mir +16G
, da ich ein Notebook benutze und Suspend nutzen möchte. Zum Schluss erstellen wir die Root Partition und benutzen den restlichen verfügbaren Speicher.
Jetzt können wir der ersten Partition das EFI
Flag geben, damit diese als unsere EFI Partition erkannt werden kann, sowie der zweiten Partition das Swap
Flag.
Dafür drücken wir die t
Taste, wählen Partition 1
aus und wählen aus der Liste das EFI Flag aus. In diesem Fall ist es der erste Eintrag, also geben wir 1
ein. Das machen wir auch mit der zweiten Partition und wählen Linux Swap
aus, was in der Liste der Eintrag 19
ist.
Welcome to fdisk (util-linux 2.40).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): n
Partition number (1-128, default 1): 1
First sector (2048-1000215182, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-1000215182, default 1000214527): +1G
Created a new partition 1 of type 'Linux filesystem' and of size 1 GiB.
Command (m for help): n
Partition number (2-128, default 2):
First sector (2099200-1000215182, default 2099200):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-1000215182, default 1000214527): +16G
Created a new partition 2 of type 'Linux filesystem' and of size 16 GiB.
Command (m for help): n
Partition number (3-128, default 3):
First sector (35653632-1000215182, default 35653632):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (35653632-1000215182, default 1000214527):
Created a new partition 3 of type 'Linux filesystem' and of size 459.9 GiB.
Command (m for help): t
Partition number (1-3, default 3): 1
Partition type or alias (type L to list all): 1
Changed type of partition 'Linux filesystem' to 'EFI System'.
Command (m for help): w
The partition table has been altered.
Syncing disks.
Command (m for help): t
Partition number (1-3, default 3): 2
Partition type or alias (type L to list all): 19
Changed type of partition 'Linux filesystem' to 'Linux swap'.
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
Wir könnten zwar die Verschlüsselung von ZFS selber benutzen und hierzu gibt es Pro und Contras, für mein Setup habe ich mich entschlossen LUKS einzurichten.
$ cryptsetup luksFormat /dev/nvme0n1p3
WARNING!
========
This will overwrite data on /dev/nvme0n1p3 irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/nvme0n1p3:
Verify passphrase:
cryptsetup luksFormat /dev/nvme0n1p3 13.40s user 0.51s system 50% cpu 27.304 total
Damit wir ZFS in den Container schreiben können, müssen wir den Container nun entschlüsseln. Wir nennen hierzu den entschlüsselten Container rootfs
:
$ cryptsetup luksOpen /dev/nvme0n1p3 rootfs
Enter passphrase for /dev/nvme0n1p3:
cryptsetup luksOpen /dev/nvme0n1p3 rootfs 5.22s user 0.18s system 72% cpu 7.421 total
Da, wie anfangs erklärt, das Arch Linux ISO ohne ZFS kommt, müssen wir dem Live Medium ZFS beibringen, in dem wir das Modul beziehen und laden.
Der kurze Weg ist, das Script mittels curl
direkt an die Bash
zu pipen. Diese Vorgehensweise sollte nur genutzt werden, wenn ihr euch sicher seit, was sich hinter dem Script befindet, nicht dass ihr euch ohne Kontrolle eine Backdoor einbaut.
curl https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init | bash
Falls GitHub nicht erreichbar sein sollte, dieses Script File verschwindet oder im nicht funktionalen Zustand sich befinden sollte, habe ich den Stand vom 05.05.2024 hier eingebunden.
#!/bin/bash
# This script load zfs kernel module for any archiso.
# github.com/eoli3n
# Thanks to CalimeroTeknik on #archlinux-fr, FFY00 on #archlinux-projects, JohnDoe2 on #regex
exec &> >(tee "debug.log")
### Vars
verbose=0
### Functions
usage () {
cat << EOF
Usage: ${0##*/} [-v]
-v increase verbosity
-h show this usage
EOF
}
print () {
echo -e "\n\033[1m> $1\033[0m"
}
get_running_kernel_version () {
# Returns running kernel version
# Get running kernel version
kernel_version=$(uname -r)
print "Current kernel version is $kernel_version"
}
init_archzfs () {
if pacman -Sl archzfs >&3; then
print "archzfs repo was already added"
return 0
fi
print "Add archzfs repo"
# Disable Sig check
pacman -Syy archlinux-keyring --noconfirm >&3 || return 1
pacman-key --populate archlinux >&3 || return 1
pacman-key --recv-keys F75D9D76 >&3 || return 1
pacman-key --lsign-key F75D9D76 >&3 || return 1
cat >> /etc/pacman.conf <<"EOF"
[archzfs]
Server = http://archzfs.com/archzfs/x86_64
Server = http://mirror.sum7.eu/archlinux/archzfs/archzfs/x86_64
Server = https://mirror.biocrafting.net/archlinux/archzfs/archzfs/x86_64
EOF
pacman -Sy >&3 || return 1
return 0
}
init_archlinux_archive () {
# $1 is date formated as 'YYYY/MM/DD'
# Returns 1 if repo does not exists
# Archlinux Archive workaround for 2022/02/01
if [[ "$1" == "2022/02/01" ]]
then
version="2022/02/02"
else
version="$1"
fi
# Set repo
repo="https://archive.archlinux.org/repos/$version/"
# If repo exists, set it
if curl -s "$repo" >&3
then
echo "Server=$repo\$repo/os/\$arch" > /etc/pacman.d/mirrorlist
else
print "Repository $repo is not reachable or doesn't exist."
return 1
fi
return 0
}
search_package () {
# $1 is package name to search
# $2 is version to match
# Set regex to match package
local regex='href="\K(?![^"]*\.sig)'"$1"'-(?=\d)[^"]*'"$2"'[^"]*x86_64[^"]*'
# href=" # match href="
# \K # don't return anything matched prior to this point
# (?![^"]*\.sig) # remove .sig matches
# '"$1"'-(?=\d) # find me '$package-' escaped by shell and ensure that after "-" is a digit
# [^"]* # match anything between '"'
# '"$2"' # match version escaped by shell
# [^"]* # match anything between '"'
# x86_64 # now match architecture
# [^"]* # match anything between '"'
# Set archzfs URLs list
local urls="http://archzfs.com/archzfs/x86_64/ http://archzfs.com/archive_archzfs/"
# Loop search
for url in $urls
do
print "Searching $1 on $url..."
# Query url and try to match package
local package
package=$(curl -s "$url" | grep -Po "$regex" | tail -n 1)
# If a package is found
if [[ -n $package ]]
then
print "Package \"$package\" found"
# Build package url
package_url="$url$package"
return 0
fi
done
# If no package found
return 1
}
download_package () {
# $1 is package url to download in tmp
print "Download to $package_file ..."
local filename="${1##*/}"
# Download package in tmp
cd /tmp || return 1
curl -sO "$1" || return 1
cd - || return 1
# Set out file
package_file="/tmp/$filename"
return 0
}
zfslinux_install () {
# Search kernel package
# https://github.com/archzfs/archzfs/issues/337#issuecomment-624312576
get_running_kernel_version
kernel_version_fixed="${kernel_version//-/\.}"
# Search zfs-linux package matching running kernel version
if search_package "zfs-linux" "$kernel_version_fixed"
then
zfs_linux_url="$package_url"
# Download package
download_package "$zfs_linux_url" || exit 1
zfs_linux_package="$package_file"
print "Extracting zfs-utils version from zfs-linux PKGINFO"
# Extract zfs-utils version from zfs-linux PKGINFO
zfs_utils_version=$(bsdtar -qxO -f "$zfs_linux_package" .PKGINFO | grep -Po 'depend = zfs-utils=\K.*')
# Search zfs-utils package matching zfs-linux package dependency
if search_package "zfs-utils" "$zfs_utils_version"
then
zfs_utils_url="$package_url"
print "Installing zfs-utils and zfs-linux"
# Install packages
if pacman -U "$zfs_utils_url" --noconfirm >&3 && pacman -U "$zfs_linux_package" --noconfirm >&3
then
zfs=1
else
return 1
fi
fi
fi
return 0
}
dkms_install () {
# Init everything to be able to install zfs-dkms
print "No zfs-linux package was found for current running kernel, fallback on DKMS method"
print "Init Archlinux Archive repository"
archiso_version=$(sed 's-\.-/-g' /version)
init_archlinux_archive "$archiso_version" || return 1
print "Download Archlinux Archives package lists and upgrade"
pacman -Syyuu --noconfirm >&3 || return 1
print "Install base-devel"
pacman -S --noconfirm base-devel linux-headers git >&3 || return 1
print "Install zfs-dkms"
# Install package
if pacman -S zfs-dkms --noconfirm >&3
then
zfs=1
else
return 1
fi
return 0
}
### Getopts
while getopts "vh" option; do
case "${option}" in
v)
verbose=$((verbose + 1))
;;
h)
usage
exit 0
;;
*)
usage
exit 0
;;
esac
done
shift $((OPTIND-1))
### Verbose mode
if [[ "$verbose" -gt 0 ]]
then
exec 3>&1
else
exec 3>/dev/null
fi
### Main
# Test if archiso is running
if ! grep 'arch.*iso' /proc/cmdline >&3
then
print "You are not running archiso, exiting."
exit 1
fi
print "Increase cowspace to half of RAM"
mount -o remount,size=50% /run/archiso/cowspace >&3
# Init archzfs repository
init_archzfs || exit 1
# Install zfs-linux if found else fallback on dkms_install
zfslinux_install || dkms_install || exit 1
# Load kernel module
if [[ "$zfs" == "1" ]]
then
modprobe zfs && echo -e "\n\e[32mZFS is ready\n"
else
print "No ZFS module found"
fi
Da wir nun alles vorbereitet haben, kann ein zpool
erstellt werden. Wenn ihr euch fragt, was ein zpool
ist, könnt ihr dies zum Beispiel in der freeBSD Dokumentation nachlesen. Allgemein ist diese sehr lesenswert, um das Wissen für die Benutzung von ZFS zu vertiefen.
Ihr müsst nur kurzweilig für die Erstellung vom Pool kreativ werden, und zwar bei der Wahl des Namens. Solltet ihr unkreativ sein, nehmt zpool
, tank
, zfspool
,.. .
$ zpool create -f -o ashift=12 -O compression=lz4 -O acltype=posixacl -O xattr=sa -O relatime=on -o autotrim=on -m none <POOLNAME> /dev/mapper/rootfs
ZFS fragt das Speichermedium nach der Größe der Sektoren nach und sollte dieser fehlerhaft oder falsch sein, muss dies korrigiert werden. Alte Speichermedien haben eine Sektorgröße von 512 Bytes, moderne von 4096 Bytes. Der Wert entspricht somit entweder 4096 = 2^12
(ashift=12
) oder 512 = 2^9
(ashift=9
).
sudo lsblk -o NAME,MOUNTPOINT,PHY-SEC
NAME MOUNTPOINT PHY-SEC
sda 4096
├─sda1 4096
└─sda2 4096
└─sda3 4096
Die geschriebenen Daten werden komprimiert geschrieben. Nicht nur verringert dies den Speicherbedarf, durch die Komprimierung wird ebenso die Lese und Schreibgeschwindigkeit erhöht, da diese weniger Blöcke benötigen. LZ
ist derzeit der empfohlene Standard, welcher genutzt werden soll.
Mit acltype=posixacl
erlauben wir die Nutzung von Posix Berechtigungen für Lese und Schreibrechte (chown/chmod).
xattr
kontrolliert, ob die erweiterten Attribute für das Dateisystem gesetzt sind.
Die OpenZFS Dokumentation besagt, für die beste Performance mit Posix Benutzern soll die Option xattr=sa
gesetzt werden, damit die Posix ACLs effizienter auf dem Medium gespeichert werden.
Steuert die Art und Weise, wie die Zugriffszeit aktualisiert wird, sollte atime=on
gesetzt sein. Wenn diese Eigenschaft eingeschaltet ist, wird die Zugriffszeit relativ zur Modifizierungs- oder Änderungszeit aktualisiert.
Dieser Vorgang informiert die Speichermedien über alle Blöcke im Pool, die nicht mehr zugewiesen sind, und ermöglicht es den Thin-Provisioning-Geräten, den Speicherplatz zurückzugewinnen.
Die -m
Option steht für mountpoint
und wir setzten hierbei den wert none
Zum schluss geben wir dem Pool noch einen Namen und definieren das Device, auf dem der Pool eingerichtet werden soll. In dem Fall ist es unser LUKS Container. Danach
Mit den folgenden Befehlen erstellen wir unser Root System Container, sowie die Mountpoints für die Datasets.
$ zfs create -o mountpoint=none <POOLNAME>/ROOT
$ zfs create -o mountpoint=/ -o canmount=noauto <POOLNAME>/ROOT/arch
$ zfs create -o mountpoint=/home <POOLNAME>/home
Pools werden exportiert, bevor diese an ein anderes System angeschlossen werden. Alle Datasets werden abgehängt und jedes Gerät wird als exportiert markiert, ist jedoch immer noch gesperrt, so dass es nicht von anderen Festplattensubsystemen verwendet werden kann.
$ zpool export <POOLNAME>
Damit der exportierte Pool eingelesen und gemountet werden kann (dieser Befehl eignet sich auch im Nachgang ein ZFS Pool in einem Live ISO händisch zu mounten), importieren wir den Pool mit dem zfs
an, wohin dieser gemountet werden soll.
$ zpool import -N -R /mnt <POOLNAME>
$ zfs mount <POOLNAME>/ROOT/arch
$ zfs mount <POOLNAME>/home
Das war es vorerst mit ZFS
und weiter geht es mit Arch Linux und sytemd-boot.
Der ersten Partition geben wir nun auch ein Filesystem. Da wir EFI und systemd zum booten benutzen werden, bügeln wir vfat
auf die Partition, da sonst diese nicht gelesen werden kann.
$ mkfs.vfat -F 32 -n EFI /dev/nvme0n1p1
$ mkdir -p /mnt/boot
$ mount /dev/nvme0n1p1 /mnt/boot
Endlich kommen wir zur Installation von Arch Linux. Hier können wir der regulären Anleitung folgen, mit einem kleinen Unterschied. Da ZFS nicht in den Repositorys verfügbar ist, können wir nicht einfach den Linux Kernel mit ZFS installieren.
Auch gibt es ein Abhängigkeitsproblem. So ist nicht jede Kernel
Version mit ZFS
kompatibel, was zu Abhängigkeitproblemen führt und eine Installation und Einrichtung nicht möglich macht. Dies kann bis zum Kernel Panic führen. Zwar gibt es im Arch User Repository [AUR]
ein DKMS
Paket welches das Updaten von Linux
und ZFS
erleichtern soll, jedoch lieferte dieser mir nie die Kernel Module aus, welche ich benötigte um ZFS nach dem Boot zu benutzen.
Ich entschloss mich für den manuellen weg und installiere im Nachgang Kernel, Header und ZFS.
$ pacstrap /mnt base linux-firmware nano efibootmgr openssh base-devel iwd openssh intel-ucode dhcpcd mkinitcpio
Jetzt brauchen wir noch eine /etc/hostid
die wir mit zgenhostid
erstellen:
$ zgenhostid
$ cp /etc/hostid /mnt/etc
$ mkdir -p /mnt/etc/zfs
Wir kopieren uns die pacman
Konfiguration vom Live Medium auf unser neues System
$ cp /etc/pacman.conf /mnt/etc/pacman.conf
Und erstellen uns eine fstab
in der die EFI Partition steht, da die root Partition nicht als solche über die fstab
gemountet werden kann.
$ genfstab /mnt | grep efi > /mnt/etc/fstab
Da wir die Grundinstallation hinter uns haben, können wir nun mit arch-chroot
in unsere neue Installation springen und unser System einrichten:
$ arch-chroot /mnt
Mittels Symlink setzen wir die unsrige Zeitzone und richten die Hardware Uhr ein
$ ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime
$ hwclock --systohc
Bearbeitet nun die /etc/locale.gen
und entfernt die #
für die Sprachen, die ihr nutzen wollt.
$ nano /etc/locale.gen
[...]
$ locale-gen
$ echo 'LANG=en_US.UTF-8' > /etc/locale.conf
Tragt noch einen Hostnamen
für euer Gerät ein:
echo 'AnorLondo' > /etc/hostname
Jetzt kommen wir zum spannenden Teil.
Um den initialen RAM Disk zu erstellen, muss mkinitcpio
konfiguriert werden, sodass ZFS
als Modul geladen wird und eingebunden werden kann. Aber auch muss der LUKS
Container vorher entschlüsselt werden. Hierfür muss die /etc/mkinitcpio.conf
bearbeitet werden und die Optionen gesetzt werden.
Da die Konfiguration voller Kommentare besteht und unleserlich ist, räumen wir diese etwas auf:
$ sed '/^#/d' -i /etc/mkinitcpio.conf
Jetzt kann diese bearbeitet werden. Wir müssen die Zeile welch mit HOOKS
beginnt, mit encrypt
und zfs
erweitern bzw. wir können diese einfach mit dem folgenden ersetzten. Auch setzen wir unter den Modulen noch die nvme, falls eine im Einsatz ist.
$ nano /etc/mkinitcpio.conf
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap encrypt consolefont block zfs filesystems resume)
MODULES=(nvme)
Innerhalb unserer neuen Installation müssen wir ein Cache für die konfigurierten Information vom zpool einrichten. Ohne diesen können die Pools nicht gefunden werden, außer über einen händischen Weg. Da wir dies aber nicht beim jeden Boot selber machen wollen, setzen wir für unseren Pool ein Cachefile.
$ zpool set cachefile=/etc/zfs/zpool.cache tank
Unserem System muss nun beigebracht werden, wo sich unser Wurzelsystem befindet.
$ zpool set bootfs=tank/ROOT/arch tank
Und Systemd weisen wir an, den Cache zu importieren und nutzen.
$ systemctl enable zfs-import-cache zfs-import.target zfs-mount zfs-zed zfs.target
Wir nähern uns der Vollendung der Installation. Jetzt konfigurieren wir von wo aus gebootet werden soll und wo sich unser Linux befindet (auch wenn immer noch nicht installiert).
$ bootctl --path=/boot install
$ echo 'default arch' >> /efi/loader/loader.conf
$ echo 'timeout 5' >> /efi/loader/loader.conf
$ blkid -s PARTUUID -o value /dev/nvme0n1p3 >> /efi/loader/entries/arch.conf
$ nano /efi/loader/entries/arch.conf
Wir haben mittels blkid
uns die PARTUUID von unserer LUKS Container Partition geholt und in unsere Konfiguration geschrieben. Diese befindet sich noch an der falschen Stelle, weshalb wir <PARTUUID>
im Beispiel mit unseren UUID aus der letzten Zeile ersetzen. Die Konfiguration sollte wie folgt aussehen:
title Arch Linux
linux /vmlinuz-linux-lts
initrd /intel-ucode.img
initrd /initramfs-linux-lts.img
options cryptdevice=PARTUUID=<PARTUUID>:cryptroot zfs=<POOLNAME>/ROOT/arch rw
Lasst uns kurz die Zeile mit den Optionen ansehen. Dort wird angewiesen, die verschlüsselte Partition zu öffnen (cryptdevice=PARTUUID=<PARTUUID>
) und den Namen cryptoot
zu geben. Nachdem wir bei booten die Passphrase angegeben haben, wird die Partition unter /dev/mapper/cryptroot
zur Verfügung stehen und ZFS
kann nun gemountet werden. ZFS
soll den <POOLNAME>/ROOT/arch
als read und write mounten.
Wie bereits erwähnt, kann es zu einem Abhängigkeitsproblem kommen, wenn ZFS
und die Kernel
Versionen nicht kompatibel zueinander sind. Am Tag meiner Installation war dies der Fall, weshalb die Installation vom ZFS
Paket fehlschlug. Dieser benötigte Linux-LTS in der Version 6.6.29, aber 6.6.30 stand zur Verfügung, weshalb ich mir die vorherige Linux Kernel Version aus dem Archiv heruntergeladen habe.
$ curl "https://archive.archlinux.org/packages/l/linux-lts/linux-lts-6.6.29-1-x86_64.pkg.tar.zst" -o /tmp/linux-lts-6.6.29-1-x86_64.pkg.tar.zst
$ curl "https://archive.archlinux.org/packages/l/linux-lts-headers/linux-lts-headers-6.6.29-1-x86_64.pkg.tar.zst" -o /tmp/linux-lts-headers-6.6.29-1-x86_64.pkg.tar.zst
$ pacman -U /tmp/linux-lts-*
$ pacman -S zfs-linux-lts
Bei der Installation wird mkinitcpio -P
ausgeführt. Ihr könnt dies aber auch nachträglich händisch anweisen.
Vergesst nicht ein Root User Password zu setzten, sonst könnt ihr euch in euer neues System nicht einloggen.
$ passwd
Verlasst euer change root und löst die Mounts auf, bevor ihr in euer installiertes Arch Linux bootet.
$ exit
$ umount -R /mnt
$ reboot
Das war es schon. Es klingt nach mehr, als es tatsächlich ist, besonders weil hier viele Erklärungen dabei stehen. Solltet ihr irgendwas vergessen haben, könnt ihr ein Live ISO booten und eure ZFS Partionen einbinden.
$ cryptsetup luksOpen /dev/nvme0n1p3 rootfs
$ zpool import -N -R /mnt <POOLNAME>
$ zfs mount <POOLNAME>/ROOT/arch
$ zfs mount <POOLNAME>/home
$ arch-chroot /mnt