ZFS Root on Ubuntu

ZFS, Native Encryption, root's and you

About

Alike my existing article for a ZFS-rootfs on Archlinux I would also like to provide instructions for achieving the same thing on Ubuntu Server 24.04.2

Please see my main article https://blog.jjgaming.net/blog/zfs-root regarding the use of a ZFS rootfs, reasons to use one and more.

This example makes some assumptions:

  1. It will create an EFI partition and a boot partition before the zfs partition on each disk
  2. It also stores the decryption passphrase key file in the initramfs for automatic unlocking of the zpool at boot time. By omitting this step the host will instead prompt for the passphrase.
  3. Only the first disk will be used for its EFI and boot partitions (The others are also provisioned this way for future proofing but are not used in this example)

Instructions


#!/bin/bash

set -e # Stop on any error

# Set up some variables
export disks=(/dev/disk/by-path/virtio-pci-0000:00:02.0) # Some disk path, works with /dev/ paths and /dev/disk paths

export zpoolType='' # Option examples: raidz2, raidz3 # mirror #'' < blank for stripe/single disk
export rootfsPassphrase=UbuntU1324
export rootPassword=${rootfsPassphrase}
export hostName=myUbuntuServer
export zpoolName=${hostName}
export timezoneFile=/usr/share/zoneinfo/UTC

# Check if we're using -partX or numbered partition suffixes suffixes
if [[ ${disks[0]} =~ /dev/disk/by-path ]]
then
  partPrefix='-part' # /dev/disk/by- partition suffixes
else
  partPrefix='' # No partition suffixes
fi

# Partition the disks with an EFI, boot and ZFS partition
for disk in ${disks[*]} ; do sgdisk "${disk}" -o \
-n 1:2M:+1G -t 1:EF00 \
-n 2:0:+2G -t 2:8300  \
-n 3 -t 3:BF01 \
; done

sleep 1 # Wait a moment for partition tables to appear

# mkfs.vfat on all first partitions
for part in ${disks[*]/%/${partPrefix}1} ; do ls "${part}" ; mkfs.vfat -F 32 -n EFI "${part}" ; done

# Install ZFS in the live environment
apt install -y zfsutils-linux

# Create our passphrase key and protect it
echo "${rootfsPassphrase}" > /etc/zfs/${zpoolName}.key
chmod 000 /etc/zfs/${zpoolName}.key

# Create our zpool with however many disks
zpool create -f -o ashift=12 \
 -O compression=lz4 \
 -O normalization=formD \
 -O acltype=posixacl \
 -O xattr=sa \
 -O relatime=on \
 -O encryption=aes-256-gcm \
 -O keylocation=file:///etc/zfs/${zpoolName}.key \
 -O keyformat=passphrase \
 -O canmount=off \
 -O mountpoint=none \
 -o autotrim=on \
 -o compatibility=openzfs-2.1-linux \
 -R /${zpoolName} \
 ${zpoolName} ${zpoolType} "${disks[*]/%/${partPrefix}3}"

# Create some additional mountpoints and mount them (Optional but often preferred)
zfs create -o mountpoint=/ -o canmount=noauto ${zpoolName}/root

mkdir -p /${zpoolName}
mount -t zfs -o zfsutil ${zpoolName}/root    /${zpoolName}
mkdir -p /${zpoolName}/home /${zpoolName}/var    /${zpoolName}/var/log

# Top level
#zfs create -o mountpoint=/home ${zpoolName}/home
#zfs create -o mountpoint=/var ${zpoolName}/var
#zfs create -o mountpoint=/var/log ${zpoolName}/var/log

# Nested under rootfs
zfs create ${zpoolName}/root/home
zfs create ${zpoolName}/root/var
zfs create ${zpoolName}/root/var/log

# zfs mount -a

# Set our bootfs as a hint to the startup environment
zpool set bootfs=${zpoolName}/root ${zpoolName}

# Top level
#mkdir -p /${zpoolName}/home /${zpoolName}/var    /${zpoolName}/var/log
#mount -t zfs -o zfsutil ${zpoolName}/home    /${zpoolName}/home
#mount -t zfs -o zfsutil ${zpoolName}/var     /${zpoolName}/var
#mount -t zfs -o zfsutil ${zpoolName}/var/log /${zpoolName}/var/log

# mkfs.ext4 on the second partition of the first disk and begin mounting
mkfs.ext4 ${disks[0]}${partPrefix}2
mkdir /${zpoolName}/boot
mount ${disks[0]}${partPrefix}2 /${zpoolName}/boot
mkdir /${zpoolName}/boot/efi
###mkfs.msdos -F 32 -n EFI ${disks[0]}${partPrefix}1
mount ${disks[0]}${partPrefix}1 /${zpoolName}/boot/efi

# Install debootstrap for our installation and begin the installation
apt install -y debootstrap
debootstrap $(basename `ls -d /cdrom/dists/*/ | grep -v stable | head -1`) /${zpoolName}

# Set up the new environmnet
echo ${hostName} > /${zpoolName}/etc/hostname
sed "s/ubuntu/${hostName}/g" /etc/hosts > /${zpoolName}/etc/hosts
echo 'deb https://ubuntu.mirror.serversaustralia.com.au/ubuntu/ noble main' > /etc/apt/sources.list # Overwrites
sed '/cdrom/d' /etc/apt/sources.list > /${zpoolName}/etc/apt/sources.list
cp /etc/netplan/*.yaml /${zpoolName}/etc/netplan/
mkdir -p /${zpoolName}/install/etc/NetworkManager/system-connections/

if [ -d /etc/NetworkManager/system-connections ]
then
  cp /etc/NetworkManager/system-connections/* /${zpoolName}/install/etc/NetworkManager/system-connections/
fi

mkdir -p /${zpoolName}/etc/zfs
cp -nv /etc/zfs/${zpoolName}.key /${zpoolName}/etc/zfs

# Copy timezoneFile information

#if [ -n "${timezoneFile}" ]
#then
#  rm /etc/localtime
#  ln -s "${timezoneFile}" /etc/localtime
#  mkdir -p /{zpoolName}/etc
#  ln -s ln -s "/${zpoolName}/${timezoneFile}" /${zpoolName}/etc/localtime
#fi

# Prepare to and chroot into the new environment
mount --make-private --rbind /dev  /${zpoolName}/dev
mount --make-private --rbind /proc /${zpoolName}/proc
mount --make-private --rbind /sys  /${zpoolName}/sys

# Next we run some setup commands in the chroot

yes "${rootPassword}" | chroot /${zpoolName} passwd root

chroot /${zpoolName} locale-gen --purge "en_US.UTF-8"
chroot /${zpoolName} update-locale LANG=en_US.UTF-8 LANGUAGE=en_US
chroot /${zpoolName} dpkg-reconfigure --frontend noninteractive locales

chroot /${zpoolName} dpkg-reconfigure --frontend noninteractive tzdata
chroot /${zpoolName} apt update
chroot /${zpoolName} apt install --yes --no-install-recommends linux-image-generic linux-headers-generic
chroot /${zpoolName} apt install --yes zfs-initramfs grub-efi-amd64-signed shim-signed

echo "PARTUUID=$(blkid -s PARTUUID -o value ${disks[0]}${partPrefix}2) \
    /boot ext4 noatime,nofail,x-systemd.device-timeout=5s 0 1" >> /${zpoolName}/etc/fstab
echo "PARTUUID=$(blkid -s PARTUUID -o value ${disks[0]}${partPrefix}1) \
    /boot/efi vfat noatime,nofail,x-systemd.device-timeout=5s 0 1" >> /${zpoolName}/etc/fstab
#cat /etc/fstab

KERNEL=`ls /${zpoolName}/usr/lib/modules/ | cut -d/ -f1 | sed 's/linux-image-//'`
chroot /${zpoolName} update-initramfs -u -k ${KERNEL}

chroot /${zpoolName} update-grub
chroot /${zpoolName} grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=${hostName}     --recheck --no-floppy

# If we made it here we've finished

mount | grep -v zfs | tac | awk "/\/${zpoolName}/ {print \$3}" | xargs -i{} umount -lf {}
zpool export -a
echo Done. Ready to be rebooted into.
Unique hits for this page: 263