#!/bin/bash

# Whatever comes in on file descriptor (FD) 3 gets redirected to where file
# descriptor 1 is pointing. File descriptor 1 points to stdout so when we
# output-redirect something into FD 3 it shows up on stdout. We can use this
# to produce arbitrary logging output inside a subshell like so:
#
# function my_func () {
#     some_command "${1:?}"
#     >&3 echo 'A log message'
# }
#
# var="$(my_func arg_1)"
#
# Here "${var}" will only capture the output of some_command "${1:?}". It
# will not capture 'echo' which will instead show up on our stdout/FD 1.
exec 3>&1

declare this_script_url
this_script_url="${SCRIPT_URL:?}"

declare zpool_name zfs_arch_dataset_name settings_file
zpool_name='zpool'
zfs_arch_dataset_name='archlinux'
settings_file='archzbm_settings.env'

declare -A partition_types
partition_types[gpt_zfs]='6a85cf4d-1dd2-11b2-99a6-080020736631'
partition_types[gpt_efi]='c12a7328-f81f-11d2-ba4b-00a0c93ec93b'
partition_types[mbr_zfs]='0xbf'
partition_types[mbr_boot]='0x83'

# https://unix.stackexchange.com/a/48550
set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

declare zpool_drive efi_drive boot_drive part_schema

function calculate_prefix_from_netmask () {
    # https://stackoverflow.com/a/50419919
    c='0'
    x='0'"$(printf -- '%o' ${1//./ })"
    while [ "${x}" -gt '0' ]; do
        # Modulo then bitwise-shift x and store as new x
        let c+="$(( x % 2 ))" 'x>>=1'
    done
    printf -- '%s' '/'"${c}";
}

function setup_env_vars () {
    printf -- '%s\n' \
        'We will go over a series of questions to create an answer file with' \
        'options you want the script to use.' \
        '' \
        'Current working directory is:'\
        "$(pwd)" \
        '' \
        'After we'"'"'re done answer file will be written to current working dir:' \
        './'"${settings_file}" \
        '' \
        'Press <Ctrl>+C to abort this process. No answer file will' \
        'be written to ./'"${settings_file}"' if you abort the script.' \
        '' \
        'When done rerun the same command you just did without '"'"'setup'"'"' argument.' \
        ''
    read -u3 -n 1 -s -r -p "Press any key to begin questionnaire"
    echo
    echo '----------------------------------------'
    echo

    read -u3 -p 'Please type kernel version to use, leave empty for latest, confirm with <Enter>: ' ARCHZBM_KERNEL_VER
    echo

    echo 'Do you want compressed datasets?'
    select arg_compressed in 'Compressed' 'Uncompressed'; do
        case "${arg_compressed}" in
            'Compressed')
                break
                ;;
            'Uncompressed')
                ARCHZBM_ZFSPROPS_NO_COMPRESSION='true'
                break
                ;;
        esac
    done <&3 && echo

    echo 'Do you want encrypted datasets?'
    select arg_encrypted in 'Encrypted' 'Unencrypted'; do
        case "${arg_encrypted}" in
            'Encrypted')
                break
                ;;
            'Unencrypted')
                ARCHZBM_ZFSPROPS_NO_ENCRYPTION='true'
                break
                ;;
        esac
    done <&3 && echo

    if [[ "${arg_encrypted}" = 'Encrypted' ]]; then
        echo 'Do you want a custom dataset decryption password?'
        select arg_custom_dataset_pw in 'Yes' 'No (use '"'"'password'"'"')'; do
            case "${arg_custom_dataset_pw}" in
                'Yes')
                    want_custom_dataset_pw='true'
                    break
                    ;;
                'No (use '"'"'password'"'"')')
                    break
                    ;;
            esac
        done <&3 && echo

        if [[ "${want_custom_dataset_pw}" ]]; then
            read -u3 -p 'Please type password, confirm with <Enter>: ' -s ARCHZBM_ZPOOL_PASSWORD
            echo
            echo
        fi
    fi

    echo 'Do you want a custom '"'"'root'"'"' user password?'
    select arg_custom_root_pw in 'Yes' 'No (use '"'"'password'"'"')'; do
        case "${arg_custom_root_pw}" in
            'Yes')
                want_custom_root_pw='true'
                break
                ;;
            'No (use '"'"'password'"'"')')
                break
                ;;
        esac
    done <&3 && echo

    if [[ "${want_custom_root_pw}" ]]; then
        read -u3 -p 'Please type password, confirm with <Enter>: ' -s ARCHZBM_ROOT_PASSWORD
        echo
        echo
    fi

    echo 'Do you want an SSH daemon in ZFSBootMenu?'
    select arg_ssh_in_zbm in "Yes" "No"; do
        case "${arg_ssh_in_zbm}" in
            Yes)
                want_ssh_in_zbm='true'
                break
                ;;
            No)
                break
                ;;
        esac
    done <&3 && echo

    if [[ "${want_ssh_in_zbm}" ]]; then
        echo 'How do you want to assign an IP address in ZFSBootMenu?'
        select arg_ip_autoconf_method in 'Statically' 'Dynamically, DHCP' 'Dynamically, BOOTP' 'Dynamically, RARP'; do
            case "${arg_ip_autoconf_method}" in
                'Statically')
                    ARCHZBM_NET_AUTOCONF='none'
                    break
                    ;;
                'Dynamically, DHCP')
                    ARCHZBM_NET_AUTOCONF='dhcp'
                    break
                    ;;
                'Dynamically, BOOTP')
                    ARCHZBM_NET_AUTOCONF='bootp'
                    break
                    ;;
                'Dynamically, RARP')
                    ARCHZBM_NET_AUTOCONF='rarp'
                    break
                    ;;
            esac
        done <&3 && echo

        read -u3 -p 'Which device to configure (eth0 is a good guess): ' ARCHZBM_NET_DEVICE
        echo

        if [[ "${arg_ip_autoconf_method}" = 'Statically' ]]; then
            read -u3 -p 'Interface IP address: ' ARCHZBM_NET_CLIENT_IP
            echo

            read -u3 -p 'Netmask: ' ARCHZBM_NET_NETMASK
            echo

            read -u3 -p 'Gateway IP address: ' ARCHZBM_NET_GATEWAY_IP
            echo
        fi

        echo 'Do you want a custom SSH listening port?'
        select arg_custom_ssh_port in 'Yes (let me specify)' 'No (keep port 22)'; do
            case "${arg_custom_ssh_port}" in
                'Yes (let me specify)')
                    want_custom_ssh_port='true'
                    break
                    ;;
                'No (keep port 22)')
                    break
                    ;;
            esac
        done <&3 && echo

        if [[ "${want_custom_ssh_port}" ]]; then
            read -u3 -p 'Please type SSH port, confirm with <Enter>: ' ARCHZBM_SSH_PORT
            echo
        fi

        echo 'Do you want the SSH daemon to use a custom keepalive send interval?'
        select arg_custom_ssh_keepalive_intvl in 'Yes (let me specify)' 'No (keep 1)'; do
            case "${arg_custom_ssh_keepalive_intvl}" in
                'Yes (let me specify)')
                    want_custom_keepalive_intvl='true'
                    break
                    ;;
                'No (keep 1)')
                    break
                    ;;
            esac
        done <&3 && echo

        if [[ "${want_custom_keepalive_intvl}" ]]; then
            read -u3 -p 'Please type server keepalive send interval, confirm with <Enter>: ' ARCHZBM_SSH_KEEPALIVE_INTVL
            echo
        fi

        read -u3 -p 'Please type SSH pub keys on one line separated by double-commas (,,) and confirm with <Enter>: ' ARCHZBM_SSH_AUTH_KEYS
        echo
    fi

    if [[ "${want_ssh_in_zbm}" ]]; then
        echo 'Do you want to define operating system'"'"'s IP address?'
        select arg_os_ip in 'Yes (let me specify)' 'Yes (use ZBM addresses)' 'No (DHCP is fine)'; do
            case "${arg_os_ip}" in
                'Yes (let me specify)')
                    want_custom_ip_in_os='true'
                    want_dns_and_ntp='true'
                    break
                    ;;
                'Yes (use ZBM addresses)')
                    ARCHZBM_OS_CLIENT_IP="${ARCHZBM_NET_CLIENT_IP}"
                    ARCHZBM_NET_CLIENT_IP_PREFIX="$(calculate_prefix_from_netmask "${ARCHZBM_NET_NETMASK}")"
                    ARCHZBM_OS_CLIENT_IP+="${ARCHZBM_NET_CLIENT_IP_PREFIX}"
                    ARCHZBM_OS_GATEWAY_IP="${ARCHZBM_NET_GATEWAY_IP}"
                    want_dns_and_ntp='true'
                    break
                    ;;
                'No (DHCP is fine)')
                    break
                    ;;
            esac
        done <&3 && echo

        if [[ "${want_custom_ip_in_os}" ]]; then
            read -u3 -p 'Interface IP address with CIDR prefix (a.b.c.d/nn): ' ARCHZBM_OS_CLIENT_IP
            echo

            read -u3 -p 'Gateway IP address: ' ARCHZBM_OS_GATEWAY_IP
            echo
        fi
    else
        echo 'Do you want to define operating system'"'"'s IP address?'
        select arg_os_ip in 'Yes (let me specify)' 'No (DHCP is fine)'; do
            case "${arg_os_ip}" in
                'Yes (let me specify)')
                    want_own_ip_in_os='true'
                    want_dns_and_ntp='true'
                    break
                    ;;
                'No (DHCP is fine)')
                    break
                    ;;
            esac
        done <&3 && echo

        if [[ "${want_own_ip_in_os}" ]]; then
            read -u3 -p 'Interface IP address with CIDR prefix (a.b.c.d/nn): ' ARCHZBM_OS_CLIENT_IP
            echo

            read -u3 -p 'Gateway IP address: ' ARCHZBM_OS_GATEWAY_IP
            echo
        fi
    fi

    if [[ "${want_ssh_in_zbm}" ]]; then
        echo 'Do you want to define OS '"'"'root'"'"' user'"'"'s SSH pub key?'
        select arg_root_pub_keys in 'Yes (let me specify)' 'Yes (use ZBM pub keys)' 'No (don'"'"'t enable sshd.service)'; do
            case "${arg_root_pub_keys}" in
                'Yes (let me specify)')
                    want_custom_pub_keys_in_os='true'
                    break
                    ;;
                'Yes (use ZBM pub keys)')
                    ARCHZBM_OS_SSH_AUTH_KEYS="${ARCHZBM_SSH_AUTH_KEYS}"
                    break
                    ;;
                'No (don'"'"'t enable sshd.service)')
                    break
                    ;;
            esac
        done <&3 && echo

        if [[ "${want_custom_pub_keys_in_os}" ]]; then
            read -u3 -p 'Please type SSH pub keys on one line separated by double-commas (,,) and confirm with <Enter>: ' ARCHZBM_OS_SSH_AUTH_KEYS
            echo
        fi
    else
        echo 'Do you want to define OS root user'"'"'s SSH pub key?'
        select arg_root_pub_keys in 'Yes (let me specify)' 'No (don'"'"'t enable sshd.service)'; do
            case "${arg_root_pub_keys}" in
                'Yes (let me specify)')
                    want_own_pub_key_in_os='true'
                    break
                    ;;
                'No (don'"'"'t enable sshd.service)')
                    break
                    ;;
            esac
        done <&3 && echo

        if [[ "${want_own_pub_key_in_os}" ]]; then
            read -u3 -p 'Please type SSH pub keys on one line separated by double-commas (,,) and confirm with <Enter>: ' ARCHZBM_OS_SSH_AUTH_KEYS
            echo
        fi
    fi

    if [[ "${want_dns_and_ntp}" ]]; then
        read -u3 -p 'Specify one or more comma-separated DNS IPs: ' ARCHZBM_OS_DNS_IP
        echo

        echo 'Do you want to override Arch Linux'"'"' NTP servers?'
        select arg_custom_ntp in 'Yes' 'No'; do
            case "${arg_custom_ntp}" in
                'Yes')
                    want_own_ntp='true'
                    break
                    ;;
                'No')
                    break
                    ;;
            esac
        done <&3 && echo

        if [[ "${want_own_ntp}" ]]; then
            read -u3 -p 'Specify one or more comma-separated NTP hostnames or IPs: ' ARCHZBM_OS_NTP_IP
            echo
        fi
    fi

    for env_var in 'ARCHZBM_KERNEL_VER' 'ARCHZBM_ZFSPROPS_NO_COMPRESSION' 'ARCHZBM_ZFSPROPS_NO_ENCRYPTION' 'ARCHZBM_ZPOOL_PASSWORD' 'ARCHZBM_ROOT_PASSWORD' 'ARCHZBM_NET_AUTOCONF' 'ARCHZBM_NET_DEVICE' 'ARCHZBM_NET_CLIENT_IP' 'ARCHZBM_NET_NETMASK' 'ARCHZBM_NET_GATEWAY_IP' 'ARCHZBM_SSH_PORT' 'ARCHZBM_SSH_KEEPALIVE_INTVL' 'ARCHZBM_SSH_AUTH_KEYS' 'ARCHZBM_OS_CLIENT_IP' 'ARCHZBM_OS_GATEWAY_IP' 'ARCHZBM_OS_SSH_AUTH_KEYS' 'ARCHZBM_OS_DNS_IP' 'ARCHZBM_OS_NTP_IP'; do
        if [[ "${!env_var}" ]]; then
            printf -- '%s='"'"'%s'"'"'\n' \
                "${env_var}" "${!env_var}" \
                >> "${settings_file}"
        fi
    done

    printf -- '%s\n' \
        'Done, please rerun script now with just' \
        '... | bash' \
        'so without the '"'"'setup'"'"' argument'
    exit 77
}

function arg_parse () {
    [[ "${1}" ]] && while :; do
        case "${1}" in
            -[[:alnum:]]*)
                >&3 printf -- '%s\n' \
                    'Short options '"'${1}'"' detected. Only known option is' \
                    'literal string '"'"'setup'"'"'. No idea what '"'${1}'"' is.' \
                    'Exiting ...'
                    exit 77
                ;;
            --*)
                >&3 printf -- '%s\n' \
                    'Long-form option '"'${1}'"' detected. Only known option is' \
                    'literal string '"'"'setup'"'"'. No idea what '"'${1}'"' is.' \
                    'Exiting ...'
                    exit 77
                ;;
            setup)
                setup_env_vars
                return
                ;;
            *)
                >&3 printf -- '%s\n' \
                    'Argument '"'${1}'"' detected. Only known option is literal' \
                    'string '"'"'setup'"'"'. No idea what '"'${1}'"' is.' \
                    'Exiting ...'
                    exit 77
                ;;
            esac
    done
}

function we_are_changerooted () {
    if [ "$(stat -c '%d:%i' '/')" != "$(stat -c '%d:%i' '/proc/1/root/.')" ]; then
        return 0
    else
        return 1
    fi
}

function no_kernel_update_in_iso () {
    #1.1
    sed -ri -e 's'$'\x1''#(IgnorePkg)[^\r\n\f]+'$'\x1''\1 = linux linux-headers'$'\x1''g' /etc/pacman.conf
}

function set_ntp () {
    #1.2
    timedatectl set-ntp true
}

function resize_cow_space () {
    #1.3
    mount -o remount,size='50%' /run/archiso/cowspace
}

function update_pacman_db () {
    #1.4
    pacman_dl_parallel
    printf -- '%s\n' 'Refreshing mirror list ...'
    printf -- '%s\n' \
        '--save /etc/pacman.d/mirrorlist' \
        '--protocol https' \
        '--latest 5' \
        '--sort age' \
        > '/etc/xdg/reflector/reflector.conf'
    systemctl start reflector
    # In an ISO and for the minimal number of packages we need we do not
    # care about partial upgrades
    #
    # Are we better off not attempting an upgrade inside the ISO?
    # Let's try and find out.
    # while ! pacman -Syyuu --needed --noconfirm --downloadonly; do
    #     sleep 5
    # done
    # pacman -Syyuu --needed --noconfirm
    pacman -Syy
}

function install_pkgs () {
    #1.5
    printf -- '%s\n' 'Installing packages ...'
    while ! pacman -S --needed --noconfirm --downloadonly "${@}"; do
        sleep 5
    done
    pacman -S --needed --noconfirm "${@}"
}

function install_zfs () {
    #1.6
    declare reset_colors='\033[0m'
    if modinfo 'zfs' &>/dev/null; then
        >&3 printf -- '%s\n' \
            'ZFS kernel module is loaded, no need to install ...'
    else
        curl -s 'https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init' | bash
    fi
    printf -- "${reset_colors}"
}

function uefi_or_bios () {
    #1.7
    local part_count_linux part_count_efi
    # Select disks with at least one partition. Among them count how many
    # with the given partition type code we have.
    part_count_linux="$(get_partitions | jq --raw-output '[.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${partition_types[mbr_boot]}"'") | .path] | length')"
    part_count_efi="$(get_partitions | jq --raw-output '[.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${partition_types[gpt_efi]}"'") | .path] | length')"
    if [[ "${part_count_linux}" -eq '1' ]] && [[ "${part_count_efi}" -eq '0' ]]; then
        part_schema='mbr'
        >&3 printf -- '%s\n' \
            'Treating this as an MBR/legacy BIOS machine ...'
    elif [[ "${part_count_linux}" -eq '0' ]] && [[ "${part_count_efi}" -eq '1' ]]; then
        part_schema='gpt'
        >&3 printf -- '%s\n' \
            'Treating this as a GPT/UEFI machine ...'
    else
        >&3 printf -- '%s\n' \
            'We are seeing partitions as follows:' \
            '- Partition type code '"${partition_types[mbr_boot]}"': '"${part_count_linux}" \
            '- Partition type code '"${partition_types[gpt_efi]}"': '"${part_count_efi}" \
            '' \
            'We are instead expecting either 1 and 0 indicating a legacy' \
            'BIOS setup or 0 and 1 indicating UEFI. '"${part_count_linux}"' and '"${part_count_efi}"' is' \
            'neither. Cowardly exiting ...'
        exit 77
    fi
    export part_schema="${part_schema}"
}

function get_partitions () {
    declare partitions_json
    partitions_json="$(lsblk --output PATH,PARTTYPE --json --tree)" || return 1
    printf -- '%s' "${partitions_json}"
    return 0
}

function get_part_parent () {
    local child_partition parent_partition
    child_partition="${1:?}"
    parent_partition="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].path=="'"${child_partition:?}"'") | .path')"
    printf -- '%s' "${parent_partition}"
    return 0
}

function get_parts () {
    local zfs_install_drive part_type_code
    declare parttype parts
    parttype="${1:?}"
    zfs_install_drive="${2:-}"
    case "${parttype}" in
        zfs)
            if [[ "${part_schema}" = 'mbr' ]]; then
                part_type_code="${partition_types[mbr_zfs]}"
            else
                part_type_code="${partition_types[gpt_zfs]}"
            fi
            parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${part_type_code}"'") | .path')" || exit 1
            ;;
        boot)
            parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="'"${partition_types[mbr_boot]}"'") | .path')" || exit 1
            ;;
        efi)
            parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="'"${partition_types[gpt_efi]}"'") | .path')" || exit 1
            ;;
        *)
            >&3 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...'
            exit 77
            ;;
    esac
    printf -- '%s' "${parts}"
    return 0
}

function we_have_exactly_one_part () {
    local parttype parts_list parts_count
    parttype="${1:?}"
    parts_list="${2:?}"
    parts_count="$(wc -l <<<"${parts_list}")"
    if [[ "$(wc -c <<<"${parts_list}")" -gt '1' ]]; then
        case "${parts_count}" in
            0)
                >&3 printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting ...'
                exit 77
                ;;
            1)
                return 0
                ;;
            *)
                return 1
                ;;
        esac
        >&3 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting ...'
        exit 77
    fi
}

function get_drive_id () {
    local drive_id_list drive_id_single
    drive_id_list="$(find -L /dev/disk/by-partuuid -samefile "${1:?}" | sort)"
    drive_id_single="$(head -n1 <<<"${drive_id_list}")"
    if [[ "$(wc -l <<<"${drive_id_single}")" -eq '1' ]] && [[ "$(wc -c <<<"${drive_id_single}")" -gt '1' ]]; then
        printf -- '%s' "${drive_id_single}"
        return 0
    fi
    >&3 printf -- '%s\n' 'No '"'${1:?}'"' partition entry in /dev/disk/by-partuuid, exiting ...'
    exit 77
}

function select_part () {
    local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive part_type_code specific_part_type
    declare part
    part_type="${1:?}" # 'boot' or 'zfs'
    zfs_install_drive="${2:-}"

    if [[ "${part_type}" = 'boot' ]]; then
        if [[ "${part_schema}" = 'mbr' ]]; then
            specific_part_type='boot'
        else
            specific_part_type='efi'
        fi
    else
        specific_part_type="${part_type}"
    fi

    if [[ "${zfs_install_drive}" ]]; then
        # This is intended to find correct boot/EFI partition
        parts="$(get_parts "${specific_part_type}" "${zfs_install_drive}")"
    else
        parts="$(get_parts "${specific_part_type}")"
    fi

    if [[ ! "${parts}" ]]; then
        case "${part_type}" in
            efi)
                part_type_human_readable='EFI System Partition (ESP) with partition type code EF00'
                ;;
            zfs)
                if [[ "${part_schema}" = 'mbr' ]]; then
                    part_type_code='bf'
                else
                    part_type_code='BF00'
                fi
                part_type_human_readable='ZFS zpool partition with partition type code '"${part_type_code}"
                ;;
        esac
        >&3 printf -- '%s\n' \
            'It looks as if there is no '"${part_type_human_readable}" \
            'on any of the disks. Did you correctly partition a disk before starting?' \
            'Check https://quico.space/quico-os-setup/arch-zbm#prep. Exiting ...'
        exit 77
    fi

    if we_have_exactly_one_part "${part_type}" "${parts}"; then
        part="${parts}"
    else
        >&3 printf -- '%s\n' 'More than one '"${part_type^^}"' partition to pick for installation. Cowardly exiting ...'
        exit 77
    fi
    printf -- '%s' "${part}"
    return 0
}

function no_zpool_exists () {
    declare zpool_list
    zpool_list="$(zpool list -H)"
    [[ "$(wc -l <<<"${zpool_list}")" -le '1' ]] && [[ "$(wc -c <<<"${zpool_list}")" -le '1' ]] && return 0
    return 1
}

function set_zpool_password () {
    local zpool_password
    if [[ "${ARCHZBM_ZPOOL_PASSWORD}" ]]; then
        zpool_password="${ARCHZBM_ZPOOL_PASSWORD}"
    else
        zpool_password='password'
    fi
    # May or may not have a newline at the end, ZFS doesn't care
    printf -- '%s' "${zpool_password}" > '/etc/zfs/'"${zpool_name}"'.key'
    chmod '000' '/etc/zfs/'"${zpool_name}"'.key'
}

function import_pool () {
    zpool import -d '/dev/disk/by-partuuid' -R '/mnt' "${zpool_name}" -N -f
    [[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && zfs load-key "${zpool_name}"
}

function create_pool () {
    # Create a temporary pool that is not cached
    #
    # Add zfsprops 'compression' unless environment variable
    # ARCHZBM_ZFSPROPS_NO_COMPRESSION is set to any value.
    #
    # Add zfsprops 'encryption' along with 'keyformat' and a 'keylocation'
    # unless environment variable ARCHZBM_ZFSPROPS_NO_ENCRYPTION is set to
    # any value.
    zpool create -f                                                     \
                 -o 'ashift=12'                                         \
                 -o 'autotrim=on'                                       \
                 -O 'acltype=posix'                                     \
                 $([[ ! "${ARCHZBM_ZFSPROPS_NO_COMPRESSION}" ]] &&      \
                    printf -- '%s '                                     \
                '-O compression=on')                                    \
                 -O 'relatime=on'                                       \
                 -O 'xattr=sa'                                          \
                 $([[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] &&       \
                    printf -- '%s '                                     \
                '-O encryption=on'                                      \
                '-O keyformat=passphrase'                               \
                '-O keylocation=file:///etc/zfs/'"${zpool_name}"'.key') \
                 -O 'normalization=formD'                               \
                 -O 'mountpoint=none'                                   \
                 -O 'canmount=off'                                      \
                 -O 'devices=off'                                       \
                 -R '/mnt'                                              \
                 "${zpool_name}" "${1:?}"
}

function create_root_dataset () {
    zfs create -o 'mountpoint=none' -o 'canmount=off' "${zpool_name}"'/root'
    # zfs set org.zfsbootmenu:commandline="ro quiet" "${zpool_name}"'/root'
    zfs set org.zfsbootmenu:commandline="ro" "${zpool_name}"'/root'
}

function create_system_dataset () {
    zfs create -o 'mountpoint=/' -o 'canmount=noauto' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
    zgenhostid
    zpool set bootfs="${zpool_name}"'/root/'"${zfs_arch_dataset_name}" "${zpool_name}"
    zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
}

function create_home_dataset () {
    zfs create -o 'mountpoint=/' -o 'canmount=off' "${zpool_name}"'/data'
    zfs create "${zpool_name}"'/data/home'
}

function export_pool () {
    zpool export "${zpool_name}"
}

function load_settings_file () {
    #1.8
    local working_dir settings_abs
    working_dir="$(pwd)"
    settings_abs="${working_dir}"'/'"${settings_file}"
    if [[ -r "${settings_abs}" ]]; then
        set -a
        source "${settings_abs}"
        set +a
    fi
}

function setup_zpool () {
    #1.9
    local drive_by_id
    zpool_drive="$(select_part 'zfs')"
    drive_by_id="$(get_drive_id "${zpool_drive}")"

    [[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && set_zpool_password
    if no_zpool_exists; then
        create_pool "${drive_by_id}"
        create_root_dataset
        create_system_dataset
        create_home_dataset
        export_pool
        import_pool
    else
        >&3 printf -- '%s\n' 'A zpool already exists, that is unexpected. Cowardly exiting 1 ...'
        exit 1
    fi
}

function mount_system () {
    #1.10
    zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
    zfs mount -a

    local zfs_parent efi_part boot_part
    zfs_parent="$(get_part_parent "${zpool_drive:?}")"

    if [[ "${part_schema}" = 'mbr' ]]; then
        boot_drive="${zfs_parent}"
        export boot_drive="${boot_drive}"
        boot_part="$(select_part 'boot' "${zfs_parent:?}")"
        mkdir -p '/mnt/boot/syslinux'
        mount "${boot_part}" '/mnt/boot/syslinux'
    else
        efi_drive="${zfs_parent}"
        export efi_drive="${efi_drive}"
        efi_part="$(select_part 'boot' "${zfs_parent:?}")"
        mkdir -p '/mnt/efi'
        mount "${efi_part}" '/mnt/efi'
    fi
}

function copy_zpool_cache () {
    #1.11
    mkdir -p '/mnt/etc/zfs'
    zpool set 'cachefile=/etc/zfs/'"${zpool_name}"'.cache' "${zpool_name}"
}

function pacman_dont_check_space () {
    # See pacman bug comment
    # https://bugs.archlinux.org/task/45070#comment142712
    #
    # When we pacstrap onto ZFS pacman incorrectly calculates and
    # overestimates required disk space. We instead assume an installation
    # gets done with at least a 10 GiB drive which is plenty. Skip pacman's
    # size check.
    #
    # We're setting this in Arch Linux ISO CD while we install proper Arch.
    # No need to revert this later as it is ephemeral anyway.
    sed -ri -e 's'$'\x1''^.*?(CheckSpace)([^\r\n\f]*)'$'\x1''#\1\2'$'\x1''g' '/etc/pacman.conf'
}

function install_archlinux () {
    #1.12
    pacman_dl_parallel
    pacman_dont_check_space
    while ! pacstrap /mnt              \
                base                   \
                base-devel             \
                linux                  \
                linux-headers          \
                linux-firmware         \
                amd-ucode              \
                efibootmgr             \
                vim                    \
                git                    \
                iwd                    \
                networkmanager         \
                network-manager-applet \
                dialog                 \
                os-prober              \
                reflector              \
                bluez                  \
                bluez-utils            \
                man-db                 \
                xdg-utils              \
                xdg-user-dirs; do
        sleep 5
    done
}

function gen_fstab () {
    #1.13
    genfstab -U /mnt | grep -v "${zpool_name}" | tr -s '\n' | sed -r -e 's/\/mnt//' -e '/./,$!d' > '/mnt/etc/fstab'
}

function configure_hosts_file () {
    cat > '/mnt/etc/hosts' <<EOF
#<ip-address>   <hostname.domain.org>   <hostname>
127.0.0.1       localhost               ${1}
::1             localhost               ${1}
EOF
}

function set_hostname () {
    #1.14
    declare new_hostname
    install_pkgs 'pwgen'
    new_hostname="$(pwgen --no-numerals --no-capitalize --ambiguous 8)"
    printf -- '%s\n' "${new_hostname}" > '/mnt/etc/hostname'
    configure_hosts_file "${new_hostname}"
}

function set_locale () {
    #1.15
    printf -- '%s\n' \
        'KEYMAP=de-latin1' \
        'FONT=Lat2-Terminus16' \
        'FONT_MAP=8859-1' \
        > '/mnt/etc/vconsole.conf'
    sed -ri -e 's'$'\x1''^(#)(en_US.UTF-8[^\r\n\f]*)'$'\x1''\2'$'\x1''g' '/mnt/etc/locale.gen'
    printf -- '%s\n' 'LANG=en_US.UTF-8' > '/mnt/etc/locale.conf'
}

function in_file_in_array_insert_n_before_m () {
    local arg_file arg_array arg_string arg_precede
    arg_file="${1:?}"
    arg_array="${2:?}"
    arg_string="${3:?}"
    arg_precede="${4:?}"

    # Look for a line that contains in this order
    # - String "${arg_array}" and a equals sign (=), assign capture group \1
    # - Followed by as few characters as possible followed by either an
    #   opening parenthesis or a space character, assign capture group \2
    # - String "${arg_precede}", capture as capture group \3
    # - Followed by either a closing parenthesis or a space which are then
    #   followed by as many non-line break characters as possible, capture
    #   as capture group \2
    #
    # For following example text we're assuming that:
    # - "${arg_array}"   equals 'HOOKS'
    # - "${arg_precede}" equals 'filesystems'
    # - "${arg_string}"  equals 'zfs'
    #
    # This finds a 'HOOKS=' array definition that contains the string
    # 'filesystems' either at the beginning of the 'HOOKS=(...)' opening
    # parenthesis, at the very end or somewhere in the middle where it may
    # be preceded or followed by one or more space characters. It saves
    # 'HOOKS=', it saves whatever precedes 'filesystems' and 'filesystems'
    # itself plus whatever comes after 'filesystems' until end of line. It
    # lastly inserts 'zfs' and a space character right in front of
    # 'filesystems'.
    sed -ri \
        -e 's'$'\x1''('"${arg_array}"'=)(.*?[( ])('"${arg_precede}"')([) ][^\r\n\f]*)'$'\x1''\1\2'"${arg_string}"' \3\4'$'\x1''g' \
        "${arg_file}"
}

function in_file_in_array_insert_n_at_the_end () {
    local arg_file arg_array arg_string
    arg_file="${1:?}"
    arg_array="${2:?}"
    arg_string="${3:?}"

    # Look for end of array, insert "${arg_string}" right before
    #
    # For following example text we're assuming that:
    # - "${arg_array}"   equals 'HOOKS'
    # - "${arg_string}"  equals 'net'
    #
    # This:
    # - Finds a 'HOOKS=' array definition, saves it as \1
    # - Finds as many non-closing parenthesis characters as possible, so all
    #   characters until just before the final ')' character of the line and
    #   saves this as \2
    # - Finds the closing parenthesis character plus all non-line break
    #   characters until end of line that come after it and saves this as \3
    # - Adds a space character and 'net' after \2
    sed -ri \
        -e 's'$'\x1''('"${arg_array}"'=)([^)]*)(\)[^\r\n\f]*)'$'\x1''\1\2 '"${arg_string}"'\3'$'\x1''g' \
        "${arg_file}"
}

function in_file_in_array_remove_n () {
    local arg_file arg_array arg_string
    arg_file="${1:?}"
    arg_array="${2:?}"
    arg_string="${3:?}"

    # Look for any line that contains "${arg_string}", delete that string
    #
    # For following example text we're assuming that:
    # - "${arg_array}"   equals 'HOOKS'
    # - "${arg_string}"  equals 'fsck'
    #
    # First -e expression removes 'fsck' wherever it is defined as the one
    # and only element of any HOOKS=(fsck) should such a line exist.
    #
    # Second -e expression finds string 'fsck' where it's preceded by space
    # character(s) and followed by either space character(s) or a closing
    # parenthesis.
    #
    # Third -e expression finds string 'fsck' where it's preceded by space
    # character(s) or an opening parenthesis and followed space
    # character(s).
    sed -ri \
        -e 's'$'\x1''((\()('"${arg_string}"')(\)))'$'\x1''\2\4'$'\x1''g' \
        -e 's'$'\x1''('"${arg_array}"'=.*?)([[:space:]]+'"${arg_string}"')([[:space:]]+|\))'$'\x1''\1\3'$'\x1''g' \
        -e 's'$'\x1''('"${arg_array}"'=.*?)([[:space:]]+|\()('"${arg_string}"'[[:space:]]+)'$'\x1''\1\2'$'\x1''g' \
        "${arg_file}"
}

function add_zfs_hook_to_initramfs () {
    #1.16
    # Add zfs hook, remove fsck hook from initramfs.
    in_file_in_array_insert_n_before_m '/mnt/etc/mkinitcpio.conf' 'HOOKS' 'zfs' 'filesystems'
    in_file_in_array_remove_n '/mnt/etc/mkinitcpio.conf' 'HOOKS' 'fsck'
    # Also unless encryption's unwanted add plain text key file into
    # initramfs since it's living inside an encrypted pool anyway.
    [[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && sed -ri \
        -e 's'$'\x1''^(FILES=)[^\r\n\f]*'$'\x1''\1(/etc/zfs/'"${zpool_name}"'.key)'$'\x1''g' \
        '/mnt/etc/mkinitcpio.conf'
}

function set_initramfs_build_list () {
    #1.17
    # No need to build fallback initramfs, our new fallback is ZFS snapshots
    sed -ri \
        -e '/^#/d' \
        -e '/^$/d' \
        -e '/^fallback/d' \
        -e 's'$'\x1''^(PRESETS=)[^\r\n\f]*'$'\x1''\1('"'"'default'"'"')'$'\x1''g' \
        '/mnt/etc/mkinitcpio.d/linux.preset'

    # Remove any existing fallback initramfs files
    find '/mnt/boot' -type f -regextype posix-extended -iregex '^/mnt/boot/initramfs-.*?-fallback.img' -delete
}

function add_zfs_files_to_new_os () {
    #1.18
    for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' $([[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && printf -- '%s' '/etc/zfs/'"${zpool_name}"'.key'); do
        rsync -av --itemize-changes {'','/mnt'}"${zfs_file}"
    done
}

function create_unpriv_user () {
    account_name="${1:?}"
    full_name="${2:-${account_name}}"
    authorized_keys_abs_path='/home/'"${account_name}"'/.ssh/authorized_keys'
    useradd --create-home --shell '/bin/bash' --comment 'Personal account of '"${full_name}" --user-group "${account_name}"
    chfn --full-name "${full_name}" "${account_name}"
    mkdir -p "$(dirname "${authorized_keys_abs_path}")"
    touch "${authorized_keys_abs_path}"
    chown -R "${account_name}"':' '/home/'"${account_name}"; chmod -R 'u=rwX,go=' "$(dirname "${authorized_keys_abs_path}")"
}

function enter_chroot () {
    #2.1
    arch-chroot /mnt /bin/bash -xe <<EOF
curl --silent '${this_script_url}' | bash
EOF
}

function keep_initiramfs_root_only_rw () {
    #2.3
    declare systemd_local_admin_override_path unit_name
    systemd_local_admin_override_path='/etc/systemd/system'
    unit_name='chmod-initramfs'
    path_unit="${systemd_local_admin_override_path%/}"'/'"${unit_name}"'.path'
    service_unit="${systemd_local_admin_override_path%/}"'/'"${unit_name}"'.service'

    cat > "${path_unit}" <<"EOF"
[Unit]
Description=chmod initramfs to be root-read-writable only

[Path]
PathChanged=/boot/initramfs-linux.img

[Install]
WantedBy=multi-user.target
WantedBy=system-update.target
EOF

    cat > "${service_unit}" <<"EOF"
[Unit]
Description=chmod initramfs to be root-read-writable only

[Service]
Type=oneshot
ExecStart=/usr/bin/chmod 600 /boot/initramfs-linux.img
EOF

    systemctl enable "${path_unit}"
}

function pacman_dl_parallel () {
    #2.4
    # We're setting this in Arch Linux ISO CD while we install proper Arch.
    # No need to revert this later as it is ephemeral anyway.
    sed -ri -e 's'$'\x1''^.*?(ParallelDownloads)[^\r\n\f]*'$'\x1''\1 = 20'$'\x1''g' '/etc/pacman.conf'
}

function unleash_makepkg () {
    #2.5
    local path_prefix
    path_prefix='/mnt'
    if we_are_changerooted; then
        path_prefix=''
    fi
    sed -ri \
        -e 's'$'\x1''^(#?(MAKEFLAGS=))[^\r\n\f]*'$'\x1''\2"-j$(nproc --ignore 1)"'$'\x1''g' \
        "${path_prefix}"'/etc/makepkg.conf'
}

function add_motd_getting_started_msg () {
    #2.6
    cat > '/etc/motd' <<"EOF"

####################

GUI basics:

    paru -S xorg plasma-meta kde-applications-meta sddm
    localectl set-x11-keymap de
    useradd --create-home --shell /bin/bash --user-group --groups wheel <user>
    passwd <user>
    systemctl enable --now sddm.service

####################

EOF
}

function get_aur_helper () {
    #2.7
    create_unpriv_user 'build'
    usermod --append --groups 'wheel' 'build'
    printf -- '%s\n' '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' > '/etc/sudoers.d/10-wheel-group-no-passwd-prompt'
    pushd /tmp
    git clone https://aur.archlinux.org/paru-bin.git
    chown -R 'build:' 'paru-bin'
    pushd 'paru-bin'
    sudo --user 'build' makepkg -si --noconfirm
    popd
    rm -rf 'paru-bin'
    popd
}

function paru_install () {
    sudo --user build paru -S --noconfirm "${@}"
}

function configure_syslinux () {
    paru_install 'syslinux'
    cp /usr/lib/syslinux/bios/*.c32 /boot/syslinux
    extlinux --install /boot/syslinux
    cat > /boot/syslinux/syslinux.cfg <<EOF
UI menu.c32
PROMPT 0

MENU TITLE ZFSBootMenu
TIMEOUT 100

DEFAULT zfsbootmenu

LABEL zfsbootmenu
  MENU LABEL ZFSBootMenu
  KERNEL /zfsbootmenu/vmlinuz-bootmenu
  INITRD /zfsbootmenu/initramfs-bootmenu.img
  APPEND zfsbootmenu quiet loglevel=0

LABEL zfsbootmenu-backup
  MENU LABEL ZFSBootMenu (Backup)
  KERNEL /zfsbootmenu/vmlinuz-bootmenu-backup
  INITRD /zfsbootmenu/initramfs-bootmenu-backup.img
  APPEND zfsbootmenu quiet loglevel=0
EOF
    dd bs=440 count=1 conv=notrunc if='/usr/lib/syslinux/bios/mbr.bin' of="${boot_drive}"
}

function configure_zfsbootmenu () {
    #2.9
    paru_install 'zfsbootmenu'
    in_file_in_array_remove_n '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' 'zfsbootmenu'

    if [[ "${part_schema}" = 'gpt' ]]; then
        cat > '/etc/zfsbootmenu/config.yaml' <<EOF
Global:
  ManageImages: true
  BootMountPoint: /efi
  InitCPIO: true
Components:
  Enabled: false
EFI:
  ImageDir: /efi/EFI/ZBM
  Versions: false
  Enabled: true
Kernel:
  CommandLine: ro loglevel=0 zbm.import_policy=hostid
  Prefix: vmlinuz
EOF
    else
        configure_syslinux
        cat > '/etc/zfsbootmenu/config.yaml' <<EOF
Global:
  ManageImages: true
  BootMountPoint: /boot/syslinux
  InitCPIO: true
Components:
  Enabled: true
  Versions: false
  ImageDir: /boot/syslinux/zfsbootmenu
Kernel:
  Prefix: vmlinuz
EOF
    fi

# Up here maybe 'ro quiet' instead of 'ro'. This is ZFSBootMenu's kernel
# command line. In MBR/Syslinux/extlinux mode /it/ must pass arguments to
# ZFSBootMenu's kernel so check /boot/syslinux/syslinux.cfg for how we start
# ZFSBootMenu in this mode. The .Kernel.CommandLine in
# /etc/zfsbootmenu/config.yaml is irrelevant for us in MBR/Syslinux/extlinux
# mode.

    # Assign cmdline for final kernel start. This is our Arch Linx kernel
    # command line.
    zfs set org.zfsbootmenu:commandline='rw nowatchdog rd.vconsole.keymap=de-latin1' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
}

function get_dropbear_hooks () {
    mkdir -p '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main'
    git -C '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook.git' .
    ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-hook.sh' '/usr/local/bin/pacman-mkinitcpio-dropbear-hook'
    ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-install.sh' '/usr/local/bin/pacman-mkinitcpio-dropbear-install'
    ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-hook.hook' '/usr/share/libalpm/hooks/pacman-mkinitcpio-dropbear-hook.hook'
    ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-install.hook' '/usr/share/libalpm/hooks/pacman-mkinitcpio-dropbear-install.hook'
}

function customize_dropbear_hooks () {
    local env_archzbm_ssh_port env_archzbm_ssh_keepalive_intvl
    env_archzbm_ssh_port="${ARCHZBM_SSH_PORT:-22}"
    env_archzbm_ssh_keepalive_intvl="${ARCHZBM_SSH_KEEPALIVE_INTVL:-1}"
    if [[ "${env_archzbm_ssh_port}" -ne '22' ]] || [[ "${env_archzbm_ssh_keepalive_intvl}" -ne '1' ]]; then
        paru_install 'rsync'
        rsync -av '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook'{,'.override'}'.patch'
    fi
    if [[ "${env_archzbm_ssh_port}" -ne '22' ]]; then
        sed -ri -e 's'$'\x1''-p [[:digit:]]+'$'\x1''-p '"${env_archzbm_ssh_port}"''$'\x1''g' '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook.override.patch'
    fi
    if [[ "${env_archzbm_ssh_keepalive_intvl}" -ne '1' ]]; then
        sed -ri -e 's'$'\x1''-K [[:digit:]]+'$'\x1''-K '"${env_archzbm_ssh_keepalive_intvl}"''$'\x1''g' '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook.override.patch'
    fi
}

function set_unique_ip_in_syslinux_kcl () {
    local zbm_config default_ip
    zbm_config="${1:?}"
    default_ip="${2:?}"

    # First -e expression removes first looks for lines that contain
    # 'APPEND' plus a space character and only in those lines removes all
    # occurrences of ' ip=' followed by as many non-space characters as
    # possible. This removes whatever 'ip=' definition already was present.
    #
    # Second -e expression similarly looks for lines that contain 'APPEND'
    # plus a space character then at their end inserts a space plus our
    # desired new 'ip=' definition. This puts in place the 'ip=' we want.
    sed -ri \
        -e \\$'\x1''APPEND '$'\x1 s'$'\x1'' ip=([^[:space:]]*)'$'\x1'''$'\x1''gi' \
        -e \\$'\x1''APPEND '$'\x1 s'$'\x1''$'$'\x1'' '"${default_ip}"''$'\x1''gi' \
        "${zbm_config}"
}

function ensure_ip_in_kcl () {
    local default_ip
    default_ip='ip='"${ARCHZBM_NET_CLIENT_IP}"':'"${ARCHZBM_NET_SERVER_IP}"':'"${ARCHZBM_NET_GATEWAY_IP}"':'"${ARCHZBM_NET_NETMASK}"':'"${ARCHZBM_NET_HOSTNAME}"':'"${ARCHZBM_NET_DEVICE}"':'"${ARCHZBM_NET_AUTOCONF}"

    if [[ "${part_schema}" = 'gpt' ]]; then
        local zbm_config kcl_length kcl_string ip_addr_found new_kcl first_kcl_elem
        local -a kcl
        paru_install 'go-yq'

        zbm_config='/etc/zfsbootmenu/config.yaml'
        kcl_length="$(yq '.Kernel.CommandLine | length' "${zbm_config}")"
        if [[ "${kcl_length}" -eq '0' ]]; then
            >&3 printf -- '%s\n' \
                'No .Kernel.CommandLine YAML element with content found in '"${zbm_config}"'. Exiting ...'
            exit 77
        else
            kcl_string="$(yq '.Kernel.CommandLine' "${zbm_config}")"
        fi

        mapfile -t kcl < <(<<<"${kcl_string}" tr ' ' '\n' | sed '/^$/d')
        for kcl_elem in "${!kcl[@]}"; do
            if grep -Piq -- 'ip=' <<<"${kcl[$kcl_elem]}"; then
                ip_addr_found='true'
                kcl["${kcl_elem}"]="${default_ip}"
            fi
        done
        if [[ ! "${ip_addr_found}" ]]; then
            kcl+=("${default_ip}")
        fi
        new_kcl=''
        first_kcl_elem='true'
        for kcl_elem in "${kcl[@]}"; do
            if [[ ! "${first_kcl_elem}" ]]; then
                new_kcl+=' '"${kcl_elem}"
            else
                new_kcl+="${kcl_elem}"
                unset -v first_kcl_elem
            fi
        done
        yq -i '.Kernel.CommandLine = "'"${new_kcl}"'"' "${zbm_config}"
    else
        local zbm_config
        zbm_config='/boot/syslinux/syslinux.cfg'
        set_unique_ip_in_syslinux_kcl "${zbm_config}" "${default_ip}"
    fi
}

function set_pub_keys () {
    local authorized_keys_file raw_pub_keys
    authorized_keys_file="${1:?}"
    raw_pub_keys="${2:?}"
    :> "${authorized_keys_file}"
    while IFS= read -r pub_key_line; do
        printf -- '%s\n' "${pub_key_line}" >> "${authorized_keys_file}"
    done < <(<<<"${raw_pub_keys}" sed -r -e 's/,,/\n/g')
    sed -i '/^$/d' "${authorized_keys_file}"
}

function we_want_ssh () {
    #2.10
    if  [[ "${ARCHZBM_NET_CLIENT_IP}" ]]       || \
        [[ "${ARCHZBM_NET_SERVER_IP}" ]]       || \
        [[ "${ARCHZBM_NET_GATEWAY_IP}" ]]      || \
        [[ "${ARCHZBM_NET_NETMASK}" ]]         || \
        [[ "${ARCHZBM_NET_HOSTNAME}" ]]        || \
        [[ "${ARCHZBM_NET_DEVICE}" ]]          || \
        [[ "${ARCHZBM_NET_AUTOCONF}" ]]        || \
        [[ "${ARCHZBM_SSH_PORT}" ]]            || \
        [[ "${ARCHZBM_SSH_KEEPALIVE_INTVL}" ]] || \
        [[ "${ARCHZBM_SSH_AUTH_KEYS}" ]]; then
        >&3 printf -- '%s\n' 'Installing SSH in ZFSBootMenu'
        return 0
    fi
    >&3 printf -- '%s\n' 'Not installing SSH in ZFSBootMenu'
    return 1
}

function configure_ssh_in_zbm () {
    #2.11
    get_dropbear_hooks
    customize_dropbear_hooks
    paru_install 'mkinitcpio-nfs-utils' 'dropbear' 'mkinitcpio-dropbear'
    in_file_in_array_insert_n_at_the_end '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' 'net'
    in_file_in_array_insert_n_at_the_end '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' 'dropbear'
    for key_type in 'dss' 'ecdsa' 'ed25519' 'rsa'; do
        dropbearkey -t "${key_type}" -f '/etc/dropbear/dropbear_'"${key_type}"'_host_key'
    done
    set_pub_keys '/etc/dropbear/root_key' "${ARCHZBM_SSH_AUTH_KEYS}"
    ensure_ip_in_kcl
}

function add_syslinux_pacman_hook () {
    mkdir -p '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main'
    git -C '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/zbm-syslinux-pacman-hook.git' .
    chmod +x '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.sh'
    ln -s '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.sh' '/usr/local/bin/pacman-zbm-syslinux-regen'
    ln -s '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.hook' '/usr/share/libalpm/hooks/pacman-zbm-syslinux-regen.hook'
}

function add_zbm_pacman_hook () {
    mkdir -p '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main'
    git -C '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/zbm-regen-pacman-hook.git' .
    ln -s '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main/pacman-zbm-image-regen.hook' '/usr/share/libalpm/hooks/pacman-zbm-image-regen.hook'
}

function get_disks_with_one_efipart () {
    local disks_with_one_efipart
    # Find disks that have exactly one EFI partition and where that EFI
    # partition is partition number 1. We expect exactly one disk to meet
    # these criteria. Anything else and we bail.
    disks_with_one_efipart="$(lsblk --output PATH,PARTTYPE --json --tree | jq --raw-output '.[][] | select(.children | length > 0) | select( any (.children[]; (.path | test("^[^[:digit:]]+1")) and (.parttype=="'"${partition_types[gpt_efi]}"'")) and ([select(.children[].parttype=="'"${partition_types[gpt_efi]}"'")] | length == 1) ) | .path')"
    if [[ "$(wc -l <<<"${disks_with_one_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_one_efipart}")" -gt '1' ]]; then
        printf -- '%s' "${disks_with_one_efipart}"
        return 0
    fi
    return 1
}

function install_os_in_chroot () {
    #2.2
    dd if='/dev/zero' of='/swapfile' bs='1M' count='2048'
    losetup '/dev/loop9' '/swapfile'
    mkswap '/dev/loop9'
    swapon '/dev/loop9'
    ### Reinit keyring
    # As keyring is initialized at boot, and copied to the install dir with pacstrap, and ntp is running
    # Time changed after keyring initialization, it leads to malfunction
    # Keyring needs to be reinitialised properly to be able to sign keys.
    rm -rf '/etc/pacman.d/gnupg'
    pacman-key --init
    pacman-key --populate archlinux
    while ! pacman -S archlinux-keyring --noconfirm --downloadonly; do
        sleep 5
    done
    pacman -S archlinux-keyring --noconfirm

    locale-gen
    source /etc/locale.conf

    keep_initiramfs_root_only_rw                #2.3
    pacman_dl_parallel                          #2.4
    unleash_makepkg                             #2.5
    add_motd_getting_started_msg                #2.6
    get_aur_helper                              #2.7
    if [[ "${ARCHZBM_KERNEL_VER}" ]]; then
        paru_install 'downgrade'
        yes | downgrade --ala-only \
            'linux='"${ARCHZBM_KERNEL_VER}" \
            'linux-headers='"${ARCHZBM_KERNEL_VER}" \
            --ignore always
    fi
    paru_install 'zfs-dkms' 'zfs-utils' 'jq'
    hwclock --systohc
    mkinitcpio -P

    # Install ZFSBootMenu image
    configure_zfsbootmenu                       #2.9
    if we_want_ssh; then                        #2.10
        configure_ssh_in_zbm                    #2.11
    fi
    generate-zbm

    # Yes, we do this twice so we immediately get a functional backup file
    generate-zbm

    if [[ "${part_schema}" = 'mbr' ]]; then
        paru_install 'rsync'
        add_syslinux_pacman_hook
    fi
    add_zbm_pacman_hook
    swapoff '/dev/loop9'
    losetup -d '/dev/loop9'
    rm '/swapfile'
}

function set_root_pw () {
    #3.2
    local root_password
    if [[ "${ARCHZBM_ROOT_PASSWORD}" ]]; then
        root_password="${ARCHZBM_ROOT_PASSWORD}"
    else
        root_password='password'
    fi
    printf -- '%s\n' 'root:'"${root_password}" | chpasswd --crypt-method 'SHA512' --root '/mnt'
}

function configure_networking () {
    #3.3
    local -a dns_addresses ntp_addresses

    # Begin network unit file with a default top section
    cat > '/mnt/etc/systemd/network/50-wired.network' <<"EOF"
[Match]
Name=en*

[Network]
EOF

    # Decide on what comes next in network unit file
    if  [[ "${ARCHZBM_OS_CLIENT_IP}" ]]  || \
        [[ "${ARCHZBM_OS_GATEWAY_IP}" ]] || \
        [[ "${ARCHZBM_OS_DNS_IP}" ]]     || \
        [[ "${ARCHZBM_OS_NTP_IP}" ]]; then

        cat >> '/mnt/etc/systemd/network/50-wired.network' <<EOF
Address=${ARCHZBM_OS_CLIENT_IP}
Gateway=${ARCHZBM_OS_GATEWAY_IP}
EOF

        if [[ "${ARCHZBM_OS_DNS_IP}" ]]; then
            mapfile -t dns_addresses < <(<<<"${ARCHZBM_OS_DNS_IP}" tr ',' '\n' | sed '/^$/d')
        else
            dns_addresses+=('8.8.8.8')
            dns_addresses+=('8.8.4.4')
        fi
        for dns_addr in "${dns_addresses[@]}"; do
            cat >> '/mnt/etc/systemd/network/50-wired.network' <<EOF
DNS=${dns_addr}
EOF
        done

        if [[ "${ARCHZBM_OS_NTP_IP}" ]]; then
            mapfile -t ntp_addresses < <(<<<"${ARCHZBM_OS_NTP_IP}" tr ',' '\n' | sed '/^$/d')
            for ntp_addr in "${ntp_addresses[@]}"; do
                cat >> '/mnt/etc/systemd/network/50-wired.network' <<EOF
NTP=${ntp_addr}
EOF
            done
        fi

        cat >> '/mnt/etc/systemd/network/50-wired.network' <<"EOF"
IPForward=yes
Domains=~.
EOF
    else
        cat >> '/mnt/etc/systemd/network/50-wired.network' <<"EOF"
DHCP=ipv4
IPForward=yes

[DHCP]
UseDNS=yes
RouteMetric=10
EOF
    fi

    systemctl enable 'systemd-networkd' --root='/mnt'
    systemctl disable 'systemd-networkd-wait-online' --root='/mnt'
}

function configure_sshd () {
    #3.4
    local pub_key_line

    cat >> '/mnt/etc/ssh/sshd_config.d/40-defaults.conf' <<"EOF"
PasswordAuthentication no
PermitRootLogin yes
EOF

    while IFS= read -r pub_key_line; do
        printf -- '%s\n' "${pub_key_line}" >> '/mnt/root/.ssh/authorized_keys'
    done < <(<<<"${ARCHZBM_OS_SSH_AUTH_KEYS}" sed -r -e 's/,,/\n/g')

    systemctl enable 'sshd.service' --root='/mnt'
}

function configure_dns () {
    #3.5
    rm '/mnt/etc/resolv.conf'
    ln -s '/run/systemd/resolve/stub-resolv.conf' '/mnt/etc/resolv.conf'

    # Optionally you may want /etc/systemd/network/50-wired.network to use
    # UseDNS=no and hardcode DNS server(s) here:
    # sed -i 's/^#DNS=.*/DNS=1.1.1.1/' /mnt/etc/systemd/resolved.conf
    systemctl enable 'systemd-resolved.service' --root='/mnt'
}

function configure_ntp () {
    #3.6
    systemctl enable 'systemd-timesyncd.service' --root='/mnt'
}

function configure_reflector () {
    #3.7
    systemctl enable 'reflector.service' 'reflector.timer' --root='/mnt'
}

function configure_zfs () {
    #3.8
    systemctl enable 'zfs-import-cache.service' 'zfs-mount.service' 'zfs-import.target' 'zfs.target' --root='/mnt'
}

function configure_zfs_mount_gen () {
    #3.9
    mkdir -p '/mnt/etc/zfs/zfs-list.cache'
    touch '/mnt/etc/zfs/zfs-list.cache/'"${zpool_name}"
    zfs list -H -o name,mountpoint,canmount,atime,relatime,devices,exec,readonly,setuid,nbmand | sed 's/\/mnt//' > '/mnt/etc/zfs/zfs-list.cache/'"${zpool_name}"
    systemctl enable 'zfs-zed.service' --root='/mnt'
}

function set_new_uefi_boot_entries () {
    #3.10
    declare -a uefi_images
    mapfile -t uefi_images < \
        <(find '/mnt/efi/EFI/ZBM' -type f -iname '*.efi' -print0 | \
        xargs -0 --no-run-if-empty --max-args '1' stat -c '%Y %n' | \
        sort -V | \
        awk '{print $2}')

    if efibootmgr | grep -Piq -- 'ZFSBootMenu'; then
        local -a old_uefi_entries
        mapfile -t old_uefi_entries < \
            <(efibootmgr | \
            grep -Pio -- '(?<=^Boot)[^\*[:space:]]+(?=\*? ZFSBootMenu)')
        for old_uefi_entry in "${old_uefi_entries[@]}"; do
            efibootmgr --bootnum "${old_uefi_entry}" --delete-bootnum &>/dev/null && {
                >&3 printf -- '%s\n' \
                    'EFI boot entry '"${old_uefi_entry}"' deleted.'
            }
        done
    fi

    if ! efibootmgr | grep -Piq -- 'ZFSBootMenu'; then
        local efi_disks_list
        efi_disks_list="$(get_disks_with_one_efipart)"
        if grep -Piq -- '^'"${efi_drive}"'$' <<<"${efi_disks_list}"; then
            for uefi_image in "${uefi_images[@]}"; do
                uefi_image_version="$(basename "${uefi_image%%.EFI}")"
                uefi_image_inverted="${uefi_image#/mnt/efi}"
                uefi_image_inverted="${uefi_image_inverted//\//\\}"
                efibootmgr --disk "${efi_drive}" \
                    --part 1 \
                    --create \
                    --label 'ZFSBootMenu '"${uefi_image_version}" \
                    --loader "${uefi_image_inverted}" &>/dev/null && {
                        >&3 printf -- '%s\n' \
                            'EFI boot entry ZFSBootMenu '"${uefi_image_version}"' added.'
            }
            done
        fi
    fi
}

function umount_all () {
    #3.11
    if [[ "${part_schema}" = 'mbr' ]]; then
        umount '/mnt/boot/syslinux'
    else
        umount '/mnt/efi'
    fi
    zfs umount -a
    zpool export "${zpool_name}"
}

function finalize_os_setup () {
    #3.1
    set_root_pw                                     #3.2
    configure_networking                            #3.3
    if [[ "${ARCHZBM_OS_SSH_AUTH_KEYS}" ]]; then
        configure_sshd                              #3.4
    fi
    configure_dns                                   #3.5
    configure_ntp                                   #3.6
    configure_reflector                             #3.7
    configure_zfs                                   #3.8
    configure_zfs_mount_gen                         #3.9
    if [[ "${part_schema}" = 'gpt' ]]; then
        set_new_uefi_boot_entries                   #3.10
    fi
    umount_all                                      #3.11
}

function main () {
    if [[ "${#@}" -gt '0' ]]; then
        arg_parse "${@}"
    fi
    if we_are_changerooted; then
        install_os_in_chroot                        #2.2
    else
        no_kernel_update_in_iso                     #1.1
        set_ntp                                     #1.2
        resize_cow_space                            #1.3
        update_pacman_db                            #1.4
        install_pkgs 'jq'                           #1.5
        install_zfs                                 #1.6
        uefi_or_bios                                #1.7
        load_settings_file                          #1.8
        setup_zpool                                 #1.9
        mount_system                                #1.10
        copy_zpool_cache                            #1.11
        install_archlinux                           #1.12
        gen_fstab                                   #1.13
        set_hostname                                #1.14
        set_locale                                  #1.15
        add_zfs_hook_to_initramfs                   #1.16
        set_initramfs_build_list                    #1.17
        add_zfs_files_to_new_os                     #1.18
        enter_chroot                                #2.1
        # We're done in chroot
        finalize_os_setup                           #3.1
    fi
}

main "${@}"