A simple tutorial on installing Debian on an encrypted LVM with btrfs and subvolumes :-)
There are plenty tutorials out there already and I used to mix several methods to create my way of the process. This blog post is a kind note for myself. My recipe for something that fits and that I can read and use when I need it – but shared so that others can use it as wels :-)
A brief roadmap:
I’ll use a Debian LiveDVD for a command line install using debootstrap. /boot will be an unencrypted partition and on it’s own – separate from the rest. The same applies for the bios boot partition to support non-UEFI systems, too.
I’m doing all the following on a local virtual machine and I prefer to work with my terminal emulator, so I’ll set a password for the user user and install ssh to be able to connect.
sudo su -
passwd user
apt update
apt install arch-install-scripts ssh cryptsetup lvm2 debootstrap vim gdisk btrfs-progs
After I connected via ssh I’m setting up the disk with three partitions: one for “/boot“, one for the encrypted LVM with two logical volumes for the system and swap and one for the bios boot partition. I use gdisk for partitioning – but of course you can use whatever you prefer.
gdisk /dev/vda
n <ENTER>
(Partition number) <ENTER>
(First sector) <ENTER>
(Last sector) +2G <ENTER>
(Partition type) 8300 <ENTER>
n <ENTER>
(Partition number) 128 <ENTER>
(First sector) -2M <ENTER>
(Last sector) <ENTER>
(Partition type) ef02 <ENTER>
n <ENTER>
(Partition number) <ENTER>
(First sector) <ENTER>
(Last sector) <ENTER>
(Partition type) 8309 <ENTER>
p <ENTER>
w <ENTER>
It’s just a habit of mine to place the bios boot partition to the very end of the disk so I set its partition number to 128. Now it’s time to set up the LUKS container and open it:
cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --hash sha256 --iter-time 2000 --key-size 256 --pbkdf argon2id --use-random --verify-passphrase /dev/vda2
WARNING!
========
This will overwrite data on /dev/vda2 irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/vda2:
Verify passphrase:
cryptsetup luksOpen /dev/vda2 crypt
Enter passphrase for /dev/vda2:
You can choose whatever name you like for the container at the end of the luksOpen command. It’s only mapping name.
Now I’m setting up the LVM:
pvcreate /dev/mapper/crypt
Physical volume "/dev/mapper/crypt" successfully created.
vgcreate vg-zebra /dev/mapper/crypt
Volume group "vg--zebra" successfully created
lvcreate -L 4G -n Swap vg-zebra
Logical volume "Swap" created.
lvcreate -l 100%FREE -n Root vg-zebra
Logical volume "Root" created.
I used a dash (a.k.a. hyphen) in the volume group’s name. This is ok, but… if you’re working on the LVM-level you have to use the name exactly as used. But later, you’re going to see /dev/mapper/vg–zebra-Root – so with two dashes. This is because the device mapper has its own conventions and the dash is used to separate volume group names and logical volume names, so if you use a dash in the volume group name it puts another dash in front of it to mask it and make it part of the name 🙂
Now I’m setting up the filesystems:
mkfs.ext2 /dev/vda1
mkswap -L swap /dev/mapper/vg--zebra-Swap
mkfs.btrfs -L btrfs-pool /dev/mapper/vg--zebra-Root
mount /dev/mapper/vg--zebra-Root /mnt
for S in rootfs home var var@lib; do btrfs subvolume create /mnt/@${S}; done
umount /mnt
Next step is to mount the filesystems and to create mountpoints:
mount -t btrfs -o subvol=@rootfs /dev/mapper/vg--zebra-Root /mnt
mkdir /mnt/{boot,home,var}
mount /dev/vda1 /mnt/boot/
mount -t btrfs -o subvol=@home /dev/mapper/vg--zebra-Root /mnt/home
mount -t btrfs -o subvol=@var /dev/mapper/vg--zebra-Root /mnt/var
mkdir /mnt/var/lib
mount -t btrfs -o subvol=@var@lib /dev/mapper/vg--zebra-Root /mnt/var/lib
swapon /dev/mapper/vg--zebra-Swap
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 1.2G 1 loop /run/live/rootfs/filesystem.squashfs
sr0 11:0 1 1.8G 0 rom /run/live/medium
vda 254:0 0 40G 0 disk
├─vda1 254:1 0 2G 0 part /mnt/boot
├─vda2 254:2 0 38G 0 part
│ └─crypt 252:0 0 38G 0 crypt
│ ├─vg--zebra-Swap 252:1 0 4G 0 lvm [SWAP]
│ └─vg--zebra-Root 252:2 0 34G 0 lvm /mnt/var/lib
│ /mnt/var
│ /mnt/home
│ /mnt
└─vda128 259:1 0 2M 0 part
Now I’m running debootstrap and creating the /mnt/etc/apt/sources.list file and /mnt/etc/fstab afterwards:
debootstrap trixie /mnt
echo '## If you want access to contrib and non-free components,
## add " contrib non-free" after every "non-free-firmware" in this file:
deb https://deb.debian.org/debian bookworm main non-free-firmware
deb-src https://deb.debian.org/debian bookworm main non-free-firmware
deb https://security.debian.org/debian-security bookworm-security main non-free-firmware
deb-src https://security.debian.org/debian-security bookworm-security main non-free-firmware
deb https://deb.debian.org/debian bookworm-updates main non-free-firmware
deb-src https://deb.debian.org/debian bookworm-updates main non-free-firmware' | sed 's/bookworm/trixie/g' | sed 's/\(http\)s/\1/' > /mnt/etc/apt/sources.list
genfstab -U /mnt >> /mnt/etc/fstab
The bookworm sources list is from the Debian-Wiki and to be able to install anything https needs to be swapped by http at this point.
Now I’m entering the chroot environment to install the missing components. You may wish to add more, swap some of mine by some of yours or skip certain stuff – this is what I did and need:
arch-chroot /mnt
export PS1="(- chroot -) "
passwd
apt install apt-transport-https locales
apt modernize-sources
dpkg-reconfigure locales
dpkg-reconfigure tzdata
apt install linux-image-amd64 initramfs-tools sudo chrony ssh vim git tmux grub-pc btrfs-progs gdisk wget curl net-tools lvm2 cryptsetup cryptsetup-initramfs man manpages
echo 'lvm2
dm-mod
dm-crypt
btrfs' >> /etc/initramfs-tools/modules
Forgot to install cryptsetup-initramfs. Yeah, I know sounds obvious, but I tend to forget that little package even though cryptsetup is already installed. So the initrd didn’t contain it and LUKS unlock didn’t happen. I repeated every single step to be sure, but still nothing. It took me quite a while to remember! 🙂 This package installs quite a few things – you can check with dpkg -L cryptsetup-initramfs and I decided not go deeper into its magic. Install it and it works! 🙂
Now I create /etc/crypttab and modify the GRUB_CMDLINE_LINUX in /etc/default/grub:
MY_UUID=$(cryptsetup luksDump /dev/vda2 | sed -n '/UUID/s/UUID:[^0-9a-z]*//p')
echo "crypt UUID=${MY_UUID} none luks" >> /etc/crypttab
sed -i 's/^\(GRUB_CMDLINE_LINUX="\)"/\1cryptdevice=UUID='$(echo ${MY_UUID})':crypt root=\/dev\/mapper\/vg--zebra-Root"/' /etc/default/grub
Now I rebuild the initrd image and verify everything we need got placed into it:
update-initramfs -u -k all
lsinitramfs /boot/initrd.img-6.12.43+deb13-amd64 | grep btrfs
lsinitramfs /boot/initrd.img-6.12.43+deb13-amd64 | grep lvm
lsinitramfs /boot/initrd.img-6.12.43+deb13-amd64 | grep cryptsetup
Next step is to install grub and create its config file
grub-install /dev/vda
grub-mkconfig -o /boot/grub/grub.cfg
# The following is optional and only needed if you want a rough verification that everything is fine
grep rootfs /boot/grub/grub.cfg
And that’s it. Exit the chroot by firing up exit and reboot your machine.