Skip to content

Instantly share code, notes, and snippets.

@Adito5393
Forked from jimboy-701/archlinuxzbm.md
Last active November 13, 2024 15:23
Show Gist options
  • Save Adito5393/1f1b53ba907f52fd39e09e93453269c8 to your computer and use it in GitHub Desktop.
Save Adito5393/1f1b53ba907f52fd39e09e93453269c8 to your computer and use it in GitHub Desktop.
Arch Linux with ZFS root, zfs-dkms, ZFSBootMenu, and Secure Boot enabled (nothing changed compared to the upstream - last merged upstream revision 2024-02-22)

Install Arch Linux with ZFS root filesystem, zfs-dkms, ZFSBootMenu, Pacman Auto-snapshots, Secure Boot enabled

Configure system BIOS

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

ZFS automated install script

Before moving on I need to point out that there exists a script that can automate the configuration and install of a ZFS root system. However as convenient as it sounds the script is limited in flexibility and scope. These limitation cannot be overcome unless one has the time and compacity to edit the script to their liking. If you want to install a ZFS root system as quickly as possible and don't care about any particulars than take a good look at this github page.

Get the latest Arch Install media prebuilt with ZFS support

It's entirely possible to build your own Archiso however that takes time and beyond the scope of this guide. For now we'll take a shortcut.

Or use a script to add ZFS support after booting the official Arch linux Install media

By the same developer hosting the prebuilt ZFS supported install media above, the purpose of this script is to skip that step entirely and build the necessary zfs modules within the within the Arch install enviroment.

From the github page, boot on any archiso system, and run:
curl -s https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init | bash

Optionally after booting the Arch Install media and entering the install enviroment, set the console font:

>setfont ter-128n

Test internet connection

ip a ping yahoo.com

Setup closest Arch repos

reflector --country Japan --latest 5 --sort rate --save /etc/pacman.d/mirrorlist

Verify zfs modules are loaded

>lsmod | grep zfs
zfs                  4218880  11
zunicode              339968  1 zfs
zzstd                 552960  1 zfs
zlua                  208896  1 zfs
zavl                   16384  1 zfs
icp                   331776  1 zfs
zcommon               110592  2 zfs,icp
znvpair               118784  2 zfs,zcommon
spl                   122880  6 zfs,icp,zzstd,znvpair,zcommon,zavl

Partitioning your disk(s)

We'll be using the UEFI boot method which requires us to create a EFI partition to launch Linux. Run the following command to identify the hard drive \ partition info:

>lsblk
NAME         MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
mmcblk0      179:0    0  29.1G  0 disk
nvme0n1      259:0    0 465.8G  0 disk

To make things simple in the above example we'll be using disk "nvme0n1" to hold both the EFI and OS filesystems.
Note: From a security standpoint it is entirely possible to have the EFI (boot) partition on a entirely different disk then the actual OS filesystem, eg: removable media mmcblk0. Look further down for the appropiate commands.

The file with the long string is our target disk (nvme0n1). Save the disk path\UUID into a variable to make things easy:

>DISK=/dev/nvme0n1

You can use almost any partitioning tool... in this example we'll use gdisk to create the partition scheme.

>sgdisk --zap-all $DISK
>sgdisk -n1:1M:+256M -t1:EF00 $DISK
>sgdisk -n2:0:0 -t2:BF00 $DISK

Warning: If you're using a Libvirt \ Qemu virtual machine the above commands may fail with an obscure error. If this is the case try using gdisk in interactive mode to manually create the partition scheme. Don't forget the label the partitions correctly: EFI Boot Partition=ef00, Linux Install Partition=bf00

>gdisk /dev/vda

Now lets verify the partitions are good before moving on:

>lsblk
NAME         MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
mmcblk0      179:0    0  29.1G  0 disk
nvme0n1      259:0    0 465.8G  0 disk
├─nvme0n1p1  259:1    0   256M  0 part
└─nvme0n1p2  259:2    0 465.5G  0 part

If you're putting the EFI partition on a seperate disk \ removeable storage device for additional security eg., mmcblk0p1.

>DISK0=/dev/mmcblk0
>DISK1=/dev/nvme0n1
>sgdisk --zap-all $DISK0
>sgdisk --zap-all $DISK1
>sgdisk -n1:1M:+256M -t1:EF00 $DISK0
>sgdisk -n2:0:0 -t2:BF00 $DISK1
>lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
mmcblk0     179:0    0  29.7G  0 disk
└─mmcblk0p1 179:1    0   512M  0 part
nvme0n1     259:0    0 238.5G  0 disk
└─nvme0n1p1 259:1    0 238.5G  0 part

Creating required filesystems and mountpoints

Create the EFI (boot) filesystem on the first partition:

>mkfs.vfat -v -F 32 -n EFI /dev/nvme0n1p1

Or if you're going the seperate disk \ removeable storage device route:

>mkfs.vfat -v -F 32 -n EFI /dev/mmcblk0p1

Create our zroot pool.
Note: The lowercase and capital o's matter in the command, if you're not using an SSD drive skip the autotrim option, and if you're using a virtual enviroment you can probably omit the compression option as well.

>zpool create -f -o ashift=12 \
-o autotrim=on \
-O devices=off \
-O relatime=on \
-O xattr=sa \
-O acltype=posixacl \
-O denodesize=legacy \
-O normalization=formD \
-O compression=lz4 \
-O canmount=off \
-O mountpoint=none \
-R /mnt \
zroot /dev/nvme0n1p2

Verify the pool:

>zpool status

Create filesystem mountpoints and then import \ export test:

>zfs create zroot/ROOT
>zfs create -o canmount=noauto -o mountpoint=/ zroot/ROOT/arch
>zfs create -o mountpoint=/home zroot/home
>zpool export zroot
>zpool import -d /dev/nvme0n1p2 -R /mnt zroot -N

Mounting everything together

Mount our mountpoints:

>zfs mount zroot/ROOT/arch
>zfs mount -a
>mkdir -p /mnt/{etc/zfs,boot/efi}
>mount /dev/nvme0n1p1 /mnt/boot/efi

Check if zfs mounted successfully:

>mount | grep mnt
zroot/ROOT/arch on /mnt type zfs (rw,relatime,xattr,posixacl)
zroot/home on /mnt/home type zfs (rw,relatime,xattr,posixacl)

Make sure the df output shows all three mountpoints:

>df -k
zroot/ROOT/arch... /mnt
zroot/home...      /mnt/home
/dev/nvme0n1p1...  /boot/efi

Configure the ZFS pool

If all is good, move on to set bootfs and create zfs cache file:

>zpool set bootfs=zroot/ROOT/arch zroot
>zpool set cachefile=/etc/zfs/zpool.cache zroot
>cp -v /etc/zfs/zpool.cache /mnt/etc/zfs

Install the essential packages for the base Arch linux system

Install packages with pacstrap:

>pacman -Syy
>pacstrap /mnt base base-devel linux linux-headers linux-firmware intel-ucode dkms efibootmgr man-db man-pages git rust cargo nano

Generate and configure the filesystem table file.
With nano comment out or delete all lines but the one containing /efi.

>genfstab -U -p /mnt >> /mnt/etc/fstab
>nano /mnt/etc/fstab

Copy over the dns settings to the new system:

>cp -v /etc/resolv.conf /mnt/etc

Chroot into the new system and perform optional tweaks before compilation.

In makepkg.conf on the "CFLAGS" line, remove any -march and -mtune flags, then add -march=native. Scroll down to the line with MAKEFLAGS="-j2" and change that to MAKEFLAGS="-j$(nproc)".Near the bottom of the file look for the file compression options, add "--threads=0" to the COMPRESSZST and COMPRESSXZ commands.
There are further tweaks to be had within this file, please consult the Archlinux wiki below.

>arch-chroot /mnt
>nano /etc/makepkg.conf

From the Archlinux wiki Eg:

/etc/makepkg.conf
...
CFLAGS="-march=native -O2 -pipe -fno-plt"
...
RUSTFLAGS="-C opt-level=2 -C target-cpu=native"
...
MAKEFLAGS="-j$(nproc)"
...
COMPRESSZST=(zstd -c -z -q --threads=0 -)
COMPRESSXZ=(xz -c -z --threads=0 -)
...

Create a regular User account and give sudo permissions

>useradd -m username
>passwd username
>usermod -aG users, sys, adm, log, scanner, power, rfkill, video, storage, optical, lp, audio, wheel username
>id username

Add "%wheel ALL=(ALL) ALL" without quotes using nano:

>nano /etc/sudoers.d/username

Su into your new User account and build \ install our pacman helper (Paru).

>su username
>sudo pacman -Syy
>git clone https://aur.archlinux.org/paru.git && cd paru
>makepkg -si

Now use Paru to build and install zfs-dkms

>cd
>paru -S zfs-dkms

Install additional base packages

This is about the bare minimal packages needed. You can even omit openssh if you don't plan on doing any remote management and terminus-font. Note: You can use dhcpcd in place of networkmanager for a less system resource alternative.

>paru -S networkmanager reflector openssh terminus-font

For recovery with a broken pacman and outdated glibc

paru -S pacman-static

In addition, for a more complete Desktop or Laptop experience I recommend install the following packages.
Note: I placed a star next to packages that will require some manual intervention to get working. Look up the package in the Archlinux Wiki for guidance.

xdg-user-dirs xdg-utils
bash-completion gpm* tmux terminus-font
inetutils net-tools dnsutils avahi* nss-mdns
firewalld*
tlp* acpi_call acpid*
bluez* bluez-utils
cups*
ntp*
alsa-utils pipewire pipewire-alsa pipewire-pulse pipewire-jack sof-firmware
smartmontools* lm_sensors*
curl wget lsftp rsync
dmidecode lsof htop
fcron
zsh* grml-zsh-config fd fzf*
exfatprogs ntfs-3g dosfstools
neovim

Enable base services

>systemctl enable zfs-import-cache
>systemctl enable zfs-import.target
>systemctl enable zfs-mount
>systemctl enable zfs-share
>systemctl enable zfs-zed
>systemctl enable zfs.target
>systemctl enable NetworkManager
>systemctl enable reflector.timer

Generate your hostid file and write it down

>sudo zgenhostid $(hostid)
>hostid

Set time zone and generate locales

>sudo ln -sf /usr/share/zoneinfo/Pacific/Guam /etc/localtime
>hwclock --systohc

Generate your locales, edit /etc/locale.gen and uncomment all locales you need, for example en_US.UTF-8.

>nano /etc/locale.gen
>locale-gen

Configure the network

Set your hostname by writing it to /etc/hostname:

>echo "hostname" > /etc/hostname

Then edit /etc/hosts, where is your previously chosen hostname:

>echo "127.0.0.1 localhost" >> /etc/hosts
>echo "::1       localhost" >> /etc/hosts
>echo "127.0.0.1 hostname.localdomain hostname" >> /etc/hosts

Configure reflector

Edit the reflector configuration file by adding the correct country and changing "--sort rate".

>nano /etc/xdg/reflector.conf

Set default console font

This will make your console look cleaner however you're welcome to customize to you're liking by using another font or changing its size.

>sudo nano /etc/vconsole.conf
FONT=ter-128n

Install the EFI bootloader: ZFSBootMenu

>mkdir -p /efi/EFI/zbm
>wget https://get.zfsbootmenu.org/latest.EFI -O /efi/EFI/zbm/zfsbootmenu.EFI
>efibootmgr --disk  --part 1 --create --label "ZFSBootMenu" --loader '\EFI\zbm\zfsbootmenu.EFI' --unicode "spl_hostid=$(hostid) zbm.timeout=3 zbm.prefer=zroot zbm.import_policy=hostid" --verbose

Set kernel boot parameters:

zfs set org.zfsbootmenu:commandline="noresume init_on_alloc=0 rw spl.spl_hostid=$(hostid)" zroot/ROOT

Edit the initcpio configuration file and regenerate initramfs:

Edit the mkinitcpio configuration file and make sure the HOOKS line are like the following:
"HOOKS=(base udev autodetect modconf block keyboard zfs filesystem)"

>nano /mnt/etc/mkinitcpio.conf
>mkinitcpio -P

Assign root password:

>passwd

Unmount, reboot and test

>umount /mnt/boot/efi
>zfs umount -a
>zpool export zroot
>reboot

Post installation Tips

One of the main features of the ZFS filesystem is its ability to take filesystem snapshots. This feature quite invalueable especially during major Operating System upgrades and software updates. However initiating a manual ZFS snapshot everytime something changes on the system can be very tedious. Using a Pacman hook is a very effective way to solve this issue.

The hook calls on the "zfs-snap-pac" script to create a snapshot whenever there is a pacman transaction. You can customize this script to your liking. For example we'll modify the date command within the GetSnapshotName() function so that it names snapshots in a more meaningful manner.

...
GetSnapshotName() {
    local time=$(date +%s)
    snapshotName="$snapshotName$time"
}
...

Example output of default snapshot naming scheme:

>date +%s
1708100748

New human friendly naming scheme:

>date +%F-%R
2024-02-17-02:25

Creating snapshots everytime there's a Pacman transaction can quickly take up valuable space if old snapshots are never deleted. Use another hook to automate this task and avoid future potential issues.

Use the hook file below to automatically call the zfs-prune-snapshots script after every Pacman transaction has completed. The arguement "2w" will instruct the script to delete any ZFS snapshot matching 2 weeks or older.

>cat /usr/share/libalpm/hooks/01-zfs-prune-pac.hook
[Trigger]
Operation = Upgrade
Operation = Remove
Type = Package
Target = *

[Action]
Description = Pruning ZFS snapshots...
When = PostTransaction
Exec = /usr/bin/zfs-prune-snapshots 2w

To fix the "WARNING: Possible missing firmware" messages whenever regenerating the initramfs:

>paru -S mkinitcpio-firmware

System Rescue \ Troubleshooting

If the system won't boot you can remount the zfs pool and EFI boot partition using the install media like before. After troubleshooting, don't forget to unmount it described in the previous step.

>lsblk
>zpool import -f -N -R /mnt zroot
>zfs list
>zfs mount zroot/ROOT/arch_mpv-libplacebo2_NEW
>ls /mnt
>lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sr0          11:0    1 883.3M  0 rom
mmcblk0     179:0    0  29.7G  0 disk
└─mmcblk0p1 179:1    0   512M  0 part /boot/efi
zram0       253:0    0     2G  0 disk [SWAP]
nvme0n1     259:0    0 238.5G  0 disk
└─nvme0n1p1 259:1    0 238.5G  0 part

>mount /dev/mmcblk0p1 /mnt/boot/efi
>zfs mount zroot/home
>mount | grep mnt
>arch-chroot /mnt

If you're mounting using a different distro eg: Alpine Linux:

>zpool import -f zroot
>mount -t zfs zroot/ROOT/alpine /mnt
>mount -t zfs zroot/home /mnt/home
>mount /dev/vda1 /mnt/boot/efi
>mount | grep mnt
>chroot /mnt /usr/bin/env sh

Reset the zfs pool and umount

>zpool export -f zroot
>zfs umount -a

Possible issues, workarounds, fixes

how to fix missing libcrypto.so.1.1?

/var stays busy at shutdown due to journald #867

Arch Linux, Aur error - FAILED unknown public key

zfs-dkms depends on a specific version of the zfs-utils, and zfs-utils depend on a specific version of zfs-dkms, which completely prevents me from updating them

References & Software

2022: Arch Linux Root on ZFS from Scratch Tutorial

Guide: Install Arch Linux on an encrypted zpool with ZFSBootMenu as a bootloader

Debian Bullseye installation with ESP on the zpool disk

Setting up Arch + LUKS + BTRFS + systemd-boot + apparmor + Secure Boot + TPM 2.0 - A long, nightmarish journey, now simplified

Configure systemd ZFS mounts

The Archzfs unofficial user repository offers multiple ways to install the ZFS kernel module.

Arch Linux pacman hooks

Paru: Feature packed AUR helper

Thank you

Please feel free to leave any helpful comments or suggestions

@timofeika
Copy link

Hello, thanks for great tutorial! I tried it in a virtual machine, but unfortunately I couldn't boot. Could you please supplement the tutorial, for example, where does the work from under the user end and at what point do you need to exit from arch-chroot.

P.S. Found few typos, first at line with "-O denodesize=legacy ", should be "dnodesize", second ">usermod -aG users, sys, adm, log, scanner, power, rfkill, video, storage, optical, lp, audio, wheel username", need to remove spaces, or command return error.

@jspuij
Copy link

jspuij commented Oct 23, 2024

@timofeika There are some minor typo's here:

>mkdir -p /boot/efi/EFI/zbm
>wget https://get.zfsbootmenu.org/latest.EFI -O /boot/efi/EFI/zbm/zfsbootmenu.EFI
>efibootmgr --disk /dev/nvme0n1  --part 1 --create --label "ZFSBootMenu" --loader '\EFI\zbm\zfsbootmenu.EFI' --unicode "spl_hostid=$(hostid) zbm.timeout=3 zbm.prefer=zroot zbm.import_policy=hostid" --verbose

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment