A(rch) to Z(ram): Install Arch Linux with (almost) full disk encryption and BTRFS

Last edited on 2022-08-14 Tagged under  #arch   #linux   #luks   #btrfs 

Arch logo

Arch Linux is an excellent Linux for a hands-on, daily use system when you are curious and motivated - practically required - to dig into the nitty gritty.

Below is my walk-through of Arch's installation guide and the choices I make along the way to create a minimal Arch environment with LUKS encryption (including /boot) that uses BTRFS as the root filesystem.

Let's go!

I've only been using Arch for a few months, but so far its proven stable and a joy to use. Boot from the installer to a command-line prompt, and begin crafting your own personal Arch just the way you like it. Then arrives the moment of truth, its time to reboot! Once you are up-and-running you will have a system that rolls forward with a continuous, incremental stream of updates to the latest stable versions of software.

My setup

  • Target device boots to UEFI
  • Wired network connection
  • Arch is the sole OS on a single disk
  • GPT partition table with two partitions:
  • BTRFS as root filesystem with multiple subvolumes
  • Unlock system at boot with single passphrase
  • GRUB as bootloader

1. Install

1.1 Prepare USB install media

Download and verify checksums for archlinux-RELEASE_VERSION-x86_64.iso.

Prepare a USB flash drive as an installer using one of these two methods:

Method #1: Ventoy

I now use Ventoy to create a multiboot installer. Simply copy archlinux-RELEASE_VERSION-x86_64.iso to the USB drive, reboot, and the auto-generated menu lists all the disk images available to boot. Read more

Method #2: dd

Write the installer to an unmounted USB drive using the dd command as root.

BE VERY CAREFUL TO NOTE THE PROPER DEVICE. ALL DATA ON THE DEVICE WILL BE OVERWRITTEN.

Example: Under Linux, if a USB drive appears as sdx1, then write the installer to sdx (remove partition number) ...

sudo dd if=archlinux-RELEASE_VERSION-x86_64.iso of=/dev/sdx bs=4M status=progress oflag=sync

1.2 Boot installer

Insert USB installer into target device and boot. Installer auto-logins as root.

1.2.1 Optional: Continue install from another Linux system via SSH

Enable SSH on the target device ...

systemctl start sshd.service

Set password for root ...

passwd

Look up IP address ...

ip a

Now, from the other system, ssh into the Arch installer ...

ssh root@ip.address.of.arch-target-device

1.3 Keyboard and font

Default console keymap is us.

Optional: List available layouts ...

localectl list-keymaps

Load a different keymap (example: colemak) ...

loadkeys colemak

Default font in the installer is very small on high resolution displays. Alternative fonts are available in /usr/share/kbd/consolefonts.

Switch to a larger font size (example: terminus ter-v24n) ...

setfont ter-v24n

1.4 Verify boot mode

If UEFI mode is enabled on a UEFI motherboard, the installer will boot Arch accordingly.

Verify system is booted via UEFI by listing contents of efivars ...

ls /sys/firmware/efi/efivars

If the directory does not exist, the system is booted in BIOS mode.

Note: If the target device has been manufactured within the last decade, chances are its a UEFI-capable device. All my current devices use UEFI boot mode and this HOWTO is based on UEFI. Some of the instructions below - drive partitioning and GRUB setup in particular - will need to be modified if using BIOS mode. Check out the Arch Wiki for details.

1.5 Connect to internet

Ethernet: Auto-configured

Wireless: Wireless network configuration

1.6 Update system clock

timedatectl set-ntp true
timedatectl status

1.7 Set disk for install

Identify the internal storage device where Arch Linux will be installed by running lsblk -f.

Set a disk variable for use in installation commands.

Example: In this HOWTO I'm installing to my internal storage device identified as nvme0n1 ...

export disk="/dev/nvme0n1"

1.8 Delete old partition layout

wipefs -af $disk
sgdisk --zap-all --clear $disk
partprobe $disk

1.8.1 Optional: Fill disk with random data

Plain dm-crypt is used for a very fast wipe with randomness.

Create a temporary crypt device (example: target) ...

cryptsetup open --type plain -d /dev/urandom $disk target

This maps the container under /dev/mapper/target with a random password.

Fill the container with a stream of zeros using dd ...

dd if=/dev/zero of=/dev/mapper/target bs=1M status=progress oflag=direct

Using if=/dev/urandom is not required as the dm-crypt cipher is used for randomness.

When dd is finished, remove the mapping ...

cryptsetup close target

Links: ArchWiki: Dm-crypt drive preparation and Cryptsetup FAQ

1.9 Partition disk

Use sgdisk to create partitions.

List partition type codes ...

sgdisk --list-types

I use a layout for a single SSD with a GPT partition table that contains two partitions:

  • Partition 1 - EFI partition (ESP) - size 512MiB, code ef00
  • Partition 2 - encrypted partition (LUKS) - remaining storage, code 8309
sgdisk -n 0:0:+512MiB -t 0:ef00 -c 0:esp $disk
sgdisk -n 0:0:0 -t 0:8309 -c 0:luks $disk
partprobe $disk

Print the new partition table...

sgdisk -p $disk

In lieu of using a swapfile or dedicated swap partition as system swap, I create a swap device in RAM after the install is complete and I've rebooted into my new Arch environment.

Link: Managing partitions with sgdisk

1.10 Encrypt partition

Latest GRUB (2.06) has added limited support for LUKS2. Note, however, that when /boot is placed on a LUKS2 partition and using GRUB as the boot loader ...

[O]nly the PBKDF2 key derival function is supported. This can mostly attributed to the fact that the libgcrypt library currently has no support for either Argon2i or Argon2id, which are the remaining KDFs supported by LUKS2.

In all circumstances, LUKS1 is successful. So that is what I use.

If the disk variable created earlier was set as a nvme-type storage device (as in this HOWTO), then the LUKS partition will be ${disk}p2. Otherwise, it will be ${disk}2 (drop the p).

Initialize the encrypted partition (partition #2) ...

cryptsetup --type luks1 -v -y luksFormat ${disk}p2

1.11 Format partitions

ESP partition (partition #1) is formatted with the vfat filesystem, and the Linux root partition (partition #2) uses btrfs ...

cryptsetup open ${disk}p2 cryptdev
mkfs.vfat -F32 -n ESP ${disk}p1
mkfs.btrfs -L archlinux /dev/mapper/cryptdev

1.12 Mount root device

mount /dev/mapper/cryptdev /mnt

1.13 Create BTRFS subvolumes

Each BTRFS filesystem has a top-level subvolume with ID=5. A subvolume is a part of the filesystem with its own independent data.

Creating subvolumes on a BTRFS filesystem allows the separation of data. This is particularly useful when creating backup snapshots of the system. An example scenario might be where its desirable to rollback a system after a broken upgrade, but any changes made in a user's /home directory should be left alone.

Changing subvolume layouts is made simpler by not mounting the top-level subvolume as / (the default). Instead, create a subvolume that contains the actual data, and mount that to /.

Use @ for the name of this new subvolume (which is the default for Snapper, a tool for making backup snapshots) ...

btrfs subvolume create /mnt/@

I create additional subvolumes for more fine-grained control over rolling back the system to a previous state, while preserving the current state of other directories. These subvolumes will be excluded from any root subvolume snapshots:

Subvolume -- Mountpoint

  • @home -- /home (preserve user data)
  • @snapshots -- /.snapshots
  • @cache -- /var/cache
  • @libvirt -- /var/lib/libvirt (virtual machine images)
  • @log -- /var/log (excluding log files makes troubleshooting easier after reverting /)
  • @tmp -- /var/tmp

The reasoning behind not excluding the entire /var out of the root snapshot is that /var/lib/pacman database in particular should mirror the rolled back state of installed packages.

Create the subvolumes ...

btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@snapshots
btrfs subvolume create /mnt/@cache
btrfs subvolume create /mnt/@libvirt
btrfs subvolume create /mnt/@log
btrfs subvolume create /mnt/@tmp

1.14 Mount subvolumes

Unmount the root partition ...

umount /mnt

Set mount options for the subvolumes ...

export sv_opts="rw,noatime,compress-force=zstd:1,space_cache=v2"

Options:

  • noatime increases performance and reduces SSD writes.
  • compress-force=zstd:1 is optimal for NVME devices. Omit the :1 to use the default level of 3. Zstd accepts a value range of 1-15, with higher levels trading speed and memory for higher compression ratios.
  • space_cache=v2 creates cache in memory for greatly improved performance.

Mount the new BTRFS root subvolume with subvol=@ ...

mount -o ${sv_opts},subvol=@ /dev/mapper/cryptdev /mnt

Create mountpoints for the additional subvolumes ...

mkdir -p /mnt/{home,.snapshots,var/cache,var/lib/libvirt,var/log,var/tmp}

Mount the additional subvolumes ...

mount -o ${sv_opts},subvol=@home /dev/mapper/cryptdev /mnt/home
mount -o ${sv_opts},subvol=@snapshots /dev/mapper/cryptdev /mnt/.snapshots
mount -o ${sv_opts},subvol=@cache /dev/mapper/cryptdev /mnt/var/cache
mount -o ${sv_opts},subvol=@libvirt /dev/mapper/cryptdev /mnt/var/lib/libvirt
mount -o ${sv_opts},subvol=@log /dev/mapper/cryptdev /mnt/var/log
mount -o ${sv_opts},subvol=@tmp /dev/mapper/cryptdev /mnt/var/tmp

Links: What BTRFS subvolume mount options should I use? and btrfs-man5(5)

1.15 Mount ESP partition

mkdir /mnt/efi
mount ${disk}p1 /mnt/efi

1.16 Select package mirrors

Synchronize package databases ...

pacman -Syy

Generate a new mirror selection using reflector.

Example: Verbosely select the 5 most recently synchronized HTTPS mirrors located in either Canada or Germany, sort them by download speed, and overwrite mirrorlist ...

reflector --verbose --protocol https --latest 5 --sort rate --country Canada --country Germany --save /etc/pacman.d/mirrorlist

1.17 Install base system

Select an appropriate microcode package to load updates and security fixes from processor vendors.

View cpuinfo ...

grep vendor_id /proc/cpuinfo

Depending on the processor, set microcode for Intel ...

export microcode="intel-ucode"

For AMD ...

export microcode="amd-ucode"

Install the base system ...

pacstrap /mnt base base-devel ${microcode} btrfs-progs linux linux-firmware bash-completion cryptsetup htop man-db mlocate neovim networkmanager openssh pacman-contrib pkgfile reflector sudo terminus-font tmux

1.18 Fstab

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

2. Configure

Chroot into the base system to configure ...

arch-chroot /mnt /bin/bash

2.1 Timezone

Set desired timezone (example: America/Toronto) and update the system clock ...

ln -sf /usr/share/zoneinfo/America/Toronto /etc/localtime
hwclock --systohc

2.2 Hostname

Assign a hostname (example: foobox) ...

echo "foobox" > /etc/hostname

Add matching entries to /etc/hosts ...

cat > /etc/hosts <<EOF
127.0.0.1   localhost
::1         localhost
127.0.1.1   foobox.localdomain foobox
EOF

2.3 Locale

Set locale (example: en_CA.UTF-8) ...

export locale="en_CA.UTF-8"
sed -i "s/^#\(${locale}\)/\1/" /etc/locale.gen
echo "LANG=${locale}" > /etc/locale.conf
locale-gen

2.4 Font and keymap

Set a console font (example: terminus ter-224n) ...

echo "FONT=ter-v24n" > /etc/vconsole.conf

Set a keyboard layout choice (example: colemak) ...

echo "KEYMAP=colemak" >> /etc/vconsole.conf

2.5 Editor

Set a system-wide default editor (example: neovim) ...

echo "EDITOR=nvim" > /etc/environment && echo "VISUAL=nvim" >> /etc/environment

2.6 Root password

Assign password to root ...

passwd

2.7 Add user

Create a user account (example: foo) with superuser privileges ...

useradd -m -G wheel -s /bin/bash foo
passwd foo

Activate wheel group access for sudo ...

sed -i "s/# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/" /etc/sudoers

2.8 NetworkManager

Enable NetworkManager to start at boot ...

systemctl enable NetworkManager

Wired network connection is activated by default. Run nmtui in the console and choose Activate a connection to setup a wireless connection.

2.9 SSH

Enable sshd server ...

systemctl enable sshd.service

After the install is complete and system has rebooted, secure remote access using SSH keys.

2.10 Keyfile

Keeping /boot on the encrypted partition results in being prompted twice for the LUKS passphrase: first instance, for GRUB to unlock and access /boot in the early stage of the boot process; second instance, to unlock the root filesystem itself as implemented in the initramfs.

To unlock system at boot by entering the passphrase a single time:

  • Create a keyfile that will be embedded in the initramfs
  • Add keyfile and encrypt hook to configure initramfs to auto-unlock encrypted root

Create keyfile crypto_keyfile.bin and restrict access to root ...

dd bs=512 count=4 iflag=fullblock if=/dev/random of=/crypto_keyfile.bin
chmod 600 /crypto_keyfile.bin

Add this keyfile to LUKS ...

cryptsetup luksAddKey ${disk}p2 /crypto_keyfile.bin

Initramfs generated by mkinitcpio uses permission 600 by default, so regular users are not able to read the keyfile.

In the next step, include keyfile in the FILES array and encrypt in HOOKS inside mkinitcpio.conf.

2.11 Mkinitcpio

Set necessary FILES and MODULES and HOOKS in /etc/mkinitcpio.conf:

FILES

Add the keyfile ...

FILES=(/crypto_keyfile.bin)

MODULES

Add btrfs support to mount the root filesystem ...

MODULES=(btrfs)

HOOKS

Set hooks ...

HOOKS=(base udev keyboard autodetect keymap consolefont modconf block encrypt filesystems fsck)

Order of the hooks matters:

  • base sets up all initial directories and installs base utilities and libraries.
  • udev starts the udev daemon and processes uevents from the kernel; creating device nodes.
  • keyboard should be placed before autodetect to include all keyboard drivers in initramfs. Systems that boot with different hardware configurations (example: laptops used both with USB external and built-in keyboards) require this at boot to unlock the encrypted device.
  • keymap and consolefont loads the specified keymap and font from /etc/vconsole.conf
  • modconf includes modprobe configuration files.
  • block adds all block device modules.
  • encrypt is required to detect and unlock an encrypted root partition. This must be placed before filesystems.

Recreate the initramfs image ...

mkinitcpio -P

2.12 Boot loader: GRUB

Install ...

pacman -S grub efibootmgr

Determine the UUID of the encrypted partition ...

blkid -s UUID -o value ${disk}p2

This string of characters is used in the GRUB_CMDLINE_LINUX_DEFAULT variable.

Open /etc/default/grub for editing:

GRUB_CMDLINE_LINUX_DEFAULT

Set ...

GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet cryptdevice=UUID=UUID_OF_ENCRYPTED_PARTITION:cryptdev"

Example: If the UUID of the encrypted partition was 180901b5-151a-45e3-ba87-28f02b124666, then ...

GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet cryptdevice=UUID=180901b5-151a-45e3-ba87-28f02b124666:cryptdev"

GRUB_PRELOAD_MODULES

Include the luks module ...

GRUB_PRELOAD_MODULES="part_gpt part_msdos luks"

GRUB_ENABLE_CRYPTODISK

Uncomment to enable ...

GRUB_ENABLE_CRYPTODISK=y

2.13 Install boot loader

Install GRUB in the ESP ...

grub-install --target=x86_64-efi --efi-directory=/efi --boot-directory=/efi --bootloader-id=GRUB

Verify that a GRUB entry has been added to the UEFI bootloader by running ...

efibootmgr

Generate the GRUB configuration file ...

grub-mkconfig -o /efi/grub/grub.cfg

Verify that grub.cfg has entries for insmod cryptodisk and insmod luks by running ...

grep 'cryptodisk\|luks' /efi/grub/grub.cfg

2.14 Reboot

Exit chroot and reboot ...

exit
umount -R /mnt
reboot

GRUB boot menu appears if configured to be displayed (default).

Important: In this early stage of boot GRUB is using the us keyboard, not any alternative keymap that might be set in vconsole.conf.

GRUB prompts for the LUKS passphrase to unlock the system ...

Enter passphrase for hd0,gpt2 (uuid_long_string_of_characters):

Under normal circumstance, it can take upwards of a half-minute or more for the passphrase to be processed. This delay can be shortened by setting a lower --iter-time when running cryptsetup luksAddkey, though with a corresponding reduction in security.

Then ... Voila!

archlinux login:

3. After the install

Arch Linux is installed. Yes!

These are a few things I like to do next ...

3.1 Check for errors

Failed systemd services ...

$ systemctl --failed

High priority errors in the systemd journal ...

$ journalctl -p 3 -xb

3.2 Sudo

Allow a user (example: foo) to execute superuser commands using sudo without being prompted for a password.

Create the file /etc/sudoers.d/sudoer_foo with ...

echo "foo ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/sudoer_foo

3.3 Package manager

Bring some color and the spirit of Pacman to pacman with Color and ILoveCandy options.

Modify /etc/pacman.conf ...

# Misc options
Color
ILoveCandy

Update system ...

$ sudo pacman -Syu

3.4 Pinky clean

Part of good system hygiene is keeping the system regularly updated and clearing away cruft. Read more

3.5 SSH keys

Create cryptographic keys and disable password logins to make remote logins more secure. Read more

3.6 Linux LTS kernel

Install the Long-Term Support (LTS) Linux kernel as a fallback option to Arch's default kernel ...

$ sudo pacman -S linux-lts

Register the new kernel in the GRUB boot loader ...

$ sudo grub-mkconfig -o /efi/grub/grub.cfg

Reboot and select LTS kernel to test.

Confirm that running kernel is indeed -lts ...

$ uname -r
5.15.59-2-lts

Optional: If you want to use LTS as the default boot kernel, it is safe to remove Arch's linux kernel ...

$ sudo pacman -R linux

Re-run grub-mkconfig to generate an updated boot config.

3.7 Swap

In lieu of using a swapfile or dedicated swap partition as system swap, I create a swap device in RAM using the Linux kernel module zram. Read more

3.8 Command: 'locate'

Find files by name.

Install and update database ...

$ sudo pacman -S mlocate
$ sudo updatedb

Package mlocate contains an updatedb.timer unit, which invokes a database update each day. The timer is enabled after install.

3.9 TRIM

Periodic TRIM optimizes performance on SSD storage.

Enable a weekly task that discards unused blocks on the drive ...

$ sudo systemctl enable fstrim.timer

3.10 Command-not-found

Automatically search the official repositories when entering an unrecognized command, courtesy of pkgfile ...

$ sudo pacman -S pkgfile
$ sudo pkgfile --update

Edit ~/.bashrc ...

if [[ -f /usr/share/doc/pkgfile/command-not-found.bash ]]; then
    . /usr/share/doc/pkgfile/command-not-found.bash
fi

Source: .bashrc

3.11 Sound

Default Arch installation already includes the kernel sound system (ALSA).

Install pipewire as sound server ...

$ sudo pacman -S pipewire pipewire-alsa pipewire-pulse pipewire-jack wireplumber alsa-utils

Reboot.

Test ...

$ pactl info | grep Pipe
Server Name: PulseAudio (on PipeWire 0.3.48)
$ speaker-test -c 2 -t wav -l 1

3.12 AUR

Arch User Repository (AUR) is a community-driven software package repository.

Compile/install/upgrade packages manually or use an AUR helper application.

Example: Install AUR helper yay ...

$ git clone https://aur.archlinux.org/yay-git.git
$ cd yay-git
$ makepkg -si

3.13 Snapshots

Create BTRFS snapshots and manage Arch system rollbacks using a combination of Snapper + snap-pac + grub-btrfs. Read more

3.14 Desktop

Many choices! Install a full-featured desktop such as GNOME, or put together a custom desktop built around a lightweight window manager.

I like Openbox. Read more

3.15 Arch news

Keep up-to-date with the latest news from the Arch development team by subscribing to:

Welcome to Arch!

Thanks for reading! Read other posts?

» Next: BTRFS snapshots and system rollbacks on Arch Linux

« Previous: Keep Arch updated and pinky clean