Encrypted Void Linux Installation Booting From EFI Stub

30 April 2026

Recently I changed my notebook’s distro from Arch to Void Linux. Since I wanted some special features that the provided installer cannot handle yet, I had to go with the good ol’ chroot method. These are the captain’s logs documenting the whole process. It exists in case I ever decide to do this installation again, or for possibly helping someone out who’s following the same path.

This post describes how to do a fresh installation with the following features:

Adjust installation environment

Login as root so you don’t have to type sudo all the time.

Let’s setup a keyboard layout and wi-fi connection for the installation.

Check all available keymaps and choose the appropriate one for your system.

find /usr/share/kbd/keymaps/ -type f -iname '*.map.gz'

Then load it without the .map.gz suffix. For example,

loadkeys br-abnt2

The installer comes with wpa_supplicant. To setup an wi-fi connection, let’s run its CLI in iteractive mode

wpa_cli

Inside its repl, scan for networks and take note if your desired SSID (the network name advertised by your router) appears

> scan
> scan_results

Add and enable a new network. Substitute MYSSID and MYPASSWORD for your actual network name and password.

> add_network
0
> set_network 0 ssid "MYSSID"
> set_network 0 psk "MYPASSWORD"
> enable_network 0

Finally, save and quit

> save_config
> quit

Partitioning

On what follows, I refer to the disk where we are installing the system as /dev/nvme0n1. Adapt it to the appropriate device descriptor in your configuration (/dev/sda, /dev/sdblsblk is your friend here.)

The scheme consists of two partitions: one (unencrypted) for the EFI bootloader and another (encrypted) for the rest. LVM then takes care of further partitioning.

Run cfdisk and create a GPT table and a (at least) 1G partition of type EFI System, allocating the rest for a Linux filesystem partition. You should get something like the below,

Device           Start       End   Sectors  Size Type
/dev/nvme0n1p1    2048   2099199   2097152    1G EFI System
/dev/nvme0n1p2 2099200 500117503 499908303  238G Linux filesystem

In most tutorials out there, the recommendation is to allocate a very small EFI partition. Generally, from 100M to 512M. In our case, however, using no bootloader produces a quite large initramfs (~150M). Since Void does not automatically clean /boot after a Kernel upgrade, I recommend playing it safe to prevent future headaches.

The first partition will contain our kernel and (from the EFI spec) must be formatted as FAT32. Also, in this setup, we leave this one unencrypted.

mkfs.fat -F 32 -n EFI /dev/nvme0n1p1

Disk encryption

Now it’s time to encrypt the remainder of the drive. Use cryptsetup to format the second partition as LUKS2 and leave it open.

cryptsetup luksFormat /dev/nvme0n1p2 --type luks2 --label luks
cryptsetup open --type luks /dev/nvme0n1p2 vault

The command will prompt you for a password. It is very important that you remember this password, or you’ll get locked out of your system. Also, keep in mind that the encryption is only as safe as the strength of this password.

Logical Volume partitioning

We use LVM to manage the partitions inside the encrypted storage. Begin by creating a volume group (with the unimaginative name void)

vgcreate void /dev/mapper/vault

If using a Swap partition, create and activate it.

lvcreate --name swap -L 8G void
mkswap /dev/void/swap
swapon /dev/void/swap

I personally go with a swapfile. jjjjjjj Create volumes for / and /home/,

lvcreate --name root -L 100G void
lvcreate --name home -l 100%FREE void

Notice the -L and -l flags! One is required for absolute sizes while the other is for relative ones.

For the filesystems, I’m going with the classic ext4. You can use any other Linux-supported filesystem in here.

mkfs.ext4 -L root /dev/void/root
mkfs.ext4 -L home -O encrypt /dev/void/home

You only need to active the -O encrypt flag when doing per-user home folder encryption.

After this, your disk should look like

$ lsblk /dev/nvme0n1 -o NAME,SIZE,TYPE,FSTYPE
NAME               SIZE TYPE  FSTYPE
nvme0n1          238.5G disk
├─nvme0n1p1          1G part  vfat
└─nvme0n1p2        238G part  crypto_LUKS
  └─vault          238G crypt LVM2_member
    ├─void-root    100G lvm   ext4
    └─void-home    138G lvm   ext4

System Installation

Mount all partitions/volumes to the appropriate subdirectories of /mnt,

mkdir -p /mnt
mkdir -p /mnt/home
mkdir -p /mnt/boot
mount /dev/void/root /mnt
mount /dev/void/home /mnt/home
mount /dev/nvme0n1p1 /mnt/boot

For package installation, first copy the XBPS RSA keys from the live CD to the mounted root directory.

mkdir -p /mnt/var/db/xbps/keys
cp /var/db/xbps/keys/* /mnt/var/db/xbps/keys/

Install the base system and necessary packages for this setup,

xbps-install -Sy -R https://repo-default.voidlinux.org/current -r /mnt \
  base-system lvm2 cryptsetup efibootmgr fscrypt

Generate system’s /etc/fstab,

xgenfstab -U /mnt > /mnt/etc/fstab

The -U flag is because I, personally, prefer to use UUIDs instead of kernel descriptors.

Enter chroot at /mnt to complete the installation. Void uses dash as /bin/sh, but I prefer bash or zsh for iterative editing.

xchroot /mnt /bin/bash

Inside the chroot

Everything from now on will happen inside the chroot, thus you are already in your new system with no access to external packages from the Live CD.

Configure the root user,

chown root:root /
chmod 755 /
passwd root

Set a hostname,

echo <YOUR_FAVORITE_HOSTNAME> > /etc/hostname

Configure locale for glibc,

echo "LANG=en_US.UTF-8" > /etc/locale.conf
echo "en_US.UTF-8 UTF-8" >> /etc/default/libc-locales
xbps-reconfigure -f glibc-locales

EFI Stub

We are using the kernel itself as an EFI stub, in contrast to the Void docs, which use GRUB.

Void ships with a kernel hook in /etc/default/efibootmgr-kernel-hook for configuring how efibootmgr should create a new boot entry after every update. It’s just a shell script defining some environment variables sourced in /etc/kernel.d/post-install. Let’s edit it:

vi /etc/default/efibootmgr-kernel-hook

You should enable the hook and properly set where the EFI partition is.

# To allow efibootmgr to modify boot entries, set
MODIFY_EFI_ENTRIES=1

# Disk where EFI Partition is.  Default is /dev/sda
DISK="/dev/nvme0n1"

# Partition number of EFI Partition.  Default is 1
PART=1

The fourth variable, OPTIONS, sets the kernel command-line options. You have to carefully set it up so the kernel can properly decrypt the filesystem during boot. After careful reading of dracut(8), Matthias Totschnig’s post on Booting Void Linux using the EFI boot stub, and lots of tweaking, I arrived at these options

OPTIONS="rd.luks.uuid=4581f162-f661-48f6-aaa4-e2355f06aa6b \
         rd.lvm.lv=void/root \
         rd.lvm.lv=void/home \
         root=/dev/mapper/void-root \
         rootfstype=ext4 \
         rootflags=rw,relatime"

You can get the UUID of the encrypted partition through

lsblk /dev/nvme0n1p2 -o UUID --nodeps --noheadings

The other options establish the LVM volumes, a device descriptor (we could also use a label or UUID here) for the root filesystem, its type and desired flags.

Finally, you can also append any other kernel parameters to it. I, for one, like to have loglevel=4.

Bonus: Hibernation using a Swap File

I like to enable hibernation on my systems. By using a swapfile, it is encrypted for free, since it lies inside the root filesystem.

Begin by creating and activating the swap file:

mkswap -U clear --size 8G --file /swapfile
swapon /swapfile

Now edit /etc/fstab so it can know how to locate our swap file. Just add the line

/swapfile             none            swap        defaults      0 0

To enable hibernation, you must edit the kernel command-line parameters to tell it which volume contains the swap (resume) and what is its offset in the disk (resume_offset). You can get the offset doing

filefrag -v /swapfile | awk '$1=="0:" {print substr($4, 1, length($4)-2)}'

The additional parameters will look like this:

    resume=/dev/mapper/void-root   # LVM volume or UUID or LABEL
    resume_offset=12818432"

If you’re using an LVM volume as swap, point resume to it. In this case, there’s no need for an offset.

Bonus: USB Keyfile

The manpage for dracut.cmdline(7) is very well-written, so we are basically following what they recommend in there. A lot of this is similar to my previous post on Encrypting an external device with LUKS.

Let’s mount the pendrive to store the keyfile. I just recommend not using /mnt for the risk of mixing it up with our installation. Supposing the device in /dev/sdX and the filesystem is in its first partition,

mkdir -p /media/usb
mount /dev/sdX1 /media/usb

We create a random 4 Kb keyfile for entropy, but you can use absolutely any file you want (GPG key, family photo, etc.)

dd if=/dev/urandom of=/media/usb/keyfile bs=4096 count=1

Now add it to the LUKS encrypted partition with

cryptsetup luksAddKey /dev/nvme0n1p2 /media/sd128/keyfile

You can verify that it works with the luksDump command. Check that there are two keyslots in the output (one for the password, and another for the keyfile).

cryptsetup luksDump /dev/nvme0n1p2

Finally, edit /etc/default/efibootmgr-kernel-hook to add the respective kernel parameters. There are two important to us, rd.luks.key and rd.luke.key.tout. The first takes the form

    rf.luks.key=<path to key>:<device where it is stored>

The path is an absolute path from the device’s filesystem root, whereas the device can specified in any of the usual ways, such LABEL or UUID. For example, if its UUID is <UUID>, you should write,

    rf.luks.key=/keyfile:UUID=<UUID>

The second parameter establishes how long the kernel will search for the keyfile before giving up and asking for a password. For a 5s timeout,

    rf.luks.key.tout=5

Note that if you leave it at 0 (the default), it will never ask for the password.

Reconfigure the kernel

The currently installed kernel lacks our command-line options. Let’s fix it by telling xbps to reconfigure the linux package.

Check your current linux kernel version with xbps-query linux. It will appear in run_depends as a package linux<major>.<minor>. Or invoke the power of awk to do it as a oneliner,

xbps-query linux | awk -F'[_: ]+' '/pkgver:/ {print $2}'

Finally, reconfigure the package with the proper version.

xbps-reconfigure -f linux<major>.<minor>

I am recommending doing this in two steps because all this parsing can be fragile. You do not want to reconfigure the wrong package.

Depending on your system, the kernel may not be the first EFI entry. You can change the boot order by first checking it with efibootmgr (no parameters) and then reordering it with a comma-separated list

efibootmgr --bootorder XXXX,YYYY,ZZZZ...

User Creation With Filesystem Encryption

If you’re not much of a bash person, begin by installing your favorite shell.

xbps-install -S zsh

Let’s add a non-root user with a zsh default shell and some useful groups. See the documentation for all default groups in Void.

useradd iago:iago               \
    --create-home               \
    --shell /usr/bin/zsh        \
    --groups wheel,users,audio,video

passwd iago

Run (some variation of) the above for each user you want in the new machine.

You can also change the shell later with

chsh -s <shell> <user_name>

Allow sudo for wheel group

To allow sudo access to all users on the whell group, run visudo and uncomment the line

#%wheel ALL=(ALL) ALL

Encrypt home folders

While full-disk encryption secures the whole disk from external threats, you may also want to allow users files to only be accessible when they are logged. This can be specially useful for multiuser systems.

We employ the kernel’s support for filesystem-level encryption in here. Notice that I’m assuming your /home partition is on ext4, as we’ve done previously. This is important because most filesystems have no support for fscrypt.

If you’ve not done it already, install fscrypt and tune the /home filesystem for encryption

tune2fs -O encrypt /dev/mapper/void-home
xbps-install -S fscrypt

Setup it globally and for the home folder

fscrypt setup
fscrypt setup /home

Instead of yet another password, we want to allow decryption on user login. We achieve this by using PAM modules. Void uses a slightly different setup from the official fscrypt manual, so keep up with me. We are just adding pam_fscrypt.so permissions to a couple configuration files.

Add the following line to the end of /etc/pam.d/passwd, after pam_unix.so:

password   optional   pam_fscrypt.so

Edit /etc/pam.d/system-login. In the auth section, append the line

auth       optional   pam_fscrypt.so

In the session section of this same file, append the line

session    optional   pam_fscrypt.so

Encrypt the (empty) home directory for each user you already created. In our example, it is

fscrypt encrypt /home/iago --user=iago

Follow the prompts and select “Your login passphrase” when asked to choose a protector.

Complete installation

The “system” part of your new installation is done! You can exit the chroot, unmount all filesystems and (hopefully) reboot into your freshly-installed Void system.

exit
umount -R /mnt
reboot

References


  1. For simplicity, this setup uses an unencrypted bootloader. You can encrypt it in case that’s incompatible with your threat model, but be warned that the setup becomes much more convoluted.↩︎