Ubuntu Root on ZFS install script

"Ubuntu."

Ok... just a few more. Let's let the last few trickle out the door.
Now, for those of you who are still here, and are interested in using some flavor of Ubuntu on ZFS, I've attempted to write a bash script that will automate the process. I'm using Ubuntu Budgie 17.04 Zesty for this. The choice to go this route is because I've repaired a damaged laptop for my wife to use (for docs and everyday stuff), and Ubuntu seems the easiest route to transition her to Linux. The Budgie flavor worked the best and had the most accessible experience, from what I tested on that particular machine. I almost went with elementaryOS, but it was too buggy for me.

Anyway, on to the script. It's messy- I'm not good at scripting, but it mostly works.
It's written following the instructions posted here.


#!/bin/bash
echo "WARNING!  This script could wipe out all your data, or worse!  I am not responsible for your decisions.  Carefully enter the ID of the disk YOU WANT TO DESTROY in the next step to ensure no data is accidentally lost.  Press Enter to continue."
read DISCLAIMER
apt install --yes openssh-server net-tools &&
cat >> /etc/ssh/sshd_config << EOF
Port 22
AddressFamily any
ListenAddress 0.0.0.0
ListenAddress ::
EOF
service sshd start
ls -la /dev/disk/by-id
echo "Enter Disk ID (must match exactly):"
read DISKID
echo "Disk ID set to $DISKID"
while true
do
    read -r -p 'MBR (y/n)?' choice
    case "$choice" in
      n|N) break;;
      y|Y) MBRBOOT=MBR &&
           break;;
      *) echo 'Response not valid';;
    esac
done
while true
do
    read -r -p 'GPT (y/n)?' choice
    case "$choice" in
      n|N) break;;
      y|Y) GPTBOOT=GPT &&
           break;;
      *) echo 'Response not valid';;
    esac
done
echo "Set a name for the ZFS pool:"
read RPOOL
echo "ZFS pool set to $RPOOL"
echo "Set a username for the new system:"
read USERNAME
echo "Username set to $USERNAME"
#echo "Set a password for the new system/user:"
#read PASSWORD
ifconfig -a
echo "Type the name of your network interface:"
read IFACE
echo "Network interface set to $IFACE"
echo 'Type the name of the ubuntu distro package to install (e.g. "ubuntu-budgie-desktop"):'
read DISTRO
echo "Distro set to $DISTRO"
apt update &&
apt install --yes debootstrap zfsutils-linux zfs-initramfs software-properties-common gdisk mdadm &&
mdadm --zero-superblock --force /dev/disk/by-id/$DISKID
sleep 10
if [[ "$MBRBOOT" == "MBR" ]]
  then
    sgdisk -g -a1 -n2:34:2047 -t2:EF02 /dev/disk/by-id/$DISKID &&
  sleep 5
  fi
if [[ "$GPTBOOT" == "GPT" ]]
  then
    sgdisk -g -n3:1M:+512M -t3:EF00 /dev/disk/by-id/$DISKID &&
   sleep 5
  fi
sgdisk -g -n9:-8M:0 -t9:BF07 /dev/disk/by-id/$DISKID &&
sleep 5
sgdisk -g -n1:0:0 -t1:BF01 /dev/disk/by-id/$DISKID &&
sleep 5
zpool create -f \
	-O atime=off \
	-O canmount=off \
	-O compression=lz4 \
	-O normalization=formD \
	-O mountpoint=/ \
	-R /mnt \
$RPOOL /dev/disk/by-id/$DISKID-part1
sleep 2
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 &&
zfs create -o setuid=off $RPOOL/home &&
zfs create -o mountpoint=/root $RPOOL/home/root &&
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 &&
chmod 1777 /mnt/var/tmp &&
debootstrap zesty /mnt &&
zfs set devices=off $RPOOL &&
echo $RPOOL > /mnt/etc/hostname
echo 127.0.1.1       $RPOOL >> /mnt/etc/hosts
echo auto $IFACE >> /mnt/etc/network/interfaces.d/$IFACE
echo iface $IFACE inet dhcp >> /mnt/etc/network/interfaces.d/$IFACE
mount --rbind /dev  /mnt/dev
mount --rbind /proc /mnt/proc
mount --rbind /sys  /mnt/sys &&
chroot /mnt /bin/bash -x <<'EOCHROOT'
cat >> /etc/apt/sources.list << EOLIST
deb http://archive.ubuntu.com/ubuntu zesty main universe restricted multiverse
deb-src http://archive.ubuntu.com/ubuntu zesty main universe restricted multiverse
deb http://security.ubuntu.com/ubuntu zesty-security main universe restricted multiverse
deb-src http://security.ubuntu.com/ubuntu zesty-security main universe restricted multiverse
deb http://archive.ubuntu.com/ubuntu zesty-updates main universe restricted multiverse
deb-src http://archive.ubuntu.com/ubuntu zesty-updates main universe restricted multiverse
EOLIST
ln -s /proc/self/mounts /etc/mtab &&
apt update &&
locale-gen en_US.UTF-8 &&
echo 'LANG="en_US.UTF-8"' > /etc/default/locale &&
dpkg-reconfigure tzdata &&
apt install --yes --no-install-recommends linux-image-generic wget nano &&
apt install --yes zfs-initramfs &&
wget -q -O /etc/cron.hourly/zfs-check https://gist.githubusercontent.com/fire/65f7aa33b91d3af2aef0/raw/a0309ef9a6bec26b497b2ee7e00aaa2889310384/zfs-check.sh &&
wget -q -O /etc/cron.monthly/zfs-scrub https://gist.githubusercontent.com/fire/65f7aa33b91d3af2aef0/raw/5b0904343897b1410fd57239879a0f43ff634883/zfs-scrub.sh &&
chmod 755 /etc/cron.hourly/zfs-check /etc/cron.monthly/zfs-scrub &&
wget -q -O /etc/sudoers.d/zfs https://gist.githubusercontent.com/fire/65f7aa33b91d3af2aef0/raw/36a2b9e37819abffc6bfc2a9a36859afabf47754/zfs.sudoers &&
chmod 440 /etc/sudoers.d/zfs &&
apt install --yes dosfstools &&
if [[ "$GPTBOOT" == "GPT" ]]
  then mkdosfs -F 32 -n EFI /dev/disk/by-id/$DISKID-part3 &&
  mkdir /boot/efi &&
  echo PARTUUID=$(blkid -s PARTUUID -o value /dev/disk/by-id/$DISKID-part3) /boot/efi vfat defaults 0 1 >> /etc/fstab &&
  mount /boot/efi &&
  apt install --yes grub-efi-amd64 &&
  sleep 2
fi
if [[ "$MBRBOOT" == "MBR" ]]
  then
  apt install --yes grub-pc &&
  sleep 2
fi
addgroup --system lpadmin
addgroup --system sambashare

#echo -e "root:$PASSWORD" | chpasswd

passwd
zfs set mountpoint=legacy $RPOOL/var/log
zfs set mountpoint=legacy $RPOOL/var/tmp
cat >> /etc/fstab << EOF
rpool/var/log /var/log zfs defaults 0 0
rpool/var/tmp /var/tmp zfs defaults 0 0
EOF
ln -s /dev/disk/by-id/$DISKID /dev/$DISKID-part3
grub-probe /
update-initramfs -c -k all
update-grub
if [[ "$GPTBOOT" == "GPT" ]]
  then
  grub-install --target=x86_64-efi --efi-directory=/boot/efi \
  --bootloader-id=ubuntu --recheck --no-floppy &&
  sleep 4
  fi
if [[ "$MBRBOOT" == "MBR" ]]
  then
  grub-install /dev/disk/by-id/$DISKID &&
  sleep 4
fi
ls /boot/grub/*/zfs.mod
sed -i -e 's/GRUB_HIDDEN_TIMEOUT=0/#GRUB_HIDDEN_TIMEOUT=5/g'
sed -i -e 's/"quiet splash"/""/g'
sed -i -e 's/#GRUB_TERMINAL=console/GRUB_TERMINAL=console/g'
update-grub
zfs snapshot $RPOOL/ROOT/[email protected]
zfs create precision/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

#echo -e "$USERNAME:$PASSWORD" | chpasswd

zfs create -V 8G -b $(getconf PAGESIZE) -o compression=zle -o logbias=throughput -o sync=always -o primarycache=metadata -o secondarycache=none -o com.sun:auto-snapshot=false $RPOOL/swap &&
mkswap -f /dev/zvol/$RPOOL/swap &&
echo /dev/zvol/$RPOOL/swap none swap defaults 0 0 >> /etc/fstab &&
swapon -av &&
apt dist-upgrade --yes &&
apt update &&
for file in /etc/logrotate.d/* ; do
    if grep -Eq "(^|[^#y])compress" "$file" ; then
        sed -i -r "s/(^|[^#y])(compress)/\1#\2/" "$file"
    fi
done
apt install --yes $DISTRO &&
while true
do
    read -r -p 'Disable root password (y/n)? ' choice
    case "$choice" in
      n|N) break;;
      y|Y) usermod -p '*' root &&
           break;;
      *) echo 'Response not valid';;
    esac
done
echo 'Exiting chroot.'
EOCHROOT
while true
do
    read -r -p "Would you like to unmount $RPOOL now (y/n)? " choice
    case "$choice" in
      n|N) break;;
      y|Y) mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {} && zpool export $RPOOL &&
           break;;
      *) echo 'Response not valid';;
    esac
done
while true
do
    read -r -p 'Would you like to reboot now (y/n)? ' choice
    case "$choice" in
      n|N) break;;
      y|Y) reboot;;
      *) echo 'Response not valid';;
    esac
done

Some things I haven't gotten around to yet are asking the user if they're using an SSD or HDD (for ashift=12 when forming the pool) and figuring out how to make passwords and some other things non-interactive. Testing the lines that are commented out on a live CD worked to change both root and user passwords, but failed when actually installing.
If you want to run this for another flavor of Ubuntu, you'll need to look up a package name. Mine is "ubuntu-budgie-desktop". I suppose you could also do ubuntu-minimal or whatever the server package is to quickly get a server up and running this way.

I included the GPT/MBR options because the laptop I'm using is real screwy and needs to be formatted GPT on the drive, but will only load GRUB2 from MBR. UEFI will result in a failed boot.

Anyway, I just wanted to offer this for anyone who wants it or is new to ZFS. Let me know if it works for you. I ran it from an Ubuntu Budgie 17.04 live USB boot, but I think it could probably run from almost any live Debian system, provided the repos point in the right place and you add any missing steps to the top of the script. You can change the version you want to install by doing a search/replace of "zesty" to "xenial" or whatever. Or straight up Debian might work.

Let me know if anyone has suggestions or improvements. I'm learning a little more about bash in hopes to transition to some real programming, so if you have tips I can chew on, throw 'em at me.

3 Likes