Hello Guix! Here is a feedback on enabling hibernation using a swapfile stored on btrfs filesystem. It wasn 't straight forward as it's not documented and I had to read the code do make it work.
Note that even tho swpafile on btrfs filesystem are supported, therea are some limitations⁵: - the filesystem must be an a single device, ie. no btrfs RAID, - the swapfile shouldn't be copy-on-write, - the swapfile shouldn't be compressed. Let's start with the btrfs specific part first. The following instructions was compiled from tutorials¹ ² ³ for other Linux distributions. First we gather the tools we'll need for the job, especially btrfs_map_physical⁴to find the offset from the start of the btrfs partition where the swapfile is located, this tool isn't packaged (I don't know if it should) so we'll build it in the old fashion way. --8<---------------cut here---------------start------------->8--- guix environment --ad-hoc gcc-toolchain curl gawk e2fsprogs btrfs-progs curl -OL https://github.com/osandov/osandov-linux/raw/master/scripts/btrfs_map_physical.c gcc -O2 -o ./btrfs_map_physical ./btrfs_map_physical.c --8<---------------cut here---------------end--------------->8--- Next we create our swapfile in its own subvolume named “swap” on the btrfs filesystem of device “/dev/sda2” (mounted at “/mnt/rootfs”) and we make sure our new swapfile isn't COWable nor compressed. To be able to hibernate in all cironstancese the swapfile need to be at least as big as the total RAM available. --8<---------------cut here---------------start------------->8--- swappath=/mnt/rootfs/swap swapfile="$swappath/swapfile" cd "$path" btrfs subvolume create swap mount -o subvol=swap /dev/sda2 ./swap truncate -s 0 "$swapfile" chattr +C "$swapfile" btrfs property set "$swapfile" compression none swap_size="$(grep MemTotal /proc/meminfo | awk '{print $2 * 1024}')" fallocate --length "$swap_size" "$swapfile" chmod 600 "$swapfile" mkswap "$swapfile" --8<---------------cut here---------------end--------------->8--- As we have a working swapfile, we'll genereate the kernel arguments to pass to the kernel. The less obvious being the offset of the swapfile from the start of the filesystem, to do so we are using the previously compiled “btrfs_map_physical” binary and few shell utilities. --8<---------------cut here---------------start------------->8--- swapon "$swapfile" swap_physical_offset=$(btrfs_map_physical "$swapfile" | sed -n "2p" | awk "{print \$NF}") swap_offset=$(echo "${swap_physical_offset} / $(getconf PAGESIZE)" | bc) swap_uuid=$(findmnt -no UUID -T "$swapfile") resume_args="resume=${swap_uuid} resume_offset=${swap_offset}" echo "${resume_args}" --8<---------------cut here---------------end--------------->8--- What's left is just to tweak our “operating-system” to make use of ourr new swapfile. --8<---------------cut here---------------start------------->8--- (operating-system (file-systems (cons* ... (file-system (mount-point "/") (device (uuid "12345678-1234-1234-1234-123456789abc" 'btrfs)) (type "btrfs") (options "compress=zstd,subvol=guix-system")) (file-system (mount-point "/swap") (device (uuid "12345678-1234-1234-1234-123456789abc" 'btrfs)) (type "btrfs") ;; Needed to access the swapfile? (not sure) (needed-for-boot? #t) (options "subvol=swap")) %base-file-systems)) (swap-devices (list "/swap/swapfile")) (kernel-arguments (cons* "resume="12345678-1234-1234-1234-123456789abc" "resume_offset=16400" %default-kernel-arguments)) ...) --8<---------------cut here---------------end--------------->8--- Now we can reconfigure our system and reboot it. At that point we are able to use “loginctl hibernate” or “loginctl hybrid-sleep” to save our current system state to the swapfile and recover it at the next reboot. As a keen reader may have noticed, enabling hibernation in Guix in this case has a quirk. The most the most inconvenient is the Guix's “resume” kernel argument format being different from Linux's⁶ « {/dev/<dev> | PARTUUID=<uuid> | <int>:<int> | <hex>} » and other distributions allowing « UUID=<uuid> »; in Guix the uuid isn't prefixed with “UUID=” or “PARTUUID=” and this isn't documented anywhere, you are forced to read the code. Also, having swap related configuration spread between “swap-devices” and “kernel-arguments” fields make me think having specific records for that first field would make sense. And that would make the “resume“ format issue irrelevant. WDYT about adding the following records to be used in “swap-devices” record in addition to the current types? --8<---------------cut here---------------start------------->8--- (swap-file (file "/swap/swapfile") (offset 16400) (device (uuid "12345678-1234-1234-1234-123456789abc")) (resume? #t) ; default #f (priority 21)) ; swapflags argument passed to swapon(2), default 0 (swap-device (device "/dev/sda3") ; any of the usual types allowed in 'swap-devices' (resume? #t) ; default #f (priority 42)) ; swapflags argument passed to swapon(2), default 0 --8<---------------cut here---------------end--------------->8--- I wasn't sure were this documentation should go in Guix, except for the btrfs instrructions which will probably end up in the cookbook. ¹ https://wiki.archlinux.org/title/btrfs#Swap_file ² https://superuser.com/a/1613639 ³ https://wiki.archlinux.org/title/Power_management/Suspend_and_hibernate#Hibernation_into_swap_file_on_Btrfs ⁴https://github.com/osandov/osandov-linux/blob/master/scripts/btrfs_map_physical.c ⁵ https://btrfs.wiki.kernel.org/index.php/FAQ#Does_Btrfs_support_swap_files.3F ⁶ https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html Cheers, - Brice