Note
This post was initially published as gist.
This is a short description of steps to have new secure Void Linux installation with following features:
/boot partition on USB stick (not encrypted)dropbear ssh servermkinitcpio to generate initramfsThis manual is heavily based on Void Linux FDE documentation, Void Linux kernel documentation, Arch Linux LVM on LUKS documentation, Arch Linux detached LUKS header documentation and Arch Linux remote unlocking documentation.
This manual assumptions:
/dev/sda - USB stick/dev/nvme0n1 - internal driveBe very careful with drive names! Check command syntax twice before pressing Enter key!
To start prepare Void Linux installation media (must be separate USB drive or whatever else you have to start the process) and boot using it.
You are expected to run all commands below on a target computer running one of live Void Linux installers (some steps can be made beforehand on another computer though).
# dd if=/dev/urandom of=/dev/sda bs=1M oflag=direct status=progress
# fdisk /dev/sda
USB stick must have dos label and two partitions - EFI (to mount as /boot/efi) and Linux (to mount as /boot).
# fdisk -l /dev/sda
Disk /dev/sda: 7.45 GiB, 8002732032 bytes, 15630336 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x0bfa9df7
Device Boot Start End Sectors Size Id Type
/dev/sda1 2048 1050623 1048576 512M ef EFI (FAT-12/16/32)
/dev/sda2 1050624 3147775 2097152 1G 83 Linux
# mkfs.vfat /dev/sda1
# mkfs.ext2 /dev/sda2
# mount /dev/sda2 /mnt
# dd if=/dev/zero of=/mnt/header.img bs=16M count=1
# dd if=/dev/urandom of=/dev/nvme0n1 bs=1M oflag=direct status=progress
# fdisk /dev/nvme0n1
Internal drive must have gpt label and single Linux partition.
# fdisk -l /dev/nvme0n1
Disk /dev/nvme0n1: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 45526BA4-E54F-2641-9A90-5735D0C3C772
Device Start End Sectors Size Type
/dev/nvme0n1p1 2048 1953523711 1953521664 931.5G Linux filesystem
# cryptsetup luksFormat --header /mnt/header.img /dev/nvme0n1p1
# cryptsetup open --header /mnt/header.img /dev/nvme0n1p1 cryptlvm
# pvcreate /dev/mapper/cryptlvm
# vgcreate lvgroup /dev/mapper/cryptlvm
# lvcreate -L 8G -n swap lvgroup
# lvcreate -L 32G -n root lvgroup
# lvcreate -l 100%FREE -n home lvgroup
# lvreduce -L -256M lvgroup/home
# mkfs.ext4 /dev/lvgroup/root
# mkfs.ext4 /dev/lvgroup/home
# mkswap /dev/lvgroup/swap
# umount /mnt
# mount /dev/lvgroup/root /mnt
# mkdir /mnt/home
# mount /dev/lvgroup/home /mnt/home
# mkdir /mnt/boot
# mount /dev/sda2 /mnt/boot
# mkdir /mnt/boot/efi
# mount /dev/sda1 /mnt/boot/efi
# swapon /dev/lvgroup/swap
# mkdir -p /mnt/var/db/xbps/keys
# cp /var/db/xbps/keys/* /mnt/var/db/xbps/keys/
# xbps-install -Sy -R https://repo-default.voidlinux.org/current -r /mnt base-system cryptsetup grub-x86_64-efi lvm2
# xchroot /mnt
[xchroot /mnt] # chown root:root /
[xchroot /mnt] # chmod 755 /
[xchroot /mnt] # passwd root
[xchroot /mnt] # echo void > /etc/hostname
[xchroot /mnt] # echo "LANG=en_US.UTF-8" > /etc/locale.conf
[xchroot /mnt] # echo "en_US.UTF-8 UTF-8" >> /etc/default/libc-locales
[xchroot /mnt] # xbps-reconfigure -f glibc-locales
Use blkid to find out UUID of USB stick partitions.
# <file system> <dir> <type> <options> <dump> <pass>
tmpfs /tmp tmpfs defaults,nosuid,nodev 0 0
/dev/lvgroup/root / ext4 defaults 0 0
/dev/lvgroup/home /home ext4 defaults 0 0
/dev/lvgroup/swap swap swap defaults 0 0
UUID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" /boot ext2 defaults 0 0
UUID="xxxx-xxxx" /boot/efi vfat defaults 0 0
[xchroot /mnt] # ln -s /etc/sv/sshd /etc/runit/runsvdir/default/
[xchroot /mnt] # ln -s /etc/sv/dhcpcd-eth0 /etc/runit/runsvdir/default/
[xchroot /mnt] # ssh-keygen -A
[xchroot /mnt] # xbps-install mkinitcpio mkinitcpio-encrypt mkinitcpio-lvm2
[xchroot /mnt] # xbps-install mkinitcpio-encryptssh mkinitcpio-dropbear
[xchroot /mnt] # xbps-alternatives -s mkinitcpio
[xchroot /mnt] # mkdir -p /etc/initcpio/{hooks,install}
[xchroot /mnt] # cp /usr/lib/initcpio/hooks/encryptssh /etc/initcpio/hooks/encryptssh3
[xchroot /mnt] # cp /usr/lib/initcpio/install/encryptssh /etc/initcpio/install/encryptssh3
[xchroot /mnt] # nvi /etc/initcpio/hooks/encryptssh3
Caution
Do not put quotes around ${cryptheader} in cryptsetup isLuks ... command (see diff below).
$ diff -u /usr/lib/initcpio/hooks/encryptssh /etc/initcpio/hooks/encryptssh3
--- /usr/lib/initcpio/hooks/encryptssh 2026-07-08 20:20:29.000000000 +0000
+++ /etc/initcpio/hooks/encryptssh3 2026-07-12 10:05:13.214090178 +0000
@@ -80,6 +80,9 @@
IFS="$OLDIFS"
unset OLDIFS
+ cryptheader="--header /boot/header.img"
+ cryptargs="${cryptargs} ${cryptheader}"
+
echo "${cryptname}" > /.cryptname
echo "${cryptargs}" > /.cryptargs
@@ -87,7 +90,7 @@
echo "${resolved}" > /.cryptdev
- if cryptsetup isLuks "${resolved}" >/dev/null 2>&1; then
+ if cryptsetup isLuks ${cryptheader} "${resolved}" >/dev/null 2>&1; then
[ ${DEPRECATED_CRYPT} -eq 1 ] && warn_deprecated
dopassphrase=1
# If keyfile exists, try to use that
[xchroot /mnt] # nvi /etc/mkinitcpio.conf
diff --git a/mkinitcpio.conf b/mkinitcpio.conf
index 4a2ce87..143021d 100644
--- a/mkinitcpio.conf
+++ b/mkinitcpio.conf
@@ -4,7 +4,7 @@
# run. Advanced users may wish to specify all system modules
# in this array. For instance:
# MODULES=(usbhid xhci_hcd)
-MODULES=()
+MODULES=(loop)
# BINARIES
# This setting includes any additional binaries a given user may
@@ -16,7 +16,7 @@ BINARIES=()
# FILES
# This setting is similar to BINARIES above, however, files are added
# as-is and are not parsed in any way. This is useful for config files.
-FILES=()
+FILES=(/boot/header.img)
# HOOKS
# This is the most important setting in this file. The HOOKS control the
@@ -49,12 +49,12 @@ FILES=()
#
## NOTE: If you have /usr on a separate partition, you MUST include the
# usr and fsck hooks.
-HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block filesystems fsck)
+HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block netconf dropbear encryptssh3 lvm2 filesystems fsck)
# COMPRESSION
# Use this to compress the initramfs image. By default, gzip compression
# is used. Use 'cat' to create an uncompressed image.
-#COMPRESSION="zstd"
+COMPRESSION="zstd"
#COMPRESSION="gzip"
#COMPRESSION="bzip2"
#COMPRESSION="lzma"
[xchroot /mnt] # mkdir /root/.ssh
[xchroot /mnt] # chmod 700 /root/.ssh
[xchroot /mnt] # nvi /root/.ssh/authorized_keys
[xchroot /mnt] # chmod 600 /root/.ssh/authorized_keys
Without /etc/dropbear/root_key file and proper public key in it you won't be able to unlock internal drive remotely.
[xchroot /mnt] # cp /root/.ssh/authorized_key /etc/dropbear/root_key
Ensure you have proper ip parameters here - host ip address, subnet mask and gateway address.
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=4 cryptdevice=/dev/nvme0n1p1:cryptlvm root=/dev/lvgroup/root net.ifnames=0 ip=192.168.0.55::192.168.0.1:255.255.255.0::eth0:none"
[xchroot /mnt] # grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id="Void" --removable
This will generate initramfs and bootloader configuration (/boot/grub/grub.cfg file).
[xchroot /mnt] # xbps-reconfigure -fa
Don't forget to remove installation media from the box.
[xchroot /mnt] # exit
# umount -R /mnt
# reboot
You have to do it from another computer.
$ ssh root@192.168.0.55
Don't forget to backup your /boot/header.img file or full USB stick.