diff --git a/install-zfs.sh b/install-zfs.sh index b14248f..29bd464 100755 --- a/install-zfs.sh +++ b/install-zfs.sh @@ -43,6 +43,8 @@ v_suitable_disks=() # (/dev/by-id/disk_id, ...); scope: find_suitable_d # Note that Linux Mint is "Linuxmint" from v20 onwards. This actually helps, since some operations are # specific to it. +c_hotswap_file=$PWD/install-zfs.hotswap.sh # see hotswap() for an explanation. + c_bpool_name=bpool c_ppa=ppa:jonathonf/zfs c_efi_system_partition_size=512 # megabytes @@ -107,49 +109,74 @@ c_udevadm_settle_timeout=10 # seconds # HELPER FUNCTIONS ############################################################# -# Chooses a function and invokes it depending on the O/S distribution. +# Invoke a function, with a primitive dynamic dispatch based on the distribution. # -# Example: +# Format: `invoke "function" [--optional]`. +# +# A target function must exist, otherwise a error is raised, unless `--optional` is specified. +# `--optional` is useful when a step is specific to a single distribution, e.g. Debian's root password. +# +# Examples: # # $ function install_jail_zfs_packages { :; } # $ function install_jail_zfs_packages_Debian { :; } # $ distro_dependent_invoke "install_jail_zfs_packages" # -# If the distribution is `Debian`, the second will be invoked, otherwise, the -# first. -# -# If the function is invoked with `--noforce` as second parameter, and there is -# no matching function: +# If the distribution is `Debian`, the second will be invoked, otherwise, the first. # # $ function update_zed_cache_Ubuntu { :; } -# $ distro_dependent_invoke "install_jail_zfs_packages" --noforce +# $ distro_dependent_invoke "update_zed_cache" --optional # -# then nothing happens. Without `--noforce`, this invocation will cause an -# error. +# If the distribution is `Debian`, nothing will happen. # -function distro_dependent_invoke { - local distro_specific_fx_name="$1_$v_linux_distribution" +# $ function update_zed_cache_Ubuntu { :; } +# $ distro_dependent_invoke "update_zed_cache" +# +# If the distribution is `Debian`, an error will be raised. +# +function invoke { + local base_fx_name=$1 + local distro_specific_fx_name=$1_$v_linux_distribution + local invoke_option=${2:-} + + if [[ ! $invoke_option =~ ^(|--optional)$ ]]; then + >&2 echo "Invalid invoke() option: $invoke_option" + exit 1 + fi + + hot_swap_script + + # Invoke it regardless when it's not optional. if declare -f "$distro_specific_fx_name" > /dev/null; then + print_step_info_header "$distro_specific_fx_name" + "$distro_specific_fx_name" - else - if ! declare -f "$1" > /dev/null && [[ "${2:-}" == "--noforce" ]]; then - : # do nothing - else - "$1" - fi + elif declare -f "$base_fx_name" > /dev/null || [[ ! $invoke_option == "--optional" ]]; then + print_step_info_header "$base_fx_name" + + "$base_fx_name" + fi +} + +# Tee-hee-hee!! +# +# This is extremely useful for debugging long procedures. Since bash scripts can't be modified while +# running, this allows the dev to create a snapshot, and if the script fails after that, resume and +# add the hotswap script, so that the new code will be loaded automatically. +# +function hot_swap_script { + if [[ -f $c_hotswap_file ]]; then + source "$c_hotswap_file" fi } -# shellcheck disable=SC2120 # allow parameters passing even if no calls pass any function print_step_info_header { + local function_name=$1 + echo -n " ############################################################################### -# ${FUNCNAME[1]}" - - [[ "${1:-}" != "" ]] && echo -n " $1" || true - - echo " +# $function_name ############################################################################### " } @@ -231,8 +258,6 @@ Root pool default create options: '"${c_default_rpool_create_options[*]/#-/$'\n' } function activate_debug { - print_step_info_header - mkdir -p "$c_log_dir" exec 5> "$c_install_log" @@ -251,8 +276,6 @@ function set_distribution_data { } function store_os_distro_information { - print_step_info_header - lsb_release --all > "$c_os_information_log" # Madness, in order not to force the user to invoke "sudo -E". @@ -276,8 +299,6 @@ function store_running_processes { } function check_prerequisites { - print_step_info_header - local distro_version_regex=\\b${v_linux_version//./\\.}\\b if [[ ! -d /sys/firmware/efi ]]; then @@ -308,8 +329,6 @@ function check_prerequisites { } function display_intro_banner { - print_step_info_header - local dialog_message='Hello! This script will prepare the ZFS pools on the system, install Ubuntu, and configure the boot. @@ -341,8 +360,6 @@ In such cases, the module building may fail abruptly, either without visible err } function save_disks_log { - print_step_info_header - # shellcheck disable=SC2012 # `ls` may clean the output, but in this case, it doesn't matter ls -l /dev/disk/by-id | tail -n +2 | perl -lane 'print "@F[8..10]"' > "$c_disks_log" @@ -360,8 +377,6 @@ LOG } function find_suitable_disks { - print_step_info_header - # In some freaky cases, `/dev/disk/by-id` is not up to date, so we refresh. One case is after # starting a VirtualBox VM that is a full clone of a suspended VM with snapshots. # @@ -426,8 +441,6 @@ If you think this is a bug, please open an issue on https://github.com/saveriomi # install_host_packages() and install_host_packages_UbuntuServer(). # function set_zfs_ppa_requirement { - print_step_info_header - apt update local zfs_package_version @@ -450,25 +463,19 @@ function set_zfs_ppa_requirement_Debian { # to it being incorrectly setup). # function set_zfs_ppa_requirement_Linuxmint { - print_step_info_header - perl -i -pe 's/^(deb cdrom)/# $1/' /etc/apt/sources.list - set_zfs_ppa_requirement + invoke "set_zfs_ppa_requirement" } # By using a FIFO, we avoid having to hide statements like `echo $v_passphrase | zpoool create ...` # from the logs. # function create_passphrase_named_pipe { - print_step_info_header - mkfifo "$c_passphrase_named_pipe" } function register_exit_hook { - print_step_info_header - function _exit_hook { rm -f "$c_passphrase_named_pipe" @@ -517,8 +524,6 @@ export ZFS_FREE_TAIL_SPACE=12 } function select_disks { - print_step_info_header - if [[ "${ZFS_SELECTED_DISKS:-}" != "" ]]; then mapfile -d, -t v_selected_disks < <(echo -n "$ZFS_SELECTED_DISKS") else @@ -558,8 +563,6 @@ Devices with mounted partitions, cdroms, and removable devices are not displayed } function select_pools_raid_type { - print_step_info_header - local raw_pools_raid_type= if [[ -v ZFS_POOLS_RAID_TYPE ]]; then @@ -603,8 +606,6 @@ function select_pools_raid_type { } function ask_root_password_Debian { - print_step_info_header - set +x if [[ ${ZFS_DEBIAN_ROOT_PASSWORD:-} != "" ]]; then v_root_password="$ZFS_DEBIAN_ROOT_PASSWORD" @@ -623,8 +624,6 @@ function ask_root_password_Debian { } function ask_encryption { - print_step_info_header - set +x if [[ -v ZFS_PASSPHRASE ]]; then @@ -655,8 +654,6 @@ Leave blank to keep encryption disabled. } function ask_boot_partition_size { - print_step_info_header - if [[ ${ZFS_BOOT_PARTITION_SIZE:-} != "" ]]; then v_boot_partition_size=$ZFS_BOOT_PARTITION_SIZE else @@ -675,8 +672,6 @@ Supported formats: '512M', '3G'" 30 100 ${c_default_boot_partition_size}M 3>&1 1 } function ask_swap_size { - print_step_info_header - if [[ ${ZFS_SWAP_SIZE:-} != "" ]]; then v_swap_size=$ZFS_SWAP_SIZE else @@ -693,8 +688,6 @@ function ask_swap_size { } function ask_free_tail_space { - print_step_info_header - if [[ ${ZFS_FREE_TAIL_SPACE:-} != "" ]]; then v_free_tail_space=$ZFS_FREE_TAIL_SPACE else @@ -719,8 +712,6 @@ For detailed informations, see the wiki page: https://github.com/saveriomiroddi/ } function ask_rpool_name { - print_step_info_header - if [[ ${ZFS_RPOOL_NAME:-} != "" ]]; then v_rpool_name=$ZFS_RPOOL_NAME else @@ -737,8 +728,6 @@ function ask_rpool_name { } function ask_pool_create_options { - print_step_info_header - local bpool_create_options_message='Insert the create options for the boot pool The mount-related options are automatically added, and must not be specified.' @@ -759,8 +748,6 @@ The encryption/mount-related options are automatically added, and must not be sp } function install_host_packages { - print_step_info_header - if [[ $v_use_ppa == "1" ]]; then if [[ ${ZFS_SKIP_LIVE_ZFS_MODULE_INSTALL:-} != "1" ]]; then add-apt-repository --yes "$c_ppa" @@ -785,8 +772,6 @@ function install_host_packages { } function install_host_packages_Debian { - print_step_info_header - if [[ ${ZFS_SKIP_LIVE_ZFS_MODULE_INSTALL:-} != "1" ]]; then echo "zfs-dkms zfs-dkms/note-incompatible-licenses note true" | debconf-set-selections @@ -807,27 +792,21 @@ function install_host_packages_Debian { # Differently from Ubuntu, Mint doesn't have the package installed in the live version. # function install_host_packages_Linuxmint { - print_step_info_header - apt install --yes zfsutils-linux - install_host_packages + invoke "install_host_packages" } function install_host_packages_elementary { - print_step_info_header - if [[ ${ZFS_SKIP_LIVE_ZFS_MODULE_INSTALL:-} != "1" ]]; then apt update apt install --yes software-properties-common fi - install_host_packages + invoke "install_host_packages" } function install_host_packages_UbuntuServer { - print_step_info_header - if [[ $v_use_ppa != "1" ]]; then apt install --yes zfsutils-linux efibootmgr @@ -859,8 +838,6 @@ function install_host_packages_UbuntuServer { } function setup_partitions { - print_step_info_header - local required_tail_space=$((v_free_tail_space > c_temporary_volume_size ? v_free_tail_space : c_temporary_volume_size)) for selected_disk in "${v_selected_disks[@]}"; do @@ -916,8 +893,6 @@ function setup_partitions { } function install_operating_system { - print_step_info_header - local dialog_message='The Ubuntu GUI installer will now be launched. Proceed with the configuration as usual, then, at the partitioning stage: @@ -959,8 +934,6 @@ Proceed with the configuration as usual, then, at the partitioning stage: } function install_operating_system_Debian { - print_step_info_header - # The temporary volume size displayed is an approximation of the format used by the installer, # but it's acceptable - the complexity required is not worth (eg. converting hypothetical units, # etc.). @@ -1013,8 +986,6 @@ CONF } function install_operating_system_UbuntuServer { - print_step_info_header - # O/S Installation # # Subiquity is designed to prevent the user from opening a terminal, which is (to say the least) @@ -1056,8 +1027,6 @@ You can switch anytime to this terminal, and back, in order to read the instruct } function custom_install_operating_system { - print_step_info_header - sudo "$ZFS_OS_INSTALLATION_SCRIPT" } @@ -1108,8 +1077,6 @@ function create_pools { } function create_swap_volume { - print_step_info_header - if [[ $v_swap_size -gt 0 ]]; then zfs create \ -V "${v_swap_size}G" -b "$(getconf PAGESIZE)" \ @@ -1121,15 +1088,11 @@ function create_swap_volume { } function copy_zpool_cache { - print_step_info_header - mkdir -p "$c_zfs_mount_dir/etc/zfs" cp /etc/zfs/zpool.cache "$c_zfs_mount_dir/etc/zfs/" } function sync_os_temp_installation_dir_to_rpool { - print_step_info_header - # On Ubuntu Server, `/boot/efi` and `/cdrom` (!!!) are mounted, but they're not needed. # local mount_dir_submounts @@ -1162,8 +1125,6 @@ function sync_os_temp_installation_dir_to_rpool { } function remove_temp_partition_and_expand_rpool { - print_step_info_header - if (( v_free_tail_space < c_temporary_volume_size )); then if [[ $v_free_tail_space -eq 0 ]]; then local resize_reference=100% @@ -1198,8 +1159,6 @@ function remove_temp_partition_and_expand_rpool { } function prepare_jail { - print_step_info_header - for virtual_fs_dir in proc sys dev; do mount --rbind "/$virtual_fs_dir" "$c_zfs_mount_dir/$virtual_fs_dir" done @@ -1210,8 +1169,6 @@ function prepare_jail { # See install_host_packages() for some comments. # function install_jail_zfs_packages { - print_step_info_header - if [[ $v_use_ppa == "1" ]]; then chroot_execute "add-apt-repository --yes $c_ppa" @@ -1236,8 +1193,6 @@ function install_jail_zfs_packages { } function install_jail_zfs_packages_Debian { - print_step_info_header - chroot_execute 'echo "deb http://deb.debian.org/debian buster main contrib" >> /etc/apt/sources.list' chroot_execute 'echo "deb-src http://deb.debian.org/debian buster main contrib" >> /etc/apt/sources.list' @@ -1257,26 +1212,20 @@ APT' } function install_jail_zfs_packages_elementary { - print_step_info_header - chroot_execute "apt install --yes software-properties-common" - install_jail_zfs_packages + invoke "install_jail_zfs_packages" } function install_jail_zfs_packages_UbuntuServer { - print_step_info_header - if [[ $v_use_ppa != "1" ]]; then chroot_execute "apt install --yes zfsutils-linux zfs-initramfs grub-efi-amd64-signed shim-signed" else - install_jail_zfs_packages + invoke "install_jail_zfs_packages" fi } function prepare_efi_partition { - print_step_info_header - # The other mounts are configured/synced in the EFI partitions sync stage. # chroot_execute "echo /dev/disk/by-uuid/$(blkid -s UUID -o value "${v_selected_disks[0]}"-part1) /boot/efi vfat defaults 0 0 > /etc/fstab" @@ -1288,8 +1237,6 @@ function prepare_efi_partition { } function configure_and_update_grub { - print_step_info_header - chroot_execute "perl -i -pe 's/GRUB_CMDLINE_LINUX_DEFAULT=\"\K/init_on_alloc=0 /' /etc/default/grub" # Silence warning during the grub probe (source: https://git.io/JenXF). @@ -1316,8 +1263,6 @@ function configure_and_update_grub { } function sync_efi_partitions { - print_step_info_header - for ((i = 1; i < ${#v_selected_disks[@]}; i++)); do local synced_efi_partition_path="/boot/efi$((i + 1))" @@ -1340,14 +1285,10 @@ function sync_efi_partitions { # See issue https://github.com/saveriomiroddi/zfs-installer/issues/110. # function update_initramfs { - print_step_info_header - chroot_execute "update-initramfs -u" } function fix_filesystem_mount_ordering { - print_step_info_header - chroot_execute "mkdir /etc/zfs/zfs-list.cache" chroot_execute "touch /etc/zfs/zfs-list.cache/$c_bpool_name /etc/zfs/zfs-list.cache/$v_rpool_name" @@ -1406,8 +1347,6 @@ function fix_filesystem_mount_ordering { # The code is a straight copy of the `fstrim` service. # function configure_pools_trimming { - print_step_info_header - chroot_execute "cat > /lib/systemd/system/zfs-trim.service << UNIT [Unit] Description=Discard unused ZFS blocks @@ -1438,15 +1377,11 @@ TIMER" } function configure_remaining_settings { - print_step_info_header - [[ $v_swap_size -gt 0 ]] && chroot_execute "echo /dev/zvol/$v_rpool_name/swap none swap discard 0 0 >> /etc/fstab" || true chroot_execute "echo RESUME=none > /etc/initramfs-tools/conf.d/resume" } function prepare_for_system_exit { - print_step_info_header - for virtual_fs_dir in dev sys proc; do umount --recursive --force --lazy "$c_zfs_mount_dir/$virtual_fs_dir" done @@ -1479,8 +1414,6 @@ function prepare_for_system_exit { } function display_exit_banner { - print_step_info_header - local dialog_message="The system has been successfully prepared and installed. You now need to perform a hard reset, then enjoy your ZFS system :-)" @@ -1496,59 +1429,59 @@ if [[ $# -ne 0 ]]; then display_help_and_exit fi -activate_debug -set_distribution_data -distro_dependent_invoke "store_os_distro_information" -store_running_processes -check_prerequisites -display_intro_banner -check_system_memory -save_disks_log -find_suitable_disks -distro_dependent_invoke "set_zfs_ppa_requirement" -register_exit_hook -create_passphrase_named_pipe +invoke "activate_debug" +invoke "set_distribution_data" +invoke "store_os_distro_information" +invoke "store_running_processes" +invoke "check_prerequisites" +invoke "display_intro_banner" +invoke "check_system_memory" +invoke "save_disks_log" +invoke "find_suitable_disks" +invoke "set_zfs_ppa_requirement" +invoke "register_exit_hook" +invoke "create_passphrase_named_pipe" -select_disks -select_pools_raid_type -distro_dependent_invoke "ask_root_password" --noforce -ask_encryption -ask_boot_partition_size -ask_swap_size -ask_free_tail_space -ask_rpool_name -ask_pool_create_options +invoke "select_disks" +invoke "select_pools_raid_type" +invoke "ask_root_password" --optional +invoke "ask_encryption" +invoke "ask_boot_partition_size" +invoke "ask_swap_size" +invoke "ask_free_tail_space" +invoke "ask_rpool_name" +invoke "ask_pool_create_options" -distro_dependent_invoke "install_host_packages" -setup_partitions +invoke "install_host_packages" +invoke "setup_partitions" if [[ "${ZFS_OS_INSTALLATION_SCRIPT:-}" == "" ]]; then # Includes the O/S extra configuration, if necessary (network, root pwd, etc.) - distro_dependent_invoke "install_operating_system" + invoke "install_operating_system" - create_pools - create_swap_volume - copy_zpool_cache - sync_os_temp_installation_dir_to_rpool - remove_temp_partition_and_expand_rpool + invoke "create_pools" + invoke "create_swap_volume" + invoke "copy_zpool_cache" + invoke "sync_os_temp_installation_dir_to_rpool" + invoke "remove_temp_partition_and_expand_rpool" else - create_pools - create_swap_volume - copy_zpool_cache - remove_temp_partition_and_expand_rpool + invoke "create_pools" + invoke "create_swap_volume" + invoke "copy_zpool_cache" + invoke "remove_temp_partition_and_expand_rpool" - custom_install_operating_system + invoke "custom_install_operating_system" fi -prepare_jail -distro_dependent_invoke "install_jail_zfs_packages" -prepare_efi_partition -configure_and_update_grub -sync_efi_partitions -update_initramfs -fix_filesystem_mount_ordering -configure_pools_trimming -configure_remaining_settings +invoke "prepare_jail" +invoke "install_jail_zfs_packages" +invoke "prepare_efi_partition" +invoke "configure_and_update_grub" +invoke "sync_efi_partitions" +invoke "update_initramfs" +invoke "fix_filesystem_mount_ordering" +invoke "configure_pools_trimming" +invoke "configure_remaining_settings" -prepare_for_system_exit -display_exit_banner +invoke "prepare_for_system_exit" +invoke "display_exit_banner"