dotfiles/guides/05_SYSTEMD_INITRAMFS.md

8.3 KiB

Systemd initramfs

The initial ramdisk is in essence a very small environment (early userspace) whihc loads various kernel modules and sets up necessary things before handing control over to init program (systemd).

By default, Arch Linux uses a BusyBox+udev based initial ramdisk, generated by mkinitcpio. This default initrd is essentially just a small script, that executes other scripts, called hooks.

As an alternative to this, it's possible to have systemd run from the very start, during that initial ramdisk phase. With this approach, the tasks ran at this phase are determined by regular systemd unit files.

Why?

Obviously, BusyBox initramfs works just fine, so why would you want to switch? Well, there's a few reasons:

  • Consistency across boot phases: The same systemd process that handles your system after boot can also manage the early userspace during boot, providing consistency in handling services, devices and dependencies throughout the entire boot process.
  • Simplified troubleshooting: The tools and logs available during the boot process will be the same as those used once the system is fully booted, allowing you to troubleshoot problems with familiar tools (journalctl, systemctl, ...)
  • Consistent Unit Files: Since systemd uses the same unit files in the initramfs as it does in the fully booted system, the configuration for many tasks (like mounting filesystems) is unified, reducing duplication of configuration files.
  • TPM Unlocking Support: Systemd has built-in support for requesting data from TPM, allowing for a setup with TPM auto-unlocking an encrypted root partition, without having to specify the decryption password.
  • Parallel Service Startup: Systemd is known for the ability to start services in parallel, which can potentially speed up the boot process compared to sequential script-based approach.
  • Integrated Mount Handling: With systemd, managing complex mount setup (e.g. LVM RAID) can be more seamless, since it natively supports these and can handle them with less custom scripting.

That said, it's important to also mention some downsides and reasons why you might not want to use systemd-based initramfs:

  • Simplicity: If you prefer a simple, more minimalistic approach, BusyBox-based initramfs might be sufficient and easier to manage.
  • Size: A systemd-based initramfs might be larger than a minimal BusyBox-based initramfs, which could be a concern on systems with very limited space.
  • Compatibility: If you're running some custom scripts or hooks, they might not work with a systemd-based initramfs.

Switching to systemd initramfs

Open /etc/mkinitcpio.conf and find a line that starts with HOOKS=

  • Change udev to systemd
  • Change keymap consolefont to sd-vconsole
  • Add sd-encrypt before block, and remove encrypt
  • If you were using mkinitcpio-numlock, also remove numlock, it doesn't work with systemd (we'll go over how to auto-enable numlock later)

Additionally, with systemd initramfs, you shouldn't be specifying root nor cryptdevice kernel arguments, as systemd can actually pick those up automatically (they'll be discovered by systemd-cryptsetup-generator and auto-mounted from initramfs via systemd-gpt-auto-generator). We will however still need the rootflags argument for selecting the btrfs subvolume (unless your default subvolume is the root partition subvolume).

So, let's edit our kernel parameters:

echo "rw loglevel=3" > /etc/kernel/cmdline  # overwrite the existing cmdline
echo "rootflags=subvol=/@" >> /etc/kernel/cmdline

You'll also need to modify the /etc/fstab, as systemd will not use the /dev/mapper/cryptfs name, but rather you'll have a /dev/gpt-auto-root (there'll also be /dev/gpt-auto-root-luks, which is the encrypted partition). If you prefer using a mapper device, you can also use /dev/mapper/root. Alternatively, you can use the label to mount. (if you followed the installation guide, that would be /dev/disk/by-label/FS.)

vim /etc/fstab

Finally, regenerate the initramfs with: pacman -S linux (you could also do mkinitcpio -P, however that won't trigger the pacman hook which auto-signs our UKI images for secure boot, so you'd have to re-sign them with sbctl manually, if you're using secure-boot) and reboot to check if it worked.

Activating numlock

Since we had to remove mkinitcpio-numlock, as that hook isintended for BusyBox based initrd, we'll want to have an alternative available.

First though, we should also remove the package: pacman -R mkinitcpio-numlock.

The simple, but imperfect option

There is a systemd-numlockontty AUR package which creates a systemd service that enables numlock in TTYs after booting (you'll need to enable it), this however doesn't happen in initramfs directly, only afterwards.

Depending on what you will need, this may be sufficient. If you are going to be typing a decryption password at this early stage and you wish to have numlock support there, you will need to do some more work.

The proper solution

To enable numlock before you're prompted for the decryption password, we'll need to create a custom initcpio hook, that will return a systemd service which will do the enabling. We'll put this hook into /usr/lib/initcpio/install/numlock, with the following content:

#!/bin/bash
build() {
add_binary /bin/bash
add_binary /usr/bin/setleds
add_binary /usr/local/bin/numlock

cat >"$BUILDROOT/usr/lib/systemd/system/numlock.service" <<EOF
[Unit]
Description=Enable numlock
Before=cryptsetup-pre.target
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/numlock
EOF

add_systemd_unit cryptsetup-pre.target
cd "$BUILDROOT/usr/lib/systemd/system/sysinit.target.wants" || exit
ln -sf /usr/lib/systemd/system/cryptsetup-pre.target cryptsetup-pre.target
ln -sf /usr/lib/systemd/system/numlock.service numlock.service
}

help() {
cat <<EOF
This hook adds support to enable numlock before sd-encrypt hook is run.
EOF
}

This script is also present in my dotfiles, so you can just copy it from there:

cp ~/dots/root/usr/lib/initcpio/install/numlock /usr/lib/initcpio/install

Next we will need to create that /usr/local/bin/numlock script. This script will do the actual enabling of numlock. Note that we can only use the binaries that we explicitly included in our hook inside our script.

#!/bin/bash
for tty in /dev/tty[0-9]; do
/usr/bin/setleds -D +num < "$tty"
done

If you ran the install_root.sh script from my dotfiles during INSTALLATION, this script will already be present in your /usr/local/bin

Now we will need to add our custom new numlock hook to /etc/mkinitcpio.conf, before the sd-encrypt hook (assuming you're using encryption), but after the keyboard and sd-vconsole hooks.

Finally, we'll need to rebuild initramfs, which we should trigger with sudo pacman -S linux, to make sure the secure-boot signing also runs. When re-building the initramfs, pay attention on the output, you should see it pass with no errors:

-> Running build hook: [base]
-> Running build hook: [systemd]
-> Running build hook: [autodetect]
-> Running build hook: [microcode]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
-> Running build hook: [sd-vconsole]
-> Running build hook: [numlock]  # <-- make sure this is present
-> Running build hook: [sd-encrypt]
-> Running build hook: [block]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]

Note

If you see some warnings there, like: ==> WARNING: Possibly missing firmware for module: 'xyz', you can usually safely ignore these. Just make sure there's no ==> ERROR: ...

If you didn't see any errors, you can now reboot.

Important

In some cases, the numlock led indicator might not turn on immediately, even though numlock was actually turned on. This may mislead you towards thinking it is not on, even though it actually is. I'd recommend trying it out by actually typing something it at this time.

Note that after this early boot stage, the indicator should light up eventually.