User:PBS/Example installation with bootmap

From ArchWiki

Tango-emblem-important.pngThis article is not officially supported.Tango-emblem-important.png

The Arch Linux community does not offer support for the information contained in this page; for installation procedures, the Installation guide is the only officially supported document. The content below is mainly maintained by User:PBS, who last reviewed it on 31 December 2021, and it may be out of date or inaccurate.

Introduction

This installation guide shows how to install Arch Linux on a UEFI system, the main novelty being that it uses my project bootmap for EFI image signing and system recovery, and is intended as an example of how to use that tool. In addition, the guide makes use of all the fancy features of the day, including

It is assumed that the target computer has a blank disk, and that you only intend to boot Arch Linux on it (no dual boot). This guide closely parallels the official Installation guide; please consult it and the rest of the Wiki to learn about the full set of options available to each step, and for further background information.

Pre-installation

On the target computer, disable Secure Boot. Acquire and boot the official installation image. Make sure to boot in UEFI mode, not BIOS mode. As in the Installation guide, bring up the network connection. Fix the keyboard layout and synchronise the system clock:

# loadkeys uk    # For example, this is necessary if pressing " gives you @
# timedatectl set-ntp true

Partitioning

Create a GPT partition table, a 256 MiB EFI system partition, and another partition which will become the main partition. These instructions were tested in a virtual machine in which the installation drive was /dev/vda; replace this with the actual drive you want to install to. In what follows, the user's input is shown in bold.

# fdisk /dev/vda
Command (m for help): g
Created a new GPT disklabel (GUID: E9EA8EBF-B43A-4A4E-8208-8A90CC3109F2).

Command (m for help): n
Partition number (1-128, default 1): 
First sector (2048-31457246, default 2048): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-31457246, default 31457246): +256M
Created a new partition 1 of type 'Linux filesystem' and of size 256 MiB.

Command (m for help): t
Selected partition 1
Partition type or alias (type L to list all): 1
Changed type of partition 'Linux filesystem' to 'EFI System'.

Command (m for help): n
Partition number (2-128, default 2): 
First sector (526336-31457246, default 526336): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (526336-31457246, default 31457246): 
Created a new partition 2 of type 'Linux filesystem' and of size 14.7 GiB.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

Check the result using lsblk(8):

# lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
vda    254:0    0    15G  0 disk 
├─vda1 254:1    0   256M  0 part 
└─vda2 254:2    0  14.7G  0 part

Filesystem creation

Create a VFAT filesystem on the EFI partition:

# mkfs.vfat /dev/vda1

Create a LUKS container on the main partition, and a btrfs filesystem inside. (We forgo the usual secure erasure step, because it is both unreliable and damaging if using an SSD, and therefore personally not judged to be worth the slight security advantage.)

# cryptsetup luksFormat /dev/vda2          # Choose disk encryption password in this step
# cryptsetup open /dev/vda2 cryptdevice    # Re-enter password
# mkfs.btrfs /dev/mapper/cryptdevice

Subvolume creation

Mount the btrfs filesystem at /mnt and create the subvolumes home, root, swap and bootmap:

# mount /dev/mapper/cryptdevice /mnt
# btrfs subvolume create /mnt/home
# btrfs subvolume create /mnt/root
# btrfs subvolume create /mnt/swap
# btrfs subvolume create /mnt/bootmap

Filesystem mounting

Unmount the btrfs filesystem, and remount only the root subvolume:

# umount /mnt
# mount /dev/mapper/cryptdevice /mnt -o subvol=root

Within the root subvolume, which is to become the new system, create the mountpoints /home and /swap, and mount the corresponding subvolumes there:

# mkdir /mnt/home
# mount /dev/mapper/cryptdevice /mnt/home -o subvol=home

# mkdir /mnt/swap
# mount /dev/mapper/cryptdevice /mnt/swap -o subvol=swap

Create the mountpoint for the EFI system partition, and mount it there. We have chosen to use /boot/efi to keep everything boot-related under one directory, but you can also use /efi. You should NOT use /boot, because mkinitcpio places its output there, as well as being the location microcode files are installed to, so these files will end up on the unencrypted EFI partition where they could be maliciously tinkered with, before being read again and made into a kernel image that you boot.

# mkdir -p /mnt/boot/efi
# mount /dev/vda1 /mnt/boot/efi
Tip: If you are planning to make use of btrfs's built-in compression, enable it in the previous mount command so that the installation will be compressed.

Check the result with lsblk(8) again:

# lsblk
NAME            MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
vda             254:0    0    15G  0 disk  
├─vda1          254:1    0   256M  0 part  /mnt/boot/efi
└─vda2          254:2    0  14.7G  0 part  
  └─cryptdevice 252:0    0  14.7G  0 crypt /mnt/swap
                                           /mnt/home
                                           /mnt

Installation

Install the essential packages using pacstrap(8):

# pacstrap /mnt base base-devel linux linux-firmware btrfs-progs efitools sudo nano

In addition to the standard packages, we have also included some packages that we will need for the steps that follow.

Configuration

Chroot

Chroot into the new system:

# arch-chroot /mnt

We don't create the fstab file before this step, as we will create it manually later.

Preliminary edits

As described in the Installation guide, run hwclock --systohc, and set the locale, keymap, and hostname. Note only the following changes:

  • There is no need to set a time zone. Instead of setting this now, the user can later install a Desktop Environment that does this automatically. This has the added benefit that it will auto-update when the location changes.
  • After setting the locale, run . /etc/locale.conf to effect the changes immediately. Otherwise, perl will print warning messages whenever it is used until the system is rebooted.

Swap file

We create a 4GB swapfile in the /swap subvolume. It must be in a separate subvolume from /root to prevent it being snapshotted. We must also disable copy-on-write and compression. Finally we set the permissions correctly and format it as swap.

# cd /swap
# truncate -s 0 swapfile
# chattr +C swapfile 
# btrfs property set swapfile compression none
# dd if=/dev/zero of=swapfile bs=1M count=4000 status=progress
# chmod 600 swapfile 
# mkswap swapfile

User creation

Because we will need to build an AUR package during install, which must be done as a regular user, we opt to create users now rather than after a reboot. Replacing user with the actual user's name, create the user

# useradd -m user

and set their password:

# passwd user
New password:
Retype new password:
passwd: password updated successfully

We choose to leave the root account locked, which means we will need to configure sudo for root access instead. Add the user to the wheel group,

# usermod -aG wheel user

Then edit the sudoers file and uncomment the following line

# EDITOR=nano visudo
%wheel ALL=(ALL) ALL

to allow all members of the wheel group to use sudo.

Filesystem tables

We create the filesystem tables manually. At boot time, the filesystem mounting will be performed in three different stages:

  • First the LUKS container must be opened. This requires the user to type the decryption password. (If TPM auto-unlock has been set-up, this will be used instead.) This is done in early userspace, and is governed by /etc/crypttab.initramfs.
  • Then the root filesystem must be mounted. This is also done by early userspace, and is governed by the kernel commandline.
  • Finally, the remaining mounts such as /home and /swap must be set up. This is done in normal userspace, and is governed by /etc/fstab.

To set up the crypttab.initramfs, first find the UUID of the main partition by running

# blkid /dev/vda2
/dev/vda2: UUID="2d258504-cee5-4d9f-826e-f80f80c5e1de" TYPE="crypto_LUKS" PARTUUID="a2cf546c-cb64-0948-babc-1353d1aa747a"

and note the UUID in bold. Then add a single line to the crypttab.initramfs to unlock it at boot, replacing the UUID below with your own:

# nano /etc/crypttab.initramfs
cryptdevice UUID=2d258504-cee5-4d9f-826e-f80f80c5e1de

The unlocked container will then be found at /dev/mapper/cryptdevice.

Similarly, to set up the fstab, find the UUID of the EFI system partition by running

# blkid /dev/vda1
/dev/vda1: SEC_TYPE="msdos" UUID="E2D7-ADAC" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="230796c9-7a1c-724d-9351-82a49c03f02d"

Then create the fstab as follows:

# nano /etc/fstab
/dev/mapper/cryptdevice /home        btrfs noatime,subvol=home                   0 0
/dev/mapper/cryptdevice /swap        btrfs noatime,subvol=swap                   0 0
/dev/mapper/cryptdevice /run/bootmap btrfs noatime,subvol=bootmap,ro,noauto,user 0 0
UUID=E2D7-ADAC          /boot/efi    vfat  noatime,ro,noauto,x-systemd.automount 0 2
/swap/swapfile          none         swap  defaults                              0 0

Lines 1 and 2 mount /home and /swap in the same way that we set up earlier in the installation. The last line actually activates the swap file. Line 4 mounts the EFI system partition on-demand, and ensure that when this happens, it is mounted read-only, to protect against accidental damage. The final 2 on that line ensures it will be fscked before mounting (not required by the other entries). Line 3 does not mount the /bootmap subvolume, but allows it to be mounted and unmounted as a regular user, again only read-only, provided the mountpoint /run/bootmap exists. Since /run is a temporary directory, to ensure the mountpoint always exists, we must add an entry to tmpfiles.d:

# nano /etc/tmpfiles.d/bootmap.conf
d /run/bootmap 0644 root root

Then effect the change immediately by running systemd-tmpfiles --create and check the directory was created with ls /run.

Note: The root filesystem mount options will be specified later, so there's no need to add them now.
Warning: systemd version 250 broke automount support, resulting in the EFI partition no longer being mounted. (See this bug.) As a temporary workaround, remove noauto,x-systemd.automount from the EFI system partition mount line.

Mkinitcpio

We make some changes to the default configuration of mkinitcpio, the tool which generates the kernel and initramfs and places them in /boot in preparation for being made into a Unified kernel image.

First we switch to the systemd-based init by editing the HOOKS line of its configuration file

# nano /etc/mkinitcpio.conf
HOOKS=(systemd autodetect modconf block keyboard sd-vconsole sd-encrypt filesystems)

We have also taken this opportunity to remove the base hook. As we are using sd-init, the only thing the base hook provides is a recovery shell. At the time of writing, the recovery shell is locked, and therefore unusable. It has also never helped the author recover from a single serious mishap. Furthermore, since the goal of bootmap is to provide an alternative, more reliable recovery mechanism, we won't need it.

For the same reasons as the above, we also disable the generation of the fallback initramfs, by editing the file

# nano /etc/mkinitcpio.d/linux.preset
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux"

PRESETS=('default')

default_image="/boot/initramfs-linux.img"

to remove all mentions of 'fallback'.

To effect these changes, rebuild the initramfs with

# mkinitcpio -P

and remove any leftover fallback initramfs with

# rm /boot/initramfs-linux-fallback.img
Note: This will need to be done again for each new kernel, e.g. linux-zen, that you install.

The contents of /boot should now look like

# ls /boot
efi initramfs-linux.img vmlinuz-linux

Systemd-boot

We install the systemd-boot bootloader to the EFI system partition by running

# bootctl install

You can check the result with ls /boot/efi and bootctl status. This step worked because the EFI system partition is currently mounted read-write, although it will be mounted read-only after a reboot into the system.

Secure Boot keys

We generate a set of Secure Boot keys and place them in /etc/efi-keys. The easiest way to do this is using the commands below. Alternative methods, offering more control, can be found at Secure Boot.

# mkdir /etc/efi-keys
# cd !$
# curl -L -O https://raw.githubusercontent.com/electrickite/sbkeys/master/sbkeys
# sh sbkeys
# rm sbkeys

Bootmap

First optionally install the relevant microcode file for your system: either amd-ucode or intel-ucode.

Download, build and install the bootmapAUR package:

# su -l user
$ mkdir build
$ cd !$
$ curl https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=bootmap > PKGBUILD
$ makepkg -s
$ exit
# pacman -U /home/user/build/bootmap-VERSION-x86_64.pkg.tar.zst

Edit the configuration file to specify that the system root subvolume is located at /root (relative to the filesystem root, not the system root):

# nano /etc/bootmap.conf
RootVol = /root

If desired, also edit the kernel commandline file to specify any additional filesystem mount options or kernel parameters. For example, you could modify it to

# nano /etc/kernel/cmdline
rw quiet audit=0 root=$ROOTDEV rootfstype=btrfs rootflags=subvol=$SUBVOL,noatime

Then generate all missing Unified kernel images for the first time:

# bootmap install

Check the result of this command with

# bootmap
Warning: The booted kernel does not correspond to any known image.
IMAGE                        SUBVOLUME        USED AS
linux-5.15.11 [nextboot]  →  /root [running]  Current

The warning is harmless and will disappear on next reboot.

The above command should have generated a single EFI image in /boot/efi/EFI/Linux, and edited the file /boot/efi/loader/loader.conf to set this image as the default. Verify that the images are signed correctly with

$ sbverify --cert /etc/efi-keys/DB.crt /boot/efi/EFI/Linux/IMAGE.efi

As a sanity check, you can also make sure that systemd-boot has detected the correct EFI image to boot by running bootctl, and seeing if the image is listed as the default for next boot.

Reboot

As in the Installation guide, exit the chroot and power off. Remove the installation medium, boot the new system, and log in as the user that you created.

Post-installation

Hibernate support

Obtain the offset of the swapfile by following the instructions at Hibernation into swap file on btrfs. Add the following options to the kernel commandline:

# nano /etc/kernel/cmdline
rw quiet resume=$ROOTDEV resume_offset=OFFSET root=$ROOTDEV rootfstype=btrfs rootflags=subvol=$SUBVOL

where OFFSET should be replaced with the actual offset. This will take effect after the next kernel update and reboot.

Tip: You may wish to consider minifying the hibernation image by setting /sys/power/image_size to 0 using tmpfiles.d.

Enable Secure Boot

Follow the instructions at Secure Boot to enroll the Secure Boot keys generated at installation into firmware. Reboot and verify that Secure Boot is now enabled by checking for the relevant line in dmesg.

Tip: Make sure to set a UEFI password, or someone could just turn Secure Boot off again!
Warning: If you don't want to end up like this person, maybe consider recording the exact key sequence required to disable Secure Boot in UEFI before enabling it, in case enrolling your own keys bricks the graphics and you need to turn it off again.

TPM auto-unlock

After booting in Secure Boot mode, as described in more detail at Trusted Platform Module#systemd-cryptenroll, enroll a key in the TPM and LUKS container using

# systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/vda2

Then specify that the LUKS container should be unlocked using the TPM if possible by appending the following information to crypttab.initramfs:

# nano /etc/crypttab.initramfs
cryptdevice UUID=2d258504-cee5-4d9f-826e-f80f80c5e1de none tpm2-device=auto

The rest

Set up the rest of the system by following the guidelines at General recommendations.

Recovery

After the first kernel update, the system will always keep a backup EFI image that boots into an old copy of the system. In the event of a hosed system, you can boot one of these backup copies through the UEFI menu, or the systemd-boot bootloader if you have set it up to show a boot menu. After that, you can mount the broken system

# mount /dev/mapper/cryptdevice /mnt -o noatime,subvol=/root

and chroot into it with

# arch-chroot /mnt

The system can then be manually inspected and fixed.

Note: Bootmap isn't intended to allow you to roll back the system to a backup version. You should be able to fix the system without resorting to that! But you can also use it that way if you want to. Be warned though, careful manual editing of the images, symlinks and subvolumes will be needed. Actual rollback support may be added in the future, depending on whether anyone other than me actually uses this setup.