User:PBS/Example installation with bootmap
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
- Btrfs system root, with flat subvolume layout.
- Full disk encryption (including swap) with hibernate support.
- Systemd-boot as bootloader.
- Secure Boot (with own keys).
- TPM auto-unlock at boot.
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
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
.
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
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.
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
.
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.