Skip to content

Instantly share code, notes, and snippets.

@vrivellino
Last active February 2, 2021 13:37
Show Gist options
  • Save vrivellino/7dcf150da4cc1d07008315643bfdbfb5 to your computer and use it in GitHub Desktop.
Save vrivellino/7dcf150da4cc1d07008315643bfdbfb5 to your computer and use it in GitHub Desktop.
Install Ubuntu 18.04: ZFS on encrypted drives with USB boot disk
#!/usr/bin/env bash
# Adapted from https://github.com/zfsonlinux/zfs/wiki/Ubuntu-18.04-Root-on-ZFS
# Should be executed from a live CD environment
set -e
## CONFIG VARS
set -x
# Disk drive ids (symlinks in /dev/disk/by-id)
bootdisk='usb-SanDisk_Cruzer_AAAAAAAAAAAAAAAAAAAA-0:0'
rdisk1='ata-SanDisk_SDSSDHII120G_AAAAAAAAAAAA'
rdisk2='ata-SanDisk_SDSSDHII120G_BBBBBBBBBBBB'
ddisk1='ata-SanDisk_Ultra_II_240GB_WWWWWWWWWWWW'
ddisk2='ata-SanDisk_Ultra_II_240GB_XXXXXXXXXXXX'
ddisk3='ata-SanDisk_Ultra_II_250GB_YYYYYYYYYYYY'
ddisk4='ata-SanDisk_Ultra_II_250GB_ZZZZZZZZZZZZ'
# dirs for key files
temp_key_dir=/dev/shm/luks-keys
dest_key_dir=/etc/keys/luks
# the hostname of the new system
system_hostname='vr-pc'
## END CONFIG
set +x
initial() {
apt-add-repository universe
apt update
apt install --yes debootstrap gdisk zfs-initramfs mdadm
}
partition() {
## Clear out software raid and partition configurations on disks
for dsk in $bootdisk $rdisk1 $rdisk2 $ddisk1 $ddisk2 $ddisk3 $ddisk4 ; do
mdadm --zero-superblock --force /dev/disk/by-id/$dsk
sgdisk --zap-all /dev/disk/by-id/$dsk
done
## Partition bootdisk for /boot (part4), /boot/efi (part3), and remaining space as a spare data partition (part1)
sgdisk -n3:1M:+512M -t3:EF00 /dev/disk/by-id/$bootdisk
sgdisk -n4:0:+512M -t4:8300 /dev/disk/by-id/$bootdisk
sgdisk -n1:0:0 -t1:8300 /dev/disk/by-id/$bootdisk
}
setup_encryption() {
## Setup encryption
mkdir -m0700 -p $temp_key_dir
for dsk in $rdisk1 $rdisk2 $ddisk1 $ddisk2 $ddisk3 $ddisk4; do
echo
if ! [[ -f $temp_key_dir/$dsk ]]; then
dd if=/dev/urandom of=$temp_key_dir/$dsk bs=32 count=1
fi
chmod 600 $temp_key_dir/$dsk
# use key file for luksFormat
cryptsetup luksFormat --key-file $temp_key_dir/$dsk -c aes-xts-plain64 -s 256 -h sha256 /dev/disk/by-id/$dsk
cryptsetup luksAddKey --key-file $temp_key_dir/$dsk /dev/disk/by-id/$dsk
done
}
luks_open() {
cryptsetup status luks-root1 || cryptsetup luksOpen --key-file $temp_key_dir/$rdisk1 /dev/disk/by-id/$rdisk1 luks-root1
cryptsetup status luks-root2 || cryptsetup luksOpen --key-file $temp_key_dir/$rdisk2 /dev/disk/by-id/$rdisk2 luks-root2
cryptsetup status luks-data1 || cryptsetup luksOpen --key-file $temp_key_dir/$ddisk1 /dev/disk/by-id/$ddisk1 luks-data1
cryptsetup status luks-data2 || cryptsetup luksOpen --key-file $temp_key_dir/$ddisk2 /dev/disk/by-id/$ddisk2 luks-data2
cryptsetup status luks-data3 || cryptsetup luksOpen --key-file $temp_key_dir/$ddisk3 /dev/disk/by-id/$ddisk3 luks-data3
cryptsetup status luks-data4 || cryptsetup luksOpen --key-file $temp_key_dir/$ddisk4 /dev/disk/by-id/$ddisk4 luks-data4
}
initialize_zfs() {
## Create zpools
if zpool status rpool > /dev/null 2>&1; then
zpool destroy rpool
fi
zpool create -o ashift=12 \
-O atime=off -O canmount=off -O compression=lz4 -O normalization=formD \
-O mountpoint=/ -R /mnt \
rpool mirror /dev/mapper/luks-root1 /dev/mapper/luks-root2
if zpool status datapool > /dev/null 2>&1; then
zpool destroy datapool
fi
zpool create -o ashift=12 \
-O atime=off -O canmount=off -O compression=lz4 -O normalization=formD \
-O mountpoint=/data -R /mnt \
datapool mirror /dev/mapper/luks-data1 /dev/mapper/luks-data2
zpool add datapool mirror /dev/mapper/luks-data3 /dev/mapper/luks-data4
## Create filesystems
# root FS
zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o canmount=noauto -o mountpoint=/ rpool/ROOT/ubuntu
zfs mount rpool/ROOT/ubuntu
# /var
zfs create -o canmount=off -o setuid=off -o exec=off rpool/var
zfs create -o com.sun:auto-snapshot=false rpool/var/cache
zfs create rpool/var/log
zfs create rpool/var/spool
zfs create -o com.sun:auto-snapshot=false -o exec=on rpool/var/tmp
zfs create -o mountpoint=/home -o setuid=off datapool/home
}
setup_initial_system() {
# format & mount boot disk
mke2fs -L /boot -t ext2 /dev/disk/by-id/$bootdisk-part4
test -d /mnt/boot || mkdir /mnt/boot
mount /dev/disk/by-id/$bootdisk-part4 /mnt/boot
# install minimal system
chmod 1777 /mnt/var/tmp
debootstrap bionic /mnt
zfs set devices=off rpool
# setup networking
echo $system_hostname > /mnt/etc/hostname
sed -i '/^127[.]0[.]1[.]1 /d' /mnt/etc/hosts
echo "127.0.1.1 $system_hostname" >> /mnt/etc/hosts
# grab first device that isn't loopback
eth_dev=$(ip link | grep -v '^ ' | awk '{ print $2 }' | cut -f 1 -d : | grep -v '^lo$' | head -n 1)
if [[ -n $eth_dev ]]; then
cat > /mnt/etc/netplan/$eth_dev.yaml <<EOF
network:
version: 2
ethernets:
NAME:
dhcp4: true
EOF
fi
cat > /mnt/etc/apt/sources.list << EOF
deb http://archive.ubuntu.com/ubuntu bionic main universe
deb-src http://archive.ubuntu.com/ubuntu bionic main universe
deb http://security.ubuntu.com/ubuntu bionic-security main universe
deb-src http://security.ubuntu.com/ubuntu bionic-security main universe
deb http://archive.ubuntu.com/ubuntu bionic-updates main universe
deb-src http://archive.ubuntu.com/ubuntu bionic-updates main universe
EOF
mount --rbind /dev /mnt/dev
mount --rbind /proc /mnt/proc
mount --rbind /sys /mnt/sys
}
copy_luks_keys() {
mkdir -p -m 700 /mnt$dest_key_dir
cp $temp_key_dir/* /mnt$dest_key_dir
chmod 400 /mnt$dest_key_dir/*
}
dest_system_basic_config() {
test -e "/root/$(basename "$0")"
locale-gen en_US.UTF-8
echo LANG=en_US.UTF-8 > /etc/default/locale
dpkg-reconfigure tzdata
ln -s /proc/self/mounts /etc/mtab
apt update
apt install --yes --no-install-recommends linux-image-generic
apt install --yes zfs-initramfs cryptsetup dosfstools
boot_part_uuid=$(blkid -s UUID -o value /dev/disk/by-id/$bootdisk-part4)
sed -i "/$boot_part_uuid/d" /etc/fstab
echo UUID=$boot_part_uuid /boot ext2 defaults 0 2 >> /etc/fstab
}
dest_system_crypttab() {
test -e "/root/$(basename "$0")"
sed -i '/^luks-\(root\|data\)[0-9] /d' /etc/crypttab
rdisk1_uuid=$(blkid -s UUID -o value /dev/disk/by-id/$rdisk1)
rdisk2_uuid=$(blkid -s UUID -o value /dev/disk/by-id/$rdisk2)
ddisk1_uuid=$(blkid -s UUID -o value /dev/disk/by-id/$ddisk1)
ddisk2_uuid=$(blkid -s UUID -o value /dev/disk/by-id/$ddisk2)
ddisk3_uuid=$(blkid -s UUID -o value /dev/disk/by-id/$ddisk3)
ddisk4_uuid=$(blkid -s UUID -o value /dev/disk/by-id/$ddisk4)
echo luks-root1 UUID=$rdisk1_uuid $dest_key_dir/$rdisk1 luks,discard,initramfs >> /etc/crypttab
echo luks-root2 UUID=$rdisk2_uuid $dest_key_dir/$rdisk2 luks,discard,initramfs >> /etc/crypttab
echo luks-data1 UUID=$ddisk1_uuid $dest_key_dir/$ddisk1 luks,discard,initramfs >> /etc/crypttab
echo luks-data2 UUID=$ddisk2_uuid $dest_key_dir/$ddisk2 luks,discard,initramfs >> /etc/crypttab
echo luks-data3 UUID=$ddisk3_uuid $dest_key_dir/$ddisk3 luks,discard,initramfs >> /etc/crypttab
echo luks-data4 UUID=$ddisk4_uuid $dest_key_dir/$ddisk4 luks,discard,initramfs >> /etc/crypttab
sed -i '/^\(CRYPTSETUP\|KEYFILE_PATTERN\)=/d' /etc/cryptsetup-initramfs/conf-hook
echo 'CRYPTSETUP=y' >> /etc/cryptsetup-initramfs/conf-hook
echo "KEYFILE_PATTERN=$dest_key_dir/*" >> /etc/cryptsetup-initramfs/conf-hook
}
dest_system_install_grub() {
test -e "/root/$(basename "$0")"
apt install dosfstools
mkdosfs -F 32 -n EFI /dev/disk/by-id/$bootdisk-part3
test -d /boot/efi || mkdir /boot/efi
bootpart_uuid=$(blkid -s PARTUUID -o value /dev/disk/by-id/$bootdisk-part3)
sed -i "/$bootpart_uuid/d" /etc/fstab
echo PARTUUID=$bootpart_uuid /boot/efi vfat nofail,x-systemd.device-timeout=1 0 1 >> /etc/fstab
mount /boot/efi
apt install --yes grub-efi-amd64
}
dest_system_zfs_legacy_mount() {
test -e "/root/$(basename "$0")"
zfs set mountpoint=legacy rpool/var/log
zfs set mountpoint=legacy rpool/var/tmp
sed -i '/^rpool\/var\//d' /etc/fstab
cat >> /etc/fstab << EOF
rpool/var/log /var/log zfs defaults 0 0
rpool/var/tmp /var/tmp zfs defaults 0 0
EOF
}
dest_system_setup_boot() {
test -e "/root/$(basename "$0")"
[[ $(grub-probe /) == zfs ]]
update-initramfs -c -k all
chmod 600 /boot/initrd*
sed -i 's/^GRUB_HIDDEN_TIMEOUT=0/## GRUB_HIDDEN_TIMEOUT=0/' /etc/default/grub
sed -i 's/^#GRUB_TERMINAL=console/GRUB_TERMINAL=console/' /etc/default/grub
sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"/## GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"\
GRUB_CMDLINE_LINUX_DEFAULT=""/' /etc/default/grub
update-grub
grub-install --target=x86_64-efi --efi-directory=/boot/efi \
--bootloader-id=ubuntu --recheck --no-floppy
ls /boot/grub/*/zfs.mod
}
dest_system_final_config_before_reboot() {
test -e "/root/$(basename "$0")"
getent group lpadmin > /dev/null || addgroup --system lpadmin
getent group sambashare > /dev/null || addgroup --system sambashare
set +x
echo
root_pw=$(getent shadow root | cut -f 2 -d :)
if [[ $root_pw == '*' ]] || [[ -z $root_pw ]]; then
echo 'Set root password for system'
passwd
echo
fi
read -p 'Enter username to create: ' username
set -x
if [[ -n $username ]]; then
zfs create datapool/home/$username
adduser $username
cp -a /etc/skel/.[!.]* /home/$username/
chown -R $username:$username /home/$username
usermod -a -G adm,cdrom,dip,lpadmin,plugdev,sambashare,sudo $username
fi
for snap in rpool/ROOT/ubuntu@install datapool/home@install ; do
if zfs list -t snap | grep -q $snap; then
zfs destroy $snap
fi
zfs snapshot $snap
done
}
if [[ -z $1 ]]; then
echo
echo "Performing pre-installation setups ..."
echo
set -x
initial
partition
setup_encryption
luks_open
initialize_zfs
echo "Performing minimal installation ..."
setup_initial_system
copy_luks_keys
## Copy script to destination system and exec via chroot
cp "$0" /mnt/root
chroot /mnt /bin/bash --login "/root/$(basename "$0")" post-install-config
set +x
echo
echo System is ready to be rebooted
echo
echo If all goes well, you can complete items specified https://github.com/zfsonlinux/zfs/wiki/Ubuntu-18.04-Root-on-ZFS#step-7-configure-swap
echo
echo The following commands will be executed:
echo "# mount | grep -v zfs | tac | awk '/\\/mnt/ {print \$3}' | xargs -i{} umount -lf {}"
echo \# zpool export datapool
echo \# zpool export rpool
echo \# reboot
echo
read -p 'Press enter to continue ...' input_str
mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}
zpool export datapool
zpool export rpool
reboot
elif [[ $1 == post-install-config ]]; then
echo
echo "Performing post-install configuration ..."
echo
set -x
dest_system_basic_config
dest_system_crypttab
dest_system_install_grub
dest_system_zfs_legacy_mount
dest_system_setup_boot
dest_system_final_config_before_reboot
set +x
else
echo "Fatal: unknown argument '$1'" >&2
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment