Kubuntu 26.04 on Encrypted ZFS Root
My general destop choice is Kubuntu. I like Ubuntu package management and general compatibility and I love Plasma. I do have other distributions around my home, but my main setup sticks to Kubuntu. So, with the new version, came time to have a new setup instructions.
Honestly, installers these days are good and you will probably be happy with default installation unless you have special needs. In my case, I do have a few quirks the default installer doesn’t support to my liking:
- ZFS for system and data
- LUKS encryption for swap and ZFS
- No Snap
Since installed doesn’t do that, I do quite a few manual steps to make it works. Mind you, everything is set for copy/paste so they are not that troublesome or excessive as they look. In any case, here they are.
The very first thing to do is to boot into installation. Once in live desktop I usually setup networking and add a few packages so I can acccess it from the network.
sudo apt update ; sudo apt install openssh-server
passwdIf you want to execute these commands directly, you can freely skip those steps. If you do want to access the system over network, do not forget to disable sleep as it will happily interrupt your installation steps.
Once in terminal, we want to switch to the root user.
sudo -iThen we add a few prerequisite packages. Most notably ZFS and something to partition disk with (in my case sgdisk).
apt update
apt install -y gdisk zfsutils-linuxNow, as promised, I like to set a few variables.
DISK1=/dev/disk/by-id/<disk>
HOST=<host>
USERNAME=<user>Note I use absolute path to the disk. I found it least problematic when scripting.
With that sorted out, we’re onto setting up partitions. My partitions are:
- 127 MiB EFI partition
- 1920 MiB boot partition
- 32 GiB swap
- rest is ZFS on top of 4K aligned LUKS
Note this will destroy any data you might have, thus there is no way back after this.
blkdiscard -f $DISK1 2>/dev/null
sgdisk --zap-all $DISK1
sgdisk -n1:1M:+127M -t1:EF00 -c1:EFI $DISK1
sgdisk -n2:0:+1920M -t2:8300 -c2:Boot $DISK1
sgdisk -n3:0:+32G -t3:8200 -c3:Swap $DISK1
sgdisk -a 8 -I -n4:0:0 -t4:8309 -c4:LUKS $DISK1
sgdisk --print $DISK1With partitions created, we can collect their IDs.
DISK1P1=`blkid -s PARTUUID -o value $DISK1-part1`
DISK1P2=`blkid -s PARTUUID -o value $DISK1-part2`
DISK1P3=`blkid -s PARTUUID -o value $DISK1-part3`
DISK1P4=`blkid -s PARTUUID -o value $DISK1-part4`I’ve been going back and forth over the years on whether to use simpler partition names or UUIDs for the rest of setup. However, UUIDs are simply more fool-proof. For example, if you every clone your drive, it will just work. Partition names get too messy when you move disks around.
Now let’s setup LUKS encryption for ZFS.
cryptsetup luksFormat -q --type luks2 \
--sector-size 4096 \
--cipher aes-xts-plain64 --key-size 256 \
--pbkdf argon2i /dev/disk/by-partuuid/$DISK1P4While your disk is encrypted, you still need to open it. I will assume you’re gonna use SSD, thus usage of perf-no_write_workqueue and perf-no_read_workqueue options.
cryptsetup luksOpen \
--persistent --allow-discards \
--perf-no_write_workqueue --perf-no_read_workqueue \
/dev/disk/by-partuuid/$DISK1P4 $DISK1P4With this we can create our pool.
zpool create \
-o ashift=12 -o autotrim=on \
-O compression=lz4 -O normalization=formD \
-O acltype=posixacl -O xattr=sa -O dnodesize=auto -O atime=off \
-O canmount=off -O mountpoint=none -R /mnt/install \
${HOST^} /dev/mapper/$DISK1P4I also recommend setting up quota but that’s something you can do later depending on how big the disk is.
On top of ZFS pool we can now create our system dataset.
zfs create \
-o devices=on \
-o canmount=noauto -o mountpoint=/ \
${HOST^}/System
zfs mount ${HOST^}/SystemAnd a separate home dataset.
zfs create \
-o canmount=noauto -o mountpoint=/home \
${HOST^}/Home
zfs mount ${HOST^}/Home
zfs set canmount=on ${HOST^}/HomeOur boot partition wont be encrypted and that’s by design. It’s not ideal but recovering system with encrypted partition is such pain that I am willing to compromise.
yes | mkfs.ext4 /dev/disk/by-partuuid/$DISK1P2
mkdir /mnt/install/boot/
mount /dev/disk/by-partuuid/$DISK1P2 /mnt/install/boot/Our EFI partition gets its FAT32 home too.
mkfs.msdos -F 32 -n EFI -i 4d65646f /dev/disk/by-partuuid/$DISK1P1
mkdir /mnt/install/boot/efi/
mount /dev/disk/by-partuuid/$DISK1P1 /mnt/install/boot/efi/And lastly, our swap partition gets its own LUKS encrption. I strongly suggest the same password as for data partition as it will save you time typing.
cryptsetup luksFormat -q --type luks2 \
--sector-size 4096 \
--cipher aes-xts-plain64 --key-size 256 \
--pbkdf argon2i /dev/disk/by-partuuid/$DISK1P3Mind you, it too needs opening.
cryptsetup luksOpen \
--persistent --allow-discards \
--perf-no_write_workqueue --perf-no_read_workqueue \
/dev/disk/by-partuuid/$DISK1P3 $DISK1P3And formatting.
mkswap /dev/mapper/$DISK1P3At this point I like to disable IPv6. I am not sure if it’s my network or a general issue, but IPv4-only network is MUCH faster in live USB environment. Mind you, this is just a temporary thing - installed system will support IPv6 just fine.
sysctl -w net.ipv6.conf.all.disable_ipv6=1
sysctl -w net.ipv6.conf.default.disable_ipv6=1
sysctl -w net.ipv6.conf.lo.disable_ipv6=1Finally, we get to install at least minimum Resolute Racoon.
apt update
apt dist-upgrade --yes
apt install --yes debootstrap
debootstrap --components=main,restricted,universe,multiverse resolute /mnt/install/To help it going, I like to set its hostname and copy over network details. The last step is optional but it helps you preserver wireless network password.
echo $HOST > /mnt/install/etc/hostname
sed "s/kubuntu/$HOST/" /etc/hosts > /mnt/install/etc/hosts
cp /etc/netplan/*.yaml /mnt/install/etc/netplan/At this time we switch into our new 26.04 environment. Anything you do after this step permanently impacts your installation. For all practical purposes, it’s like you’re logged in your new setup. Because you are.
mount --rbind /dev /mnt/install/dev
mount --rbind /proc /mnt/install/proc
mount --rbind /sys /mnt/install/sys
chroot /mnt/install /usr/bin/env \
DISK1P1=$DISK1P1 DISK1P2=$DISK1P2 DISK1P3=$DISK1P3 DISK1P4=$DISK1P4 \
HOST=$HOST USERNAME=$USERNAME \
bash --loginI like to setup my locale.
dpkg-reconfigure localesAnd my timezone.
dpkg-reconfigure tzdataWith those out of way, it’s time to tell our system which partitions it will need to decrypt.
echo -n > /etc/crypttab
echo "$DISK1P3 PARTUUID=$DISK1P3 none luks,discard,initramfs,keyscript=decrypt_keyctl" >> /etc/crypttab
echo "$DISK1P4 PARTUUID=$DISK1P4 none luks,discard,initramfs,keyscript=decrypt_keyctl" >> /etc/crypttabThen we need to tell it where to mount what.
echo -n > /etc/fstab
echo "PARTUUID=$DISK1P2 /boot ext4 noatime,nofail,x-systemd.device-timeout=3s 0 1" >> /etc/fstab
echo "PARTUUID=$DISK1P1 /boot/efi vfat noatime,nofail,x-systemd.device-timeout=3s 0 1" >> /etc/fstab
echo "/dev/mapper/$DISK1P4 none auto nofail,nosuid,nodev,noauto 0 0" >> /etc/fstab
echo "/dev/mapper/$DISK1P3 none swap sw,nofail 0 0" >> /etc/fstabOur new kernel packages go next.
apt update
apt install --yes --no-install-recommends linux-image-generic linux-headers-genericFollowed by packages needed for making it boot.
apt install --yes zfs-initramfs cryptsetup keyutils plymouth-theme-spinner
update-initramfs -c -k allTo make EFI work, we need to install GRUB too. I modify its /etc/default/grub a bit to ensure it will resume from our swap. Note that you bootloader-id should stay ubuntu if you’re using secure boot.
apt install --yes grub-efi-amd64-signed shim-signed
sed -i "s/^GRUB_CMDLINE_LINUX_DEFAULT.*/GRUB_CMDLINE_LINUX_DEFAULT=\"quiet splash \
RESUME=UUID=$(blkid -s UUID -o value /dev/mapper/$DISK1P3)\"/" \
/etc/default/grub
sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=1/' /etc/default/grub
echo "GRUB_DISABLE_MEMTEST=true" >> /etc/default/grub
update-grub
grub-install --target=x86_64-efi --efi-directory=/boot/efi \
--bootloader-id=Ubuntu --recheck --no-floppyWith booting sorted, it’s time to setup desktop and I like to start by forbidding snap altogether. Yes, there is a certail level of madness in using Ubuntu without snap but that’s story for another time.
apt remove --yes snapd 2>/dev/null
echo 'Package: snapd' > /etc/apt/preferences.d/snapd
echo 'Pin: release *' >> /etc/apt/preferences.d/snapd
echo 'Pin-Priority: -1' >> /etc/apt/preferences.d/snapd
apt updateAt last, we come to the desktop installation itself. I like to add a few tools here too.
apt install --yes kubuntu-desktop man-db lshw iw wgetSince disabling snap also disabled installation of Firefox, we need to readd it from PPA to have a browser installed once everything is done.
add-apt-repository --yes ppa:mozillateam/ppa
cat << 'EOF' | sed 's/^ //' | tee /etc/apt/preferences.d/mozillateamppa
Package: firefox*
Pin: release o=LP-PPA-mozillateam
Pin-Priority: 501
EOF
apt update && apt install --yes firefoxWith desktop installed, we’re literally on the final stretch of completing install. So, it’s a good time to setup our user.
adduser --disabled-password --gecos '' $USERNAME
usermod -a -G adm,cdrom,dialout,dip,lpadmin,plugdev,sudo,tty $USERNAME
echo "$USERNAME ALL=NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME
passwd $USERNAMEHere I also like to setup my sleep options in order to enable hibernation.
sed -i 's/.*AllowSuspend=.*/AllowSuspend=yes/' /etc/systemd/sleep.conf
sed -i 's/.*AllowHibernation=.*/AllowHibernation=yes/' /etc/systemd/sleep.conf
sed -i 's/.*AllowSuspendThenHibernate=.*/AllowSuspendThenHibernate=yes/' /etc/systemd/sleep.conf
sed -i 's/.*HibernateMode=.*/HibernateMode=platform shutdown/' /etc/systemd/sleep.conf
sed -i 's/.*HibernateDelaySec=.*/HibernateDelaySec=13min/' /etc/systemd/sleep.confMy preferred way of handling sleep on laptop is to suspend when closing the screen and hibernate on power key. If your preference differ, skip this step.
apt install -y pm-utils
sed -i 's/.*HandlePowerKey=.*/HandlePowerKey=hibernate/' /etc/systemd/logind.conf
sed -i 's/.*HandleLidSwitch=.*/HandleLidSwitch=suspend-then-hibernate/' /etc/systemd/logind.conf
sed -i 's/.*HandleLidSwitchExternalPower=.*/HandleLidSwitchExternalPower=ignore/' /etc/systemd/logind.conf
sed -i 's/.*HoldoffTimeoutSec=.*/HoldoffTimeoutSec=13s/' /etc/systemd/logind.confSince we’re good with our new environment we can exit it.
exitNow we can disable devices flag on the system dataset and ensure all data is written. That is followed by unmounting all partitions and exporting ZFS pool.
zfs set devices=off ${HOST^}/System
sync
umount /mnt/install/boot/efi
umount /mnt/install/boot
mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}
zpool export -aWith cleanup done, we can finally reboot.
rebootIf everything went well, you now get to enjoy KDE Plasma.
