Arch Install with Secure Boot, btrfs, TPM2 LUKS encryption, Unified Kernel Images.

This is a twist on my current Arch install method.

This install will result in a very clean base install using btrfs for a filesystem, mkinitcpio set up to generate UKIs, Secure Boot handled by sbctl, and your TPM handling encryption unlocking. You just need to bake in your DE of choice.

You'll need to grab the latest Arch Linux ISO, write it a USB drive and boot off it. From there, do your usual checks for internet access, and then we can begin.

Disk preparation

We'll use a 512MB FAT32 system partition for our EFI partition and we're going to use btrfs, inside of a LUKS encrypted partition, for the root. We're going to take the time and effort to use correct partition types, so systemd can detect them (see this page for more information on Discoverable Partitions Specification).

First of all, let's find our drive name:

$ lsblk
NAME  MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0   7:0    0   673M  1 loop /run/archiso/airootfs
sr0    11:0    1 793.3M  0 rom  /run/archiso/bootmnt
vda   254:0    0    30G  0 disk 

Our drive, in this case, is /dev/vda/, so let's use sgdisk to setup the disk:

$ sgdisk -Z /dev/vda
$ sgdisk -n1:0:+512M -t1:ef00 -c1:EFI -N2 -t2:8304 -c2:LINUXROOT /dev/vda
$ partprobe -s /dev/vda

We should now have an EFI partition on /dev/vda1 and our root partition on /dev/vda2. Let's check:

$ lsblk /dev/vda
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
vda    254:0    0   30G  0 disk 
├─vda1 254:1    0  512M  0 part 
└─vda2 254:2    0 29.5G  0 part

Looking good, next, let's encrypt our root partition with LUKS, using cryptsetup:

$ cryptsetup luksFormat --type luks2 /dev/vda2
$ cryptsetup luksOpen /dev/vda2 linuxroot

Let's create the filesystems:

$ mkfs.vfat -F32 -n EFI /dev/vda1
$ mkfs.btrfs -f -L linuxroot /dev/mapper/linuxroot

Let's mount our partitions, and create our btrfs subvolumes:

$ mount /dev/mapper/linuxroot /mnt
$ mkdir /mnt/efi
$ mount /dev/vda1 /mnt/efi
$ btrfs subvolume create /mnt/home
$ btrfs subvolume create /mnt/srv
$ btrfs subvolume create /mnt/var
$ btrfs subvolume create /mnt/var/log
$ btrfs subvolume create /mnt/var/cache
$ btrfs subvolume create /mnt/var/tmp

Base install

Let's update the pacman mirrors, and then pacstrap a base install:

$ reflector --country GB --age 24 --protocol http,https --sort rate --save /etc/pacman.d/mirrorlist
$ pacstrap -K /mnt base base-devel linux linux-firmware amd-ucode vim nano cryptsetup btrfs-progs dosfstools util-linux git unzip sbctl kitty networkmanager sudo 

Update our locale settings:

$ sed -i -e "/^#"en_GB.UTF-8"/s/^#//" /mnt/etc/locale.gen
$ systemd-firstboot --root /mnt --prompt
Welcome to your new installation of Arch Linux!
Please configure your system!

-- Press any key to proceed --
‣ Please enter system keymap name or number (empty to skip, "list" to list options): uk
/mnt/etc/vconsole.conf written.
‣ Please enter timezone name or number (empty to skip, "list" to list options): Europe/London
/mnt/etc/localtime written
‣ Please enter hostname for new system (empty to skip): archinstall9001
/mnt/etc/hostname written.

$ arch-chroot /mnt locale-gen
Generating locales...
  en_GB.UTF-8... done
Generation complete.

User creation

Let's create our local user account, in this case the username will be "walian", add him/her to the wheel group, and then give the wheel group sudo privileges:

$ arch-chroot /mnt useradd -G wheel -m walian 
$ arch-chroot /mnt passwd walian
$ sed -i -e '/^# %wheel ALL=(ALL:ALL) NOPASSWD: ALL/s/^# //' /mnt/etc/sudoers

Unified Kernel fun

We want to create Unified Kernel Images, so first, let's create our kernel cmdline file. This doesn't need to contain anything because we're using Discoverable Partitions, we just do it so mkinitcpio doesn't complain:

$ echo "quiet rw" >/mnt/etc/kernel/cmdline

Let's create the EFI folder structure:

$ mkdir -p /mnt/efi/EFI/Linux

We need to change the HOOKS in mkinitcpio.conf to use systemd, so make yours look like:

/mnt/etc/mkinitcpio.conf

# vim:set ft=sh
MODULES=()

BINARIES=()

FILES=()

HOOKS=(base systemd autodetect modconf kms keyboard sd-vconsole sd-encrypt block filesystems fsck)

And now let's update the .preset file, to generate a UKI:

/mnt/etc/mkinitcpio.d/linux.preset

# mkinitcpio preset file to generate UKIs

ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux"
ALL_microcode=(/boot/*-ucode.img)

PRESETS=('default' 'fallback')

#default_config="/etc/mkinitcpio.conf"
#default_image="/boot/initramfs-linux.img"
default_uki="/efi/EFI/Linux/arch-linux.efi"
default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"

#fallback_config="/etc/mkinitcpio.conf"
#fallback_image="/boot/initramfs-linux-fallback.img"
fallback_uki="/efi/EFI/Linux/arch-linux-fallback.efi"
fallback_options="-S autodetect"

And now let's generate our UKIs:

$ arch-chroot /mnt mkinitcpio -P
==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio.conf'
  -> -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -U /efi/EFI/Linux/arch-linux.efi --splash /usr/share/systemd/bootctl/splash-arch.bmp --microcode /boot/amd-ucode.img
==> Starting build: '6.4.12-arch1-1'
  -> Running build hook: [base]
  -> Running build hook: [systemd]
  -> Running build hook: [autodetect]
  -> Running build hook: [modconf]
  -> Running build hook: [kms]
  -> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci'
  -> Running build hook: [sd-vconsole]
  -> Running build hook: [sd-encrypt]
  -> Running build hook: [block]
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/tmp/mkinitcpio.Hp46P3'
==> Image generation successful
==> Creating unified kernel image: '/efi/EFI/Linux/arch-linux.efi'
  -> Using UEFI stub: '/usr/lib/systemd/boot/efi/linuxx64.efi.stub'
  -> Using os-release file: '/etc/os-release'
  -> Using cmdline file: '/etc/kernel/cmdline'
  -> Using splash image: '/usr/share/systemd/bootctl/splash-arch.bmp'
  -> Using kernel image: '/boot/vmlinuz-linux'
  -> Using microcode image: '/boot/amd-ucode.img'
==> Unified kernel image generation successful
==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'
==> Using configuration file: '/etc/mkinitcpio.conf'
  -> -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -U /efi/EFI/Linux/arch-linux-fallback.efi -S autodetect --microcode /boot/amd-ucode.img
==> Starting build: '6.4.12-arch1-1'
  -> Running build hook: [base]
  -> Running build hook: [systemd]
  -> Running build hook: [modconf]
  -> Running build hook: [kms]
==> WARNING: Possibly missing firmware for module: 'ast'
  -> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci'
  -> Running build hook: [sd-vconsole]
  -> Running build hook: [sd-encrypt]
  -> Running build hook: [block]
==> WARNING: Possibly missing firmware for module: 'aic94xx'
==> WARNING: Possibly missing firmware for module: 'bfa'
==> WARNING: Possibly missing firmware for module: 'qed'
==> WARNING: Possibly missing firmware for module: 'qla1280'
==> WARNING: Possibly missing firmware for module: 'qla2xxx'
==> WARNING: Possibly missing firmware for module: 'wd719x'
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/tmp/mkinitcpio.nCqFo4'
==> Image generation successful
==> Creating unified kernel image: '/efi/EFI/Linux/arch-linux-fallback.efi'
  -> Using UEFI stub: '/usr/lib/systemd/boot/efi/linuxx64.efi.stub'
  -> Using os-release file: '/etc/os-release'
  -> Using cmdline file: '/etc/kernel/cmdline'
  -> Using kernel image: '/boot/vmlinuz-linux'
  -> Using microcode image: '/boot/amd-ucode.img'
==> Unified kernel image generation successful

If we have a look into our EFI partition, we should see our UKIs:

$ ls -lR /mnt/efi
/mnt/efi:
total 4
drwxr-xr-x 3 root root 4096 Aug 25 20:51 EFI

/mnt/efi/EFI:
total 4
drwxr-xr-x 2 root root 4096 Aug 25 21:02 Linux

/mnt/efi/EFI/Linux:
total 118668
-rwxr-xr-x 1 root root 29928960 Aug  9 14:07 arch-linux.efi
-rwxr-xr-x 1 root root 91586048 Aug  9 14:07 arch-linux-fallback.efi

Services and Boot Loader

OK, we're just about done in the archiso, we just need to enable some services, and install our bootloader:

$ systemctl --root /mnt enable systemd-resolved systemd-timesyncd NetworkManager
$ systemctl --root /mnt mask systemd-networkd
$ arch-chroot /mnt bootctl install --esp-path=/efi

OK, let's reboot, and then finish off the installation. Whilst you're rebooting, head into your UEFI/BIOS and put Secure Boot into "Setup Mode", you'll need to check with your PC/Motherboard manufacturer for exact details on how to do that:

$ sync
$ systemctl reboot --firmware-setup

Secure Boot with TPM2 Unlocking

Ok, now that we've rebooted and logged back in, we need to first check that Secure Boot is in "Setup Mode":

$ sbctl status
Installed:  ✓ sbctl is installed
Setup Mode: ✗ Enabled
Secure Boot:    ✗ Disabled
Vendor Keys:    none

Looks good. Let's first create and enroll our personal Secure Boot keys:

$ sudo sbctl create-keys
$ sudo sbctl enroll-keys -m

We use the -m option to enroll the Microsoft vendor key as well as our self-created platform key. IF you're sure that none of your hardware has any OPROMs signed by Microsoft, you can leave this option out. WARNING - Your system CAN get bricked if you're mistaken. I know it sucks, but it's usually safer to just install the Microsoft vendor key. YOU HAVE BEEN WARNED!!! :-)

Let's use sbctl to sign our .efi files. We'll sign the systemd-boot .efi file, and our UKI files which mkinitcpio is now generating for us. We'll use the -s option so sbctl will automatically resign them for us when we update the kernel or bootloader through pacman:

$ sudo sbctl sign -s -o /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed /usr/lib/systemd/boot/efi/systemd-bootx64.efi
$ sudo sbctl sign -s /efi/EFI/BOOT/BOOTX64.EFI
$ sudo sbctl sign -s /efi/EFI/Linux/arch-linux.efi
$ sudo sbctl sign -s /efi/EFI/Linux/arch-linux-fallback.efi

Let's reinstall the kernel to make sure it resigns the UKI:

$ sudo pacman -S linux
... (I've cut out the blurb)** ...
(4/4) Signing EFI binaries...
Generating EFI bundles....
✓ Signed /efi/EFI/Linux/arch-linux-fallback.efi
✓ Signed /efi/EFI/Linux/arch-linux.efi

Looking good. Reboot your PC now, so the Secure Boot settings will get saved. Once rebooted we need to configure automatic unlocking of the root filesystem, by binding a LUKS key to the TPM. Let's generate a new key, add it to our volume so it can be used to unlock it in addition to the existing keys, and bind this new key to PCRs 0 and 7 (the system firmware and Secure Boot state). First things first, let's generate a recovery key in case it all gets messed up some time in the future:

$ sudo systemd-cryptenroll /dev/gpt-auto-root-luks --recovery-key

Keep this key safe. Keep it hidden. Moving on, we'll now enroll our system firmware and Secure Boot state. Normally, this would allow our TPM to unlock our encrypted drive, as long as the state hasn't changed. If you're particularly paranoid, you can add --tpm2-with-pin=yes, so we get prompted for a PIN code on boot.

$ sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7  /dev/gpt-auto-root-luks

Now we need to reboot and test this. If all goes well, we should be able to unlock our encrypted root partition with just a PIN code.

Job done

And with that, we have a very nice base install completed. You can now go ahead and install your Desktop Environment. I'll throw a YouTube video together in the next few days, walking through this install and update this blogpost. Until then, have a good one!

Update

I've embedded a YouTube video below for your viewing pleasure.

More information

Discoverable Partitions Specification. Install Arch with Secure boot, TPM2-based LUKS encryption, and systemd-homed Podman Trusted Platform Module Linux Boot Partitions Brave New Trusted Boot World Unlocking LUKS2 volumes with TPM2, FIDO2, PKCS#11 Security Hardware on systemd 248

social