Arch Linux Btrfs with hibernation in a swapfile

2023-03-09

The Arch Wiki technically contains everything needed to make this work, but
it isn't the best organized and not obvious how it should be done. Here I
document the process I took to make it work on two different PCs.

This post expects you to already know how to use the Linux command line.

If you need to see the images larger, simply zoom the page, or right-click and
open the image in a new tab and zoom there.

Feel free to skip around as needed.


Preparing for Installation

Head over to archlinux.org and get an ISO of Arch and write it to a USB or DVD,
if that's your style (it's too big for CD nowadays!). A tool like balenaEtcher
is user friendly, and I use it often. Of course dd works well, too, but it is
much easier to screw up and overwrite your data using it if done carelessly.
I suppose one could use cat as well, with a redirect:
cat arch.iso > /dev/sdX, where X is the appropriate drive. Still, cat is
dangerous like dd.

Get it booted up, be it UEFI or BIOS mode, and check to make sure the network
works. It's possible to do it with WiFi, but I prefer wired for setup.

Run # ip a and ensure you have an appropriate IP address and ping google.com
or some other known, good address.

Checking network connection before installing

Now, if the connection is working, the process can continue.
Run # lsblk and obtain the correct drive path.
In my case, it's /dev/vda but on a real machine, it's like /dev/sda.

A prompt with the output of lsblk

Either fdisk or gdisk can be used, whether using BIOS or UEFI, respectively.
These instructions are assuming a blank disk.
Since I'm using BIOS boot, I'll be using MBR instead of GPT, electing for fdisk.
I create the very first partition of 1 GiB for /boot, which will be formatted
as ext4; however, if GPT and UEFI is being used, it must be formatted as FAT32,
with type of EF00. Here, I leave the MBR type the default 0x83 for Linux.
I create the second partition which will be LUKS encrypted and formatted Btrfs.
It's also default type 0x83. I also marked the first partition as active.
Make sure you're partitioning the correct drive!

Fdisk partitioning

Once partitioning is done, the partitions must be formatted.
While the boot partition can be encrypted, frankly it's easier if it's not.
Here, since I'm using MBR, I can format as ext4 with
# mkfs.ext4 /dev/vda1, where vda1 corresponds to my 1st partition.
However with UEFI, it must be FAT32, # mkfs.fat -F32 /dev/vda1.
The UEFI spec calls for 32-bit FAT, which is why it is forced with -F 32.

Formatting the boot partition with ext4

Now that's done, I want to encrypt my main partition.
This isn't necessary for the rest of this post to work, but commands would be
adjusted accordingly.

# cryptsetup luksFormat /dev/vda2, where again, vda2 is replaced with your
appropriate partition and supply the new encryption password.
After the LUKS encryption is done, it needs to be opened:
# cryptsetup open /dev/vda2 luks, the luks at the end can be anything. It's
just what the mapping will be called, in this case /dev/mapper/luks.

Once the partition is authenticated, it can be formatted as Btrfs:
# mkfs.btrfs /dev/mapper/luks.

formatting LUKS and btrfs
It's time to create the Btrfs subvolumes.
I mount the new partition to /mnt: # mount /dev/mapper/luks /mnt.
Here I created the following: @, @home, @log, @snapshots, @swap.
The @ subvolume will just be the root /, and the others mounted within that.

Creating btrfs subvolumes

Now it must be remounted to have the subvolumes mounted in the correct place.
First the current should be unmounted: # umount /mnt.
Now, mounting the root subvolume @ to /mnt with the following:
# mount -o noatime,subvol=@ /dev/mapper/luks /mnt. The mount option noatime
is chosen to reduce the overhead of keeping access times for every file.

After the root is mounted, the new mount points needs to be made, whether with
the -m option with mount or manually with mkdir. Both options are shown here.
After mounting, I run mount again to ensure they mounted with the proper
options at the expected mount point, just to be safe.

Creating subvolume mountpoints


Installing Arch (finally)

While this step is optional, it doesn't hurt to generate a new mirrorlist
that works best for your location. It will be copied to the system during the
installation process. I recommend reading reflector's man page.

running reflector to sort repos based on speed and latest sync

Finally time to install Arch!
At the minimum you'll need base and linux packages. However, I added packages
that make my life easier further in the installation.
# pacstrap -K /mnt base base-devel linux linux-firmware screen neovim dhclient

pacstrap installing the Arch system

Arch is technically installed now
But it's in a completely useless state without being able to boot itself.

We need to generate a fstab for the new installation:
# genfstab -U /mnt >> /mnt/etc/fstab.
It should autodetect if it's on an SSD, but if not, it can be added to the
mount options afterwards.
After it is generated, check that it looks okay.

generating the new fstab

And I ended up adding ssd to my btrfs mount options using vim, as shown.

fstab but with ssd in mount options

Time to chroot!
We're going to change root to the new installation as if we were booted to it.
# arch-chroot /mnt

arch-chroot

Setting the timezone.
You can ls to find the appropriate region if you don't know already. While
I'm in Tennessee as of posting, I'm in Central time: America/Chicago.
I created a symlink to /etc/localtime as directed by the installation guide.
# ln -sf /usr/share/zoneinfo/America/Chicago /etc/localtime afterwards I run
# hwclock --systohc to sync the time to the CMOS of the system.

setting the timezone with a symlink

Setting the default language and hostname
This is fairly self-explanatory, although sed isn't necessary to uncomment
your respective locale. That said, if you would rather use a text editor like
nano it needs to be installed with pacman first, since I installed neovim in
my example here.

setting the language and hostname

mkinitcpio.conf
The init ramdisk needs to be configured to know how to decrypt the partition.
Add encrypt before filesystems and after block in the HOOKS about half-
way down the config file, using your editor of choice: I use neovim.
Note: That is not an underscore (it was my cursor) between encrypt
and filesystems, simply a space.

editing mkinitcpio.conf to support encryption

After mkinitcpio.conf is edited, run # mkinitcpio -P to generate the new initrd.

Setting up GRUB
Note: Keep in mind we're still in the arch-chroot!
It's best to read the Arch Wiki for GRUB beforehand, in case your setup is different, i.e., UEFI.
The GRUB package needs to be downloaded first, along with CPU microcode, if you
prefer (intel-ucode or amd-ucode). I recommend including the respective one.
# pacman -S grub intel-ucode
GRUB will find the microcode image and include it automatically.
Here, since my boot drive is still /dev/vda, that's where I'll install GRUB.
# grub-install --target=i386-pc /dev/vda,
where i386-pc means a BIOS/MBR system. For UEFI, it's x86_64-efi with the EFI
partition mount point supplied. Check the wiki as mentioned.

installing grub to the boot drive

Setting GRUB to boot encrypted partitions
Before the grub.cfg is created, some kernel boot parameters need to be specified
in /etc/default/grub.
First the UUID for the root partition (not the mapped partition!) needs to be saved. It may be easier to do all of this in an SSH session from another PC. The SSH daemon probably needs starting: # systemctl start sshd. In that case, using a multiplexer like GNU screen (which I had installed during pacstrap) is probably a good idea in the event the connection is dropped. Otherwise, continue forward.
While some mega-cool regex stuff could probably be done, I'd spend more time
trying to figure that out than just appending the UUID to the end of the config
and then just moving it inside the text editor later. In that case, I did the
following: # blkid -o value /dev/vda2 | head -n1 | tee -a /etc/default/grub.

getting the root UUID and appending to grub config

Afterwards, I used neovim to move the UUID into the kernel boot arguments.
The following is needed:
cryptdevice=UUID=<your UUID here>:root root=/dev/mapper/root

using neovim to correct the kernel options

Once the grub config is edited appropriately, run # grub-mkconfig -o /boot/grub/grub.cfg.

Creating the swap file
Before we forget, we need to disable Btrfs COW for the swap subvolume. This can
be achieved by adding the +C attribute to the mount directory.
# chattr +C /swap
This can be verified with # lsattr -d /swap as shown in the picture.
using chattr to set the swap mount

Luckily newer versions of Btrfs can generate the swap file for us.
However we need btrfs-progs: # pacman -S btrfs-progs. Then run,
# btrfs filesystem mkswapfile --size <your ram size>G /swap/swapfile
If you're confused about needing btrfs-progs, it's because we're in chroot. The
ISO of Arch has btrfs-progs included, but we're in the installed system's environment.
If you intend to hibernate, I recommend the swap size being at least equal
to that of your memory. Since my VM here has 2GiB of RAM, my swap will be 2G.
Afterwards I need to append the new swap file to the fstab so the system will
automatically use it. # echo "/swap/swapfile none swap defaults 0 0" | tee -a /etc/fstab
I made a typo in the image, 'default' should be 'defaults'.

creating swap file and appending to fstab

Piping to tee (with -a so we don't overwrite!) just allows me to ensure the
edited fstab is correct. Well, mine would be without the typo. Just pretend mine
is right!


Finally ready to reboot!

Maybe not quite ready. We still need to exit chroot and unmount partitions.
Simply # exit from chroot then unmount everything: # umount -R /mnt.
If no errors occurred when unmounting, it's safe to # reboot or # poweroff.
Note: Be sure to remove installation media after the reboot.
If all is well, the GRUB menu should load and start the kernel automatically,
landing you at the prompt to enter your LUKS password created a while ago.

Kernel needing LUKS pass to mount root

Characters will not echo to the screen.


First login to a base system

Login as root. And run # dhclient to get an IP address.

login prompt to the root account

Welcome to Arch Linux! It's not very convenient without a GUI, though.
Before that, I need to create a normal user and give it sudo privileges.
I decided giving the user the group wheel is the best, given it allows
for many other things. Use your editor of choice by adjusting the EDITOR variable.
I use neovim, so ran visudo with the following: # EDITOR=nvim visudo.
Uncomment the %wheel line as shown in the picture.

running visudo and uncommenting %wheel

Save and close the editor.

Create the new user: # useradd -m <username>, where -m generates a home dir.
Afterwards, assign a login password for the new user: # passwd <username>.

creating a new user and giving a password

Installing the desktop environment
I'm choosing KDE here given how feature rich it is, but which DE to use is your choice.
I elected to do a base KDE Plasma install with some necessities just for example.
Having a terminal emulator is important, so I chose the default KDE Konsole and
Firefox for my web browser.
It's best to read the wiki for desktop environments and display managers.

installing KDE plasma with pacman

Enabling the display manager and network manager
Before we can use the DE, we need to enable the display manager SDDM, and the
network manager to handle network connections (no more dhclient!).
# systemctl enable sddm and # systemctl enable NetworkManager

enabling sddm and NetworkManager with systemctl

Reboot once more.


Welcome to KDE

If all is well, SDDM should start up after rebooting (and unlocking LUKS).
Login as the new normal user.

SDDM login screen

KDE Plasma 5.27 desktop

SDDM is unthemed by default. That's easily fixed in System Settings.
Select the theme you like, apply, then also 'Apply Plasma settings'.

setting SDDM theme


Getting hibernation working

If hibernation isn't needed, you're done. Continue setting up the system how
you like. If you want hibernation, continue reading.

Editing mkinitcpio.conf, again.
I recommend, again, reading the Arch Wiki here.

Open up a terminal (Konsole for me). Root privileges will be needed. Either change
to root with $ su or $ sudo -s or just prefix everything with $ sudo.
Whatever you choose, open /etc/mkinitcpio.conf in a text editor of choice.
Add resume to the same place as before in HOOKS but after filesystems this time. Save and close the editor.

editing mkinitcpio.conf to support hibernation

Editing /etc/default/grub, again.
The kernel also needs to know where to retrieve the saved hibernation state when
waking up. GRUB, of course, can provide this with the kernel options.

Again we need a UUID of a partition, this time of the mapped LUKS partition and
not the physical partition as before. Before this, we'll need the offset of the
swap file on the Btrfs filesystem. This can be retrieved as root with the following:
# btrfs inspect-internal map-swapfile -r /swap/swapfile

It's probably best to write down the output number, unless you can remember it.
Now the UUID is needed.
# blkid -o value /dev/mapper/root | head -n1
I'd just copy the output to my clipboard, now we have a GUI to use. Open /etc/default/grub
as before and append resume=UUID=<your mapped root UUID> resume_offset=<number you got>
to the kernel arguments like we did for the LUKS setup.

adding resume support to GRUB kernel options

If all looks great, reboot the system one last time.

Now I have a Hibernate option in Plasma!

photo showing the new hibernate option in the KDE application launcher

Go ahead and test it out. If the system doesn't fully shut off but reboots, try
the following: modify /etc/systemd/sleep.conf.

Uncommenting HibernateMode and removing platform so that it's
HibernateMode=shutdown will likely be enough to fix it. If that doesn't work, it's possible your system firmware doesn't support hibernation. You'd need to consult help elsewhere in that case.

editing sleep.conf



That's it!

No seriously, that's the end of this post. Hopefully it helps someone!
Please email me any suggestions or corrections.