Skip to main content

KDE Neon Laptop Build Standard - With ZfsBootMenu

Note on encrypted root

I'm extremely picky but, I don't like the "official" method of allowing boot with only a single prompt for the ZFS encryption passphrase. The passphrase exists on your machine. Yes, it's on an encrypted dataset and in a file with acls so only the root user can access it but... I'd rather the passphrase exist only in my head. If that means I need to enter the passphrase twice, it's not ideal, but it's good enough for now.

Actually, I'm thinking that along side ZfsBootMenu, I could install the Ubuntu kernel and initramfs as a UKI directly into uEFI. That way, the default action could be to boot the Ubuntu kernel directly and the secondary action, if F9 is pressed, could be to boot ZfsBootMenu for additional features.

Note on sudo in the commands below

Many of the commands below are prefixed with sudo, even if they are to be executed in a root shell (e.g. once you change into the chroot). This is simply to make it easier if you need to just re-run select commands later and might not be in the same environment. It's never used to give unnecessary access to a command.

Linux OS Partitions

Reference: https://docs.zfsbootmenu.org/en/v2.3.x/guides/ubuntu/uefi.html

export ID="NeonJammy"
export DISK="/dev/nvme0n1"
export ESP_PART="1"
export ESP_SIZE="512m"
export ESP_DEVICE="${DISK}p${BOOT_PART}"
export POOL_PART="2"
export POOL_SIZE="24g"
export POOL_NAME="SSD1"
export POOL_DEVICE="${DISK}p${POOL_PART}"
export TMP_PART="3"
export TMP_SIZE="24g"
export TMP_DEVICE="${DISK}p${TMP_PART}"
# Future ChromeOS Partition?
#export COS_PART="8"
#export COS_SIZE="32g"
#export COS_DEVICE="${DISK}p${COS_PART}"
export SWAP_PART="9"
export SWAP_SIZE="16g"
export SWAP_DEVICE="${DISK}p${SWAP_PART}"
sudo blkdiscard "${DISK}" -f
sudo sgdisk -n "${ESP_PART}:1m:+${ESP_SIZE}" -t "${ESP_PART}:ef00" -c "0:ESP" "${DISK}"
sudo sgdisk -n "${POOL_PART}:0:+${POOL_SIZE}" -t "${POOL_PART}:bf00" -c "0:${POOL_NAME}" "${DISK}"
sudo sgdisk -n "${TMP_PART}:0:+${TMP_SIZE}" -t "${TMP_PART}:8303" -c "0:TMP" "${DISK}"
sudo mkfs.fat -F 32 -n ESP "${DISK}p${ESP_PART}"
sudo parted ${DISK} p
Model: SAMSUNG MZVL4512HBLU-00BH1 (nvme)
Disk /dev/nvme0n1: 512GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number Start End Size File system Name Flags
1 1049kB 538MB 537MB fat32 ESP boot, esp
2 538MB 26.3GB 25.8GB SSD1
3 26.3GB 52.1GB 25.8GB TMP

later:

sgdisk -n "${SWAP_PART}:0:-${SWAP_SIZE}" -t "${SWAP_PART}:8200" -c "0:SWAP" "${DISK}"

Save your the encryption password to a temporary file. This is not written to disk (it's in a memory overlay that will disappear once the installation OS is shut down).

 echo 'SomeKeyphrase' > /tmp/${POOL_NAME}.key
chmod 000 /tmp/${POOL_NAME}.key
sudo zpool create -f -o ashift=12 \
-O compression=lz4 \
-O acltype=posixacl \
-O xattr=sa \
-O relatime=on \
-O encryption=aes-256-gcm \
-O keylocation=file:///tmp/${POOL_NAME}.key \
-O keyformat=passphrase \
-o autotrim=on \
-o compatibility=openzfs-2.1-linux \
-m none ${POOL_NAME} "/dev/disk/by-partlabel/${POOL_NAME}"

sudo zfs create -o mountpoint=none "${POOL_NAME}/ROOT"
sudo zfs create -o mountpoint=/ -o canmount=noauto ${POOL_NAME}/ROOT/${ID}
sudo zpool set bootfs=${POOL_NAME}/ROOT/${ID} ${POOL_NAME}

Install KDE Neon into the TMP partition

Copy the Installed OS to the ZFS datasets

sudo zpool export ${POOL_NAME}
sudo mkdir /target
sudo zpool import -R /target ${POOL_NAME}
sudo zfs set overlay=on ${POOL_NAME}/ROOT/${ID}
sudo zfs set mountpoint=/ ${POOL_NAME}/ROOT/${ID}
sudo zpool export ${POOL_NAME}
sudo zpool import -R /target ${POOL_NAME}
sudo zfs load-key ${POOL_NAME}
sudo zfs mount ${POOL_NAME}/ROOT/${ID}

sudo mkdir /source
sudo mount --bind --make-slave /tmp/calamares-root-* /source
sudo rsync -avPX /source/. /target/.

Make the OS in the ZFS datasets bootable

We bind certain system directories into the /target directory and then chroot into it.

sudo mount /dev/disk/by-partlabel/ESP /target/boot/efi
sudo mount -t efivarfs efivarfs /sys/firmware/efi/efivars || true
sudo mount --rbind /dev /target/dev
sudo mount --rbind /proc /target/proc
sudo mount --rbind /sys /target/sys
sudo mount --rbind /dev/pts /target/dev/pts
sudo mount --rbind /var/log /target/var/log
sudo mount --make-rslave /target/dev
sudo mount --make-rslave /target/proc
sudo mount --make-rslave /target/sys
sudo mount --make-rslave /target/dev/pts
sudo mount --make-rslave /target/var/log
sudo chroot /target

The following is run inside the new KDE Neon installation as a chroot

sudo unlink /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
. /var/log/installation-settings
sudo apt install -y zfsutils-linux zfs-initramfs openssh-server curl efibootmgr
sudo systemctl enable zfs.target
sudo systemctl enable zfs-import-cache
sudo systemctl enable zfs-mount
sudo systemctl enable zfs-import.target
sudo update-initramfs -c -k all

sudo rm -fr /boot/efi/EFI/*
sudo mkdir -p /boot/efi/EFI/ZBM
sudo curl -o /boot/efi/EFI/ZBM/VMLINUZ.EFI -L https://get.zfsbootmenu.org/efi
sudo cp /boot/efi/EFI/ZBM/VMLINUZ.EFI /boot/efi/EFI/ZBM/VMLINUZ-BACKUP.EFI
sudo efibootmgr -c -d "${DISK}" -p "${ESP_PART}" \
-L "ZFSBootMenu (Backup)" \
-l '\EFI\ZBM\VMLINUZ-BACKUP.EFI'

sudo efibootmgr -c -d "${DISK}" -p "${ESP_PART}" \
-L "ZFSBootMenu" \
-l '\EFI\ZBM\VMLINUZ.EFI'
echo "/dev/disk/by-partlabel/ESP /boot/efi vfat defaults 0 0" | sudo tee -a /etc/fstab

TODO Get the command line from .... oh, the grub config is gone.

zfs set org.zfsbootmenu:commandline="quiet splash" ${POOL_NAME}/ROOT

Setup encrypted swap

sudo sgdisk -n "${SWAP_PART}:-${SWAP_SIZE}:-10m" -t "${SWAP_PART}:8200" -c "0:SWAP" "${DISK}"
echo "swap /dev/disk/by-partlabel/SWAP /dev/urandom swap,cipher=aes-cbc-essiv:sha256,size=256,plain" | sudo tee -a /etc/crypttab
echo "/dev/mapper/swap none swap defaults 0 0" | sudo tee -a /etc/fstab
echo "RESUME=none" |sudo tee /etc/initramfs-tools/conf.d/resume

Limit ZFS ARC Size

echo "options zfs zfs_arc_max=4294967296" | sudo tee /etc/modprobe.d/zfs.config

Update everything

apt update; apt -y full-upgrade; apt autoremove

Take a snapshot of the fresh installation

sudo zfs snapshot ${POOL_NAME}/ROOT@fresh-install

Exit from the chroot

exit

Remove the TMP partition

sudo umount $(mount | grep "${TMP_DEVICE}" | awk '{print $3}')
sudo blkdiscard "${TMP_DEVICE}" -f
sudo sgdisk -d ${TMP_PART} ${DISK}

Backup Image

If you want to take a backup of the fresh install for re-use, consider a command like this:

sudo umount $(mount | grep "${ESP_DEVICE}" | awk '{print $3}')
sudo umount -R /target || true
sudo zpool trim --wait ${POOL_NAME}
sudo zpool export ${POOL_NAME} || true
sudo dd if=${DISK} bs=512 count=51382272s | tee >(sha256sum >/dev/stderr) | zstd -10 | tee >(sha256sum >/dev/stderr) | mbuffer | ssh [user]@[some other machine] "mbuffer >/path/to/FreshInstall.dd.zst"

sudo dd if=/dev/nvme0n1 bs=512 count=76171264 | tee >(sha256sum >/dev/stderr) | zstd -10 | tee >(sha256sum >/dev/stderr) | mbuffer | ssh [email protected] "mbuffer >/mnt/zfs/RaidZ3Disk/shares/Backups/spare-e7470/FreshInstall.dd.zst"

sudo dd if=/dev/nvme0n1 bs=512 count=76171264 | tee >(sha256sum >/dev/stderr) | zstd -10 | tee >(sha256sum >/dev/stderr) | mbuffer | ssh [email protected] "mbuffer >/mnt/zfs/RaidZ3Disk/shares/Backups/spare-e7470/WorkInstall.dd.zst"

To Restore the above image:

ssh ssh [user]@[some other machine] "dd if=/path/to/FreshInstall.dd.zst bs=512 | mbuffer" | mbuffer zstd -d -c | tee >(sha256sum >/dev/stderr) sudo dd if=/dev/nvme0n1 bs=512