Last active
November 29, 2022 16:06
-
-
Save Halfwalker/daf4ba4d01c62109752a09104c9c167b to your computer and use it in GitHub Desktop.
ZFS-root installer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # LUKS | |
| # https://fossies.org/linux/cryptsetup/docs/Keyring.txt | |
| # https://hamy.io/post/0009/how-to-install-luks-encrypted-ubuntu-18.04.x-server-and-enable-remote-unlocking/#gsc.tab=0 | |
| # https://www.arminpech.de/2019/12/23/debian-unlock-luks-root-partition-remotely-by-ssh-using-dropbear/ | |
| # >>>>>>>>>> ZFS native encryption <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | |
| # https://arstechnica.com/gadgets/2021/06/a-quick-start-guide-to-openzfs-native-encryption/ | |
| # https://talldanestale.dk/2020/04/06/zfs-and-homedir-encryption/ | |
| ### Simple script to unlock home dataset etc. at boot | |
| # https://gbyte.dev/blog/unlock-mount-several-zfs-datasets-boot-single-passphrase | |
| # https://github.com/dynerose/Remote-unlock-native-ZFS | |
| # NOTE: Intesting ideas | |
| # https://blobfolio.com/2018/06/replace-grub2-with-systemd-boot-on-ubuntu-18-04/ | |
| # https://github.com/dyindude/ubuntu-zfs | |
| # systemd-boot and systemd-boot-manager | |
| # https://forum.manjaro.org/t/manjaros-grub-probe-compatibility-with-zfs/127134/12 | |
| # dropbear remote unlocking | |
| # https://hamy.io/post/0009/how-to-install-luks-encrypted-ubuntu-18.04.x-server-and-enable-remote-unlocking/ | |
| # remote key via https https://github.com/stupidpupil/https-keyscript | |
| # Also look into tang keyserver | |
| # AWS keyserver for luks | |
| # https://icicimov.github.io/blog/server/LUKS-with-AWS-SSM-and-KMS-in-Systemd/ | |
| # | |
| # This will set up a single-disk system with root-on-zfs, using | |
| # bionic/18.04 or focal/20.04 or jammy/22.04. | |
| # | |
| # >>>>>>>>>> NOTE: This will totally overwrite the disk chosen <<<<<<<<<<<<< | |
| # | |
| # 1) Boot an Ubuntu live cd to get a shell. Ubuntu live-server is a good choice. | |
| # 2) Open a shell (ctrl-t) and become root (sudo -i) | |
| # 3) Copy this script onto the box somehow - scp from somewhere | |
| # 4) Make it executable (chmod +x ZFS-root.sh) | |
| # 5) Run it (./ZFS-root.sh) | |
| # 6) Add -d to enable set -x debugging (./ZFS-root.sh -d) | |
| # | |
| # It will ask a few questions (username, which disk, bionic/focal etc) | |
| # and then fully install a minimal Ubuntu system. Depending on the choices | |
| # several partitions and zfs datasets will be created. | |
| # | |
| # Part Name Use | |
| # =========================================================================== | |
| # 1 BOOT EFI partition, also has syslinux | |
| # 2 SWAP Only created if HIBERNATE is enabled (may be encrypted with LUKS) | |
| # 3 ZFS Main zfs pool (rpool) for full system (rpool/ROOT/bionic) | |
| # | |
| # Datasets created | |
| # ================ | |
| # rpool/ROOT/bionic Contains main system | |
| # rpool/ROOT/bionic@base_install Snapshot of install main system | |
| # rpool/home Container for user directories | |
| # rpool/home/<username> Dataset for initial user | |
| # rpool/home/root Dataset for root user | |
| # | |
| # One option is to enable LUKS full disk encryption. If HIBERNATE is enabled | |
| # and a SWAP partition created, then that will be encrypted as well. | |
| # | |
| # NOTE: The HIBERNATE option will be disabled if the appropriate feature is not | |
| # enabled in the power options of the system bios (/sys/power/state) | |
| # | |
| # NOTE: If installing under KVM, then the SCSI disk driver must be used, | |
| # not the virtio one. Otherwise the disks will not be linked into the | |
| # /dev/disk/by-id/ directory. | |
| # Return codes from whiptail | |
| # 0 OK in menu | |
| # 1 Cancel in menu | |
| # 255 Esc key hit | |
| if [[ $EUID -ne 0 ]]; then | |
| echo "This script must be run as root" 1>&2 | |
| exit 1 | |
| fi | |
| # Grab any possible pre-config settings in ZFS-root.conf | |
| if [ -e ZFS-root.conf ] ; then | |
| . ZFS-root.conf | |
| fi | |
| # No magenta overrides for whiptail dialogs please | |
| export NEWT_COLORS="none" | |
| # Build location - will be removed prior to build | |
| # NOTE: Can NOT have "zfs" in the name | |
| ZFSBUILD=/mnt/builder | |
| # Partition numbers of each partition | |
| PARTITION_BOOT=1 | |
| PARTITION_SWAP=2 | |
| PARTITION_DATA=3 | |
| # ZFS encryption options | |
| ZFSENC_ROOT_OPTIONS="-o encryption=aes-256-gcm -o keylocation=prompt -o keyformat=passphrase" | |
| # NOTE: for keyfile, put key in local /etc/zfs, then later copy to target /etc/zfs | |
| # to be used for encrypting /home | |
| ZFSENC_HOME_OPTIONS="-o encryption=aes-256-gcm -o keylocation=file:///etc/zfs/zroot.rawkey -o keyformat=raw" | |
| # Check for a local apt-cacher-ng system - looking for these hosts | |
| # aptcacher.local | |
| # bondi.local | |
| # First see if PROXY is already set in ZFS-root.conf | |
| if [[ ! -v PROXY ]] ; then | |
| echo "Searching for local apt-cacher-ng proxy systems ..." | |
| PROXY="" | |
| for CACHER in bondi.local aptcacher.local ; do | |
| echo -n "... testing ${CACHER}" | |
| CACHER=$(ping -w 2 -c 1 ${CACHER} | fgrep "bytes from" | cut -d' ' -f4) | |
| if [ "${CACHER}" != "" ] ; then | |
| echo " - found !" | |
| PROXY="http://${CACHER}:3142/" | |
| break | |
| else | |
| echo " - not found :(" | |
| fi | |
| done | |
| PROXY=$(whiptail --inputbox "Enter an apt proxy. Cancel or hit <esc> for no proxy" --title "APT proxy setup" 8 70 $(echo $PROXY) 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| (( RET )) && PROXY= | |
| fi # Check if PROXY is set already | |
| if [ ${PROXY} ]; then | |
| # export http_proxy=${PROXY} | |
| # export ftp_proxy=${PROXY} | |
| # This is for apt-get | |
| echo "Acquire::http::proxy \"${PROXY}\";" > /etc/apt/apt.conf.d/03proxy | |
| fi # PROXY | |
| apt-get -qq update | |
| apt-get -qq --no-install-recommends --yes install software-properties-common | |
| apt-add-repository -y universe | |
| # Get userid and full name of main user | |
| # First see if USERNAME or UCOMMENT are already set in ZFS-root.conf | |
| if [[ ! -v USERNAME ]] || [[ ! -v UCOMMENT ]] ; then | |
| [[ ! -v USERNAME ]] && USERNAME=deano | |
| [[ ! -v UCOMMENT ]] && UCOMMENT="Dean Carpenter" | |
| USERINFO=$(whiptail --inputbox "Enter username (login id) and full name of user\nAs in <username> <space> <First and Last name>\n\nlogin full name here\n|---| |------------ - - - - -" --title "User information" 11 70 "$(echo $USERNAME $UCOMMENT)" 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| USERNAME=$(echo $USERINFO | cut -d' ' -f1) | |
| UCOMMENT=$(echo $USERINFO | cut -d' ' -f2-) | |
| fi # Check if USERNAME/UCOMMENT set | |
| # Get password, confirm and loop until confirmation OK | |
| if [[ ! -v UPASSWORD ]]; then | |
| DONE=false | |
| until ${DONE} ; do | |
| PW1=$(whiptail --passwordbox "Please enter a password for user $(echo $USERNAME)" 8 70 --title "User password" 3>&1 1>&2 2>&3) | |
| PW2=$(whiptail --passwordbox "Please re-enter the password to confirm" 8 70 --title "User password confirmation" 3>&1 1>&2 2>&3) | |
| [ "$PW1" = "$PW2" ] && DONE=true | |
| done | |
| UPASSWORD="$PW1" | |
| fi # Check if UPASSWORD already set | |
| # Hostname - cancel or blank name will exit | |
| if [[ ! -v HOSTNAME ]] ; then | |
| HOSTNAME=test | |
| HOSTNAME=$(whiptail --inputbox "Enter hostname to be used for new system. This name may also be used for the main ZFS poolname." --title "Hostname for new system." 8 70 $(echo $HOSTNAME) 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| (( RET )) && HOSTNAME= | |
| if [ ! ${HOSTNAME} ]; then | |
| echo "Must have a hostname" | |
| exit 1 | |
| fi | |
| fi # Check if HOSTNAME already set | |
| if [[ ! -v HOSTNAME ]] ; then | |
| POOLNAME=${HOSTNAME} | |
| POOLNAME=$(whiptail --inputbox "Enter poolname to use for main system - defaults to hostname" --title "ZFS main poolname" 8 70 $(echo $POOLNAME) 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| (( RET )) && POOLNAME= | |
| if [ ! ${POOLNAME} ]; then | |
| echo "Must have a ZFS poolname" | |
| exit 1 | |
| fi | |
| fi # Check if POOLNAME already set | |
| # Set main disk here - be sure to include the FULL path | |
| # Get list of disks, ask user which one to install to | |
| # Ignore cdrom etc. Limit disk name length to avoid menu uglyness | |
| readarray -t disks < <(ls -l /dev/disk/by-id | egrep -v '(CDROM|CDRW|-ROM|CDDVD|-part|md-|dm-|wwn-)' | sort -t '/' -k3 | tr -s " " | cut -d' ' -f9 | cut -c -48 | sed '/^$/d') | |
| # If no disks available (kvm needs to use scsi, not virtio) then error out | |
| if [ ${#disks[@]} -eq 0 ] ; then | |
| whiptail --title "No disks available in /dev/disk/by-id" --msgbox "No valid disk links were found in /dev/disk/by-id - ensure your target disk has a link in that directory.\n\nKVM/qemu VMs need to use the SCSI storage driver, not the default virtio one (which does not create links in /dev/disk/by-id)" 12 70 | |
| exit 1 | |
| fi | |
| TMPFILE=$(mktemp) | |
| # Find longest disk name | |
| m=-1 | |
| for disk in "${disks[@]}" | |
| do | |
| if [ ${#disk} -gt $m ] | |
| then | |
| m=${#disk} | |
| fi | |
| done | |
| # Set dialog box size to num disks | |
| list_height=$(( ${#disks[@]} + 1 )) | |
| box_height=$(( ${#disks[@]} + 8 )) | |
| box_width=$(( ${m} + 16 )) | |
| DONE=false | |
| until ${DONE} ; do | |
| whiptail --title "List of disks" --separate-output --checklist --noitem \ | |
| "Choose disk(s) to install to" ${box_height} ${box_width} ${list_height} \ | |
| $( for disk in $(seq 0 $(( ${#disks[@]}-1)) ) ; do echo "${disks[${disk}]}" OFF ; done) 2> "${TMPFILE}" | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| readarray -t zfsdisks < <(cat ${TMPFILE}) | |
| if [ ${#zfsdisks[@]} != 0 ] ; then | |
| DONE=true | |
| fi | |
| done | |
| # Single disk can only be "single" | |
| if [ ${#zfsdisks[@]} -eq 1 ] ; then | |
| RAIDLEVEL="single" | |
| fi | |
| # Check if raid level already set in ZFS-root.conf | |
| if [[ ! -v RAIDLEVEL ]] ; then | |
| #_# DISK="/dev/disk/by-id/${DISK}" | |
| if [ ${#zfsdisks[@]} -gt 1 ] ; then | |
| RAIDLEVEL=$(whiptail --title "ZPOOL raid level" --radiolist "Select ZPOOL raid level" 12 60 5 \ | |
| single "No raid, just single disks as vdevs" OFF \ | |
| mirror "All disks mirrored" OFF \ | |
| raidz1 "All disks in raidz1 format" OFF \ | |
| raidz2 "All disks in raidz2 format" OFF \ | |
| raidz3 "All disks in raidz3 format" OFF 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| fi | |
| fi # Check RAIDLEVEL already set | |
| # We use ${RAIDLEVEL} to set zpool raid level - just vdevs means that should be blank | |
| if [ "${RAIDLEVEL}" = "single" ] ; then RAIDLEVEL= ; fi | |
| if [[ ! -v DISCENC ]] ; then | |
| DISCENC=$(whiptail --title "Select disk encryption" --radiolist "Choose which (if any) disk encryption to use" 11 60 4 \ | |
| NOENC "No disk encryption" ON \ | |
| ZFSENC "Enable ZFS dataset encryption" OFF \ | |
| LUKS "Enable LUKS full disk encryption" OFF \ | |
| 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| fi # Check DISCENC already set | |
| # If encryption enabled, need a passphrase | |
| if [ "${DISCENC}" != "NOENC" ] ; then | |
| if [[ ! -v PASSPHRASE ]] ; then | |
| DONE=false | |
| until ${DONE} ; do | |
| PW1=$(whiptail --passwordbox "Please enter a good long encryption passphrase" 8 70 --title "Encryption passphrase" 3>&1 1>&2 2>&3) | |
| PW2=$(whiptail --passwordbox "Please re-enter the encryption passphrase" 8 70 --title "Encryption passphrase confirmation" 3>&1 1>&2 2>&3) | |
| [ "$PW1" = "$PW2" ] && DONE=true | |
| done | |
| PASSPHRASE="$PW1" | |
| fi # If PASSPHRASE not already set in ZFS-root.conf | |
| fi | |
| # We check /sys/power/state - if no "disk" in there, then HIBERNATE is disabled | |
| cat /sys/power/state | fgrep disk > /dev/null | |
| HIBERNATE_AVAIL=${?} | |
| # Force Hibernate to n if not available, overriding anything in ZFS-root.conf | |
| [ ${HIBERNATE_AVAIL} -ne 0 ] && HIBERNATE=n | |
| # | |
| # Slightly fugly - have to check if ANY of these are not set | |
| # | |
| if [[ ! -v GOOGLE ]] || [[ ! -v HWE ]] || [[ ! -v ZFSPPA ]] || [[ ! -v HIBERNATE ]] || [[ ! -v DELAY ]] || [[ ! -v SOF ]] || [[ ! -v GNOME ]] || [[ ! -v KDE ]] ; then | |
| # Hibernate can only resume from a single disk, and currently not available for ZFS encryption | |
| if [ "${DISCENC}" == "ZFSENC" ] || [ ${#zfsdisks[@]} -gt 1 ] || [ ${HIBERNATE_AVAIL} -ne 0 ] ; then | |
| # Set basic options for install - ZFSENC so no Hibernate available (yet) | |
| whiptail --title "Set options to install" --separate-output --checklist "Choose options\n\nNOTE: 18.04 HWE kernel requires pool attribute dnodesize=legacy" 18 83 7 \ | |
| GOOGLE "Add google authenticator via pam for ssh logins" OFF \ | |
| HWE "Install Hardware Enablement kernel" OFF \ | |
| ZFSPPA "Update to latest ZFS 2.1 from PPA" OFF \ | |
| DELAY "Add delay before importing root pool - for many-disk systems" OFF \ | |
| SOF "Install Sound Open Firmware binaries (for some laptops)" OFF \ | |
| GNOME "Install full Ubuntu Gnome desktop" OFF \ | |
| KDE "Install full Ubuntu KDE Plasma desktop" OFF 2>"${TMPFILE}" | |
| else | |
| # Set basic options for install - ZFSENC so no Hibernate available (yet) | |
| whiptail --title "Set options to install" --separate-output --checklist "Choose options\n\nNOTE: 18.04 HWE kernel requires pool attribute dnodesize=legacy" 19 83 8 \ | |
| GOOGLE "Add google authenticator via pam for ssh logins" OFF \ | |
| HWE "Install Hardware Enablement kernel" OFF \ | |
| ZFSPPA "Update to latest ZFS 2.1 from PPA" OFF \ | |
| HIBERNATE "Enable swap partition for hibernation" OFF \ | |
| DELAY "Add delay before importing root pool - for many-disk systems" OFF \ | |
| SOF "Install Sound Open Firmware binaries (for some laptops)" OFF \ | |
| GNOME "Install full Ubuntu Gnome desktop" OFF \ | |
| KDE "Install full Ubuntu KDE Plasma desktop" OFF 2>"${TMPFILE}" | |
| fi | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| # Set any selected options to 'y' | |
| while read -r TODO ; do | |
| eval "${TODO}"='y' | |
| done < "${TMPFILE}" | |
| # Any options not enabled in the basic options menu we now set to 'n' | |
| for option in GNOME KDE HWE HIBERNATE ZFSPPA DELAY SOF GOOGLE; do | |
| [ ${!option} ] || eval "${option}"='n' | |
| done | |
| fi # Check ALL options from ZFS-root.conf | |
| # Show google authenticator info - file in /root/google_auth.txt is like | |
| # AGNGG2UOIDJXDJNZ | |
| # "RATE_LIMIT 3 30 | |
| # " WINDOW_SIZE 3 | |
| # " DISALLOW_REUSE | |
| # " TOTP_AUTH | |
| # 75667428 | |
| # 93553495 | |
| # 65484719 | |
| # 23383624 | |
| # 28747791 | |
| if [ ${GOOGLE} = "y" ] ; then | |
| apt-get -qq --no-install-recommends --yes install python3-qrcode libpam-google-authenticator qrencode | |
| # Generate a google auth config | |
| google-authenticator --time-based --disallow-reuse --label=${HOSTNAME} --qr-mode=UTF8 --rate-limit=3 --rate-time=30 --secret=/tmp/google_auth.txt --window-size=3 --force --quiet | |
| # Grab secret to build otpauth line below | |
| GOOGLE_SECRET=$(head -1 /tmp/google_auth.txt) | |
| # Have to tell whiptail library newt to use black/white text, otherwise QR code | |
| # is inverted and Authy can't read it | |
| # Set issuer to Ubuntu so we get a nice Ubuntu logo for the Authy secret | |
| export NEWT_COLORS='white,black' | |
| whiptail --title "Google Authenticator QR code and config" --msgbox "Config for ${USERNAME} is in /home/${USERNAME}/.google_authenticator\n\nBe sure to save the 5 emergency codes below\n\n$(cat /tmp/google_auth.txt)\n\nQR Code for use with OTP application (Authy etc.)\notpauth://totp/${HOSTNAME}.local:${USERNAME}?secret=${GOOGLE_SECRET}&Issuer=Ubuntu\n\n$(qrencode -m 3 -t UTF8 otpauth://totp/${HOSTNAME}.local:${USERNAME}?secret=${GOOGLE_SECRET}&issuer=Ubuntu)" 45 83 | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| export NEWT_COLORS="none" | |
| fi | |
| # SSH authorized keys from github for dropbear and ssh | |
| if [[ ! -v AUTHKEYS ]] ; then | |
| AUTHKEYS=$(whiptail --inputbox "Dropbear and ssh need authorized ssh pubkeys to allow access to the server. Please enter any github users to pull ssh pubkeys from. none means no keys to install\n\nDropbear is used for remote unlocking of disk encryption\n\n ssh -p 2222 root@<ip addr>" --title "SSH pubkeys for ssh and dropbear" 13 70 $(echo none) 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| (( RET )) && AUTHKEYS=none | |
| fi # Check for github user ssh keys in AUTHKEYS | |
| # If it's NOT a ZFS encryption setup, then clear out the ZFSENC_ROOT_OPTIONS variable | |
| if [ "${DISCENC}" != "ZFSENC" ] ; then | |
| ZFSENC_ROOT_OPTIONS="" | |
| ZFSENC_HOME_OPTIONS="" | |
| fi | |
| # Swap size - if HIBERNATE enabled then this will be an actual disk partition. | |
| # If DISCENC == LUKS then partition will be encrypted. If SIZE_SWAP is not | |
| # defined here, then will be calculated to accomodate memory size (plus fudge factor). | |
| if [[ ! -v SIZE_SWAP ]] ; then | |
| MEMTOTAL=$(cat /proc/meminfo | fgrep MemTotal | tr -s ' ' | cut -d' ' -f2) | |
| SIZE_SWAP=$(( (${MEMTOTAL} + 20480) / 1024 )) | |
| # We MUST have a swap partition of at least ram size if HIBERNATE is enabled | |
| # So don't even prompt the user for a size. Her own silly fault if it's | |
| # enabled but she doesn't want a swap partition | |
| if [ ${HIBERNATE} = "n" ] ; then | |
| SIZE_SWAP=$(whiptail --inputbox "If HIBERNATE enabled then this will be a disk partition otherwise it will be a regular ZFS dataset. If LUKS enabled then the partition will be encrypted.\nIf SWAP size not set here (left blank), then it will be calculated to accomodate memory size. Set to zero (0) to disable swap.\n\nSize of swap space in megabytes (default is calculated value)\nSet to zero (0) to disable swap" \ | |
| --title "SWAP size" 15 70 $(echo $SIZE_SWAP) 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| fi | |
| fi # Check for Swap size in ZFS-root.conf | |
| # Use zswap compressed page cache in front of swap ? https://wiki.archlinux.org/index.php/Zswap | |
| # Only used for swap partition (encrypted or not) | |
| USE_ZSWAP="\"zswap.enabled=1 zswap.compressor=lz4 zswap.max_pool_percent=25\"" | |
| # What suite is this script running under ? bionic or focal | |
| # Xenial does not support a couple of zfs feature flags, so have to | |
| # not use them when creating the pools, even if the target system | |
| # is bionic. Pool can be upgraded after booting into the target. | |
| SCRIPT_SUITE=$(lsb_release -cs) | |
| # Suite to install - bionic focal jammy | |
| if [[ ! -v SUITE ]] ; then | |
| SUITE=$(whiptail --title "Select Ubuntu distribtion" --radiolist "Choose distro" 11 50 5 \ | |
| jammy "22.04 jammy" ON \ | |
| focal "20.04 focal" OFF \ | |
| bionic "18.04 Bionic" OFF \ | |
| 3>&1 1>&2 2>&3) | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| fi # Check for Ubuntu suite to install | |
| # | |
| # TODO: Make use of SUITE_EXTRAS maybe | |
| # | |
| case ${SUITE} in | |
| jammy) | |
| SUITE_NUM="22.04" | |
| SUITE_EXTRAS="netplan.io expect" | |
| SUITE_BOOTSTRAP="wget,whois,rsync,gdisk,netplan.io,gpg-agent" | |
| # Install HWE packages - set to blank or to "-hwe-22.04" | |
| # Gets tacked on to various packages below | |
| [ "${HWE}" = "y" ] && HWE="-hwe-${SUITE_NUM}" || HWE= | |
| # Specific zpool features available in jammy | |
| # Depends on what suite this script is running under | |
| case ${SCRIPT_SUITE} in | |
| bionic | focal | jammy) | |
| SUITE_ROOT_POOL="-O dnodesize=auto" | |
| ;; | |
| xenial) | |
| SUITE_ROOT_POOL="" | |
| ;; | |
| esac | |
| ;; | |
| focal) | |
| SUITE_NUM="20.04" | |
| SUITE_EXTRAS="netplan.io expect" | |
| SUITE_BOOTSTRAP="wget,whois,rsync,gdisk,netplan.io" | |
| # Install HWE packages - set to blank or to "-hwe-20.04" | |
| # Gets tacked on to various packages below | |
| [ "${HWE}" = "y" ] && HWE="-hwe-${SUITE_NUM}" || HWE= | |
| # Specific zpool features available in focal | |
| # Depends on what suite this script is running under | |
| case ${SCRIPT_SUITE} in | |
| bionic | focal | jammy) | |
| SUITE_ROOT_POOL="-O dnodesize=auto" | |
| ;; | |
| xenial) | |
| SUITE_ROOT_POOL="" | |
| ;; | |
| esac | |
| ;; | |
| bionic) | |
| SUITE_NUM="18.04" | |
| SUITE_EXTRAS="netplan.io expect" | |
| SUITE_BOOTSTRAP="wget,whois,rsync,gdisk,netplan.io" | |
| # Install HWE packages - set to blank or to "-hwe-18.04" | |
| # Gets tacked on to various packages below | |
| [ "${HWE}" = "y" ] && HWE="-hwe-${SUITE_NUM}" || HWE= | |
| # Specific zpool features available in bionic | |
| # Depends on what suite this script is running under | |
| case ${SCRIPT_SUITE} in | |
| bionic | focal | jammy) | |
| SUITE_ROOT_POOL="-O dnodesize=legacy" | |
| ;; | |
| xenial) | |
| SUITE_ROOT_POOL="" | |
| ;; | |
| esac | |
| ;; | |
| # Default to focal 20.04 | |
| *) | |
| SUITE_NUM="20.04" | |
| SUITE_EXTRAS="netplan.io expect" | |
| SUITE_BOOTSTRAP="wget,whois,rsync,gdisk,netplan.io" | |
| # Install HWE packages - set to blank or to "-hwe-20.04" | |
| # Gets tacked on to various packages below | |
| [ "${HWE}" = "y" ] && HWE="-hwe-${SUITE_NUM}" || HWE= | |
| # Specific zpool features available in focal | |
| # Depends on what suite this script is running under | |
| case ${SCRIPT_SUITE} in | |
| bionic | focal) | |
| SUITE_ROOT_POOL="-O dnodesize=auto" | |
| ;; | |
| xenial) | |
| SUITE_ROOT_POOL="" | |
| ;; | |
| esac | |
| ;; | |
| esac | |
| box_height=$(( ${#zfsdisks[@]} + 24 )) | |
| whiptail --title "Summary of install options" --msgbox "These are the options we're about to install with :\n\n \ | |
| Proxy $([ ${PROXY} ] && echo ${PROXY} || echo None)\n \ | |
| $(echo $SUITE $SUITE_NUM) $([ ${HWE} ] && echo WITH || echo without) $(echo hwe kernel ${HWE})\n \ | |
| Disk $(for disk in $(seq 0 $(( ${#zfsdisks[@]}-1)) ) ; do \ | |
| if [ ${disk} -ne 0 ] ; then echo -n " " ; fi ; echo ${zfsdisks[${disk}]} ; done)\n \ | |
| Raid $([ ${RAIDLEVEL} ] && echo ${RAIDLEVEL} || echo vdevs)\n \ | |
| Hostname $(echo $HOSTNAME)\n \ | |
| Poolname $(echo $POOLNAME)\n \ | |
| User $(echo $USERNAME $UCOMMENT)\n\n \ | |
| DELAY = $(echo $DELAY) : Enable delay before importing zpool\n \ | |
| ZFS ver = $(echo $ZFSPPA) : Update to latest ZFS 2.1 via PPA\n \ | |
| GOOGLE = $(echo $GOOGLE) : Install google authenticator\n \ | |
| GNOME = $(echo $GNOME) : Install full Ubuntu Gnome desktop\n \ | |
| KDE = $(echo $KDE) : Install full Ubuntu KDE Plasma desktop\n \ | |
| SOF = $(echo $SOF) : Install Sound Open Firmware binaries\n \ | |
| HIBERNATE = $(echo $HIBERNATE) : Enable SWAP disk partition for hibernation\n \ | |
| DISCENC = $(echo $DISCENC) : Enable disk encryption (No, LUKS, ZFS)\n \ | |
| Swap size = $(echo $SIZE_SWAP)M $([ ${SIZE_SWAP} -eq 0 ] && echo ': DISABLED')\n" \ | |
| ${box_height} 70 | |
| RET=${?} | |
| [[ ${RET} = 1 ]] && exit 1 | |
| # Log everything we do | |
| rm -f /root/ZFS-setup.log | |
| exec > >(tee -a "/root/ZFS-setup.log") 2>&1 | |
| [ "$1" = "-d" ] && set -x | |
| # Pre-OK the zfs-dkms licenses notification | |
| cat > /tmp/selections <<-EOFPRE | |
| # zfs-dkms license notification | |
| zfs-dkms zfs-dkms/note-incompatible-licenses note | |
| EOFPRE | |
| cat /tmp/selections | debconf-set-selections | |
| # In case ZFS is already installed in this liveCD, check versions to see | |
| # if we need to update/upgrade | |
| # NOTE: Chances are that the kernel module is (eg) 0.8.x and the packages are 0.7.x | |
| # so we may as well just upgrade to latest by PPA. Which means building | |
| # the newest module, which can take a while. | |
| # Update ZFS if module mismatch, ZFS encryption selected or update-zfs selected | |
| # Check if ZFS currently installed in this livecd env | |
| ZFS_LIVECD= | |
| if [ -f /usr/sbin/zfs ] || [ -f /sbin/zfs ] ; then | |
| # Get currently installed version | |
| ZFS_INSTALLED=$(dpkg -s zfsutils-linux | fgrep Version | cut -d' ' -f2) | |
| modprobe zfs | |
| ZFS_MODULE=$(cat /sys/module/zfs/version) | |
| ZFS_LIVECD=y | |
| fi | |
| [ "$ZFS_LIVECD" = "y" ] && echo "ZFS installed with ${ZFS_INSTALLED}, module with ${ZFS_MODULE}" | |
| # Add ZFS ppa if requested | |
| if [ ${ZFSPPA} = "y" ] ; then | |
| apt-add-repository --yes --update ppa:jonathonf/zfs | |
| fi | |
| # NOW, install ZFS, perhaps from ppa above | |
| apt-get -qq --no-install-recommends --yes install libelf-dev zfs-zed zfsutils-linux zfs-initramfs | |
| # Logic for restarting ZFS | |
| # If livecd package version != currently running module, OR | |
| # If ppa requested | |
| # Then restart ZFS | |
| if [[ ("${ZFS_LIVECD}" = "y" && "${ZFS_INSTALLED}" != "${ZFS_MODULE}") || "${ZFSPPA}" = "y" ]] ; then | |
| echo "ZFS needs an update" | |
| systemctl stop zfs-zed | |
| modprobe -r zfs | |
| modprobe zfs | |
| systemctl start zfs-zed | |
| # ensure new system uses updated ZFS | |
| ZFSPPA="y" | |
| fi | |
| # Create an encryption key for non-root datasets (/home). The root dataset | |
| # is encrypted with the passphrase above, but other datasets use a key that | |
| # is stored in /etc/zfs/zroot.rawkey. This key isn't available unless the root | |
| # dataset is unlocked, so we're still secure. | |
| dd if=/dev/urandom of=/etc/zfs/zroot.rawkey bs=32 count=1 | |
| apt-get -qq --no-install-recommends --yes install openssh-server debootstrap gdisk zfs-initramfs dosfstools mdadm | |
| # Unmount any mdadm disks that might have been automounted | |
| # Stop all found mdadm arrays - again, just in case. Sheesh. | |
| find /dev -iname md* -type b -exec bash -c "umount {} > /dev/null 2>&1 ; mdadm --stop --force {} > /dev/null 2>&1 ; mdadm --remove {} > /dev/null 2>&1" \; | |
| for disk in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| zpool labelclear -f /dev/disk/by-id/${zfsdisks[${disk}]} | |
| # Wipe mdadm superblock from all partitions found, even if not md raid partition | |
| mdadm --zero-superblock --force /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP} > /dev/null 2>&1 | |
| wipefs --all --force /dev/disk/by-id/${zfsdisks[${disk}]} | |
| sgdisk --zap-all /dev/disk/by-id/${zfsdisks[${disk}]} | |
| sgdisk --clear /dev/disk/by-id/${zfsdisks[${disk}]} | |
| # Legacy (BIOS) booting | |
| sgdisk -a 1 /dev/disk/by-id/${zfsdisks[${disk}]} # Set sector alignment to 1MiB | |
| sgdisk -n ${PARTITION_BOOT}:1M:+1000M /dev/disk/by-id/${zfsdisks[${disk}]} # Create partition 1/BOOT 1M size | |
| sgdisk -A ${PARTITION_BOOT}:set:2 /dev/disk/by-id/${zfsdisks[${disk}]} # Turn legacy boot attribute on | |
| sgdisk -c ${PARTITION_BOOT}:"BOOT_EFI_${disk}" /dev/disk/by-id/${zfsdisks[${disk}]} # Set partition name to BOOT_EFI_n | |
| sgdisk -t ${PARTITION_BOOT}:EF00 /dev/disk/by-id/${zfsdisks[${disk}]} # Set partition type to EFI | |
| # | |
| # TODO: figure out partitions for both ZFS and LUKS encryption | |
| # both swap and main partitions | |
| # | |
| # For laptop hibernate need swap partition, encrypted or not | |
| if [ "${HIBERNATE}" = "y" ] ; then | |
| if [ ${DISCENC} != "NOENC" ] ; then | |
| # ZFS or LUKS Encrypted - should be partition type 8309 (Linux LUKS) | |
| sgdisk -n ${PARTITION_SWAP}:0:+${SIZE_SWAP}M -c ${PARTITION_SWAP}:"SWAP_${disk}" -t ${PARTITION_SWAP}:8309 /dev/disk/by-id/${zfsdisks[${disk}]} | |
| else | |
| sgdisk -n ${PARTITION_SWAP}:0:+${SIZE_SWAP}M -c ${PARTITION_SWAP}:"SWAP_${disk}" -t ${PARTITION_SWAP}:8200 /dev/disk/by-id/${zfsdisks[${disk}]} | |
| fi # DISCENC for ZFS or LUKS | |
| fi # HIBERNATE | |
| # Main data partition for root | |
| if [ ${DISCENC} = "LUKS" ] ; then | |
| # LUKS Encrypted - should be partition type 8309 (Linux LUKS) | |
| # wipefs --all --force /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_DATA} | |
| zpool labelclear -f /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_DATA} | |
| sgdisk -n ${PARTITION_DATA}:0:0 -c ${PARTITION_DATA}:"ZFS_${disk}" -t ${PARTITION_DATA}:8300 /dev/disk/by-id/${zfsdisks[${disk}]} | |
| apt-get -qq --no-install-recommends --yes install cryptsetup | |
| else | |
| # Unencrypted or ZFS encrypted | |
| sgdisk -n ${PARTITION_DATA}:0:0 -c ${PARTITION_DATA}:"ZFS_${disk}" -t ${PARTITION_DATA}:BF00 /dev/disk/by-id/${zfsdisks[${disk}]} | |
| fi # DISCENC for LUKS | |
| done | |
| # Refresh partition information | |
| partprobe | |
| # Have to wait a bit for the partitions to actually show up | |
| echo "Wait for partition info to settle out" | |
| sleep 5 | |
| # Build list of partitions to use for ... | |
| # Boot partition (mirror across all disks) | |
| PARTSBOOT= | |
| PARTSSWAP= | |
| # ZFS partitions to create zpool with | |
| ZPOOLDISK= | |
| for disk in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| PARTSSWAP="/dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP} ${PARTSSWAP}" | |
| PARTSBOOT="/dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_BOOT} ${PARTSBOOT}" | |
| if [ "${DISCENC}" = "LUKS" ]; then | |
| ZPOOLDISK="/dev/mapper/root_crypt${disk} ${ZPOOLDISK}" | |
| else | |
| ZPOOLDISK="/dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_DATA} ${ZPOOLDISK}" | |
| fi | |
| done | |
| # Create SWAP volume for HIBERNATE, encrypted maybe | |
| # Just using individual swap partitions - could use mdadm to mirror/raid | |
| # them up, but meh, why ? | |
| # NOTE: Need --disable-keyring so we can pull the derived key from the encrypted partition | |
| # otherwise it's in the kernel keyring | |
| if [ ${HIBERNATE} = "y" ] ; then | |
| # Hibernate, so we need a real swap partition(s) | |
| for disk in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| case ${DISCENC} in | |
| LUKS) | |
| echo "Encrypting swap partition ${disk} size ${SIZE_SWAP}M" | |
| echo ${PASSPHRASE} | cryptsetup luksFormat --type luks2 --disable-keyring -c aes-xts-plain64 -s 512 -h sha256 /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP} | |
| echo ${PASSPHRASE} | cryptsetup luksOpen --disable-keyring /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP} swap_crypt${disk} | |
| mkswap -f /dev/mapper/swap_crypt${disk} | |
| if [ ${disk} -eq 0 ] ; then | |
| # Get derived key to insert into other encrypted devices | |
| # To be more secure do this into a small ramdisk | |
| # swap must be opened 1st to enable resume from hibernation | |
| /lib/cryptsetup/scripts/decrypt_derived swap_crypt${disk} > /tmp/key | |
| fi | |
| # Add the derived key to all the other devices | |
| echo ${PASSPHRASE} | cryptsetup luksAddKey /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP} /tmp/key | |
| # Add the generated key from /etc/zfs/zroot.rawkey | |
| echo ${PASSPHRASE} | cryptsetup luksAddKey /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP} /etc/zfs/zroot.rawkey | |
| ;; | |
| ZFSENC) | |
| # ZFS encryption can just use a regular partition | |
| mkswap -f /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP} | |
| ;; | |
| NOENC) | |
| # Not LUKS, so just use a regular partition | |
| mkswap -f /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP} | |
| ;; | |
| esac | |
| done | |
| fi #HIBERNATE | |
| # Encrypt root volume maybe | |
| # NOTE: Need --disable-keyring so we can pull the derived key from the encrypted partition | |
| # otherwise it's in the kernel keyring | |
| if [ "${DISCENC}" = "LUKS" ] ; then | |
| for disk in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| # Encrypted LUKS root | |
| echo "Encrypting root ZFS ${disk}" | |
| echo ${PASSPHRASE} | cryptsetup luksFormat --type luks2 -c aes-xts-plain64 -s 512 -h sha256 /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_DATA} | |
| echo ${PASSPHRASE} | cryptsetup luksOpen /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_DATA} root_crypt${disk} | |
| # If no encrypted SWAP then use 1st root device as derived key | |
| # otherwise assume derived key was created above in "Create SWAP volume" | |
| if [ ${disk} -eq 0 ] && [ ${HIBERNATE} = "n" ] ; then | |
| # Get derived key to insert into other encrypted devices | |
| # To be more secure do this into a small ramdisk | |
| /lib/cryptsetup/scripts/decrypt_derived root_crypt${disk} > /tmp/key | |
| fi | |
| # Add the derived key to all the other devices | |
| echo ${PASSPHRASE} | cryptsetup luksAddKey /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_DATA} /tmp/key | |
| # Add the generated key from /etc/zfs/zroot.rawkey | |
| echo ${PASSPHRASE} | cryptsetup luksAddKey /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_DATA} /etc/zfs/zroot.rawkey | |
| done | |
| fi | |
| # COMPLETELY clear out build dir | |
| rm -rf ${ZFSBUILD} | |
| mkdir -p ${ZFSBUILD} | |
| # Create root pool | |
| case ${DISCENC} in | |
| LUKS) | |
| echo "Creating root pool ${POOLNAME}" | |
| zpool create -f -o ashift=12 -o autotrim=on ${SUITE_ROOT_POOL} \ | |
| -O acltype=posixacl -O canmount=off -O compression=lz4 \ | |
| -O atime=off \ | |
| -O normalization=formD -O relatime=on -O xattr=sa \ | |
| -O mountpoint=/ -R ${ZFSBUILD} \ | |
| ${POOLNAME} ${RAIDLEVEL} ${ZPOOLDISK} | |
| ;; | |
| # With ZFS encryption we don't encrypt the pool, we encrypt individual | |
| # datasets hierarchies | |
| NOENC|ZFSENC) | |
| # Unencrypted | |
| # Certain features must be disabled to boot | |
| # -o feature@project_quota=disabled \ | |
| # -o feature@spacemap_v2=disabled \ | |
| echo "Creating root pool ${POOLNAME}" | |
| zpool create -f -o ashift=12 -o autotrim=on ${SUITE_ROOT_POOL} \ | |
| -O acltype=posixacl -O canmount=off -O compression=lz4 \ | |
| -O atime=off \ | |
| -O normalization=formD -O relatime=on -O xattr=sa \ | |
| -O mountpoint=none -R ${ZFSBUILD} \ | |
| ${POOLNAME} ${RAIDLEVEL} ${ZPOOLDISK} | |
| ;; | |
| *) | |
| # Unknown option | |
| echo "Unknown option DISCENC = ${DISCENC}" | |
| exit 1 | |
| ;; | |
| esac | |
| # Main filesystem datasets | |
| echo "Creating main zfs datasets" | |
| # Container for root filesystems - possibly zfs native encrypted | |
| if [ ${DISCENC} = "ZFSENC" ] ; then | |
| echo "${PASSPHRASE}" | zfs create -o canmount=off -o mountpoint=none ${ZFSENC_ROOT_OPTIONS} ${POOLNAME}/ROOT | |
| else | |
| zfs create -o canmount=off -o mountpoint=none ${POOLNAME}/ROOT | |
| fi | |
| # Actual dataset for suite we are installing now | |
| zfs create -o canmount=noauto -o mountpoint=/ \ | |
| ${POOLNAME}/ROOT/${SUITE} | |
| zpool set bootfs=${POOLNAME}/ROOT/${SUITE} ${POOLNAME} | |
| zfs mount ${POOLNAME}/ROOT/${SUITE} | |
| if [ ${DISCENC} != "NOENC" ] ; then | |
| # Making sure we have the non-root key used for other datasets (/home) | |
| mkdir -p ${ZFSBUILD}/etc/zfs | |
| cp /etc/zfs/zroot.rawkey ${ZFSBUILD}/etc/zfs | |
| fi | |
| # zfs create pool/home and main user home dataset - possibly zfs native encrypted | |
| if [ ${DISCENC} = "ZFSENC" ] ; then | |
| echo "${PASSPHRASE}" | zfs create -o canmount=off -o mountpoint=none -o compression=lz4 -o atime=off ${ZFSENC_HOME_OPTIONS} ${POOLNAME}/home | |
| else | |
| zfs create -o canmount=off -o mountpoint=none -o compression=lz4 -o atime=off ${POOLNAME}/home | |
| fi | |
| zfs create -o canmount=on -o mountpoint=/home/${USERNAME} ${POOLNAME}/home/${USERNAME} | |
| zfs create -o canmount=on -o mountpoint=/root ${POOLNAME}/home/root | |
| # If no HIBERNATE partition (not laptop, no resume etc) then just create | |
| # a zvol for swap. Could not create this in the block above for swap because | |
| # the root pool didn't exist yet. | |
| if [ ${HIBERNATE} = "n" ] && [ ${SIZE_SWAP} -ne 0 ] ; then | |
| # No Hibernate, so just use a zfs volume for swap | |
| echo "Creating swap zfs dataset size ${SIZE_SWAP}M" | |
| # zfs create -V ${SIZE_SWAP}M -b $(getconf PAGESIZE) -o compression=zle \ | |
| zfs create -V ${SIZE_SWAP}M -o compression=zle \ | |
| -o logbias=throughput -o sync=always \ | |
| -o primarycache=metadata -o secondarycache=none \ | |
| -o com.sun:auto-snapshot=false ${POOLNAME}/swap | |
| fi #HIBERNATE | |
| # Show what we got before installing | |
| echo "---------- $(tput setaf 1)About to debootstrap into ${ZFSBUILD}$(tput sgr0) -----------" | |
| zfs list -t all | |
| df -h | |
| echo "---------- $(tput setaf 1)About to debootstrap into ${ZFSBUILD}$(tput sgr0) -----------" | |
| read -t 15 QUIT | |
| # Install basic system | |
| echo "debootstrap to build initial system" | |
| debootstrap --include=${SUITE_BOOTSTRAP} ${SUITE} ${ZFSBUILD} | |
| zfs set devices=off ${POOLNAME} | |
| # If this system will use Docker (which manages its own datasets & snapshots): | |
| zfs create -o com.sun:auto-snapshot=false -o mountpoint=/var/lib/docker ${POOLNAME}/docker | |
| # Set up boot partition (UEFI) potentially as mdadm mirror for multi-disk | |
| if [ ${#zfsdisks[@]} -eq 1 ] ; then | |
| BOOTDEVRAW=${PARTSBOOT} | |
| else | |
| apt-get -qq --no-install-recommends --yes install mdadm | |
| BOOTDEVRAW="/dev/md/BOOT_EFI" | |
| echo y | mdadm --create ${BOOTDEVRAW} --metadata=1.0 --force --level=mirror --raid-devices=${#zfsdisks[@]} --homehost=${HOSTNAME} --name=efi --assume-clean ${PARTSBOOT} | |
| fi | |
| mkfs.vfat -v -F 32 -s 1 -n "BOOT_EFI" ${BOOTDEVRAW} > /dev/null | |
| echo "UUID=$(blkid -s UUID -o value ${BOOTDEVRAW}) \ | |
| /boot/efi vfat nofail,x-systemd.device-timeout=1,x-systemd.after=zfs-mount.service 0 1" >> ${ZFSBUILD}/etc/fstab | |
| mkdir ${ZFSBUILD}/boot/efi | |
| echo ${HOSTNAME} > ${ZFSBUILD}/etc/hostname | |
| echo "127.0.1.1 ${HOSTNAME}" >> ${ZFSBUILD}/etc/hosts | |
| if [ ${PROXY} ]; then | |
| # This is for apt-get | |
| echo "Acquire::http::proxy \"${PROXY}\";" > ${ZFSBUILD}/etc/apt/apt.conf.d/03proxy | |
| fi # PROXY | |
| # Set up networking for netplan | |
| # renderer: networkd is for text mode only, use NetworkManager for gnome | |
| cat > ${ZFSBUILD}/etc/netplan/01_netcfg.yaml <<-EOF | |
| network: | |
| version: 2 | |
| renderer: networkd | |
| ethernets: | |
| alleths: | |
| match: | |
| name: e* | |
| dhcp4: true | |
| dhcp6: true | |
| optional: true | |
| EOF | |
| # Google Authenticator config - put to /root to be moved to /home/${USERNAME} in setup.sh | |
| if [ ${GOOGLE} = "y" ] ; then | |
| cp /tmp/google_auth.txt ${ZFSBUILD}/root | |
| fi | |
| # sources | |
| cat > ${ZFSBUILD}/etc/apt/sources.list <<-EOF | |
| deb http://archive.ubuntu.com/ubuntu ${SUITE} main multiverse | |
| deb-src http://archive.ubuntu.com/ubuntu ${SUITE} main multiverse | |
| deb http://security.ubuntu.com/ubuntu ${SUITE}-security main multiverse | |
| deb-src http://security.ubuntu.com/ubuntu ${SUITE}-security main multiverse | |
| deb http://archive.ubuntu.com/ubuntu ${SUITE}-updates main multiverse | |
| deb-src http://archive.ubuntu.com/ubuntu ${SUITE}-updates main multiverse | |
| EOF | |
| # We put universe into its own .list file so ansible apt_repository will match | |
| echo "deb http://archive.ubuntu.com/ubuntu ${SUITE} universe" > ${ZFSBUILD}/etc/apt/sources.list.d/ubuntu_universe.list | |
| echo "deb http://archive.ubuntu.com/ubuntu ${SUITE}-updates universe" >> ${ZFSBUILD}/etc/apt/sources.list.d/ubuntu_universe.list | |
| echo "deb http://security.ubuntu.com/ubuntu ${SUITE}-security universe" >> ${ZFSBUILD}/etc/apt/sources.list.d/ubuntu_universe.list | |
| echo "Creating Setup.sh in new system for chroot" | |
| cat > ${ZFSBUILD}/root/Setup.sh <<-EOF | |
| #!/bin/bash | |
| export BOOTDEVRAW=${BOOTDEVRAW} | |
| export DELAY=${DELAY} | |
| export SUITE=${SUITE} | |
| export POOLNAME=${POOLNAME} | |
| export PASSPHRASE=${PASSPHRASE} | |
| export USERNAME=${USERNAME} | |
| export UPASSWORD="${UPASSWORD}" | |
| export UCOMMENT="${UCOMMENT}" | |
| export DISCENC=${DISCENC} | |
| export AUTHKEYS=${AUTHKEYS} | |
| export ZFSPPA=${ZFSPPA} | |
| export GOOGLE=${GOOGLE} | |
| export SOF=${SOF} | |
| export PROXY=${PROXY} | |
| export HWE=${HWE} | |
| export GNOME=${GNOME} | |
| export KDE=${KDE} | |
| export HIBERNATE=${HIBERNATE} | |
| export SIZE_SWAP=${SIZE_SWAP} | |
| export PARTITION_BOOT=${PARTITION_BOOT} | |
| export PARTITION_SWAP=${PARTITION_SWAP} | |
| export PARTITION_DATA=${PARTITION_DATA} | |
| EOF | |
| for disk in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| echo "zfsdisks[${disk}]=${zfsdisks[${disk}]}" >> ${ZFSBUILD}/root/Setup.sh | |
| done | |
| # Add SSHPUBKEY and Host keys from ZFS-root.conf if defined | |
| [[ -v SSHPUBKEY ]] && echo "export SSHPUBKEY=\"${SSHPUBKEY}\"" >> ${ZFSBUILD}/root/Setup.sh | |
| [[ -v HOST_ECDSA_KEY_PUB ]] && echo "export HOST_ECDSA_KEY_PUB=\"${HOST_ECDSA_KEY_PUB}\"" >> ${ZFSBUILD}/root/Setup.sh | |
| [[ -v HOST_RSA_KEY_PUB ]] && echo "export HOST_RSA_KEY_PUB=\"${HOST_RSA_KEY_PUB}\"" >> ${ZFSBUILD}/root/Setup.sh | |
| # Ugly hack to get multiline variable into Setup.sh | |
| # Note using single quotes like this HOST_RSA_KEY='blahblah' surrounded by double quotes | |
| if [[ -v HOST_ECDSA_KEY ]] ; then | |
| echo -n "export HOST_ECDSA_KEY='" >> ${ZFSBUILD}/root/Setup.sh | |
| echo "${HOST_ECDSA_KEY}'" >> ${ZFSBUILD}/root/Setup.sh | |
| fi | |
| if [[ -v HOST_RSA_KEY ]] ; then | |
| echo -n "export HOST_RSA_KEY='" >> ${ZFSBUILD}/root/Setup.sh | |
| echo "${HOST_RSA_KEY}'" >> ${ZFSBUILD}/root/Setup.sh | |
| fi | |
| cat >> ${ZFSBUILD}/root/Setup.sh << '__EOF__' | |
| # Setup inside chroot | |
| ln -s /proc/self/mounts /etc/mtab | |
| apt-get -qq update | |
| # Preseed a few things | |
| cat > /tmp/selections << EOFPRE | |
| # zfs-dkms license notification | |
| zfs-dkms zfs-dkms/note-incompatible-licenses note | |
| # tzdata | |
| tzdata tzdata/Zones/US select Eastern | |
| tzdata tzdata/Zones/America select New_York | |
| tzdata tzdata/Areas select US | |
| console-setup console-setup/codeset47 select # Latin1 and Latin5 - western Europe and Turkic languages | |
| EOFPRE | |
| cat /tmp/selections | debconf-set-selections | |
| # Set up locale - must set langlocale variable (defaults to en_US) | |
| cat > /etc/default/locale << EOFLOCALE | |
| # LC_ALL=en_US.UTF-8 | |
| LANG=en_US.UTF-8 | |
| LANGUAGE=en_US:en | |
| EOFLOCALE | |
| cat > /etc/locale.gen << EOFLOCALEGEN | |
| en_US.UTF-8 UTF-8 | |
| EOFLOCALEGEN | |
| cat /etc/default/locale >> /etc/environment | |
| locale-gen --purge "en_US.UTF-8" | |
| dpkg-reconfigure -f noninteractive locales | |
| echo "America/New_York" > /etc/timezone | |
| ln -fs /usr/share/zoneinfo/US/Eastern /etc/localtime | |
| dpkg-reconfigure -f noninteractive tzdata | |
| # Make sure the kernel is installed and configured before ZFS | |
| apt-get -qq --yes --no-install-recommends install software-properties-common debconf-utils | |
| apt-get -qq --yes --no-install-recommends install linux-generic${HWE} linux-headers-generic${HWE} linux-image-generic${HWE} | |
| # | |
| # The "old" kernel links mess up zfsbootmenu generation, so remove them | |
| # Don't need them for an initial install anyway | |
| # | |
| rm /boot/vmlinuz.old /boot/initrd.img.old | |
| if [ ${ZFSPPA} = "y" ] ; then | |
| apt-add-repository --yes --update ppa:jonathonf/zfs | |
| fi | |
| apt-get -qq --no-install-recommends --yes install libelf-dev zfs-zed zfsutils-linux zfs-initramfs | |
| # jammy/22.04 moved zfs from /sbin/zfs to /usr/sbin/zfs | |
| ZFSLOCATION=$(which zfs) | |
| if [ "${DISCENC}" != "NOENC" ] ; then | |
| apt-get -qq --yes install cryptsetup keyutils | |
| fi | |
| # Ensure cachefile exists and zfs-import-cache is active | |
| # https://github.com/zfsonlinux/zfs/issues/8885 | |
| zpool set cachefile=/etc/zfs/zpool.cache ${POOLNAME} | |
| systemctl enable zfs.target zfs-import-cache zfs-mount zfs-import.target | |
| # Configure Dracut to load ZFS support | |
| # Need gcc to get libgcc_s.so for dracut_install to work | |
| apt-get --yes install dracut-core zfs-dracut bsdmainutils gcc | |
| cat << END > /etc/dracut.conf.d/100-zol.conf | |
| nofsck="yes" | |
| add_dracutmodules+=" zfs " | |
| omit_dracutmodules+=" btrfs " | |
| END | |
| # Fix zfs dracut - https://github.com/openzfs/zfs/issues/13398 | |
| # Need gcc to get libgcc_s.so for dracut_install to work | |
| sed -i '/\*\*/s/\*\*/*\/*/' /usr/lib/dracut/modules.d/90zfs/module-setup.sh | |
| # NOTE: Very important | |
| # Do NOT install initramfs-tools next to dracut | |
| # They wrestle and knock each other out | |
| apt-mark hold initramfs-tools | |
| # | |
| # Install rEFInd and syslinux | |
| # | |
| mount /boot/efi | |
| DEBIAN_FRONTEND=noninteractive apt-get --yes install refind | |
| refind-install | |
| mkdir -p /boot/efi/EFI/zfsbootmenu | |
| cat <<- END > /boot/efi/EFI/zfsbootmenu/refind_linux.conf | |
| "Boot to ZFSbootMenu" "zbm.prefer=${POOLNAME} zbm.import_policy=hostid zbm.set_hostid ro quiet loglevel=0" | |
| END | |
| # If we're running under legacy bios then rEFInd will be installed | |
| # to /boot/efi/EFI/BOOT - we want it in /boot/efi/EFI/refind | |
| [ -e /boot/efi/EFI/BOOT ] && mvrefind /boot/efi/EFI/BOOT /boot/efi/EFI/refind | |
| # Change timout for rEFInd from 20secs to 10secs | |
| sed -i 's,^timeout .*,timeout 10,' /boot/efi/EFI/refind/refind.conf | |
| # For multiple disks, looks like we need a startup.nsh | |
| if [ ${#zfsdisks[@]} -ge 1 ] ; then | |
| cat <<-'END' > /boot/efi/startup.nsh | |
| fs0: | |
| EFI\refind\refind_x64.efi | |
| END | |
| fi | |
| # Set up syslinux | |
| mkdir /boot/efi/syslinux | |
| apt-get install --yes syslinux syslinux-common extlinux dosfstools unzip | |
| cp -r /usr/lib/syslinux/modules/bios/* /boot/efi/syslinux | |
| # Install extlinux | |
| extlinux --install /boot/efi/syslinux | |
| # Install the syslinux GPTMBR data | |
| for DISK in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| dd bs=440 count=1 conv=notrunc if=/usr/lib/syslinux/mbr/gptmbr.bin of=/dev/disk/by-id/${zfsdisks[${DISK}]} | |
| done | |
| # | |
| # Install and configure ZFSBootMenu | |
| # | |
| DEBIAN_FRONTEND=noninteractive apt-get --yes install kexec-tools | |
| apt-get --yes install libconfig-inifiles-perl libsort-versions-perl libboolean-perl fzf mbuffer make curl | |
| # Assign command-line arguments to be used when booting the final kernel | |
| # For hibernation and resume to work we have to specify which device to resume from | |
| # Can only reume from ONE device though, so we default to the 1st disk swap partition | |
| # ZFS native encryption and non-encrypted can use SWAP partition directly | |
| # LUKS encryption uses the 1st swap_crypt0 device | |
| if [ ${HIBERNATE} = "y" ] ; then | |
| cat <<-END > /etc/dracut.conf.d/resume-from-hibernate.conf | |
| add_dracutmodules+=" resume " | |
| END | |
| if [ ${DISCENC} = "LUKS" ] ; then | |
| zfs set org.zfsbootmenu:commandline="rw quiet resume=/dev/mapper/swap_crypt0" ${POOLNAME}/ROOT | |
| zfs set org.zfsbootmenu:commandline="rw quiet resume=/dev/mapper/swap_crypt0" ${POOLNAME}/ROOT/${SUITE} | |
| cat <<-END > /etc/dracut.conf.d/resume-swap-uuid.conf | |
| # add_device+=" UUID=$(blkid -s UUID -o value /dev/disk/by-id/${zfsdisks[0]}-part${PARTITION_SWAP}) " | |
| add_device+=" /dev/mapper/swap_crypt0 " | |
| END | |
| else | |
| zfs set org.zfsbootmenu:commandline="rw quiet resume=UUID=$(blkid -s UUID -o value /dev/disk/by-id/${zfsdisks[0]}-part${PARTITION_SWAP})" ${POOLNAME}/ROOT | |
| zfs set org.zfsbootmenu:commandline="rw quiet resume=UUID=$(blkid -s UUID -o value /dev/disk/by-id/${zfsdisks[0]}-part${PARTITION_SWAP})" ${POOLNAME}/ROOT/${SUITE} | |
| fi | |
| else | |
| zfs set org.zfsbootmenu:commandline="rw quiet" ${POOLNAME}/ROOT | |
| zfs set org.zfsbootmenu:commandline="rw quiet" ${POOLNAME}/ROOT/${SUITE} | |
| fi | |
| zfs set canmount=noauto ${POOLNAME}/ROOT | |
| zfs set canmount=noauto ${POOLNAME}/ROOT/${SUITE} | |
| # | |
| # Install the ZFSBootMenu package directly | |
| # | |
| mkdir -p /boot/efi/EFI/zfsbootmenu | |
| ## Using the zfsbootmenu image directly | |
| # curl -L https://get.zfsbootmenu.org/zfsbootmenu.EFI -o /boot/efi/EFI/zfsbootmenu/zfsbootmenu.efi | |
| # curl -L https://github.com/zbm-dev/zfsbootmenu/releases/download/v1.11.0/zfsbootmenu-x86_64-v1.11.0.EFI -o /boot/efi/EFI/zfsbootmenu/zfsbootmenu.efi | |
| ## curl -L https://github.com/zbm-dev/zfsbootmenu/releases/download/v2.0.0/zfsbootmenu-release-x86_64-v2.0.0.tar.gz -o /tmp/zfsbootmenu.tar.gz | |
| ## tar xvzf /tmp/zfsbootmenu.tar.gz --strip-components=1 -C /boot/efi/EFI/zfsbootmenu | |
| ## cp /boot/efi/EFI/refind/icons/os_linux.png /boot/efi/EFI/zfsbootmenu/zfsbootmenu.png | |
| ## cat > /boot/efi/syslinux/syslinux.cfg << EOF | |
| ## UI menu.c32 | |
| ## PROMPT 0 | |
| ## | |
| ## MENU TITLE Boot Menu | |
| ## TIMEOUT 50 | |
| ## DEFAULT ZFSBootMenu-2.0.0_1 | |
| ## | |
| ## LABEL ZFSBootMenu-2.0.0_1 | |
| ## MENU LABEL ZFSBootMenu 2.0.0_1 | |
| ## KERNEL /EFI/zfsbootmenu/vmlinuz-bootmenu | |
| ## INITRD /EFI/zfsbootmenu/initramfs-bootmenu.img | |
| ## APPEND zbm.prefer=test zbm.import_policy=hostid zbm.set_hostid ro quiet loglevel=0 | |
| ## EOF | |
| # OR install the git repo and build locally | |
| rm -rf /tmp/zfsbootmenu && mkdir -p /tmp/zfsbootmenu | |
| cd /tmp/zfsbootmenu && curl -L https://github.com/zbm-dev/zfsbootmenu/tarball/master | tar xz --strip=1 && make install | |
| PERL_MM_USE_DEFAULT=1 cpan 'YAML::PP' | |
| # | |
| # Configure ZFSBootMenu | |
| # | |
| cat <<-END > /etc/zfsbootmenu/config.yaml | |
| Global: | |
| ManageImages: true | |
| BootMountPoint: /boot/efi | |
| DracutConfDir: /etc/zfsbootmenu/dracut.conf.d | |
| PreHooksDir: /etc/zfsbootmenu/generate-zbm.pre.d | |
| PostHooksDir: /etc/zfsbootmenu/generate-zbm.post.d | |
| InitCPIO: false | |
| InitCPIOConfig: /etc/zfsbootmenu/mkinitcpio.conf | |
| Components: | |
| ImageDir: /boot/efi/EFI/zfsbootmenu | |
| Versions: 3 | |
| Enabled: true | |
| syslinux: | |
| Config: /boot/efi/syslinux/syslinux.cfg | |
| Enabled: true | |
| EFI: | |
| ImageDir: /boot/efi/EFI/zfsbootmenu | |
| Versions: 2 | |
| Enabled: false | |
| Kernel: | |
| CommandLine: zbm.prefer=${POOLNAME} ro quiet loglevel=0 | |
| END | |
| # Create pre and post hooks dirs and syslinux snippets dir | |
| mkdir -p /etc/zfsbootmenu/generate-zbm.pre.d | |
| mkdir -p /etc/zfsbootmenu/generate-zbm.post.d | |
| mkdir /boot/efi/snippets | |
| # Copy syslinux-update.sh script and modify to suit | |
| # This makes generate-zbm create a valid syslinux.cfg that can | |
| # also include memtest86 snippet | |
| cp /tmp/zfsbootmenu/contrib/syslinux-update.sh /etc/zfsbootmenu/generate-zbm.post.d | |
| chmod +x /etc/zfsbootmenu/generate-zbm.post.d/syslinux-update.sh | |
| sed -i ' | |
| s/^SYSLINUX_ROOT.*/SYSLINUX_ROOT="\/boot\/efi"/ | |
| s/^KERNEL_PATH.*/KERNEL_PATH="EFI\/zfsbootmenu"/ | |
| s/^SYSLINUX_CONFD.*/SYSLINUX_CONFD="\/boot\/efi\/snippets"/ | |
| s/^cp .*/cp "\${SYSLINUX_CFG}" "\${SYSLINUX_ROOT}\/syslinux\/syslinux.cfg"/ | |
| ' /etc/zfsbootmenu/generate-zbm.post.d/syslinux-update.sh | |
| # Header for syslinux.cfg | |
| cat > /boot/efi/snippets/01_header << EOF | |
| UI menu.c32 | |
| PROMPT 0 | |
| MENU TITLE Boot Menu | |
| TIMEOUT 50 | |
| EOF | |
| # Download and install memtest86 | |
| # EFI version is latest v10, syslinux version is v4 | |
| rm -rf /tmp/memtest86 && mkdir -p /tmp/memtest86/mnt | |
| mkdir -p /boot/efi/EFI/tools/memtest86 | |
| curl -L https://www.memtest86.com/downloads/memtest86-usb.zip -o /tmp/memtest86/memtest86-usb.zip | |
| curl -L https://www.memtest86.com/downloads/memtest86-4.3.7-iso.zip -o /tmp/memtest86/memtest86-iso.zip | |
| # For EFI | |
| unzip -d /tmp/memtest86 /tmp/memtest86/memtest86-usb.zip memtest86-usb.img | |
| losetup -P /dev/loop33 /tmp/memtest86/memtest86-usb.img | |
| mount -o loop /dev/loop33p1 /tmp/memtest86/mnt | |
| cp /tmp/memtest86/mnt/EFI/BOOT/BOOTX64.efi /boot/efi/EFI/tools/memtest86/memtest86.efi | |
| umount /tmp/memtest86/mnt | |
| losetup -d /dev/loop33 | |
| # For Syslinux | |
| unzip -d /tmp/memtest86 /tmp/memtest86/memtest86-iso.zip Memtest86-4.3.7.iso | |
| mount -o loop /tmp/memtest86/Memtest86-4.3.7.iso /tmp/memtest86/mnt | |
| cp /tmp/memtest86/mnt/isolinux/memtest /boot/efi/EFI/tools/memtest86/memtest86.syslinux | |
| umount /tmp/memtest86/mnt | |
| # Syslinux entry for memtest86+ | |
| cat > /boot/efi/snippets/05_memtest86 << EOF | |
| LABEL Memtest86+ | |
| KERNEL /EFI/tools/memtest86/memtest86.syslinux | |
| EOF | |
| # | |
| # Set up LUKS unlocking | |
| # | |
| if [ "${DISCENC}" = "LUKS" ] ; then | |
| for DISK in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| echo "root_crypt${DISK} UUID=$(blkid -s UUID -o value /dev/disk/by-id/${zfsdisks[${DISK}]}-part${PARTITION_DATA}) /etc/zfs/zroot.rawkey discard,keyfile-timeout=10s" >> /etc/crypttab | |
| if [ ${HIBERNATE} = "y" ] ; then | |
| echo "swap_crypt${DISK} UUID=$(blkid -s UUID -o value /dev/disk/by-id/${zfsdisks[${DISK}]}-part${PARTITION_SWAP}) /etc/zfs/zroot.rawkey discard,keyfile-timeout=10s" >> /etc/crypttab | |
| fi | |
| done | |
| # | |
| # Early-stage script for zfsbootmenu - scan for ZFS_ partitions which | |
| # should be LUKS encrypted and try to open them all | |
| # | |
| cat > /usr/local/bin/zfsbootmenu_luks_unlock.sh <<-'EOF' | |
| #!/bin/bash | |
| sources=( | |
| /lib/profiling-lib.sh | |
| /etc/zfsbootmenu.conf | |
| /lib/zfsbootmenu-core.sh | |
| /lib/kmsg-log-lib.sh | |
| /etc/profile | |
| ) | |
| for src in "${sources[@]}"; do | |
| # shellcheck disable=SC1090 | |
| if ! source "${src}" > /dev/null 2>&1 ; then | |
| echo -e "\033[0;31mWARNING: ${src} was not sourced; unable to proceed\033[0m" | |
| exit 1 | |
| fi | |
| done | |
| unset src sources | |
| # We only unlock the ZFS partition(s) since the SWAP ones can use the | |
| # /etc/zfs/zroot.rawkey to unlock once main pool is open | |
| # ZFS_PARTS=(/dev/disk/by-partlabel/{SWAP_*,ZFS_*}) | |
| ZFS_PARTS=(/dev/disk/by-partlabel/ZFS_*) | |
| echo "Found these partitions for LUKS encryption" | |
| echo $ZFS_PARTS | |
| echo "" | |
| # Read passphrase for LUKS encryption into $REPLY | |
| read -s -p "LUKS encryption passphrase : " | |
| for idx in ${!ZFS_PARTS[@]} ; do | |
| # Grab just ZFS_0 or SWAP_0 | |
| test_luks=$(basename ${ZFS_PARTS[$idx]}) | |
| # luks is the full path to the disk partition | |
| luks=${ZFS_PARTS[$idx]} | |
| # Set $dm to root_crypt0 or swap_crypt0 depending on basename | |
| [ ${test_luks%_*} = "ZFS" ] && dm=root_crypt${idx} | |
| [ ${test_luks%_*} = "SWAP" ] && dm=swap_crypt${idx} | |
| if ! cryptsetup isLuks ${luks} >/dev/null 2>&1 ; then | |
| zwarn "LUKS device ${luks} missing LUKS partition header" | |
| exit | |
| fi | |
| if cryptsetup status "${dm}" >/dev/null 2>&1 ; then | |
| zinfo "${dm} already active, continuing" | |
| continue | |
| fi | |
| header="$( center_string "[CTRL-C] cancel luksOpen attempts" )" | |
| tput clear | |
| colorize red "${header}\n\n" | |
| # https://fossies.org/linux/cryptsetup/docs/Keyring.txt | |
| echo $REPLY | cryptsetup luksOpen ${luks} ${dm} | |
| ret=$? | |
| # successfully entered a passphrase | |
| if [ "${ret}" -eq 0 ] ; then | |
| zdebug "$( | |
| cryptsetup status "${dm}" | |
| )" | |
| continue | |
| fi | |
| # ctrl-c'd the process | |
| if [ "${ret}" -eq 1 ] ; then | |
| zdebug "canceled luksOpen attempts via SIGINT" | |
| exit | |
| fi | |
| # failed all password attempts | |
| if [ "${ret}" -eq 2 ] ; then | |
| if timed_prompt -e "emergency shell" \ | |
| -r "continue unlock attempts" \ | |
| -p "Continuing in %0.2d seconds" ; then | |
| continue | |
| else | |
| emergency_shell "unable to unlock LUKS partition" | |
| fi | |
| fi | |
| done | |
| EOF | |
| chmod +x /usr/local/bin/zfsbootmenu_luks_unlock.sh | |
| echo 'zfsbootmenu_early_setup+=" /usr/local/bin/zfsbootmenu_luks_unlock.sh "' > /etc/zfsbootmenu/dracut.conf.d/luks_zbm.conf | |
| fi #DISCENC | |
| # Using a swap partition ? | |
| if [ ${HIBERNATE} = "y" ] ; then | |
| # Hibernate is enabled - we HAVE to use a swap partition | |
| # Also, only works with a single disk (as in laptop) | |
| if [ "${DISCENC}" = "LUKS" ] ; then | |
| # LUKS encrypted | |
| for disk in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| echo "/dev/mapper/swap_crypt${disk} none swap discard,sw 0 0" >> /etc/fstab | |
| done | |
| else | |
| # Not LUKS encrypted | |
| for disk in $(seq 0 $(( ${#zfsdisks[@]} - 1))) ; do | |
| echo "UUID=$(blkid -s UUID -o value /dev/disk/by-id/${zfsdisks[${disk}]}-part${PARTITION_SWAP}) none swap discard,sw 0 0" >> /etc/fstab | |
| done | |
| fi # DISCENC for LUKS | |
| # If using zswap enable lz4 compresstion | |
| if [ "ZZ${USE_ZSWAP}" != "ZZ" ]; then | |
| echo "lz4" >> /etc/modules | |
| fi | |
| else | |
| # No swap partition - maybe using a zvol for swap | |
| echo "Enabling swap size ${SIZE_SWAP} on /dev/zvol/${POOLNAME}/swap" | |
| mkswap -f /dev/zvol/${POOLNAME}/swap | |
| if [ ${SIZE_SWAP} -ne 0 ] ; then | |
| echo "/dev/zvol/${POOLNAME}/swap none swap discard,sw 0 0" >> /etc/fstab | |
| fi | |
| fi # HIBERNATE | |
| # | |
| # Potentially add delay before importing root pool in initramfs | |
| # | |
| if [ ${DELAY} = "y" ] ; then | |
| echo "On systems with lots of disks, enumerating them can sometimes take a long" | |
| echo "time, which means the root disk(s) may not have been enumerated before" | |
| echo "ZFS tries to import the root pool. That drops you to an initramfs prompt" | |
| echo "where you have to 'zfs import -N <root_pool> ; exit'" | |
| echo "So we set a delay in the initramfs to wait 25s before importing" | |
| echo "In /etc/default/zfs set ZFS_INITRD_POST_MODPROBE_SLEEP=10" | |
| echo "In /etc/default/zfs set ZFS_INITRD_PRE_MOUNTROOT_SLEEP=10" | |
| # First ensure the lines are there ... then ensure they have the right value | |
| grep -qxF 'ZFS_INITRD_POST_MODPROBE_SLEEP' /etc/default/zfs || echo "ZFS_INITRD_POST_MODPROBE_SLEEP='10'" >> /etc/default/zfs | |
| grep -qxF 'ZFS_INITRD_PRE_MOUNTROOT_SLEEP' /etc/default/zfs || echo "ZFS_INITRD_PRE_MOUNTROOT_SLEEP='10'" >> /etc/default/zfs | |
| sed -i "s/ZFS_INITRD_POST_MODPROBE_SLEEP=.*/ZFS_INITRD_POST_MODPROBE_SLEEP='10'/" /etc/default/zfs | |
| sed -i "s/ZFS_INITRD_PRE_MOUNTROOT_SLEEP=.*/ZFS_INITRD_PRE_MOUNTROOT_SLEEP='10'10/" /etc/default/zfs | |
| fi | |
| echo "-------- installing basic packages ------------------------------------------" | |
| # | |
| # Install basic packages | |
| # | |
| apt-get -qq --no-install-recommends --yes install expect most vim-nox rsync whois gdisk \ | |
| openssh-server avahi-daemon libnss-mdns | |
| # | |
| # Copy Avahi SSH service file into place | |
| # | |
| cp /usr/share/doc/avahi-daemon/examples/ssh.service /etc/avahi/services | |
| # For ZFSENC we need to set up a script and systemd unit to load the keyfile | |
| if [ ${DISCENC} = "ZFSENC" ] ; then | |
| cat > /usr/local/bin/zfs-multi-mount.sh <<-'EOF' | |
| #!/usr/bin/env bash | |
| PATH=/usr/bin:/sbin:/bin | |
| help() { | |
| echo "Usage: $(basename "$0") [OPTION]... [SOURCE_POOL/DATASET]..." | |
| echo | |
| echo " -s, --systemd use when within systemd context" | |
| echo " -n, --no-mount only load keys, do not mount datasets" | |
| echo " -h, --help show this help" | |
| exit 0 | |
| } | |
| for arg in "$@"; do | |
| case $arg in | |
| -s | --systemd) | |
| systemd=1 | |
| shift | |
| ;; | |
| -n | --no-mount) | |
| no_mount=1 | |
| shift | |
| ;; | |
| -h | --help) help ;; | |
| -?*) | |
| die "Invalid option '$1' Try '$(basename "$0") --help' for more information." ;; | |
| esac | |
| done | |
| datasets=("$@") | |
| [ ${#datasets[@]} -eq 0 ] && mapfile -t datasets < <(zfs list -H -o name) | |
| attempt=0 | |
| attempt_limit=3 | |
| function ask_password { | |
| if [ -v systemd ]; then | |
| key=$(systemd-ask-password "Enter $dataset passphrase:" --no-tty) # While booting. | |
| else | |
| read -srp "Enter $dataset passphrase: " key ; echo # Other places. | |
| fi | |
| } | |
| function load_key { | |
| ! zfs list -H -o name | grep -qx "$dataset" && echo "ERROR: Dataset '$dataset' does not exist." && return 1 | |
| [[ $attempt == "$attempt_limit" ]] && echo "No more attempts left." && exit 1 | |
| keystatus=$(zfs get keystatus "$1" -H -o value) | |
| echo "Testing $dataset status $keystatus" | |
| [[ $keystatus != "unavailable" ]] && return 0 | |
| # Get the keylocation | |
| key=$(zfs get keylocation "$1" -H -o value) | |
| if [ $key != "prompt" ] ; then | |
| if zfs load-key "$1" ; then | |
| return 0 | |
| else | |
| echo "Keyfile location invalid" | |
| exit 1 | |
| fi | |
| fi | |
| if [ ! -v key ]; then | |
| ((attempt++)) | |
| ask_password | |
| fi | |
| if ! echo "$key" | zfs load-key "$1"; then | |
| unset key | |
| load_key "$1" | |
| fi | |
| attempt=0 | |
| return 0 | |
| } | |
| for dataset in "${datasets[@]}"; do | |
| ! load_key "$dataset" && exit 1 | |
| # Mounting as non-root user on Linux is not possible, | |
| # see https://github.com/openzfs/zfs/issues/10648. | |
| [ ! -v no_mount ] && sudo zfs mount "$dataset" && echo "Dataset '$dataset' has been mounted." | |
| done | |
| unset key | |
| exit 0 | |
| EOF | |
| chmod 755 /usr/local/bin/zfs-multi-mount.sh | |
| cat > /etc/systemd/system/zfs-load-key.service << 'EOF' | |
| [Unit] | |
| Description=Import keys for all datasets | |
| DefaultDependencies=no | |
| Before=zfs-mount.service | |
| Before=systemd-user-sessions.service | |
| After=zfs-import.target | |
| OnFailure=emergency.target | |
| [Service] | |
| Type=oneshot | |
| RemainAfterExit=yes | |
| ExecStart=zfs-multi-mount.sh --systemd --no-mount | |
| [Install] | |
| WantedBy=zfs-mount.service | |
| EOF | |
| systemctl enable zfs-load-key.service | |
| fi # if ZFSENC | |
| # Set hostkeys if defined via ZFS-root.conf | |
| if [[ -v HOST_ECDSA_KEY ]] ; then | |
| echo "${HOST_ECDSA_KEY}" > /etc/ssh/ssh_host_ecdsa_key | |
| echo "${HOST_ECDSA_KEY_PUB}" > /etc/ssh/ssh_host_ecdsa_key.pub | |
| chmod 600 /etc/ssh/ssh_host_ecdsa_key | |
| chmod 644 /etc/ssh/ssh_host_ecdsa_key.pub | |
| fi | |
| if [[ -v HOST_RSA_KEY ]] ; then | |
| echo "${HOST_RSA_KEY}" > /etc/ssh/ssh_host_rsa_key | |
| echo "${HOST_RSA_KEY_PUB}" > /etc/ssh/ssh_host_rsa_key.pub | |
| chmod 600 /etc/ssh/ssh_host_rsa_key | |
| chmod 644 /etc/ssh/ssh_host_rsa_key.pub | |
| fi | |
| # Setup system groups | |
| addgroup --system lpadmin | |
| addgroup --system sambashare | |
| apt-get -qq --yes dist-upgrade | |
| # Install acpi support | |
| if [ -d /proc/acpi ] ; then | |
| apt-get -qq --yes install acpi acpid | |
| service acpid stop | |
| fi # acpi | |
| # Nicer PS1 prompt | |
| cat >> /etc/bash.bashrc << EOF | |
| PS1="${debian_chroot:+($debian_chroot)}\[\$(tput setaf 2)\]\u@\[\$(tput bold)\]\[\$(tput setaf 5)\]\h\[\$(tput sgr0)\]\[\$(tput setaf 7)\]:\[\$(tput bold)\]\[\$(tput setaf 4)\]\w\[\$(tput setaf 7)\]\\$ \[\$(tput sgr0)\]" | |
| # https://unix.stackexchange.com/questions/99325/automatically-save-bash-command-history-in-screen-session | |
| PROMPT_COMMAND="history -a; history -c; history -r; \${PROMPT_COMMAND}" | |
| EOF | |
| cat >> /etc/skel/.bashrc <<-EOF | |
| PS1="${debian_chroot:+($debian_chroot)}\[\$(tput setaf 2)\]\u@\[\$(tput bold)\]\[\$(tput setaf 5)\]\h\[\$(tput sgr0)\]\[\$(tput setaf 7)\]:\[\$(tput bold)\]\[\$(tput setaf 4)\]\w\[\$(tput setaf 7)\]\\$ \[\$(tput sgr0)\]" | |
| # https://unix.stackexchange.com/questions/99325/automatically-save-bash-command-history-in-screen-session | |
| PROMPT_COMMAND="history -a; history -c; history -r; \${PROMPT_COMMAND}" | |
| EOF | |
| cat >> /etc/skel/.bash_aliases <<-EOF | |
| alias ls='ls --color=auto' | |
| alias l='ls -la' | |
| alias lt='ls -lat | head -25' | |
| EOF | |
| cp /etc/skel/.bash_aliases /root | |
| cat >> /root/.bashrc <<-"EOF" | |
| # PS1='\[\033[01;37m\]\[\033[01;41m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]$ ' | |
| PS1='\[\033[01;37m\]\[\033[01;41m\]\u@\[\033[00m\]\[$(tput bold)\]\[$(tput setaf 5)\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]$ ' | |
| # https://unix.stackexchange.com/questions/99325/automatically-save-bash-command-history-in-screen-session | |
| PROMPT_COMMAND="history -a; history -c; history -r; ${PROMPT_COMMAND}" | |
| HISTSIZE=5000 | |
| export LC_ALL=en_US.UTF-8 | |
| export LANG=en_US.UTF-8 | |
| export LANGUAGE=en_US.UTF-8 | |
| EOF | |
| # Create user | |
| useradd -c "${UCOMMENT}" -p $(echo "${UPASSWORD}" | mkpasswd -m sha-512 --stdin) -M --home-dir /home/${USERNAME} --user-group --groups adm,cdrom,dip,lpadmin,plugdev,sambashare,sudo --shell /bin/bash ${USERNAME} > /dev/null 2>&1 | |
| # Since /etc/skel/* files aren't copied, have to do it manually | |
| rsync -a /etc/skel/ /home/${USERNAME} | |
| mkdir /home/${USERNAME}/.ssh | |
| chmod 700 /home/${USERNAME}/.ssh | |
| if [ "${AUTHKEYS}" != "none" ] ; then | |
| for SSHKEY in ${AUTHKEYS} ; do | |
| FETCHKEY=$(wget --quiet -O- https://github.com/${SSHKEY}.keys) | |
| if [ ${#FETCHKEY} -ne 0 ] ; then | |
| echo "####### Github ${SSHKEY} key #######" >> /home/${USERNAME}/.ssh/authorized_keys | |
| echo "${FETCHKEY}" >> /home/${USERNAME}/.ssh/authorized_keys | |
| echo "#" >> /home/${USERNAME}/.ssh/authorized_keys | |
| fi | |
| done | |
| fi | |
| if [[ -v SSHPUBKEY ]] ; then | |
| echo "####### ZFS-root.conf configured key #######" >> /home/${USERNAME}/.ssh/authorized_keys | |
| echo "${SSHPUBKEY}" >> /home/${USERNAME}/.ssh/authorized_keys | |
| fi | |
| chown -R ${USERNAME}.${USERNAME} /home/${USERNAME} | |
| # | |
| # Set up Dropbear - after user is created with .ssh/authorized_keys | |
| # so those keys can be used in the initramfs | |
| # | |
| if [ "${DISCENC}" != "NOENC" ] ; then | |
| echo "------------------------------------------------------------" | |
| echo " Installing dropbear for remote unlocking" | |
| echo "------------------------------------------------------------" | |
| apt-get install --yes dracut-network dropbear-bin | |
| rm -rf /tmp/dracut-crypt-ssh && mkdir -p /tmp/dracut-crypt-ssh | |
| cd /tmp/dracut-crypt-ssh && curl -L https://github.com/dracut-crypt-ssh/dracut-crypt-ssh/tarball/master | tar xz --strip=1 | |
| ##comment out references to /helper/ folder from module-setup.sh | |
| sed -i '/inst \"\$moddir/s/^\(.*\)$/#&/' /tmp/dracut-crypt-ssh/modules/60crypt-ssh/module-setup.sh | |
| cp -ri /tmp/dracut-crypt-ssh/modules/60crypt-ssh /usr/lib/dracut/modules.d | |
| mkdir -p /etc/cmdline.d | |
| echo 'ip=dhcp rd.neednet=1' > /etc/cmdline.d/dracut-network.conf | |
| echo 'add_dracutmodules+=" crypt-ssh "' >> /etc/zfsbootmenu/dracut.conf.d/dropbear.conf | |
| echo 'install_items+=" /etc/cmdline.d/dracut-network.conf "' >> /etc/zfsbootmenu/dracut.conf.d/dropbear.conf | |
| # Have dracut use main user authorized_keys for access | |
| echo "dropbear_acl=/home/${USERNAME}/.ssh/authorized_keys" >> /etc/zfsbootmenu/dracut.conf.d/dropbear.conf | |
| echo ${PASSPHRASE} > /etc/zfs/zroot.key | |
| chmod 000 /etc/zfs/zroot.key | |
| echo 'install_items+=" /etc/zfs/zroot.key "' >> /etc/dracut.conf.d/zfskey.conf | |
| fi | |
| # For ZFS encryption point to the /etc/zfs/zroot.key in the initramfs | |
| if [ "${DISCENC}" = "ZFSENC" ] ; then | |
| zfs change-key -o keylocation=file:///etc/zfs/zroot.key -o keyformat=passphrase ${POOLNAME}/ROOT | |
| fi | |
| # For LUKS point to the larger 32-byte (zfs enc compatible) /etc/zfs/zroot.rawkey in the initramfs | |
| if [ "${DISCENC}" = "LUKS" ] ; then | |
| echo 'install_items+=" /etc/zfs/zroot.rawkey "' >> /etc/dracut.conf.d/zfskey.conf | |
| fi | |
| dracut -v -f --regenerate-all | |
| # generate-zbm only there if we built from scratch, not using downloaded image | |
| [ -e /usr/bin/generate-zbm ] && generate-zbm --debug | |
| # Allow read-only zfs commands with no sudo password | |
| cat /etc/sudoers.d/zfs | sed -e 's/#//' > /etc/sudoers.d/zfsALLOW | |
| # Install main ubuntu gnome desktop, plus maybe HWE packages | |
| if [ "${GNOME}" = "y" ] ; then | |
| # NOTE: 18.04 has an xserver-xorg-hwe-18.04 package, 20.04 does NOT | |
| case ${SUITE} in | |
| focal | jammy) | |
| apt-get -qq --yes install ubuntu-desktop | |
| ;; | |
| bionic) | |
| apt-get -qq --yes install ubuntu-desktop xserver-xorg${HWE} | |
| ;; | |
| *) | |
| # Default to not specifying hwe xorg just in case | |
| apt-get -qq --yes install ubuntu-desktop | |
| ;; | |
| esac | |
| fi # GNOME | |
| # Install main ubuntu kde desktop | |
| if [ "${KDE}" = "y" ] ; then | |
| apt-get -qq --yes install kde-full | |
| fi # KDE | |
| if [ "${GNOME}" = "y" ] || [ "${KDE}" = "y" ] ; then | |
| # Ensure networking is handled by NetworkManager | |
| sed -i 's/networkd/NetworkManager/' /etc/netplan/01_netcfg.yaml | |
| # NOTE: Using <<-EOF so it wills strip leading TAB chars | |
| # MUST be TAB chars, not spaces | |
| cat > /etc/NetworkManager/conf.d/10-globally-managed-devices.conf <<-EOF | |
| [keyfile] | |
| unmanaged-devices=*,except:type:wifi,except:type:wwan,except:type:ethernet | |
| EOF | |
| fi # GNOME KDE | |
| # Enable hibernate in upower and logind if desktop is installed | |
| if [ -d /etc/polkit-1/localauthority/50-local.d ] ; then | |
| cat > /etc/polkit-1/localauthority/50-local.d/com.ubuntu.enable-hibernate.pkla <<-EOF | |
| [Re-enable hibernate by default in upower] | |
| Identity=unix-user:* | |
| Action=org.freedesktop.upower.hibernate | |
| ResultActive=yes | |
| [Re-enable hibernate by default in logind] | |
| Identity=unix-user:* | |
| Action=org.freedesktop.login1.hibernate;org.freedesktop.login1.handle-hibernate-key;org.freedesktop.login1;org.freedesktop.login1.hibernate-multiple-sessions;org.freedesktop.login1.hibernate-ignore-inhibit | |
| ResultActive=yes | |
| EOF | |
| fi # Hibernate | |
| # Install Sound Open Firmware binaries if requested | |
| if [ "${SOF}" = "y" ]; then | |
| # Ensure we have the tools we need | |
| apt-get -qq --yes install rsync git | |
| git clone https://github.com/thesofproject/sof-bin.git /usr/local/share/sof-project | |
| cd /usr/local/share/sof-project | |
| LATEST=$(ls -dC1 v* | tail -1) | |
| LATESTBASE=$(basename $LATEST .x) | |
| LATESTFILE=$(ls -C1 ${LATEST}/${LATESTBASE}-rc* | tail -1) | |
| ./install.sh $LATESTFILE | |
| fi # Sound Open Firmware | |
| # Configure google authenticator if we have a config | |
| if [ "${GOOGLE}" = "y" ]; then | |
| apt-get -qq --no-install-recommends --yes install python3-qrcode qrencode libpam-google-authenticator | |
| cp /root/google_auth.txt /home/${USERNAME}/.google_authenticator | |
| chmod 400 /home/${USERNAME}/.google_authenticator | |
| chown ${USERNAME}.${USERNAME} /home/${USERNAME}/.google_authenticator | |
| # Set pam to use google authenticator for ssh | |
| echo "auth required pam_google_authenticator.so" >> /etc/pam.d/sshd | |
| sed -i "s/^ChallengeResponseAuthentication.*/ChallengeResponseAuthentication yes/" /etc/ssh/sshd_config | |
| # Enable this to force use of token always, even with SSH key | |
| # sed -i "s/.*PasswordAuthentication.*/PasswordAuthentication no/" /etc/ssh/sshd_config | |
| fi # GOOGLE_AUTH | |
| # Add IP address(es) to main tty issue | |
| ls -1 /sys/class/net | egrep -v "lo|vir|docker" | xargs -I {} echo " {} : \4{{}}" >> /etc/issue | |
| echo "" >> /etc/issue | |
| # Set apt/dpkg to automagically snap the system datasets on install/remove | |
| cat > /etc/apt/apt.conf.d/30pre-snap <<-EOF | |
| # Snapshot main datasets before installing or removing packages | |
| # We use a DATE variable to ensure all snaps have SAME date | |
| Dpkg::Pre-Invoke { "export DATE=\$(/usr/bin/date +%F-%H%M%S) ; ${ZFSLOCATION} snap \$(${ZFSLOCATION} list -o name | /usr/bin/grep -E 'ROOT/.*$' | sort | head -1)@apt_\${DATE}"; }; | |
| EOF | |
| zfs snapshot ${POOLNAME}/ROOT/${SUITE}@base_install | |
| umount /boot/efi | |
| # End of Setup.sh | |
| __EOF__ | |
| chmod +x ${ZFSBUILD}/root/Setup.sh | |
| # Bind mount virtual filesystem, create Setup.sh, then chroot | |
| mount -t proc /proc ${ZFSBUILD}/proc | |
| mount -t sysfs sys ${ZFSBUILD}/sys | |
| mount -B /dev ${ZFSBUILD}/dev | |
| mount -t devpts pts ${ZFSBUILD}/dev/pts | |
| # chroot and set up system | |
| # chroot ${ZFSBUILD} /bin/bash --login -c /root/Setup.sh | |
| unshare --mount --fork chroot ${ZFSBUILD} /bin/bash --login -c /root/Setup.sh | |
| # Remove any lingering crash reports | |
| rm -f ${ZFSBUILD}/var/crash/* | |
| umount -n ${ZFSBUILD}/{dev/pts,dev,sys,proc} | |
| # Copy setup log | |
| cp /root/ZFS-setup.log ${ZFSBUILD}/home/${USERNAME} | |
| # umount to be ready for export | |
| zfs umount -a | |
| # Back in livecd - unmount filesystems we may have missed | |
| # Have to escape any / in path | |
| ZFSBUILD_C=$(echo ${ZFSBUILD} | sed -e 's!/!\\/!'g) | |
| # mount | grep -v zfs | tac | awk '/\/mnt/ {print \$3}' | xargs -i{} umount -lf {} | |
| mount | grep -v zfs | tac | awk '/${ZFSBUILD_C}/ {print $3}' | xargs -i{} umount -lf {} | |
| zpool export ${POOLNAME} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment