Welcome to mrk's website!

[Gentoo] LVM-on-LUKS2 with custom initramfs and EFISTUB booting.

Edit Date Note
2020.01.10T02:41 Updated script to use UUID. Also need {console,tty,null} devices for before devtmpfs is mounted.

Packages

Packages Compile notes
sys-apps/busybox Compile with USE+=static.
sys-fs/cryptsetup Compile with USE+=static.
sys-fs/lvm2 Compile with USE+=static. On my minimal profile OpenRC system this needed eudev with USE+=static-libs as well. Otherwise the lvm2 compile would fail with a linking error about not finding -ludev.

Initramfs folder setup

As with the linked reference guide, we'll also use /usr/src/initramfs to store the initramfs files.

  1. Create the folder hierarchy

    • mkdir /usr/src/initramfs
    • cd /usr/src/initramfs
    • mkdir -p {bin,dev,etc,lib,lib64,mnt/root,proc,root,sbin,sys}
  2. Copy needed binaries/files. Verify that the binaries are static by using file and/or ldd.

    • cp -a /dev/{console,null,tty} dev/
    • cp -a /dev/{urandom,random} dev/
    • cp -a /bin/busybox bin/busybox
    • cp -a /sbin/cryptsetup sbin/cryptsetup
    • cp -a /sbin/lvm.static sbin/lvm

Setup LVM to not use udev

In etc/lvm/lvm.conf, place the following.

devices {
    # Disable scanning udev for md/multipath components.
    # This is required with recent versions of lvm2, even if you use another solution for
    # your LV device nodes; without it lvm commands will stall for minutes waiting for udev.
    multipath_component_detection = 0
    md_component_detection = 0
}
activation {
    # Set to 0 to disable udev synchronisation (if compiled into the binaries).
    udev_sync = 0
    # Set to 0 to disable the udev rules installed by LVM2
    udev_rules = 0
}

Create the init script

Do touch init && chmod +x init. Now place the following into init. Adjust the config section with crypt device, Volume Group, and Logical Volume names.

#!/bin/busybox sh

#
# Init script for root on LUKS + LVM.
# https://wiki.gentoo.org/wiki/Custom_Initramfs
#

###### START config ###############

# What is the device?
CRYPT_UUID="a8a160c4-dedc-403b-9040-8e402574f3c6"
# What should we name the crypt device when we open it?
CRYPT_NAME="immanuel-crypt"

# The Volume Group where the root Logical Volume lies.
VG_NAME="immanuel-vg"
# The name of the root Logical Volume in above ${VG_NAME}.
LV_ROOT="gentoo-root"

###### END   config ###############

fail() {
    echo "ERROR: $@"
    echo "Something went wrong. Sleeping for 10 and then shutting down."
    sleep 10
    exit 1
}

# Mount the /dev, /proc, and /sys filesystems.
echo "INFO: Mounting temporary /dev, /proc, and /sys."
mount -t devtmpfs none /dev  || fail "/dev mount failed."
mount -t proc     none /proc || fail "/proc mount failed."
mount -t sysfs    none /sys  || fail "/sys mount failed."

# Waiting for drive to come online.
UUID_DEVICE=""
for i in $(seq 1 5); do
	echo "INFO: [attempt ${i}] Trying to find ${CRYPT_UUID}."
	sleep 1
	UUID_DEVICE="$(findfs UUID="${CRYPT_UUID}")"
	if [ -n "${UUID_DEVICE}" ]; then
		echo "INFO: Found UUID!"
		break
	fi
done
[ -z "${UUID_DEVICE}" ] && fail "Could not find the UUID within time."


# Do your stuff here.
echo "INFO: Unlocking LUKS ${CRYPT_DEVICE} as ${CRYPT_NAME}"
mkdir -p -m0700 /run/cryptsetup
cryptsetup --tries=5 luksOpen "${UUID_DEVICE}" "${CRYPT_NAME}" \
	|| fail "luksOpen failed."

echo "INFO: Activating root LV."
lvm vgscan --mknodes # Creates /dev/mapper/control
lvm lvchange -a ly "${VG_NAME}/${LV_ROOT}" || fail "Could not find LV root."
lvm vgscan --mknodes # Creates /dev/mapper/${VG_NAME} and
                     # /dev/${VG_NAME}/${LV_ROOT}

# Mount the root filesystem.
echo "INFO: Mounting the root filesystem system."
mount "/dev/${VG_NAME}/${LV_ROOT}" /mnt/root \
	|| fail "Failed to mount root file system."

# Clean up.
echo "INFO: Unmounting up temporary /proc and /sys."
umount /dev  || fail "/dev failed to unmount."
umount /proc || fail "/proc failed to unmount."
umount /sys  || fail "/sys failed to unmount."

# Boot the real thing.
echo "INFO: Switching root to real system."
exec switch_root /mnt/root /sbin/init || fail "Failed to switch_root."
An 'up-to-date' version can be found here https://gitlab.com/mrkmr/gentoo-conf/-/blob/master/initramfs/init.

Kernel configuration

Now we need to include the initramfs in our kernel. After make menuconfig in /usr/src/linux, search for CONFIG_INITRAMFS_SOURCE. Set this value to /usr/src/initramfs. Now the kernel will use the initramfs folder we created with our init script on boot!

Next we need to ensure we have EFISTUB enabled so we can directly boot the kernel from UEFI without a bootloader. Ensure CONFIG_EFI_STUB is enabled. Now make, make modules_install, and make install.

You may also want to add quiet loglevel=5 to CONFIG_CMDLINE so that it is easier to see initramfs output like the cryptsetup password prompt.

EFISTUB booting

The way I have my partition setup is no /boot partition (it's a part of the root partition). I have an EFI partition mounted on /boot/efi. Copy your recently compiled kernel (should be in /boot/vmlinuz-XXX-gentoo-rX) into /boot/efi.

Now we can create the EFI boot entry to directly boot our kernel. Here the /boot/efi partition is partition 1 on disk nvme0n1. My kernel is vmlinuz-5.4.80-gentoo-r1. efibootmgr --create --disk /dev/nvme0n1 --part 1 --label Gentoo 5.4.80 r1 --loader \vmlinuz-5.4.80-gentoo-r1

Sources

https://wiki.gentoo.org/wiki/Custom_Initramfs