Arch Install with Drive Encryption, BTRFS, and ZRAM

March 1, 2024

I tend to use Fedora as my daily OS, but every once in a while I get the itch to check out Arch again. This is my collection of arch install notes I’ve accumulated over the years. Following these notes will get you a base Arch install with LUKS encryption, BTRFS, and zram swap enabled. I’ve collected all this into an Arch Install Script. The script just runs the steps outlined below.

Keep in mind these notes are based around a UEFI system with secure boot turned off.

See the Arch Install Guide for more details about the install process.

Basic Install

First start by syncing the system clock with

# timedatectl

Partitions

You can then partition the disks. I tend to like to keep it simple with 2 partitions, 1 for boot and 1 for root respectively.

# parted --script "${device}" mklabel gpt \
	mkpart efi fat32 1Mib 261MiB \
	set 1 boot on \
	mkpart system btrfs 261MiB 100%

Encryption Setup & Formatting

This is an overview for setting up LUKS on a partition.

Find your device with lsblk and run the following commands to encrypt the root partition.

# mkfs.fat -F32 -n EFI /dev/disk/by-partlabel/efi
# cryptsetup -y -v luksFormat /dev/disk/by-partlabel/system
# cryptsetup open /dev/disk/by-partlabel/system root
# mkfs.btrfs -f /dev/mapper/root

Create BTRFS subvolumes

Subvolumes allow for snapshots and rollbacks. There are several possible subvolume layouts, this is what I’ve settled on based on archinstaller:

# mount -t btrfs /dev/mapper/root /mnt
# btrfs subvolume create /mnt/@
# btrfs subvolume create /mnt/@home
# btrfs subvolume create /mnt/@log
# btrfs subvolume create /mnt/@pkg
# btrfs subvolume create /mnt/@snapshots
# btrfs subvolume set-default <subvol-id-of-@> /mnt
# umount -R /mnt

Mount files systems

Now we need to mount the file system to prep for installing Arch. I’ve chosen noatime and compress=zstd mount options based on some recommendations from the original btrfs website.

# m_opts=noatime,compress=zstd
# mount btrfs -o defaults,$m_opts,subvol=@ /dev/mapper/root /mnt
# mount --mkdir -t btrfs -o defaults,$m_opts,subvol=@home /dev/mapper/root /mnt/home
# mount --mkdir -t btrfs -o defaults,$m_opts,subvol=@snapshots /dev/mapper/root /mnt/.snapshots
# mount --mkdir -t btrfs -o defaults,$m_opts,subvol=@log /dev/mapper/root /mnt/var/log
# mount --mkdir -t btrfs -o defaults,$m_opts,subvol=@pkg /dev/mapper/root /mnt/var/cache/pacman/pkg
# mount --mkdir -t btrfs -o defaults,$m_opts,subvolid=5 /dev/mapper/root /mnt/btrfs
# mount --mkdir LABEL=EFI /mnt/boot

Install Packages

Update the arch mirror list with reflector.

# reflector --latest 5 --sort rate --country US --save /etc/pacman.d/mirrorlist

Now that the drives are mounted and mirrors updated you can run pacstrap to install the base packages and kernel. I’ve go a selection of packages I like here, but feel free to change it up.

# pacstrap -K /mnt base base-devel linux linux-firmware linux-headers \
	e2fsprogs btrfs-progs exfat-utils efibootmgr man-db man-pages \
	texinfo cryptsetup networkmanager sudo ufw neovim git ${cpu}-ucode \
	reflector greetd

It may be worth trying linux-zen if you experience UI hangs during high CPU load. Although the performance benefits are dubious.

Remember to install microcode, amd-ucode or intel-ucode respectively.

Configuration Stuff

Generate fstab:

# genfstab -U /mnt >> /mnt/etc/fstab

Chroot into the new system:

# arch-chroot /mnt

Set the time zone:

# ln -sf /usr/share/zoneinfo/${region}/${city} /etc/localtime

Run hwclock to generate /etc/adjtime:

# hwclock --systohc

Uncomment en_US.UTF-8 UTF-8 in /etc/locale.gen then generate the locales:

# sed -d `s/#en_US.UTF-8/en_US.UTF-8/` /etc/locale.gen
# locale-gen

Create /etc/locale.conf and set the LANG variable:

# echo "LANG=en_US.UTF-8" > /etc/locale.conf

Create /etc/hostname with the name of this computer and populate the /etc/hosts file:

# echo "${hostname}" > /etc/hostname
# cat > /mnt/etc/hosts << EOF
127.0.0.1     localhost
::1           localhost
127.0.1.1     ${hostname}.localdomain ${hostname}
EOF

Refresh initramfs, be sure to add encrypt to /etc/mkinitcpio.conf:

# /etc/mkinitcpio.conf
...
HOOKS=(base udev ... block encrypt filesystems ...)
...
# mkinitcpio -P

Set the root password:

# passwd

Enable Network Manager and Firewall:

# systemctl enable NetworkManager.service
# systemctl enable ufw.service

Remember to run ufw enable after re-booting into the system.

Setup Swap with zram

Instead of a swap partition which takes disk space and has some encryption considerations you can use zram. Enabling zram swaps programs into a compressed area of ram, instead of a dedicated partition.

You can activate zram at boot with a udev rule. Alternatively, you can use zram-generator.

Here are the steps for the udev rule method.

Load module at boot:

# /etc/modules-load.d/zram.conf
zram

Create udev rule, adjust disksize as necessary. 4GB seems common, although the standard $swap = \sqrt{ram}$ still seems to hold.

# /etc/udev/rules.d/99-zram.rules
ACTION=="add", KERNEL=="zram0", ATTR{comp_algorithm}="zstd", ATTR{disksize}="4G", RUN="/usr/bin/mkswap -U clear /dev/%k", TAG+="systemd"

Add /dev/zram device to fstab.

# /etc/fstab
/dev/zram0 none swap defaults,pri=100 0 0

zswap and zram are mutually exclusive. Be sure to disable zswap with the zswap.enabled=0 kernel parameter. I have this set in the next section.

zram will not work with hibernate. Sleep should still work.

Setup Boot Loader

I like to use systemd-boot since it’s included in the base install.

Install and update into boot partition:

# bootctl --path=/boot install
# bootctl update

Create a pacman hook update boot partition when systemd-boot updates:

# mkdir -p /etc/pacman.d/hooks
# cat > /etc/pacman.d/hooks/100-systemd-boot.hook << EOF
[Trigger]
Type = Package
Operation = Upgrade
Target = systemd

[Action]
Description = Updating systemd-boot.
When = PostTransaction
Exec = /usr/bin/systemctl restart systemd-boot-update.service
EOF

If you at some point enable secure boot see here for a hook to sign the new boot manager.

Create boot config files:

# cat > /boot/loader/loader.conf << EOF
default arch
timeout 0
console-mode max
editor no
EOF

# cat > /boot/loader/entries/arch.conf << EOF
title Arch Linux
linux /vmlinuz-linux
initrd /<cpu>-ucode.img
initrd /initramfs-linux.img
options cryptdevice=UUID=<device-UUID>:root root=/dev/mapper/root rw quiet splash zswap.enabled=0

You should now be ready to exit the chroot, unmount the filesystem, and reboot the system.

# exit
# umount -R /mnt
# reboot

Remove the install media and log in to your fresh system.

You can get <device-UUID> with blkid /dev/<partname> and grab the top-level encrypted partition

Post-install Stuff

Now you should have a minimal Arch system with btrfs, systemd-boot and zram swap. Here are some nice-to-haves for setting up the rest of the desktop system.

Activating Trim

Periodically activating trim is good for SSD health:

# systemctl enable fstrim.timer

Create an Admin User

Create an admin user and edit sudoers to allow sudo privileges for wheel group.

# useradd -mU -G wheel "${username}"
# echo "$username:$password" | chpasswd
# sed -i 's/# %wheel ALL=(ALL) ALL/%wheel ALL=(ALL) ALL/' /etc/sudoers

Disable root Account

I like to have root disabled.

# passwd -l root

Make sure sudo works for you user before doing this.

Disable Terminal Bell

I hate the terminal bell, this disables it forever:

# rmmod pcspkr
# echo "blacklist pcspkr" > /mnt/etc/modprobe.d/nobeep.conf

Optimize Swappiness for zram

Generally vm.swappiness=10 is a common recommendation. However, PopOS and Fedora have done some experiments and found that with zram swappiness values higher than 100 lead to performance improvements

# cat > /mnt/etc/sysctl.d/99-vm-zram-parameters.conf << EOF
vm.swappiness = 180
vm.watermark_boost_factor = 0
vm.watermark_scale_factor = 125
vm.page-cluster = 0
EOF

Add color output to pacman

sed -i "s/^#Color/Color/" /mnt/etc/pacman.conf

Audio

Install pipewire and associated packages.

$ sudo pacman -S pipewire pipewire-docs pipewire-audio pipewire-pulse pipewire-jack wireplumber
$ systemctl --user enable --now pipewire
$ systemctl --user enable --now pipewire-pulse

You can then use wpctl to manage sources and sinks.

Autologin

I like to use greetd for simple autologin setup. Configure greetd autologin buy adding the following to /etc/greetd/config.toml:

[initial_session]
command = "<command>" # e.g. "sway"
user = "<username>"

Then enable the service:

$ sudo systemctl enable greetd