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.
Download the official Arch Linux ISO.
Check which USB stick you are going to install an ISO to with lsblk.
Format the USB with dd.
sudo dd bs=4M if=archlinux-<your-date>-x86_64.iso of=/dev/<your-usb> status=progress oflag=sync
The assumption is that a laptop is being used, intended to connect to the Internet via a wireless connection.
iwctl station <device> connect <SSID>To continue setup of your machine via SSH, check if the sshd service is running and set the root password via passwd.
systemctl status sshd.service
passwdCheck you IP address with ip addr show. SSH from another machine via ssh root@<ip>.
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 pl
setfont ter-v24bOptionally turn off the pcspeaker as well: setterm -blength 0.
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_sizeIf 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/nvme0n1Tools 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 10240MiB \
set 1 esp on \
mkpart Linux btrfs 10240MiB 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/nvme0n1p1Simple as that:
cryptsetup luksFormat /dev/nvme0n1p2It 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 rootFormat it as BTRFS:
mkfs.btrfs /dev/mapper/rootMount (temporarily) the newly created btrfs file system in \mnt:
mount /dev/mapper/root /mntAnd 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/@snapshotsAt 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/bootAnd 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-zen linux-lts linux-firmware git vim neovim btrfs-progs efibootmgr limine cryptsetup iwd impala reflector zsh bash-completion avahi nss-mdns acpi acpi_call acpid alsa-utils pipewire pipewire-alsa pipewire-pulse pipewire-jack wireplumber ufw bluez bluez-utils cups util-linux terminus-font openssh man man-pages sudo rsync amd-ucodeN.B. Replace amd-ucode with intel-ucode if you have an Intel processor.
Make fstab file from your current filesystem layout. Edit it manually in case of some obvious errors.
genfstab -U /mnt >> /mnt/etc/fstabIt's time to chroot to our /mnt point.
arch-chroot /mntSet the local time properly. Use your time zone.
ln -sf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime
hwclock --systohcIn the file /etc/locale.gen uncomment all the needed locales. In my case en_US.UTF-8 UTF-8 and pl_PL.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.confCreate /etc/vconsole.conf file and add the following inside it. (FONT variable is optional)
vim /etc/vconsole.conf
KEYMAP=pl
FONT=ter-v24bSet a hostname.
echo arch > /etc/hostnameSet up the root password
passwdAdd a new user, add it to wheel group and set a password.
useradd -mG wheel mgajewskik
passwd mgajewskikExecute the following command and uncomment the line to let members of group wheel execute any program
EDITOR=vim visudoModify /etc/mkinitcpio.conf to have btrfs in MODULES, /usr/bin/btrfs in BINARIES, and sd-encrypt in HOOKS. Add sd-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 sd-encrypt and filesystem hooks.
MODULES=(btrfs)
...
BINARIES=(/usr/bin/btrfs)
...
HOOKS=(base systemd autodetect microcode modconf kms keyboard keymap sd-vconsole block sd-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' \
--unicodeN.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 rd.luks.name=<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 rd.luks.name=<device-UUID>=root root=/dev/mapper/root rw rootflags=subvol=@ rootfstype=btrfs
module_path: boot():/initramfs-linux-fallback.imgRemember 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 systemd-networkd, systemd-resolved and iwd services before rebooting. Otherwise, you won't be able to connect.
systemctl enable iwd
systemctl enable systemd-networkd
systemctl enable systemd-resolved
systemctl enable bluetooth
systemctl enable cups
systemctl enable ufw
systemctl enable reflector.timerAdd configuration files for systemd-networkd setup, check network interface with ip link show:
/etc/systemd/network/25-wireless.network
[Match]
Name=wlp2s0
[Link]
RequiredForOnline=routable
[Network]
DHCP=yes
IgnoreCarrierLoss=3s
or link default examples from the documentation: https://wiki.archlinux.org/title/Systemd-networkd#Configuration
ln -s /usr/lib/systemd/network/80-wifi-station.network.example /etc/systemd/network/80-wifi-station.network
ln -s /usr/lib/systemd/network/89-ethernet.network.example /etc/systemd/network/89-ethernet.networkI don't like the sound of the PC speaker so I like to blacklist kernel modules responsible for it.
To do so, let's create the /etc/modprobe.d/nobeep.conf file with contents:
blacklist pcspkr
blacklist snd_pcsphttps://wiki.archlinux.org/title/PC_speaker#Disabling_the_PC_speaker
To change the lid behaviour modify the /etc/systemd/logind.conf file:
HandlePowerKey=ignore
HandlePowerKeyLongPress=poweroff
HandleLidSwitch=suspend
HandleLidSwitchExternalPower=suspend
HandleLidSwitchDocked=ignoreUncomment Color from /etc/pacman.conf
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
rebootDo 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>For convenience, an impala TUI can be used to make this wireless connection.
To make the systemd-resolved work properly in a stub mode, link the default configuration to the /etc/resolv.conf and restart the service:
ln -sf ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
systemctl restart systemd-resolved.servicehttps://wiki.archlinux.org/title/Systemd-resolved#DNS
If you want to continue using SSH, make sure the sshd service is enabled and edit the /etc/ssh/sshd_config to include the following lines. Remember to revert this change back! You don't want to leave open SSH root access to your machine.
PermitRootLogin yes
PasswordAuthentication yesThen restart the sshd service with:
systemctl enable --now sshd.serviceA set of useful stuff...
pacman -Syu wget htop gvfs gvfs-smb imagemagick usbutils zip unzip brightnessctl noto-fonts ttf-jetbrains-mono libreoffice-fresh libreoffice-fresh-pl firefox ghostty ttf-nerd-fonts-symbols-common ttf-nerd-fonts-symbols ttf-jetbrains-mono-nerd fzf go tmux udiskie bind pavucontrol inetutils nmapOptional (only for Intel video):
pacman -Syu intel-media-driver mesa vulkan-intelIt is not recommended to use makepkg as root, so this step is supposed to be performed with a user account.
sudo pacman -S --needed git base-devel && git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -siCheck 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 trueCheck 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.timerAdd /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 35g --uuid clear /swap/swapfile
swapon -p 0 /swap/swapfileAdd the following in the /etc/fstab:
/swap/swapfile none swap defaults,pri=0 0 0Add resume in /etc/mkinitcpio.conf HOOKS...
HOOKS=(base systemd autodetect microcode modconf kms keyboard keymap sd-vconsole block sd-encrypt filesystems resume fsck)... and execute:
mkinitcpio -PNote: It is not mandatory anymore (on UEFI systems) but 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 i.e. findmnt -no UUID -T /swap/swapfile.
To find the proper swap offset run:
sudo btrfs inspect-internal map-swapfile -r /swap/swapfileCheck all possible commands (some will not be available before reboot): https://wiki.archlinux.org/title/Power_management/Suspend_and_hibernate#High_level_interface_(systemd)
yay -Syu snapper limine-snapper-sync limine-mkinitcpio-hookAdd sd-btrfs-overlayfs at the end of the HOOKS in /etc/mkinitcpio.conf and execute mkinitcpio -P
The /.snapshots directory needs to be unmounted and removed before proceeding. Snapper will create it on it's own during the setup process.
umount /.snapshots
rm -rf /.snapshots
snapper -c root create-config /
snapper -c home create-config /home
sudo mount -a
sudo sed -i 's/^TIMELINE_CREATE="yes"/TIMELINE_CREATE="no"/' /etc/snapper/configs/{root,home}
sudo sed -i 's/^NUMBER_LIMIT="50"/NUMBER_LIMIT="10"/' /etc/snapper/configs/{root,home}
sudo sed -i 's/^NUMBER_LIMIT_IMPORTANT="10"/NUMBER_LIMIT_IMPORTANT="10"/' /etc/snapper/configs/{root,home}Symlink configuration from /etc/limine-snapper-sync.conf to /etc/default/limine with sudo ln -sf /etc/limine-snapper-sync.conf /etc/default/limine change in /etc/default/limine:
MAX_SNAPSHOT_ENTRIES=20
LIMIT_USAGE_PERCENT=85
ROOT_SNAPSHOTS_PATH="/@snapshots"In /etc/limine-snapper-sync.conf and /etc/default/limine find and disable/remove (if not used):
#COMMANDS_BEFORE_SAVE="limine-reset-enroll"
#COMMANDS_AFTER_SAVE="limine-enroll-config"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.
term_font_scale: 2x2
/+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
//SnapshotsThere will be an additional Limine config file in the /boot/EFI/limine/limine.conf that will take precedence during the boot process. Remove it or back it up somewhere as we want to use the new /boot/limine.conf file that will be synced with Snapper.
Execute the command limine-snapper-sync and if there are no errors systemctl enable --now limine-snapper-sync.service.
Open the /boot/limine.conf file to check if the boot order has not changed which can happen if many kernels were added along the standard linux kernel. Change the ordering if that happened.
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
To create a snapshot of the root subvolume run:
sudo snapper -c root create -c number -d "first manual snapshot"The snapshot should be automatically added to the /boot/limine.conf file and should be available during the boot process.
Optionally install snap-pac. It triggers snapper to create snapshots during system updates.
pacman -Syu snap-pacTo create a backup of the home subvolume run: sudo snapper -c home create -c number -d "description"
To restore a root snapshot, reboot and select which snapshot to boot from. After the reboot run sudo limine-snapper-restore and reboot again.
To restore desired files from the home snapshot run: sudo snapper -c home undochange <old_number>..0 /path/to/file/or/dir
More information regarding Snapper integration with Limine (on Btrfs) here: https://wiki.archlinux.org/title/Limine#Snapper_snapshot_integration_for_Btrfs
To enable cleanup of the old snapshots enable the snapper-claenup service with: systemctl enable --now snapper-cleanup.timer.
This will make sure that the total count will stay within the given limits from the configration files. When the total count exceeds the declared snapshot boot entries in limine, no more snapshots will be added as boot entries. This is the most problematic for the snapshots tagged as important which will include different versions of the linux kernel as they will be pushed away from the boot list by other less important snapshots that were created on other packages addition or deletetion.
pacman -Syu fwupd udisks2
fwupdmgr get-devices
fwupdmgr refresh
fwupdmgr get-updates
fwupdmgr update
systemctl enable --now fwupd-refresh.timerFor the graphical environment I will use:
- UWSM for setting up Wayland compositors
- MangoWC as my main Wayland compositor
- Noctalia as a minimal desktop shell
yay -S uwsm libnewt mangowc fuzzel noctalia-shell xdg-desktop-portal xdg-desktop-portal-wlr xdg-desktop-portal-gnome xdg-desktop-portal-gtk wl-clipboard wl-clipboard-x11 wl-clip-persist slurp hyprshot satty hypridle vicinae cliphist nemo nemo-audio-tag nemo-image-converter nemo-fileroller papirus-icon-theme phinger-cursors arc-gtk-theme nwg-lookAdd this to your .zprofile or .profile file to kickstart uwsm on each login shell:
if uwsm check may-start && uwsm select; then
exec uwsm start default
fiI will be using ZSH, so I am creating the .zprofile file and running: chsh -s $(which zsh)
I will also install Hyprland as a spare compositor:
yay -S hyprland hyprland-protocols nwg-displays xdg-desktop-portal-hyprlandNow run systemctl reboot and enter your newly configured system.
To setup other declarative configuration clone this repository: https://github.com/mgajewskik/configs
ZSH and Neovim plugins will be installed automatically on the first run.