Hibernating to an Encrypted Swapfile on BTRFS with NixOS
Note that BTRFS swapfiles across multiple devices (pools, RAIDs) are not supported1.
BTRFS on Linux kernels before version 5.0 can result in filesystem corruption2. From the Arch Wiki:
Btrfs on Linux kernel before version 5.0 does not support swap files. Failure to heed this warning may result in file system corruption. While a swap file may be used on Btrfs when mounted through a loop device, this will result in severely degraded swap performance.
You must make a BTRFS subvolume and disable copy-on-write:
sudo btrfs subvolume create /swap
sudo chattr +C /swap
Create a swapfile of your chosen size. I chose the size of my memory +4kB for swap headers and such
sudo fallocate --length 15698384KiB /swap/swapfile
Format the swapfile
sudo mkswap /swap/swapfile
Enable the swapfile
sudo swapon /swap/swapfile
Add the following to your NixOS hardware configuration file3:
fileSystems."/swap" = {
device = "/dev/disk/by-uuid/[uuid-of-your-btrfs-partition]";
fsType = "btrfs";
options = [ "subvol=swap" "noatime" ];
};
swapDevices = [ { device = "/swap/swapfile"; } ];
The UUID for the swap subvolme is the exact same as the UUID
for the whole BTRFS partition. You don’t need to specify a
subvol
option to mount the root BTRFS partition.
Next, we need to find some values to add to our kernel boot parameters. Specifically, we need the UUID of the swap file and the swap file offest. Find the UUID of the swap device with:
findmnt -no UUID -T /swap/swapfile
Note that this UUID should point to your mounted, mapped
BTRFS filesystem. In /dev/disk/by-uuid/[...]
, it’s probably a
symlink to /dev/dm-X.
Now we need to find the physical swap file offset. In
filesystems other than BTRFS, this is relatively easy, as we
can use filefrag
. Unfortunately, filefrag does not report
the real physical offset of files on BTRFS systems (this is
intended)4. Fortunately, a user called Osandov has recognized
this problem and published a program to find the physical
offset of a file on BTRFS
here.
Download the program and compile it:
wget https://raw.githubusercontent.com/osandov/osandov-linux/master/scripts/btrfs_map_physical.c
gcc btrfs_map_physical.c -o btrfs_map_physical
Now use the program to find the offset:
sudo ./btrfs_map_physical /swap/swapfile
Find the first physical offset provided and divide it by the page size of your system. Usually this is 4096, but you can also find it with:
getconf PAGESIZE
Add this to your NixOS hardware configuration:
boot.resumeDevice = "/dev/disk/by-uuid/[swap_device_uuid]"
boot.kernelParams = [ "resume_offset=[offset]" ];
And finally, rebuild and switch:
sudo nixos-rebuild switch
If you have any issues with your device not shutting off after hibernating, add this to your NixOS config:
systemd.sleep.extraConfig = ''
[Sleep]
HibernateMode=shutdown
'';