Phase 4: Bootloader & Reboot

Phase 4: Bootloader & Reboot

Configure mkinitcpio

mkinitcpio generates the initial ramdisk (initramfs) — the tiny Linux environment that runs BEFORE your root filesystem is available. It handles LUKS decryption, btrfs assembly, and hardware detection during early boot.

Verify current HOOKS

# BEFORE: see the default HOOKS line
grep '^HOOKS' /etc/mkinitcpio.conf

Update HOOKS

The HOOKS line controls what runs in the initramfs and in what ORDER. Order matters — encrypt must come before btrfs and filesystems.

# DRY RUN: preview the replacement
sed 's/^HOOKS=.*/HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block encrypt btrfs filesystems fsck)/' /etc/mkinitcpio.conf | grep '^HOOKS'
# APPLY
sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block encrypt btrfs filesystems fsck)/' /etc/mkinitcpio.conf
# VERIFY
grep '^HOOKS' /etc/mkinitcpio.conf
# Also verify MODULES is empty (NVIDIA modules load via kms hook, not here)
grep '^MODULES' /etc/mkinitcpio.conf
Hook Why

base udev autodetect

Core infrastructure — hardware detection, device nodes

modconf

Load modules from /etc/modprobe.d/

kms

Kernel Mode Setting — early GPU init for smooth boot. Modern nvidia (>=560) supports this

keyboard keymap consolefont

Keyboard input + layout + font for LUKS passphrase prompt

block

Block device drivers (NVMe, SATA, USB)

encrypt

LUKS decryption — prompts for passphrase, opens cryptroot. Must be before btrfs/filesystems

btrfs

Btrfs module loaded early — required for multi-device, good practice for single

filesystems

Mount the root filesystem

fsck

Filesystem check on boot

Generate initramfs

# -P = generate for ALL presets in all installed kernels
mkinitcpio -P

Enable fallback presets

Arch ships with fallback commented out in the preset files. The preset controls what mkinitcpio -P generates — if fallback isn’t enabled, the fallback boot entry has no image to load.

# BEFORE: see what's configured
grep -n 'PRESETS\|fallback' /etc/mkinitcpio.d/linux.preset
Default state (fallback commented out)
PRESETS=('default')
#PRESETS=('default' 'fallback')
#fallback_image="/boot/initramfs-linux-fallback.img"
#fallback_options="-S autodetect"

Fix linux.preset:

# Comment out default-only PRESETS, uncomment the one with fallback
sed -i "s/^PRESETS=('default')/#PRESETS=('default')/" /etc/mkinitcpio.d/linux.preset
sed -i "s/^#PRESETS=('default' 'fallback')/PRESETS=('default' 'fallback')/" /etc/mkinitcpio.d/linux.preset
# Uncomment fallback image path and options
sed -i 's/^#fallback_image/fallback_image/' /etc/mkinitcpio.d/linux.preset
sed -i 's/^#fallback_options/fallback_options/' /etc/mkinitcpio.d/linux.preset
# VERIFY
grep -n 'PRESETS\|fallback' /etc/mkinitcpio.d/linux.preset

Fix linux-lts.preset (same pattern):

sed -i "s/^PRESETS=('default')/#PRESETS=('default')/" /etc/mkinitcpio.d/linux-lts.preset
sed -i "s/^#PRESETS=('default' 'fallback')/PRESETS=('default' 'fallback')/" /etc/mkinitcpio.d/linux-lts.preset
sed -i 's/^#fallback_image/fallback_image/' /etc/mkinitcpio.d/linux-lts.preset
sed -i 's/^#fallback_options/fallback_options/' /etc/mkinitcpio.d/linux-lts.preset
# VERIFY
grep -n 'PRESETS\|fallback' /etc/mkinitcpio.d/linux-lts.preset
# Regenerate all images (now includes fallback)
mkinitcpio -P
# Verify all 4 images exist
ls -lh /boot/initramfs-*
Actual output (P16g — 4 images confirmed)
-rw------- 1 root root 190M Apr  2 17:48 /boot/initramfs-linux-fallback.img
-rw------- 1 root root 190M Apr  2 17:48 /boot/initramfs-linux-lts-fallback.img
-rw------- 1 root root 132M Apr  2 17:48 /boot/initramfs-linux-lts.img
-rw------- 1 root root 132M Apr  2 17:48 /boot/initramfs-linux.img
  • Default images (~132M) — autodetected modules only (fast, minimal)

  • Fallback images (~190M) — ALL kernel modules included (slower boot, but works when autodetect misses a driver)

Preset verification output (after sed fix)
[root@archiso /]# grep -n 'PRESETS\|fallback' /etc/mkinitcpio.d/linux.preset
7:#PRESETS=('default')
8:PRESETS=('default' 'fallback')
15:#fallback_config="/etc/mkinitcpio.conf"
16:fallback_image="/boot/initramfs-linux-fallback.img"
17:#fallback_uki="/efi/EFI/Linux/arch-linux-fallback.efi"
18:fallback_options="-S autodetect"
Warnings during fallback build (qat_6xxx, aic94xx, bfa, qla2xxx, wd719x, etc.) are safe to ignore — these are firmware for enterprise storage controllers and Intel QuickAssist hardware not present in the P16g. The consolefont warning is cosmetic (no custom font configured).

Install systemd-boot

systemd-boot is a simple UEFI boot manager. It reads entries from the EFI partition and presents a menu. Lighter than GRUB, no config generation — you write the entries directly.

# --esp-path = where the EFI System Partition is mounted
# Do NOT use --boot-path — we're not using XBOOTLDR. Everything lives on the ESP.
bootctl --esp-path=/boot/efi install
# Create the entries directory on the ESP (bootctl may not create it without --boot-path)
mkdir -p /boot/efi/loader/entries
The "world accessible" and "Not booted with EFI" warnings in chroot are cosmetic and expected. The "Running in a chroot, enabling --graceful" message is normal.

Boot entries and kernels MUST be on the same partition

Per the Boot Loader Specification: "those files must be located on the same partition" as the .conf entry file, and paths are "absolute paths relative to the root of that file system."

This means entries (.conf files) AND kernels (vmlinuz-, initramfs-) must ALL reside on the ESP (VFAT). The ext4 /boot partition is for OS-level kernel management (pacman installs here), but systemd-boot reads from the ESP.

Why NOT ext4 XBOOTLDR? The spec states: "the ESP and XBOOTLDR must use a file system readable by the firmware. For most systems this means VFAT." The ThinkPad P16g firmware cannot read ext4 — systemd-boot launches but finds no entries, causing a "reboot to firmware" loop.

Field-validated: ThinkPad P16g Gen 3 — ext4 XBOOTLDR (type ea00) did NOT work. Kernels and entries on the ESP (VFAT) = boots correctly. This matches the Razer’s working configuration.

Loader Configuration

# default = which entry boots automatically after timeout
# timeout = seconds to show the menu (3s is enough to press a key)
# editor no = prevents editing boot params at the menu (security)
cat > /boot/efi/loader/loader.conf << 'EOF'
default arch.conf
timeout 3
console-mode max
editor no
EOF
cat /boot/efi/loader/loader.conf

Capture LUKS UUID

Same variable pattern as crypttab — capture the UUID once, use it in all 3 boot entries.

ROOT_LUKS_UUID=$(blkid -s UUID -o value /dev/nvme0n1p3)
echo "Root LUKS UUID: $ROOT_LUKS_UUID"

Create Boot Entries

Three entries: main kernel (daily driver), fallback (all modules, for debugging), LTS (safety net if mainline breaks NVIDIA).

Kernel parameters explained:

  • cryptdevice=UUID=…​:cryptroot — tells the encrypt hook which partition to decrypt and what to name the mapper device

  • root=/dev/mapper/cryptroot — the decrypted device to mount as root

  • rootflags=subvol=@ — mount the @ btrfs subvolume as /

  • nvidia_drm.modeset=1 — enable NVIDIA DRM kernel modesetting (required for Wayland/Hyprland)

  • mem_sleep_default=s2idle — use modern standby instead of deep sleep (prevents resume issues on Intel HX + NVIDIA laptops)

Main Entry

cat > /boot/efi/loader/entries/arch.conf << EOF
title   Arch Linux
linux   /vmlinuz-linux
initrd  /intel-ucode.img
initrd  /initramfs-linux.img
options cryptdevice=UUID=$ROOT_LUKS_UUID:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle
EOF

Fallback Entry

# Fallback includes ALL kernel modules (not just autodetected ones)
# Use this if the main entry fails to boot — slower but more compatible
cat > /boot/efi/loader/entries/arch-fallback.conf << EOF
title   Arch Linux (fallback)
linux   /vmlinuz-linux
initrd  /intel-ucode.img
initrd  /initramfs-linux-fallback.img
options cryptdevice=UUID=$ROOT_LUKS_UUID:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle
EOF

LTS Entry

# LTS kernel — if a mainline kernel update breaks NVIDIA or WiFi,
# boot this to get a working system while you troubleshoot
cat > /boot/efi/loader/entries/arch-lts.conf << EOF
title   Arch Linux LTS
linux   /vmlinuz-linux-lts
initrd  /intel-ucode.img
initrd  /initramfs-linux-lts.img
options cryptdevice=UUID=$ROOT_LUKS_UUID:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle
EOF

Verify Boot Entries

# List all entries systemd-boot sees
bootctl list
Expected output (entries on ESP)
type: Boot Loader Specification Type #1 (.conf)
title: Arch Linux (default)
   id: arch.conf
source: /boot/efi/loader/entries/arch.conf
 linux: /vmlinuz-linux
initrd: /intel-ucode.img
        /initramfs-linux.img
options: cryptdevice=UUID=<ROOT-LUKS-UUID>:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle

type: Boot Loader Specification Type #1 (.conf)
title: Arch Linux LTS
   id: arch-lts.conf
 linux: /vmlinuz-linux-lts
initrd: /intel-ucode.img
        /initramfs-linux-lts.img

type: Boot Loader Specification Type #1 (.conf)
title: Arch Linux (fallback)
   id: arch-fallback.conf
 linux: /vmlinuz-linux
initrd: /intel-ucode.img
        /initramfs-linux-fallback.img
# Verify the UUID is correct in all entries (not a variable name)
grep 'cryptdevice' /boot/efi/loader/entries/*.conf
# Verify kernel images exist on the ESP (where systemd-boot reads them)
ls -lh /boot/efi/vmlinuz-* /boot/efi/initramfs-* /boot/efi/intel-ucode.img

Copy Kernels to ESP

systemd-boot reads kernels from the same partition as the entry files. Since entries are on the ESP, kernels must be there too. The ext4 /boot is for OS-level storage (pacman installs here).

# Copy default kernel images to ESP (no fallback — 512M ESP can't hold all 4)
sudo cp /boot/vmlinuz-linux /boot/efi/
sudo cp /boot/vmlinuz-linux-lts /boot/efi/
sudo cp /boot/initramfs-linux.img /boot/efi/
sudo cp /boot/initramfs-linux-lts.img /boot/efi/
sudo cp /boot/intel-ucode.img /boot/efi/
# Verify — should be ~310M used, ~200M free
ls -lh /boot/efi/vmlinuz-* /boot/efi/initramfs-* /boot/efi/intel-ucode.img
df -h /boot/efi

Pacman Hook: Auto-Sync Kernels to ESP

Without this hook, every kernel or nvidia update requires manually copying files to the ESP. This hook runs automatically after pacman installs/upgrades kernel packages.

sudo mkdir -p /etc/pacman.d/hooks
sudo tee /etc/pacman.d/hooks/99-esp-kernel-sync.hook << 'EOF'
[Trigger]
Type = Path
Operation = Install
Operation = Upgrade
Target = usr/lib/modules/*/vmlinuz
Target = usr/lib/initcpio/*
Target = boot/*

[Action]
Description = Syncing kernels and initramfs to ESP...
When = PostTransaction
Exec = /bin/sh -c 'cp /boot/vmlinuz-* /boot/efi/ && cp /boot/initramfs-linux.img /boot/initramfs-linux-lts.img /boot/intel-ucode.img /boot/efi/'
EOF
# Verify hook exists
cat /etc/pacman.d/hooks/99-esp-kernel-sync.hook
This hook copies only the default initramfs images (not fallback) to keep the ESP under 512M. Fallback images remain on /boot (ext4) only.

Register Boot Entry with efibootmgr

Some firmware (ThinkPad) doesn’t auto-detect the systemd-boot EFI binary. Register it explicitly:

efibootmgr --create --disk /dev/nvme0n1 --part 1 --label "Arch Linux" --loader /EFI/systemd/systemd-bootx64.efi
# Verify Arch Linux is first in boot order
efibootmgr -v | head -3

Reboot

# Exit chroot — returns to the live ISO environment
exit
# Unmount everything recursively (order doesn't matter with -R)
umount -R /mnt
reboot

Remove the USB when the system powers off.

At boot you will see TWO passphrase prompts:

  1. cryptroot — from the kernel cmdline cryptdevice= parameter (the encrypt hook)

  2. crypthome — from /etc/crypttab (systemd reads this during init)

This is expected. If you configured a keyfile in Phase 3 (optional), you’ll only see one prompt.

If boot fails: Press the down arrow at the systemd-boot menu to select "Arch Linux (fallback)" or "Arch Linux LTS". If all entries fail, boot the USB again and arch-chroot /mnt to fix.