Switching to ZFS on Linux

I recently made the transition to ZFS-on-Root on Void Linux as btrfs on root recently gave me some issues and while I did like btrfs the snapshots were less familiar to me than the snapshots I was used to with FreeBSD. I also ran into some filesystem corruption which I was able to solve by booting into a live image and following repair procedures from the documentation, however that left me wanting more, and wanting to switch to an encrypted root install too. While I could do FDE with btrfs and use subvolumes, I would much rather have all of the zpool features available to me for expanding volumes, and so on.

The Problem

While I do very much think btrfs is a great filesystem solution, I’m also VERY LAZY/would like things to be more convenient. Even if that convenience means extra initial preparation. With btrfs there are no options to boot directly into a snapshot for example. With ZFSBootMenu there are, as well as a plethora of other convenient options.

I have a system that boots via UEFI with A LOT of storage. I haven’t transitioned all my storage to ZFS yet but I plan to. I decided to test run this setup and install in a VM. For virtualization I was using KVM+QEMU+OMVF and I ran into some serious issues. For some reason or another after the first boot, refind, which I was running on top of ZFSBootMenu for the convenience it provides, would stop booting to ZFSBootMenu. This was an issue with the virtualized environment, and does not reflect Bare Metal operation. I still have no idea what’s going on but I think it’s likely omvf being weird about something or other, what’s strange is this doesn’t happen with my FreeBSD VM.

The Solution

I followed This Guide from the ZFSBootMenu developers. There is also an open PR for the Void Linux handbook which to my knowledge is almost ready to be merged. This is a great guide and if you do it while you’re fully awake and pay attention to what you’re doing, you probably won’t have to start over 3 times like I did.

I started by taking an archive of my home directory, and moving it to my bulk storage, so I could move it back in place and take ownership of all the files if necessary, once the install was done.(Ownership was retained however because I had the same UID, see man useradd for options.)

Where things start to differ is that I didn’t use the hrmpf image, as I wanted a Graphical Environment to more easily read the documentation on while I was working. So I grabbed the latest void-xfce ISO. Afterwards I prepared the Live Environment with the necesssary tools.

Getting to that point is as simple as this:

  • Open a Terminal CTRL+ALT+T and run:
sudo su -s /bin/bash
xbps-install -S linux5.13-headers zfs gptfdisk
xbps-reconfigure -a #Just in case
modprobe zfs

After this you should have all the tools neccessary to follow the guide, durring the install phase I would replace linux5.10 and its headers package with a newer kernel and headers. You can easily do this after you finish your install, however.

Using rEFInd

The guide makes reference to using refind on top of ZFSBootMenu, which is something I wanted to do as I spent a long time making a custom theme, as well as regularly hotplugging devices to boot from. The documentation in the guide is a little out of date for default, I believe so for me the correct proceudre was this:

xbps-install refind
refind-install
rm /boot/efi/EFI/BOOT/refind_linux.conf

After this point I just continued with the final steps in the documentation.

Post-Install Automation and Convenience

As I stated above, the purpose of doing all this work was to make backups/snapshots automatically, that could be used to recover from mistakes, bad upgrades, recover old data, etc. So once my install was done, I needed to start setting all of that up.

Void Linux does not use systemd, so in order to run tasks on a timer a cron implementation is needed. Void does not ship with a cron implementation by default, so I opted for installing dcron and starting its service.

# xbps-install -S dcron
# ln -s /etc/sv/dcron /var/service

Now that dcron is running we need to make some scripts to make snapshots. I am using scripts instead of invoking the snapshot command directly to make the crontab easier to read as well as the snapshots easier to read. I am using two separate scripts for this, because I believe that separating individual tasks into separate scripts or programs is cleaner in the long run, as well as the fact I want to take a Daily Snapshot of my root but 4 daily snapshots of my /home directory.

So first I created a local path for the scripts, this is preference for me but I use ~/.local/bin:

  • So I ran mkdir -p ~/.local/bin.

Then in your prefered text editor create the script ~/.local/bin/snapshot.sh:

#!/bin/sh
/bin/zfs snapshot -r zroot/home@`date +"%F"-"%H":"%M"`

Make the script executable with: chmod +x ~/.local/bin/snapshot.sh

What this is doing is calling the zfs program and taking a snapshot of the /home volume. Then appending a date to the snapshot name so we know when it was taken.

Now we will do the same thing for the / or root volume by creating ~/.local/bin/rootsnapshot.sh:

#!/bin/sh
/bin/zfs snapshot -r zroot/ROOT/void@daily`date +"%F"-"%H":"%M"`

Make the script executable with: chmod +x ~/.local/bin/rootsnapshot.sh

You’ll see that I appended @daily to these snapshots, as above I stated I only wanted daily snapshots of my root volume. This is because I make fewer changes to this volume, and I have some fallback solutions for when I do make those changes like installing or removing packages which we’ll get to later.

Now we need to configure cron to start automatically taking snapshots.

  • Run sudo crontab -e be aware that on Void this defaults to the vi editor. We just want to append a few lines tot he bottom, you can get to the bottom easily with Shift+G then SHIFT+A then add the following lines:
0 */6 * * * /home/hadet/.local/bin/snapshot.sh
0 3 * * * /bin/zfs-prune-snapshots -v  1w zroot/home >> /var/log/snapshot.log
0 0 * * * /home/hadet/.local/bin/snapshot.sh

Replace hadet with your username. Now I’ll break down what each line does:

  1. Every 6 hours on the hour we take a snapshot of the home directory
  2. Every Day at 03:00 we prune snapshots 1 week old or older
  3. Every Day at 00:00 we take a snapshot of the root/void volume which is separate from the /home volume.

Snapshots do not take up a lot of space, so this frequency is not a problem, and in production environments, I’ve set this up to happen once an hour without problems for user directories within /home for example.

If you’re fine with only having those snapshots that’s great. However I wasn’t and I wanted some quick fallback options if there was a bad upgrade or I removed some packages that I actually needed.

XBPS is split into a number of tools, but none of them have the capacity for pre or post invocation hooks. That’s fine for what I want to do since we can just write an alias or even shell function. As I stated earlier. I’m lazy so I wrote some aliases for myself to make this easier, as a function wasn’t really necessary, though probably could have monitored for actual interaction.

Aliases:

# Oopsie Precautions
alias rootsnap="sudo zfs snapshot -r zroot/ROOT/void@oopsie-`date +"%F"-"%H":"%M"`"
alias homesnap="sudo zfs snapshot -r zroot/ROOT/home@oopsie-`date +"%F"-"%H":"%M"`"
# XBPS
alias vupcheck="xbps-install Su"
alias vup="sudo zfs snapshot -r zroot/ROOT/void@upgrade-`date +"%F"-"%H":"%M"`; sudo xbps-install -Su"   # Snapshot and Upgrade
alias vps="sudo xbps-query -Rs" # search
alias vpi="sudo zfs snapshot -r zroot/ROOT/void@newpkgs-`date +"%F"-"%H":"%M"`; sudo xbps-install -S" # install a single package or list of packages
alias vpr="sudo xbps-remove" # remove a single package
alias vpra="sudo zfs snapshot -r zroot/ROOT/void@rmpkgs-`date +"%F"-"%H":"%M"`; sudo xbps-remove -R" # remove a single package and all of its dependencies that are not required by other packages:
alias vphan="sudo zfs snapshot -r zroot/ROOT/void@rmorphans-`date +"%F"-"%H":"%M"`; sudo xbps-remove -o" # Remove dependencies that are no longer needed
alias vpc="sudo zfs snapshot -r zroot/ROOT/void@clearcache-`date +"%F"-"%H":"%M"`; sudo xbps-remove -O" # Clean up all local caches.

Now you’ll see I have some commands to run to immediately take a snapshot if I feel I’m at a point where I might make an oopsie. I also have snapshots being taken when I run a full upgrade, install a new package, remove a package and its dependencies, remove orphaned dependencies, or clear my local repo cache. This is very good because it means if I break something or remove something I shouldn’t have, I can fall back to an older snapshot and figure out what went wrong.

Advantages

I’ve outlined a number of advantages already, there are three main advantages I was after that are now possible:

  1. Unlike btrfs I can boot into a snapshot of the root system directly from ZFSBootMenu if something goes wrong. This is fantastic functionality, and it’s also incredibly fast, much faster than restoring using timeshift.
  2. I can easily and quickly add redundancy, or storage to my existing zpool with a few easy configuration steps, this makes data migration or data redundancy much simpler and eventually actually seprate refind into a small bootdisk to improve portability.
  3. I have snapshots of my home directory which saves me so much trouble when working on projects that I don’t typically use git with, like Blender or kdenlive.

There are a lot of other advantages to this setup now that I have it running. I’ve barely scratched the surface of cool configurations I can do on this, as most of my ZFS experience prior to this was on FreeBSD, to which I only ever just added devices to zpools, or RAID, etc.

Hopefully this was informative to anyone interested in doing a setup like this, I’m certainly very happy with it now that I got it all figured out.

In closing, I am still working on my vision expenses, and have been streaming on Twitch 2-3 times a week. I want to start doing SysAdmin streams, and would love to discuss some topics with interested individuals. Feel free to join my Discord server and ask questions.

Thank you for reading, as always.

Written on February 16, 2022