#!/usr/bin/env bash
#
# Copyright (C) 2024 Laurent Jourden <laurent85@enarel.fr>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Install Arch Linux on a usb drive using an
# Archuseriso iso image.

set -e -u

appname="${0##*/}"
bcachefs_options=('--compression=lz4')
btrfs_compress=""
btrfs_subvol_home=""
btrfs_subvol_root=""
desktop=""
encryption="no"
encryption_type="luks"
esp_files_settings=()
esp_label=""
esp_size=""
ext4_journal="yes"
img_label=""
iso_label=""
iso_file=""
lock_fs_options="no"
medium_version=""
# Drive min size in GiB
MINCAPACITY="16"
partuuid=""
root_files_settings=()
root_flags="defaults"
rootfs="ext4"
root_label=""
root_size=""
usb_device=""
username="archie"
WD="${PWD}"
workdir="$(mktemp -u auiwork.XXXXXXXX)"
valid_medium_version="v13"
zroot=""

_usage() {
    echo
    echo "${appname}, make a standard installation on a usb drive."
    echo
    echo 'Synopsis:'
    echo "${appname} [options] <archuseriso iso image> <usb device>"
    echo
    echo 'Help:'
    echo "${appname} --help"
    exit "${1}"
}

_help() {
    echo
    echo "${appname}, make a standard installaiton on a usb drive."
    echo
    echo 'Synopsis:'
    echo "${appname} [options] <archuseriso iso image> <usb device>"
    echo
    echo 'Options:'
    echo '-h, --help                  Command line help'
    echo '--bcachefs-compress=algo    Bcachefs compression algorithm: gzip, lz4 (default), none, zstd'
    echo '--btrfs-compress=algo       Btrfs compression algorithm: lzo, none, zlib, zstd (Default)'
    echo '--encrypt                   Disk encryption, encrypt the root partition'
    echo '                            Option not applying to zfs filesystem'
    echo '--ext4-no-journal           Disable ext4 journal'
    echo '--rootfs=fstype             Root filesystem type: bcachefs, btrfs, ext4 (default), f2fs, zfs'
    echo '                            type zfs requires an iso image with zfs support' 
    echo '--size-esp=integer[g|G]     EFI System Partition size in GiB (ESP)'
    echo '--size-rootfs=integer[g|G]  Root file system partition size in GiB'
    echo "--username=<user>           Custom username, defaults to ${username}"
    echo '--zfsonroot                 Same as --rootfs=zfs'
    echo '                            Option requires an iso image with zfs support' 
    echo
    echo 'Example:'
    echo "sudo ${appname} --username=foobar archuseriso-xfce-1231-x64.iso /dev/sdc"
    echo
    exit "${1}"
}

_msg_info() {
    local _msg="${1}"
    printf '[%s] INFO: %s\n' "${appname}" "${_msg}"
}

_cleanup() {
    local _workdirs
    _workdirs=('squashfs' 'iso' 'usbro' 'usbrw/boot' 'usbrw/home' 'usbrw/root' 'usbesp' 'usbrw')
    if [[ "${rootfs}" == "zfs" ]]; then
        if zpool status "${zroot}" &> /dev/null; then
            [[ "$(zfs get mounted "${zroot}/data/home" -H -o value)" == "yes" ]] && \
                  zfs unmount "${zroot}/data/home"
            [[ "$(zfs get mounted "${zroot}/data/root" -H -o value)" == "yes" ]] && \
                  zfs unmount "${zroot}/data/root"
            if mountpoint -q -- "${WD}/${workdir}/usbrw/boot"; then
                umount -- "${WD}/${workdir}/usbrw/boot"
            fi
            [[ "$(zfs get mounted "${zroot}/ROOT/default" -H -o value)" == "yes" ]] && \
                  zfs unmount "${zroot}/ROOT/default"
            zpool export "${zroot}"
            sleep 3
        fi
    fi
    for _workdir in ${_workdirs[*]}; do
        if mountpoint -q -- "${WD}/${workdir}/${_workdir}"; then
            umount -- "${WD}/${workdir}/${_workdir}"
        fi
    done
    for _workdir in ${_workdirs[*]}; do
        if [[ -d "${WD}/${workdir}/${_workdir}" ]]; then
            rmdir -- "${WD}/${workdir}/${_workdir}"
        fi
    done
    if [[ -f "${WD}/${workdir}/grub-embed.cfg" ]]; then
        rm -- "${WD}/${workdir}/grub-embed.cfg"
    fi
    if [[ -d "${WD}/${workdir}" ]]; then
        rmdir -- "${WD}/${workdir}"
    fi
    if [[ "${encryption}" == "yes" && -e "/dev/mapper/${crypt_mapper:-auicrypt}" ]]; then
        cryptsetup close "${crypt_mapper:-auicrypt}"
    fi
}

_unmount() {
    echo
    _msg_info "Unmount working directories, may take some time"
    _cleanup
    _msg_info "Done!"
}

_luks_format() {
    echo
    _msg_info "Disk encryption setup, type in a passphrase of your choice"
    if ! cryptsetup --type=luks2 --label="${crypt_label:-AUI_LUKS}" --uuid="${crypt_uuid:=$(uuidgen)}" -q luksFormat -- "${usb_device}2" > /dev/null; then
        echo 'Encryption setup failed!'
        exit 1
    fi
    _msg_info "Done!"
    echo
    _msg_info "Type in your passphrase to unlock partition"
    if ! cryptsetup -- open "${usb_device}2" "${crypt_mapper:-auicrypt}"; then
        echo 'Error: Could not unlock partition! Exiting.'
        exit 1
    fi
    root_device="/dev/mapper/${crypt_mapper:-auicrypt}"
    _msg_info "Done!"
    echo
}

_luks_encryption_setup() {
    echo
    _msg_info "Disk encryption setup"
    sed -i -- "s|^HOOKS=(.*block|& encrypt|" "${WD}/${workdir}/usbrw/etc/mkinitcpio.conf"
    _msg_info "initramfs update"
    arch-chroot -- "${WD}/${workdir}/usbrw" mkinitcpio -P &> /dev/null
    _msg_info "Done!"
    if [[ "${bootmodes[*]}" =~ 'grub.esp' ]]; then
        sed -i -- "s|root=|cryptdevice=UUID=${crypt_uuid}:${crypt_mapper:-auicrypt} &|" \
                  "${WD}/${workdir}/usbrw/boot/grub/grub.cfg"
    fi
    if [[ "${bootmodes[*]}" =~ 'uefi-x64.refind.esp' ]]; then
        sed -i -- "s|root=|cryptdevice=UUID=${crypt_uuid}:${crypt_mapper:-auicrypt} &|" \
                  "${WD}/${workdir}/usbrw/boot/refind_linux.conf"
    fi
    if [[ "${bootmodes[*]}" =~ 'uefi-x64.systemd-boot.esp' ]]; then
        sed -i -- "s|root=|cryptdevice=UUID=${crypt_uuid}:${crypt_mapper:-auicrypt} &|" \
                  "${WD}/${workdir}/usbrw/boot/loader/entries/"*.conf
    fi
    if [[ "${bootmodes[*]}" =~ 'bios.syslinux.mbr' ]]; then
        sed -i -- "s|rw$|cryptdevice=UUID=${crypt_uuid}:${crypt_mapper:-auicrypt} &|" \
                  "${WD}/${workdir}/usbrw/boot/syslinux/archiso_sys-linux.cfg"
    fi
    _msg_info "Done!"
}

_bcachefs_encryption_setup() {
    echo
    _msg_info "Disk encryption setup"
    sed -i -- "s|\(^HOOKS=(.*filesystems \)fsck|\1bcachefs|" "${WD}/${workdir}/usbrw/etc/mkinitcpio.conf"
    _msg_info "initramfs update"
    arch-chroot -- "${WD}/${workdir}/usbrw" mkinitcpio -P &> /dev/null
    _msg_info "Done!"
}


_check() {
    if [[ $# -ne 2 ]]; then
        echo 'Error: Invalid arguments!'
        _usage 1
    fi
    if [[ ${EUID} -ne 0 ]]; then
        echo 'This script must be run as root!'
        exit 1
    fi
    iso_file="${1}"
    usb_device="${2}"
    if [[ ! $(LC_ALL=C stat -c %F -- "${usb_device}" 2> /dev/null) =~ 'block special file' ]]; then
        echo "Error: ${usb_device} is not a block device!"
        _usage 1
    fi
    case "$(LC_ALL=C stat -c %t -- "${usb_device}" 2> /dev/null)" in
        '8')
            # SCSI disk devices (0-15)
            ;;
        '65')
            # SCSI disk devices (16-31)
            ;;
        '66')
            # SCSI disk devices (32-47)
            ;;
        '67')
            # SCSI disk devices (48-63)
            ;;
        '68')
            # SCSI disk devices (64-79)
            ;;
        '69')
            # SCSI disk devices (80-95)
            ;;
        '70')
            # SCSI disk devices (96-111)
            ;;
        '71')
            # SCSI disk devices (112-127)
            ;;
        '128')
            # SCSI disk devices (128-143)
            ;;
        '129')
            # SCSI disk devices (144-159)
            ;;
        '130')
            # SCSI disk devices (160-175)
            ;;
        '131')
            # SCSI disk devices (176-191)
            ;;
        '132')
            # SCSI disk devices (192-207)
            ;;
        '133')
            # SCSI disk devices (208-223)
            ;;
        '134')
            # SCSI disk devices (234-239)
            ;;
        '135')
            # SCSI disk devices (240-255)
            ;;
        *)
            echo "Error: ${usb_device} is not a scsi disk device!"
            _usage 1
    esac
    if ! [[ $(lsblk -dnro rm      -- "${usb_device}" 2> /dev/null) -eq 1 || \
            $(lsblk -dnro hotplug -- "${usb_device}" 2> /dev/null) -eq 1      ]]; then
        echo "Error: ${usb_device} is not a removable block device!"
        _usage 1
    fi
    if [[ ! "$(lsblk -dnro tran -- "${usb_device}" 2> /dev/null)" == 'usb' ]]; then
        echo "Error: ${usb_device} is not a usb device!"
        _usage 1
    fi
    if grep -q -- "${usb_device}" /proc/self/mountinfo > /dev/null; then
        echo "Error: ${usb_device} appears in active mounts, unmount drive partitions before proceeding!"
        exit 1
    fi
    if [[ ! -f "${iso_file}" ]]; then
        echo "file ${iso_file} not found!"
        _usage 1
    fi
    if [[ ! $(file -- "${iso_file}" 2> /dev/null) =~ 'MBR boot sector' ]]; then
        echo "Error: ${iso_file} is not an iso image!"
        _usage 1
    fi
    # Set efi boot partition size in MiB
    if [[ -n "${esp_size}" ]]; then
        if ! [[ "${esp_size}" =~ ^[1-9][0-9]?+$ ]]; then
            echo "FAT partition size error: Invalid size argument (GiB): ${esp_size}"
            _usage 1
        fi
        esp_size=$(( esp_size * 1024 ))
    else
        esp_size=512
    fi
    # Set root partition size in MiB
    if [[ -n "${root_size}" ]]; then
        if ! [[ "${root_size}" =~ ^[1-9][0-9]?+$ ]]; then
            echo "Persistent partition size error: Invalid size argument (GiB): ${root_size}"
            _usage 1
        fi
        root_size=$(( root_size * 1024 ))
    fi
    if [[ "${rootfs}" == "bcachefs" ]]; then
        if ! pacman -Q bcachefs-tools &> /dev/null; then
            echo 'bcachefs-tools package not installed, aborting!'
            exit 0
        fi
    fi
    if [[ "${rootfs}" == "btrfs" ]]; then
        if ! pacman -Q btrfs-progs &> /dev/null; then
            echo 'btrfs-progs package not installed, aborting!'
            exit 0
        fi
    fi
    if [[ "${rootfs}" == "f2fs" ]]; then
        if ! pacman -Q f2fs-tools &> /dev/null; then
            echo 'f2fs-tools package not installed, aborting!'
            exit 0
        fi
    fi
}

_init() {
    local _esp_size _root_size
    drivesize=$(blockdev --getsize64 "${usb_device}") # Bytes
    # Logical sector size
    # Check usb drive capacity
    if [[ ${drivesize} -lt $(( MINCAPACITY * 1048576 )) ]]; then
        echo 'Storage capacity error!'
        exit 1
    fi
    # check partitions size don't exceed drive's capacity
    _esp_size=$(( esp_size * 1048576 )) # Bytes
    if [[ -n "${root_size}" ]]; then
        _root_size=$(( root_size * 1048576 )) # Bytes
    else
        _root_size=0
    fi
    if [[ ! ${drivesize} -gt $(( _esp_size + _root_size )) ]]; then
        echo "Size settings error: exceeds drive storage capacity!"
        exit 1
    fi
}

_confirm_write() {
    # Confim write
    echo
    _msg_info "The process may take a long time depending on the drive's speed, be patient!"
    _msg_info ""
    _msg_info "ISO file:   ${iso_file}"
    _msg_info "USB device: ${usb_device}"
    _msg_info "Username:   ${username}"
    echo
    read -r -n1 -p "Confirm write to $(lsblk -dno model,size -- "${usb_device}") (N/y)? "
    echo
    if [[ ! "${REPLY}" =~ ^[Yy]$ ]]; then
        echo 'Aborted!'
        exit 0
    fi
}

_usb_prepare() {
    # Check & prepare working directory
    for mountpoint in "${workdir}" "${workdir}/"{iso,squashfs} "${workdir}/usb"{esp,rw}; do
        if findmnt -nr -- "${mountpoint}" > /dev/null; then
            echo "Error: ${mountpoint} appears in active mounts, unmount before proceeding!"
            exit 1
        fi
    done
    for mountpoint in "${workdir}" "${workdir}/"{iso,squashfs} "${workdir}/usb"{esp,rw}; do
        if [[ -e "${WD}/${mountpoint}" ]]; then
            echo "Error: ${mountpoint} exists in working directory! Delete or rename before proceeding!"
            exit 1
        fi
    done
    if [[ -e "/dev/mapper/${crypt_mapper:-auicrypt}" ]]; then
        echo "Error: cryptsetup mapping /dev/mapper/${crypt_mapper:-auicrypt} exists! cannot proceed."
        exit 1
    fi
    mkdir -p -- "${WD}/${workdir}"/{iso,squashfs} "${WD}/${workdir}/usb"{esp,rw}

    # Mount iso
    echo
    _msg_info 'Mount iso'
    _msg_info "mountpoint: ${WD}/${workdir}/iso"
    if ! mount -o ro -- "${iso_file}" "${WD}/${workdir}/iso"; then
        echo "Error: mount iso failed!"
        exit 1
    fi

    # check iso
    if [[ ! -e "${WD}/${workdir}/iso/.drive/mediumdef.sh" ]]; then
        echo "Error: ${iso_file} is not a compatible iso image!"
        echo
        _usage 1
    fi

    # source medium def variables
    source "${WD}/${workdir}/iso/.drive/mediumdef.sh"
    if [[ ! "${medium_version}" == "${valid_medium_version}" ]]; then
        echo -e "Error: ${medium_version} gen iso image, gen version mismatch. ${appname} requires gen ${valid_medium_version}!"
       echo
       _usage 1
    fi
    
    # check ZFS support
    if [[ "${rootfs}" == "zfs" ]]; then
        if ! grep -q 'zfs-utils ' "${WD}/${workdir}/iso/.drive/pkglist.${arch}.txt" && \
           ! grep -q 'zfs-linux ' "${WD}/${workdir}/iso/.drive/pkglist.${arch}.txt"; then
            echo "Error: ZFS support is missing in this ISO image ${iso_file}!"
            _usage 1
        fi
    fi

    if [[ ! -d "${WD}/${workdir}/iso/EFI/" ]]; then
        echo "Error: missing EFI directory, ${iso_file} is not a compatible iso image!"
        echo
        _usage 1
    fi

    _msg_info "Done!"
}

_partitions() {
    local _part_type_linux _part_type_msft _part_type_solaris_root
    local _part_type_linux_or_solaris

    # GPT Partition type : Linux Filesystem
    _part_type_linux="0FC63DAF-8483-4772-8E79-3D69D8477DE4"
    # GPT Partition type : Microsoft basic data
    _part_type_msft="EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"
    # GPT Partition type : Solaris root
    _part_type_solaris_root="6A85CF4D-1DD2-11B2-99A6-080020736631"

    # Root partition type
    # Linux type for ext4, f2fs, btrfs
    # Solaris root type for zfs
    if [[ "${rootfs}" == "zfs" ]]; then
        _part_type_linux_or_solaris="${_part_type_solaris_root}"
    else
        _part_type_linux_or_solaris="${_part_type_linux}"
    fi

    # Wipe drive
    udevadm lock --device="${usb_device}" -- wipefs --all --force -- "${usb_device}"* > /dev/null
    sleep 3
    partprobe -- "${usb_device}"
    echo

    # New gpt partition table
    _msg_info "Drive partitions"
    _msg_info "New gpt label"
    if ! echo 'label: gpt' | udevadm lock --device="${usb_device}" -- \
            sfdisk -W always -- "${usb_device}" > /dev/null; then
        echo 'Failed to create new gpt partition table!'
        exit 1
    fi
    sleep 3
    partprobe -- "${usb_device}"

    # Partitions
    _msg_info "Partition #1 in MiB: ${esp_size}"
    _msg_info "Partition #2 in MiB: ${root_size:-All Free Space}"
    if ! echo -e ",${esp_size}M,${_part_type_msft},\n\
                  ,${root_size:+${root_size}M},${_part_type_linux_or_solaris},\n" | \
            udevadm lock --device="${usb_device}" -- sfdisk --append -W always -- "${usb_device}" > /dev/null; then
        echo 'Failed to create partitions!'
        exit 1
    fi
    sleep 3

    partprobe -- "${usb_device}"
    _msg_info "Done!"
}

_format_bcachefs() {
    _msg_info "partition #2: type Bcachefs, label ${root_label}"
    if ! udevadm lock --device="${root_device}" -- \
            mkfs.bcachefs -L "${root_label}" ${bcachefs_options[*]} "${root_device}" > /dev/null; then
        echo 'Failed to format partition!'
        exit 1
    fi
    if bcachefs unlock -c "${root_device}"; then
        _msg_info "Type in your passphrase to unlock partition"
        bcachefs unlock -k session "${root_device}"
    fi
}

_format_btrfs() {
    _msg_info "partition #2: type Btrfs, label ${root_label}"
    if ! udevadm lock --device="${root_device}" -- \
            mkfs.btrfs -L "${root_label}" -- "${root_device}" > /dev/null; then
        echo 'Failed to format partition!'
        exit 1
    fi
}

_format_f2fs() {
    _msg_info "partition #2: type F2FS, label ${root_label}"
    if ! udevadm lock --device="${root_device}" -- \
            mkfs.f2fs -l "${root_label}" -O encrypt,extra_attr,compression -- "${root_device}" > /dev/null; then
        echo 'Failed to format partition!'
        exit 1
    fi
}

_format_ext4() {
    _msg_info "partition #2: type Ext4, label ${root_label}"
    if ! udevadm lock --device="${root_device}" -- \
            mkfs.ext4 -L "${root_label}" -O encrypt -q -- "${root_device}" > /dev/null; then
        echo 'Failed to format partition!'
        exit 1
    fi
    # disable ext4 journal
    if [[ "${ext4_journal}" == "no" ]]; then
        _msg_info "Ext4 partitions: disable journal"
        tune2fs -O '^has_journal' -- "${root_device}" &> /dev/null
    fi
}

_format_zfs() {
    partuuid=/dev/disk/by-partuuid/"$(blkid -s PARTUUID -o value "${root_device}")"
    zroot="zroot${aui_suffix,,}"

    _msg_info "partition #2: type ZFS, pool name ${root_label}"
    if ! udevadm lock --device="${root_device}" --  \
            zpool create -f -o ashift=12 \
            -O acltype=posixacl         \
            -O relatime=on              \
            -O xattr=sa                 \
            -O dnodesize=legacy         \
            -O normalization=formD      \
            -O mountpoint=none          \
            -O canmount=off             \
            -O devices=off              \
            -O compression=lz4          \
            -R "${WD}/${workdir}/usbrw" \
            "${zroot}" "${partuuid}" > /dev/null; then
        echo 'Failed to format partition!'
        exit 1
    fi
    zfs create -o mountpoint=none "${zroot}/data"
    zfs create -o mountpoint=none "${zroot}/ROOT"
    zfs create -o mountpoint=/ -o canmount=noauto "${zroot}/ROOT/default"
    zfs create -o mountpoint=/home "${zroot}/data/home"
    zfs create -o mountpoint=/root "${zroot}/data/root"
    zpool set bootfs="${zroot}/ROOT/default" "${zroot}"
    zpool export "${zroot}"
    sleep 3
}

_format() {
    echo
    _msg_info "Format partitions"
    _msg_info "partition #1: type FAT, label ${esp_label}"
    if ! udevadm lock --device="${usb_device}1" -- \
            mkfs.fat -F32 -n "${esp_label}" -- "${usb_device}1" &> /dev/null; then
        echo 'Failed to format partition!'
        exit 1
    fi
    root_device="${usb_device}2"
    if [[ "${encryption}" == "yes" ]]; then
        case "${rootfs}" in
            'btrfs'|'f2fs'|'ext4')
                _luks_format;;
            'bcachefs')
                encryption_type="bcachefs"
                bcachefs_options+=('--encrypted');;
        esac
    fi

    # format root partition
    # Default ext4
    _format_"${rootfs}"

    _msg_info "Done!"
}

_mount() {
    # Mount usb device
    echo
    _msg_info "Mount usb partitions"
    _msg_info "device: ${usb_device}1, mountpoint: ${WD}/${workdir}/usbesp"
    mount -- "${usb_device}1" "${WD}/${workdir}/usbesp"

    _msg_info "device: ${root_device}, mountpoint: ${WD}/${workdir}/usbrw"
    case "${rootfs}" in
        'btrfs')
            mount -t btrfs -- "${root_device}" "${WD}/${workdir}/usbrw"
            # create rootfs and home subvolumes for persistence
            btrfs subvolume create -- "${WD}/${workdir}/usbrw/${btrfs_subvol_root}" > /dev/null
            btrfs subvolume create -- "${WD}/${workdir}/usbrw/${btrfs_subvol_home}" > /dev/null
            umount "${WD}/${workdir}/usbrw"
            sleep 3
            mount -o "${root_flags}" -- "${root_device}" "${WD}/${workdir}/usbrw"
            mkdir -p -- "${WD}/${workdir}/usbrw/home"
            mount -o subvol="${btrfs_subvol_home}" -- "${root_device}" "${WD}/${workdir}/usbrw/home"
            ;;
        'bcachefs' | 'ext4' | 'f2fs')
            mount -t "${rootfs}" -- "${root_device}" "${WD}/${workdir}/usbrw"
            ;;
        'zfs')
            zpool import -d /dev/disk/by-partuuid -R "${WD}/${workdir}/usbrw" -N "${zroot}"
            zfs mount "${zroot}/ROOT/default"
            zfs mount "${zroot}/data/root"
            zfs mount "${zroot}/data/home"
            ;;
        *)
            echo "rootfs type unkown!"
            _usage 1
            ;;
    esac
    _msg_info "Done!"

    # Mount squashfs
    _msg_info "Mount live image: mountpoint ${WD}/${workdir}/squashfs"
    mount -o ro -- "${WD}/${workdir}/iso/arch/x86_64/airootfs.sfs" "${WD}/${workdir}/squashfs"
    _msg_info "Done!"
}

_copy() {
    echo
    _msg_info "Install to ${WD}/${workdir}/usbrw, may take some time"
    cp -aT -- "${WD}/${workdir}/squashfs" "${WD}/${workdir}/usbrw"
    # Copy install setup
    cp -aT -- "${WD}/${workdir}/iso/.drive/install/auirootfs" "${WD}/${workdir}/usbrw"
    mount -o bind -- "${WD}/${workdir}/usbesp" "${WD}/${workdir}/usbrw/boot"
    _msg_info "Done!"
}

# Make GRUB standalone EFI binary
_make_grub_efi_binary() {
    local _grub_efi_arch="${1}"
    local _grub_efi_name="${2}"
    local _grubmodules=()

    IFS='' read -r -d '' grubembedcfg <<'EOF' || true
regexp --set=1:archiso_bootdevice '^\(([^)]+)\)\/?[Ee][Ff][Ii]\/?' "$cmdpath"
if ! [ -d "$cmdpath" ]; then
     # On some firmware, GRUB has a wrong cmdpath when booted from an optical disc.
     # https://gitlab.archlinux.org/archlinux/archiso/-/issues/183
     if regexp '^\(([^)]+)\)\/?[Ee][Ff][Ii]\/[Bb][Oo][Oo][Tt]\/?$' "$cmdpath"; then
         cmdpath="${archiso_bootdevice}/EFI/BOOT"
     fi
     if regexp '^\(([^)]+)\)\/?[Ee][Ff][Ii]\/[Gg][Rr][Uu][Bb]\/?$' "$cmdpath"; then
         cmdpath="${archiso_bootdevice}/EFI/grub"
     fi
fi
configfile "(${archiso_bootdevice})/grub/grub.cfg"
EOF

printf '%s\n' "$grubembedcfg" > "${WD}/${workdir}/grub-embed.cfg"

    # Create EFI binary
    # Module list from https://bugs.archlinux.org/task/71382#comment202911
    _grubmodules=(all_video at_keyboard boot btrfs cat chain configfile echo efifwsetup efinet ext2 f2fs fat font  \
                  gfxmenu gfxterm gzio halt hfsplus iso9660 jpeg keylayouts linux loadenv loopback lsefi lsefimmap \
                  minicmd normal part_apple part_gpt part_msdos png read reboot regexp search search_fs_file       \
                  search_fs_uuid search_label serial sleep tpm usb usbserial_common usbserial_ftdi                 \
                  usbserial_pl2303 usbserial_usbdebug video xfs zstd)
    grub-mkstandalone -O "${_grub_efi_arch}"                                  \
                      --modules="${_grubmodules[*]}"                          \
                      --locales="en@quot"                                     \
                      --themes=""                                             \
                      --sbat=/usr/share/grub/sbat.csv                         \
                      --disable-shim-lock                                     \
                      -o "${WD}/${workdir}/usbesp/EFI/grub/${_grub_efi_name}" \
                      "boot/grub/grub.cfg=${WD}/${workdir}/grub-embed.cfg"
}

_esp_bootloader_cfg_uefi-ia32.grub.esp() {
    # Copy new GRUB cfg
    cp -rT -- "${WD}/${workdir}/iso/.drive/install/grub" "${WD}/${workdir}/usbesp/grub"

    _make_grub_efi_binary "i386-efi" "grubia32.efi"
}

_esp_bootloader_cfg_uefi-x64.grub.esp() {
    # Copy new GRUB cfg
    cp -rT -- "${WD}/${workdir}/iso/.drive/install/grub/" "${WD}/${workdir}/usbesp/grub"

    _make_grub_efi_binary "x86_64-efi" "grubx64.efi"
}

_esp_bootloader_cfg_uefi-x64.refind.esp() {
# Copy new rEFInd config
    cp -- "${WD}/${workdir}/iso/.drive/install/refind/refind.conf" "${WD}/${workdir}/usbesp/EFI/refind/"
    cp -- "${WD}/${workdir}/iso/.drive/install/refind/refind_linux.conf" "${WD}/${workdir}/usbesp/"
    if [[ -f "${WD}/${workdir}/iso/EFI/refind/icons/os_arch.png" ]]; then
        cp -- "${WD}/${workdir}/iso/EFI/refind/icons/os_arch.png" "${WD}/${workdir}/usbesp/vmlinuz-linux.png"
    fi
    esp_files_settings+=('EFI/refind/refind.conf'
                         'refind_linux.conf')
}

_esp_bootloader_cfg_uefi-x64.systemd-boot.esp() {
    # Remove live config
    [[ -d "${WD}/${workdir}/usbesp/loader/" ]] && rm -r -- "${WD}/${workdir}/usbesp/loader/"
    # Copy new systemd-boot config
    cp -rT -- "${WD}/${workdir}/iso/.drive/install/loader" "${WD}/${workdir}/usbesp/loader"
}

_esp_bootloader_cfg_bios.syslinux.mbr() {
    # Copy new syslinux config
    cp -rT -- "${WD}/${workdir}/iso/.drive/install/syslinux" "${WD}/${workdir}/usbesp/syslinux"
}

_esp_default_bootloader_uefi-ia32.grub.esp() {
    # Move EFI binary to ESP /EFI/BOOT
    mv -- "${WD}/${workdir}/usbesp/EFI/grub/grubia32.efi" "${WD}/${workdir}/usbesp/EFI/BOOT/BOOTIA32.EFI"
}

_esp_default_bootloader_uefi-x64.grub.esp() {
    # Move EFI binary to ESP /EFI/BOOT
    mv -- "${WD}/${workdir}/usbesp/EFI/grub/grubx64.efi" "${WD}/${workdir}/usbesp/EFI/BOOT/BOOTx64.EFI"
}

_esp_default_bootloader_uefi-x64.refind.esp() {
mv -- "${WD}/${workdir}/usbesp/EFI/refind/refind.conf" "${WD}/${workdir}/usbesp/EFI/BOOT/"
    if [[ -f "${WD}/${workdir}/iso/EFI/BOOT/icons/os_arch.png" ]]; then
        cp -- "${WD}/${workdir}/iso/EFI/BOOT/icons/os_arch.png" "${WD}/${workdir}/usbesp/vmlinuz-linux.png"
    fi
}

_esp() {
    # ESP setup
    echo
    _msg_info "ESP setup"
    _msg_info "Copy to ${WD}/${workdir}/usbesp"
    cp -LrT -- "${WD}/${workdir}/iso/.drive/install/esp" "${WD}/${workdir}/usbesp"
    cp -r -- "${WD}/${workdir}/iso/.drive/install/syslinux" "${WD}/${workdir}/usbesp/"

    # EFI boot loader setup
    for bootmode in "${bootmodes[@]}"; do
        if typeset -f "_esp_bootloader_cfg_${bootmode}" &> /dev/null; then
            _esp_bootloader_cfg_${bootmode}
        fi
    done

    for _mediumfile in "${esp_files_settings[@]}"; do
        if [[ -f "${WD}/${workdir}/usbesp/${_mediumfile}" ]]; then
            sed -i -- "s|%ARCHISO_LABEL%|${iso_label}|g;
                       s|%DESKTOP%|${desktop}|g;
                       s|%ESP_LABEL%|${esp_label}|g;
                       s|%ROOT_LABEL%|${root_label}|g;
                       s|%ROOT_FLAGS%|${root_flags}|g;
                       s|=${iso_label}|=${img_label}|g"  \
                      "${WD}/${workdir}/usbesp/${_mediumfile}"
        fi
    done

    # ZFS boot setup
    if [[ "${rootfs}" == "zfs" ]]; then
        for _mediumfile in "${esp_files_settings[@]}"; do
            if [[ -f "${WD}/${workdir}/usbesp/${_mediumfile}" ]]; then
                sed -i -- "s|root=.*rootflags=defaults|zfs="${zroot}/ROOT/default" zfs_import_dir="${partuuid}" zfs_force=1 zfs_boot_only=1|" \
                          "${WD}/${workdir}/usbesp/${_mediumfile}"
            fi
        done
    fi

    # Set default EFI boot loader
    if typeset -f "_esp_default_bootloader_${ia32_uefi_default_bootloader}" &> /dev/null; then
        _esp_default_bootloader_${x64_uefi_default_bootloader}
    fi
    if typeset -f "_esp_default_bootloader_${x64_uefi_default_bootloader}" &> /dev/null; then
        _esp_default_bootloader_${x64_uefi_default_bootloader}
    fi
    _msg_info "Done!"
}

_rootfs() {
    # System settings
    # * remove live settings except volatile journal
    # * fstab
    # * live user renamed 'archie', no password set

    _msg_info "Remove live settings"

    # fstab
    for _mediumfile in "${root_files_settings[@]}"; do
        if [[ -f "${WD}/${workdir}/usbrw/${_mediumfile}" ]]; then
            sed -i -- "s|%ESP_LABEL%|${esp_label}|g;
                       s|%ROOT_LABEL%|${root_label}|g;
                       s|%BTRFS_SUBVOL_HOME%|${btrfs_subvol_home}|g" \
                      "${WD}/${workdir}/usbrw/${_mediumfile}"
            if [[ "${rootfs}" != "btrfs" && "${_mediumfile}" =~ 'fstab' ]]; then
                # remove unused btrfs line in fstab
                sed -i -- '/btrfs/d' "${WD}/${workdir}/usbrw/${_mediumfile}"
            fi
        fi
    done

    # sshd, mirrorlist, logind.conf
    sed -i -- 's/^\(PermitRootLogin \).\+/#\1prohibit-password/' "${WD}/${workdir}/usbrw/etc/ssh/sshd_config"
    rm -r -- "${WD}/${workdir}/usbrw/etc/systemd/logind.conf.d"

    # live services
    arch-chroot -- "${WD}/${workdir}/usbrw" sh -c 'systemctl disable pacman-init.service choose-mirror.service --quiet; sleep 3'
    rm -r -- "${WD}/${workdir}/usbrw/etc/systemd/system/"{choose-mirror.service,pacman-init.service,etc-pacman.d-gnupg.mount,getty@tty1.service.d}
    rm -- "${WD}/${workdir}/usbrw/root/"{.automated_script.sh,.zlogin}
    rm -- "${WD}/${workdir}/usbrw/etc/initcpio/hooks/archiso"

    # autologin
    if [[ -e "${WD}/${workdir}/usbrw/etc/lightdm/lightdm.conf" ]]; then
        sed -i -- 's/^\(autologin-user=\)live$/#\1/' "${WD}/${workdir}/usbrw/etc/lightdm/lightdm.conf"
        sed -i -- 's/^\(autologin-session=\).*/#\1/' "${WD}/${workdir}/usbrw/etc/lightdm/lightdm.conf"
    fi
    if [[ -e "${WD}/${workdir}/usbrw/etc/sddm.conf.d/autologin.conf" ]]; then
        rm -- "${WD}/${workdir}/usbrw/etc/sddm.conf.d/autologin.conf"
    fi

    # sudo
    sed -i -- 's/^#\s\(%wheel\s.*)\sALL\)$/\1/' "${WD}/${workdir}/usbrw/etc/sudoers"
    sed -i -- 's/^\(%wheel\s.*NOPASSWD\)/# \1/' "${WD}/${workdir}/usbrw/etc/sudoers"

    _msg_info "Done!"

    # Arch keyring
    _msg_info "Initialize pacman keys"
    arch-chroot -- "${WD}/${workdir}/usbrw" pacman-key --init &> /dev/null
    arch-chroot -- "${WD}/${workdir}/usbrw" pacman-key --populate archlinux &> /dev/null
    _msg_info "Done!"

    # username
    if [[ -d "${WD}/${workdir}/usbrw/home/live" ]]; then
        _msg_info "User setup ${username}"
        mv -- "${WD}/${workdir}/usbrw/home/live" "${WD}/${workdir}/usbrw/home/${username}"
        sed -i -- "s/live/${username}/g" "${WD}/${workdir}/usbrw/etc/"{passwd,group,shadow,gshadow}
        if [[ -e "${WD}/${workdir}/usbrw/etc/samba/smb.conf" ]]; then
            sed -i -- "s/live/${username}/g" "${WD}/${workdir}/usbrw/etc/samba/smb.conf"
        fi
    else
        _msg_info "no user account found on the live image!"
        username="root"
    fi
    _msg_info "Done!"

    # ZFS on root setup
    if [[ "${rootfs}" == "zfs" ]]; then
        arch-chroot -- "${WD}/${workdir}/usbrw" sh -c 'systemctl mask zfs-import-cache.service'
        arch-chroot -- "${WD}/${workdir}/usbrw" zgenhostid
        sed -i -- 's/\(^HOOKS.*\)filesystems/\1zfs filesystems/;
                   s/\(^HOOKS.*\)fsck/\1/;
                   s/^MODULES=(/&zfs/' \
                  "${WD}/${workdir}/usbrw/etc/mkinitcpio.conf"
    fi

    # initramfs
    _msg_info "initramfs update"
    arch-chroot -- "${WD}/${workdir}/usbrw" mkinitcpio -P &> /dev/null
    _msg_info "Done!"

    if [[ "${encryption}" == "yes" ]]; then
        _"${encryption_type}"_encryption_setup
    fi
}

_biosbootloader() {
    _msg_info "Install boot loader"
    if ! syslinux --directory syslinux --install -- "${usb_device}1" &> /dev/null; then
        echo 'Boot loader installation failed!'
        exit 1
    fi
    if ! dd bs=440 count=1 conv=notrunc if=/usr/lib/syslinux/bios/gptmbr.bin of="${usb_device}" 2> /dev/null; then
        echo 'Boot loader installation failed!'
        exit 1
     fi
     if ! udevadm lock --device="${usb_device}" -- \
             sfdisk --part-attrs "${usb_device}" 1 LegacyBIOSBootable &> /dev/null; then
         echo 'Boot loader installation failed!'
         exit 1
     fi
     _msg_info "Done!"
}

# arguments
OPTS=$(getopt -o 'h' --long 'bcachefs-compress:'                           \
                     --long 'btrfs-compress:,encrypt,ext4-no-journal,help' \
                     --long 'rootfs:,size-esp:,size-rootfs:,username:'     \
                     --long 'zfsonroot'                                    \
                     -n "${appname}" -- "$@")
[[ $? -eq 0 ]] || _usage 1
eval set -- "${OPTS}"
unset OPTS
[[ $# -eq 1 ]] && _usage 1

while true; do
    case "$1" in
        '-h'|'--help')
            _help 0 ;;
        '--bcachefs-compress')
            if [[ "${2}" == 'gzip' || \
                  "${2}" == 'lz4'  || \
                  "${2}" == 'none' || \
                  "${2}" == 'zstd' ]]; then
                 bcachefs_options+=("--compression=${2}")
            fi
            shift 2 ;;
        '--btrfs-compress')
            if [[ "${2}" == 'lzo'  || \
                  "${2}" == 'no'   || \
                  "${2}" == 'none' || \
                  "${2}" == 'zlib' || \
                  "${2}" == 'zstd'      ]]; then
                btrfs_compress="${2}"
            fi
            shift 2 ;;
        '--encrypt')
            if ! [[ "${rootfs}" == "zfs" ]]; then 
                encryption="yes"
            fi
            shift ;;
        '--ext4-no-journal')
            ext4_journal="no"
            shift ;;
        '--rootfs')
            if ! [[ "${rootfs}" == "zfs" ]]; then 
                case "${2}" in
                    'btrfs')
                        btrfs_subvol_home="/home"
                        btrfs_subvol_root="/rootfs"
                        root_flags="subvol=${btrfs_subvol_root},compress=${btrfs_compress:-zstd}"
                        rootfs="${2}" ;;
                    'bcachefs'|'f2fs'|'ext4')
                        rootfs="${2}" ;;
                    'zfs')
                        rootfs="${2}"
                        encryption="no" ;;
                    *)
                        echo "${2} is not a valid file system type option"
                        _usage 1 ;;
                esac
            fi
            shift 2 ;;
        '--size-esp')
            esp_size="${2/[gG]}"
            shift 2 ;;
        '--size-rootfs')
            root_size="${2/[gG]}"
            shift 2 ;;
        '--username')
            username="${2}"
            shift 2 ;;
        '--zfsonroot')
            rootfs="zfs"
            encryption="no"
            shift ;;
        '--')
            shift
            break ;;
    esac
done

trap _cleanup EXIT
_check "$@"
_init
_confirm_write
_usb_prepare
_partitions
_format
_mount
_copy
_esp
_rootfs
_unmount
_biosbootloader
echo
_msg_info "Success!"
echo
echo "IMPORTANT NOTICE !!!"
echo "- The root account has no password !!!"
echo "- The user account name is ${username} and no password was set !!!"
echo "- You must set a root and a user password by yourself after the first boot !!!"

# vim:ts=4:sw=4:et:
