Encrypted BTRFS Arch Installation
Posted on Sun 09 June 2024 in /hacks
I have recently had to do a re-install for my laptop (technically netbook) and I was interested in the 'easy snapshotting' available with BTRFS, so this is a quick guide to how I set my netbook.
First I used the latest arch ISO written to a USB drive, and booted into the live environment.
To check if your live environment was booted into UEFI mode (desired), you can read the contents of the fw_platform_size
.
cat /sys/firmware/efi/fw_platform_size
This should read 64 ideally, if this does not exist try going through the motherboard bios to see if UEFI is available for your motherboard. If the value is 32, then... go look somewhere else. Although most of this guide is identical for legacy BIOS boot.
As I use a UK keyboard, set the keyboard layout
loadkeys uk
I am using a USB-Ethernet network interface that has a driver in the Linux kernel, so I am able to access the internet. If you are using Wi-Fi, iwctl comes pre-installed in the Arch live boot environment, and is easily configured.
I prefer to set up the GUID Partition Table (GPT) with gdisk instead of fdisk and my disk is a NVMe drive so appears as /dev/nvmen0
instead of /dev/sda
for example.
gdisk /dev/nvmen0
First we want to create a new GPT
Command (? for help): o
Now to create our partitions.
I will have 3 partitions; an EFI partition (FAT32), a boot partition (unencrypted EXT4) and an encrypted root partition (BTRFS).
The EFI partition does not need to be big, but it is recommended at least 100MB, so I chose 128MB and to bring the first two partitions to a round 1GB cumulatively, I chose my boot partition to be 896MB leaving the remaining for my root partition.
The only other important detail to note here, is that the EFI flag must be EF00
, the rest can be default (8300
).
Command (? for help): n
Partition number (1-128, default 1): [default]
First Sector (34-xxxxx, default = 2048) or {+-}size{KMGTP}: [default]
Last Sector (2048-xxxxx, default = xxxxx) or {+-}size{KMGTP}: +128M
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): EF00
Command (? for help): n
Partition number (2-128, default 2): [default]
First Sector (34-xxxxx, default = 264192) or {+-}size{KMGTP}: [default]
Last Sector (264192-xxxxx, default = xxxxx) or {+-}size{KMGTP}: +896M
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): [default]
Command (? for help): n
Partition number (2-128, default 3): [default]
First Sector (34-xxxxx, default = 2099200) or {+-}size{KMGTP}: [default]
Last Sector (2099200-xxxxx, default = xxxxx) or {+-}size{KMGTP}: [default]
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): [default]
Now you can review the new table
Command (? for help): p
Once you are happy with the partition table, you can write it with:
Command (? for help): w
Do you want to proceed? (Y/N): Y
Now we have a partition table, we can create the filesystems on top of them. First lets start with the EFI partition, which must be FAT32:
mkfs.fat -F32 -n EFI /dev/nvme0n1p1
Again, if you are not using NVMe drive, this will be something else, but ensure you don't mix up your partitions! Boot partition is also very straight forward, but the filesystem is going to be EXT4. There are some legacy excuses to use EXT2 instead due to the lack of journaling, but this is outdated advice and I would recommend EXT4.
mkfs.ext4 /dev/nvme0n1p2
Now the EFI and boot partition are set up, we can start with the encrypted root file system. Create the encryption layer (you can specify the type i.e. LUKS2 but LUKS2 is the default):
cryptsetup luksFormat /dev/nvme0n1p3
Use a key that is memorable but secure.
Alternatively you can generate a binary file from /dev/urandom
and use that as a key, but ensure to copy this on to USB to avoid being locked out of your drive.
Now we can open the encryption:
cryptsetup open /dev/nvme0n1p3 crypt_root
I chose the name crypt_root
, but this could be anything, but what ever name is chosen, must be used later in setup.
Now we can create our BTRFS on this encrypted volume.
mkfs.btrfs /dev/mapper/crypt_root
And I chose 6 BTRFS subvolumes:
@
for/
, the root filesystem (this is the one that will be snapshotted),@home
for/home
containing user files,@snapshots
for/.snapshots
which will contain my snapshots,@log
for/var/log
, no point it snapshotting logs,@pkg
for/var/cache/pacman/pkg
the cache for pacman can get large,@swap
for/.swap/
for containing my swap files.
I prefer using swap files than swap partitions as they offer the same advantages, but are also easier to resize.
Of course, we can't have a swap file in a partition that will be snapshotted, so I create a new partition to house my swap files (in /.swap
).
But first we will mount this BTRFS partition
mount /dev/mapper/crypt_root /mnt
And create the subvolumes:
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@snapshots
btrfs subvolume create /mnt/@log
btrfs subvolume create /mnt/@pkg
btrfs subvolume create /mnt/@swap
Now unmount the partition and remount with the BTRFS flags
mount -o compress=zstd,noatime,space_cache=v2,subvol=@,discard=async /dev/mapper/crypt_root /mnt
mount --mkdir -o compress=zstd,noatime,space_cache=v2,subvol=@home,discard=async /dev/mapper/crypt_root /mnt/home
mount --mkdir -o compress=zstd,noatime,space_cache=v2,subvol=@snapshots,discard=async /dev/mapper/crypt_root /mnt/.snapshots
mount --mkdir -o compress=zstd,noatime,space_cache=v2,subvol=@log,discard=async /dev/mapper/crypt_root /mnt/var/log
mount --mkdir -o compress=zstd,noatime,space_cache=v2,subvol=@pkg,discard=async /dev/mapper/crypt_root /mnt/var/cache/pacman/pkg
mount --mkdir -o compress=zstd,noatime,space_cache=v2,subvol=@swap,discard=async /dev/mapper/crypt_root /mnt/.swap
And add the other partitions
mount --mkdir /dev/nvmen0p2 /mnt/boot
mount --mkdir /dev/nvmen0p1 /mnt/boot/efi
Now we have all the partitions and subvolumes mounted, we can install Linux on this partition, for Arch Linux we can use pacstrap:
pacstrap -K /mnt base linux linux-firmware neovim cryptsetup btrfs-prog grub-btrfs snapper grub-x86_64-efi efibootmgr bash-completion
Swap neovim with your editor of choice.
We can use genfstab
packaged with the Arch ISO.
Alternatively you can construct the /etc/fstab
yourself with the mount commands above.
I am using -U
to use UUID instead of -L
for labels.
genfstab -U /mnt >> /mnt/etc/fstab
This does a pretty good job, but it contains the subvolid=xxx
, remove these from the mount parameters for each of the BTRFS partitions.
If you are not installing Arch Linux and are using normal chroot, you must first bind mount /dev
, /dev/pts
, /proc
, /sys
from the host system to the one to chroot in (mount --bind /dev /mnt/dev
, etc.).
arch-chroot /mnt
Now inside the new system, we can start by setting the time
ln -sn /usr/share/zoneinfo/Europe/London /etc/localtime
hwclock --systohc
Of course, replace the zone info with your own region and city. Now you can generate localisation, key map and hostname:
locale-gen
echo "LANG=en_GB.UTF-8" > /etc/locale.conf
echo "KEYMAP=uk" > /etc/vconsole.conf
echo "netbook" > /etc/hostname
Of course, this is all configuration, so yours will likely be different.
To set up a 15GB swap file.
dd if=/dev/zeros of=/.swap/swapfile bs=1M count=15360
mkswap /.swap/swapfile
swapon /.swap/swapfile
Now add it to your /etc/fstab
/.swap/swapfile none swap defaults,x-systemd.after=/.swap
I have added the x-systemd.after=/.swap
, which is systemd dependent, but this should really be fine without it.
Your UUID for the encrypted disk can be retrieved with:
blkid /dev/mapper/crypt_root -sUUID -ovalue
Now we can edit the grub and mkinitcpio files for our encrypted BTRFS system.
Edit /etc/default/grub
to give initramfs the knowledge of the encrypted device replacing myuuid
with the one given with the command above.
GRUB_CMDLINE_LINUX="root=/dev/mapper/crypt_root cryptdevice=UUID=myuuid:crypt_root"
GRUB_ENABLE_CRYPTODISK=y
Now update /etc/mkinitcpio.conf
to load the modules required.
MODULES=(btrfs)
HOOKS=(base udev autodetect modconf kms keyboard encrypt keymap consolefont block filesystems fsck grub-btrfs-overlayfs)
As seen above, I have opted out of using systemd specific modules. This should mean that this should work independent of the chosen init system.
After setting the config files, we can update/generate the binaries:
mkinitcpio -P
grub-install --target=x86_64-efi --bootloader-id=GRUB --efi-directory=/boot/efi
grub-mkconfig -o /boot/grub/grub.cfg
Now your bootloader (grub) and initramfs are installed, you are able to reboot into your new installation!