LUKS2 encrypted BTRFS system partition with Limine/Snapper integration and hybernate to swapfile
- Table of Contents
- Introduction
- Preparation
- Format the ESP partition
- Prepare the system partition
- Installing the Arch Linux
- Setting up Limine bootloader
- Final steps
- Useful post-install steps
This is my typical installation of Arch Linux with LUKS2 encrypted BTRFS system partition on an UEFI system. This document does not replace the official Arch Linux installation guide and the accompanying documentation. It is merely a condensed note of the most important steps, intended for my personal use. Use it at your own risk, and keep in mind that some of the instructions below may be outdated or unsuitable for your specific case.
The following features are characteristic of this installation:
- The system partition is encrypted with LUKS2 and formatted with the BTRFS file system using subvolumes.
- The ESP partition (FAT32) is not encrypted and is mounted as
/boot
(if that's not secure enough for someone, additional measures can be taken, such as using Secure Boot). - I like small, fast and sexy lightweight bootloaders, which is why Limine is the choice of mine. It has a wonderful integration with Snapper also.
- The installation also provides functional network connectivity after the reboot.
- The goal of the installation is a system intended to be further configured for everyday desktop use.
It is assumed that the system has been successfully booted from the official Arch Linux ISO.
The assumption is that a laptop is being used, intended to connect to the Internet via a wireless connection.
iwctl station <device> connect <SSID>
Use the command localectl list-keymaps
or something like ls /usr/share/kbd/keymaps/**/*.map.gz | grep bg
to list keymaps. Or just set:
loadkeys us
setfont ter-132b
I usually explicitly use UEFI 64-bit systems such as the Lenovo ThinkPad X1, T470s, T480s, T14, T14s, and other similar models, but you can check whether the current system is one by running the command:
cat /sys/firmware/efi/fw_platform_size
If this command prints 64 or 32 then the current system is UEFI.
N.B. The assumption here is that we are setting up a new computer that will run only Arch Linux. No dual boot with Windows or other Linux installations.
One partition is needed for the ESP, which will be used for /boot
with a FAT32 file system. The remaining disk space will be encrypted with LUKS2 and formatted with BTRFS. This setup allows for flexible management of subvolumes within the container for specific directories, including snapshots or swap.
I prefer to have enough space in /boot
for various purposes (including possibly multiple kernel versions), so I usually make this partition at least 2GB.
To start fresh, let's wipe all existing partitions on the disk. If there's any chance you might miss something currently stored on this disk, now is the time to proactively make a backup.
sgdisk --zap-all /dev/nvme0n1
Tools like fdisk
, cfdisk
, cgdisk
, and others can be used here, but parted
has the advantage of being script-friendly.
parted --script /dev/nvme0n1 \
mklabel gpt \
mkpart ESP fat32 1MiB 2049MiB \
set 1 esp on \
mkpart Linux btrfs 2050MiB 100%
Most probably the partition will be /dev/nvme0n1p1
(partition 1
on disk nvme0n1
), but this can be checked with lsblk
command.
Let's format the ESP partition as FAT32.
mkfs.fat -F 32 /dev/nvme0n1p1
Simple as that:
cryptsetup luksFormat /dev/nvme0n1p2
It should be more than obvious that the password used to encrypt this system partition must not be random and obvious, but it also must not be forgotten.
Later, the UUID of this container will be needed, which can be obtained using the command cryptsetup luksUUID /dev/nvme0n1p2
or the command ls -l --time-style=+ /dev/disk/by-uuid/
. Make sure to save this UUID somewhere.
First, open the container (should appear as /dev/mapper/root
using the command below):
cryptsetup open /dev/nvme0n1p2 root
Format it as BTRFS:
mkfs.btrfs /dev/mapper/root
Mount (temporarily) the newly created btrfs file system in \mnt
:
mount /dev/mapper/root /mnt
And let's make some subvolumes.
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@var_log
btrfs subvolume create /mnt/@var_cache
btrfs subvolume create /mnt/@snapshots
At a later stage, another one for swap (and hibernation) will be added. However, this is done from the comfort of an already installed and functioning system. It is not critical to perform this manually at this point.
Let's unmount the temporarily mounted file system and mount the subvolumes instead (incl. the the vfat ESP partition). If you don't want compression just remove the compress option, but level 1
is great for NMVE disks. Also noatime
is fine for personal desktop setup.
umount /mnt
mount -o compress=zstd:1,noatime,subvol=@ /dev/mapper/root /mnt
mount --mkdir -o compress=zstd:1,noatime,subvol=@home /dev/mapper/root /mnt/home
mount --mkdir -o compress=zstd:1,noatime,subvol=@var_log /dev/mapper/root /mnt/var/log
mount --mkdir -o compress=zstd:1,noatime,subvol=@var_cache /dev/mapper/root /mnt/var/cache
mount --mkdir -o compress=zstd:1,noatime,subvol=@snapshots /dev/mapper/root /mnt/.snapshots
mount --mkdir /dev/nvme0n1p1 /mnt/boot
And we are ready to go with the installation itself.
Let's install some packages. Many guides will tell you that pacstrap -K /mnt base linux linux-firmware
is more than enough. Even if that’s true, there are somehow higher expectations...
pacman -Syy
pacstrap -K /mnt base base-devel linux linux-firmware git vim btrfs-progs efibootmgr limine cryptsetup dhcpcd iwd networkmanager reflector bash-completion avahi acpi acpi_call acpid alsa-utils pipewire pipewire-alsa pipewire-pulse pipewire-jack wireplumber sof-firmware firewalld bluez bluez-utils cups util-linux terminus-font openssh man sudo rsync intel-ucode
N.B. Replace intel-ucode
with amd-ucode
if you have an AMD processor.
Make fstab file from your current filesystem layout. Edit it manually in case of some obvious errors.
genfstab -U /mnt >> /mnt/etc/fstab
It's time to chroot to our /mnt point.
arch-chroot /mnt
Set the local time properly. Use your time zone.
ln -sf /usr/share/zoneinfo/Europe/Sofia /etc/localtime
hwclock --systohc
In the file /etc/locale.gen
uncomment all the needed locales. In my case en_US.UTF-8 UTF-8
and bg_BG.UTF-8 UTF-8
. Save the changes in the file and execute command locale-gen
after that. At the end add the English locale in /etc/locale.conf
.
vim /etc/locale.gen
locale-gen
echo LANG=en_US.UTF-8 > /etc/locale.conf
Create /etc/vconsole.conf
file and add the following inside it. (FONT variable is optional)
vim /etc/vconsole.conf
KEYMAP=us
FONT=ter-132b
Set a hostname.
echo arch > /etc/hostname
Set up the root password
passwd
Add a new user, add it to wheel group and set a password.
useradd -mG wheel yovko
passwd yovko
Execute the following command and uncomment the line to let members of group wheel execute any program
EDITOR=vim visudo
Modify /etc/mkinitcpio.conf
to have btrfs
in MODULES, /usr/bin/btrfs
in BINARIES, and encrypt
in HOOKS. Add encrypt
hook after block
and before filesystems
.
If hibernation is to be used, resume
needs to be added (somewhere after udev
). If it is from a swap file inside an encrypted container (as in this case), then resume
should be placed after the encrypt
and filesystem
hooks.
MODULES=(btrfs)
...
BINARIES=(/usr/bin/btrfs)
...
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt filesystems fsck)
...
Don't forget to execute mkinitcpio -P
command.
Limine setup on UEFI systems is very simple. Just make /boot/EFI/limine
directory and copy the relevant file BOOT file there. For more detailed instructions check the Limine page in the Arch wiki.
mkdir -p /boot/EFI/limine
cp /usr/share/limine/BOOTX64.EFI /boot/EFI/limine/
Now we need to create an entry for Limine in the NVRAM:
efibootmgr --create --disk /dev/nvme0n1 --part 1 \
--label "Arch Linux Limine Bootloader" \
--loader '\EFI\limine\BOOTX64.EFI' \
--unicode
N.B. --disk /dev/nvme0n1 --part 1
means /dev/nvme0n1p1
N.B. Do NOT include boot
when pointing to Limine Bootloader file.
N.B. In a Limine config, boot():/
represents the partition on which limine.conf
is located.
Finally let's make a basic configuration for Limine. Use the command vim /boot/EFI/limine/limine.conf
and write inside the following (or at least the first of the two sections):
timeout: 3
/Arch Linux
protocol: linux
path: boot():/vmlinuz-linux
cmdline: quiet cryptdevice=UUID=<device-UUID>:root root=/dev/mapper/root rw rootflags=subvol=@ rootfstype=btrfs
module_path: boot():/initramfs-linux.img
/Arch Linux (fallback)
protocol: linux
path: boot():/vmlinuz-linux
cmdline: quiet cryptdevice=UUID=<device-UUID>:root root=/dev/mapper/root rw rootflags=subvol=@ rootfstype=btrfs
module_path: boot():/initramfs-linux-fallback.img
Remember being advised to save an UUID? Now is the time to use it. Replace the <device-UUID>
above with the UUID of your LUKS container.
Enable NetworkManager
and systemd-networkd
services before rebooting. Otherwise, you won't be able to connect. The systemd-resolved
service is a kind of optional, but most probably it is better to enable it. Also you may need dhcpcd.service
and (if you need WiFi) iwd.service
.
systemctl enable NetworkManager
systemctl enable dhcpcd
systemctl enable iwd
systemctl enable systemd-networkd
systemctl enable systemd-resolved
systemctl enable bluetooth
systemctl enable cups
systemctl enable avahi-daemon
systemctl enable firewalld
systemctl enable acpid
systemctl enable reflector.timer
It's time to exit the chroot, unmount the /mnt
, close the crypted container and reboot to the newly installed Arch Linux.
exit
umount -R /mnt
cryptsetup close root
reboot
Do not forget to unplug the installation media (USB stick).
Enjoy!
Act as root
, otherwise use sudo
.
A working network configuration needs to be adjusted according to this guide. In my case I just need to do (once again):
iwctl station <device> connect <SSID>
A set of useful stuff... and some Intel video drivers (specific to my hardware).
pacman -Syu wget htop gvfs gvfs-smb inetutils imagemagick usbutils easyeffects openbsd-netcat nss-mdns bat zip unzip brightnessctl xdg-user-dirs noto-fonts nerd-fonts ttf-jetbrains-mono libreoffice-fresh libreoffice-fresh-bg chromium thunderbird
Optional (only for Intel video):
pacman -Syu intel-media-driver mesa vulkan-intel mesa-vdpau
pacman -S --needed git base-devel && git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si
Check if ntp
is active and if the time is right. Enable and start the time synchronization service (usually it is not enabled).
timedatectl
timedatectl set-ntp true
Check if the SSD drive supports TRIM with the command lsblk --discard
. Non-zero values of DISC-GRAN (discard granularity) and DISC-MAX (discard max bytes) indicate TRIM support.
The util-linux
package provides fstrim.service
and fstrim.timer
systemd unit files. Enabling the timer will activate the service weekly. This is the so-called periodic TRIM.
pacman -S --needed util-linux
systemctl enable --now fstrim.timer
Add /etc/pacman.d/hooks/99-limine.hook
file with the following content:
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = limine
[Action]
Description = Deploying Limine after upgrade...
When = PostTransaction
Exec = /usr/bin/cp /usr/share/limine/BOOTX64.EFI /boot/EFI/limine/
Create swapfile in swap subvolume and enable it. I use swap to hibernate my laptop so, the size is the size of my RAM.
btrfs subvolume create /swap
btrfs filesystem mkswapfile --size 32g --uuid clear /swap/swapfile
swapon -p 0 /swap/swapfile
Add the following in the /etc/fstab
:
/swap/swapfile none swap defaults,pri=0 0 0
Add resume
in /etc/mkinitcpio.conf
HOOKS...
HOOKS=(base udev keyboard autodetect microcode modconf kms keymap consolefont block encrypt filesystems resume fsck)
... and execute:
mkinitcpio -P
When using the resume=
kernel parameter, it must point to the unlocked/mapped device (/dev/mapper/root
) that contains the file system with the swap file. So, let's find it.
findmnt -no UUID -T /swap/swapfile
btrfs inspect-internal map-swapfile -r /swap/swapfile
The output of the first command is the UUID of the resume=UUID=<UUID>
and the output of the second one is the resume_offset=
kernel parameter(s).
Finally, let's change the cmdline
in the /boot/EFI/limine/limine.conf
file adding resume=UUID=<UUID-of-/dev/mapper/root> resume_offset=<the-number>
at the end of the line.
cmdline: quiet cryptdevice=UUID=<device-UUID>:root root=/dev/mapper/root rw rootflags=subvol=@ rootfstype=btrfs resume=UUID=<UUID-of-/dev/mapper/root> resume_offset=<the-number>
Test the hybernation with the command systemctl hibernate
.
pacman -Syu snapper
yay -S limine-snapper-sync limine-mkinitcpio-hook
Add btrfs-overlayfs
at the end of the HOOKS in /etc/mkinitcpio.conf
and execute mkinitcpio -P
umount /.snapshots
snapper -c root create-config /
snapper -c home create-config /home
mount -a
chmod 750 /.snapshots
Change in /etc/snapper/configs/root
and /etc/snapper/configs/home
TIMELINE_CREATE, NUMBER_LIMIT and NUMBER_LIMIT_IMPORTANT according to your preferences. Also in /etc/default/limine
MAX_SNAPSHOT_ENTRIES, LIMIT_USAGE_PERCENT and especially ROOT_SNAPSHOTS_PATH.
My settings are:
TIMELINE_CREATE="no"
NUMBER_LIMIT="5"
NUMBER_LIMIT_IMPORTANT="5"
MAX_SNAPSHOT_ENTRIES=5
LIMIT_USAGE_PERCENT=85
ROOT_SNAPSHOTS_PATH="/@snapshots"
Create (if it isn't there yet) /boot/limine.conf
file and add //Snapshots
inside. Replace <machine-id>
with the output from the command cat /etc/machine-id
.
/+Arch Linux
comment: Arch Linux
comment: machine-id=<machine-id>
//Linux
protocol: linux
path: boot():/vmlinuz-linux
cmdline: quiet cryptdevice=UUID=XXXXXX-XXXXXX:root root=/dev/mapper/root rw rootflags=subvol=@ rootfstype=btrfs resume=UUID=YYYYY-YYYYYY resume_offset=ZZZZZZZ
module_path: boot():/initramfs-linux.img
//Snapshots
Execute the command limine-snapper-sync
and if there are no errors systemctl enable --now limine-snapper-sync.service
.
Available (important) commands are:
limine-snapper-list
- displays the current Limine snapshot entrieslimine-snapper-sync
- synchronizes Limine snapshot entries with the Snapper listlimine-snapper-info
- provides information about versions, the total number of bootable snapshots, etc.limine-snapper-restore
- restores the system, including matching kernel versions, from a selected bootable snapshot
Optionally install snap-pac
. It triggers snapper to create snapshots during system updates.
pacman -Syu snap-pac
More information regarding Snapper integration with Limine (on Btrfs) here: https://wiki.archlinux.org/title/Limine#Snapper_snapshot_integration_for_Btrfs
pacman -Syu fwupd udisks2
fwupdmgr get-devices
fwupdmgr refresh
fwupdmgr get-updates
fwupdmgr update
systemctl enable --now fwupd-refresh.timer
TODO: Not finished yet
pacman -S --needed sudo pacman -S hyprland nwg-displays xdg-desktop-portal-hyprland swaylock wofi waybar dolphin kitty seatd uwsm mako
yay -S wlogout
systemctl enable --now seatd.service
Optional: Configure Bulgarian (Phonetic) keyboard support (or any other non-default layout) and proper monitor:
~/.config/hypr/hyprland.conf:
input {
# Bulgarian Phonetic support
kb_layout = us, bg
kb_variant = , phonetic
kb_options = grp:win_space_toggle
}
monitorv2 {
output = eDP-1
mode = 3840x2400@60
position = 0x0
scale = 2
}
Add in your ~/.profile
file:
if uwsm check may-start; then
exec uwsm start hyprland.desktop
fi
And follow:
- Arch Linux Hyprland guide: https://wiki.archlinux.org/title/Hyprland
- UWSM guide: https://wiki.archlinux.org/title/Universal_Wayland_Session_Manager
- Hyprland Master tutorial: https://wiki.hypr.land/Getting-Started/Master-Tutorial/