#!/usr/bin/env bash
################################################################################
#                                                                              #
# blackarch-installer - Official Installer for BlackArch Linux                 #
#                                                                              #
# AUTHOR                                                                       #
# noptrix@nullsecurity.net                                                     #
#                                                                              #
################################################################################


# blackarch-installer version
VERSION='1.2.25'

# path to blackarch-installer
BI_PATH='/usr/share/blackarch-installer'

# true / false
TRUE=0
FALSE=1

# return codes
SUCCESS=0
FAILURE=1

# verbose mode - default: quiet
VERBOSE='/dev/null'

# colors
WHITE="$(tput setaf 7)"
# WHITEB="$(tput bold ; tput setaf 7)"
# BLUE="$(tput setaf 4)"
BLUEB="$(tput bold ; tput setaf 4)"
CYAN="$(tput setaf 6)"
CYANB="$(tput bold ; tput setaf 6)"
# GREEN="$(tput setaf 2)"
# GREENB="$(tput bold ; tput setaf 2)"
RED="$(tput setaf 1)"
# REDB="$(tput bold; tput setaf 1)"
YELLOW="$(tput setaf 3)"
# YELLOWB="$(tput bold ; tput setaf 3)"
BLINK="$(tput blink)"
NC="$(tput sgr0)"

# installation mode
INSTALL_MODE=''

# install modes
INSTALL_REPO='1'
INSTALL_FULL_ISO='2'
INSTALL_BLACKMAN='3'

# chosen locale
LOCALE=''

# set locale
SET_LOCALE='1'

# list locales
LIST_LOCALE='2'

# chosen keymap
KEYMAP=''

# set keymap
SET_KEYMAP='1'

# list keymaps
LIST_KEYMAP='2'

# network interfaces
NET_IFS=''

# chosen network interface
NET_IF=''

# network configuration mode
NET_CONF_MODE=''

# network configuration modes
NET_CONF_AUTO='1'
NET_CONF_WLAN='2'
NET_CONF_MANUAL='3'
NET_CONF_SKIP='4'

# hostname
HOST_NAME=''

# host ipv4 address
HOST_IPV4=''

# gateway ipv4 address
GATEWAY=''

# subnet mask
SUBNETMASK=''

# broadcast address
BROADCAST=''

# nameserver address
NAMESERVER=''

# DualBoot flag
DUALBOOT=''

# LUKS flag
LUKS=''

# avalable hard drive
HD_DEVS=''

# chosen hard drive device
HD_DEV=''

# Partitions
PARTITIONS=''

# partition label: gpt or dos
PART_LABEL=''

# boot partition
BOOT_PART=''

# root partition
ROOT_PART=''

# crypted root
CRYPT_ROOT='r00t'

# swap partition
SWAP_PART=''

# boot fs type - default: ext4
BOOT_FS_TYPE=''

# root fs type - default: ext4
ROOT_FS_TYPE=''

# chroot directory / blackarch linux installation
CHROOT='/mnt'

# normal system user
NORMAL_USER=''

# default BlackArch Linux repository URL
#BA_REPO_URL='https://www.mirrorservice.org/sites/blackarch.org/blackarch/$repo/os/$arch'
#BA_REPO_URL='https://blackarch.unixpeople.org/$repo/os/$arch'
BA_REPO_URL='https://ftp.halifax.rwth-aachen.de/blackarch/$repo/os/$arch'

# default ArchLinux repository URL
AR_REPO_URL='https://mirror.rackspace.com/archlinux/$repo/os/$arch'

# where the blackarch-config-* packages stage their files (inside $CHROOT)
BA_CONFIG='/usr/share/blackarch/config'

# X (display + window managers ) setup - default: false
X_SETUP=$FALSE

# VirtualBox setup - default: false
VBOX_SETUP=$FALSE

# VMware setup - default: false
VMWARE_SETUP=$FALSE

# BlackArch Linux tools setup - default: false
BA_TOOLS_SETUP=$FALSE

# wlan ssid
WLAN_SSID=''

# wlan passphrase
WLAN_PASSPHRASE=''

# check boot mode
BOOT_MODE=''

# check type of ISO (Full? netinst?)
ISO_TYPE=''


# Exit on CTRL + c
ctrl_c() {
  err "Keyboard Interrupt detected, leaving..."
  exit $FAILURE
}

trap ctrl_c 2


# check exit status
check()
{
  es=$1
  func="$2"
  info="$3"

  if [ "$es" -ne 0 ]
  then
    echo
    warn "Something went wrong with $func. $info."
    sleep 5
  fi
}


# print formatted output
wprintf()
{
  fmt="${1}"

  shift
  printf "%s$fmt%s" "$WHITE" "$@" "$NC"

  return $SUCCESS
}


# print warning
warn()
{
  printf "%s[!] WARNING: %s%s\n" "$YELLOW" "$@" "$NC"

  return $SUCCESS
}


# print error and return failure
err()
{
  printf "%s[-] ERROR: %s%s\n" "$RED" "$@" "$NC"

  return $FAILURE
}

# leet banner (very important, very 1337)
banner()
{
  columns="$(tput cols)"
  str="--==[ blackarch-installer v$VERSION ]==--"

  printf "${BLUEB}%*s${NC}\n" "${COLUMNS:-$(tput cols)}" | tr ' ' '-'

  echo "$str" |
  while IFS= read -r line
  do
    printf "%s%*s\n%s" "$CYANB" $(( (${#line} + columns) / 2)) \
      "$line" "$NC"
  done

  printf "${BLUEB}%*s${NC}\n\n\n" "${COLUMNS:-$(tput cols)}" | tr ' ' '-'

  return $SUCCESS
}


# check boot mode
check_boot_mode()
{
  efivars=$(ls /sys/firmware/efi/efivars > /dev/null 2>&1; echo $?)
  if [ "$efivars" -eq "0" ]
  then
     BOOT_MODE="uefi"
  fi

  return $SUCCESS
}


# check type of iso
check_iso_type()
{
  if [ "$(which dnsspider 2> /dev/null)" ]
  then
    ISO_TYPE='full'
  else
    ISO_TYPE='net'
  fi

  return $SUCCESS
}


# sleep and clear
sleep_clear()
{
  sleep "$1"
  clear

  return $SUCCESS
}


# confirm user inputted yYnN
confirm()
{
  header="$1"
  ask="$2"

  while true
  do
    title "$header"
    wprintf "$ask"
    read -r input
    case $input in
      y|Y|yes|YES|Yes) return $TRUE ;;
      n|N|no|NO|No) return $FALSE ;;
      *) clear ; continue ;;
    esac
  done

  return $SUCCESS
}

install_keyrings()
{
  keyrings='archlinux-keyring'

  if grep -q '^\[blackarch\]' /etc/pacman.conf
  then
    keyrings="$keyrings blackarch-keyring"
  fi

  pacman -Sy --noconfirm --needed $keyrings > $VERBOSE 2>&1 ||
    warn 'Could not update keyrings, package installation may fail.'
}

# print menu title
title()
{
  banner
  printf "${CYAN}>> %s${NC}\n\n\n" "${@}"

  return "${SUCCESS}"
}


# check for environment issues
check_env()
{
  if [ -f '/var/lib/pacman/db.lck' ]
  then
    err 'pacman locked - Please remove /var/lib/pacman/db.lck'
  fi
}


# check user id
check_uid()
{
  if [ "$(id -u)" != '0' ]
  then
    err 'You must be root to run the BlackArch installer!'
  fi

  return $SUCCESS
}


# welcome and ask for installation mode
ask_install_mode()
{
  while
    [ "$INSTALL_MODE" != "$INSTALL_REPO" ] && \
    [ "$INSTALL_MODE" != "$INSTALL_BLACKMAN" ] && \
    [ "$INSTALL_MODE" != "$INSTALL_FULL_ISO" ]
  do
    title 'Welcome to the BlackArch Linux installer!'
    wprintf '[+] Available installation modes:'
    printf "\n
  1. Install from BlackArch repository (online)
  2. Install from BlackArch Full-ISO (offline)
  3. Install from sources using blackman (online)\n\n"
    wprintf '[?] Choose an installation mode: '
    read -r INSTALL_MODE
    if [ "$INSTALL_MODE" = "$INSTALL_FULL_ISO" ]
    then
      if [ "$ISO_TYPE" = "net" ]
      then
        err 'WTF, Full-ISO mode with Netinstall? Nope!'
        ask_install_mode
        INSTALL_MODE=''
      fi
    fi
    clear
  done

  return $SUCCESS
}


# ask for output mode
ask_output_mode()
{
  title 'Environment > Output Mode'
  wprintf '[+] Available output modes:'
  printf "\n
  1. Quiet (default)
  2. Verbose (output of system commands: mkfs, pacman, etc.)\n\n"
  wprintf "[?] Make a choice: "
  read -r output_opt
  if [ "$output_opt" = 2 ]
  then
    VERBOSE='/dev/stdout'
  fi

  return $SUCCESS
}


# ask for locale to use
ask_locale()
{
  while [ "$locale_opt" != "$SET_LOCALE" ] && \
    [ "$locale_opt" != "$LIST_LOCALE" ]
  do
    title 'Environment > Locale Setup'
    wprintf '[+] Available locale options:'
    printf "\n
  1. Set a locale
  2. List available locales\n\n"
    wprintf "[?] Make a choice: "
    read -r locale_opt
    if [ "$locale_opt" = "$SET_LOCALE" ]
    then
      break
    elif [ "$locale_opt" = "$LIST_LOCALE" ]
    then
      less /etc/locale.gen
      echo
    else
      clear
      continue
    fi
    clear
  done

  clear

  return $SUCCESS
}


# set locale to use
set_locale()
{
  title 'Environment > Locale Setup'
  wprintf '[?] Set locale [en_US.UTF-8]: '
  read -r LOCALE

  # default locale
  if [ -z "$LOCALE" ]
  then
    echo
    warn 'Setting default locale: en_US.UTF-8'
    sleep 1
    LOCALE='en_US.UTF-8'
  fi
  localectl set-locale "LANG=$LOCALE"
  check $? 'setting locale'

  return $SUCCESS
}


# ask for keymap to use
ask_keymap()
{
  while [ "$keymap_opt" != "$SET_KEYMAP" ] && \
    [ "$keymap_opt" != "$LIST_KEYMAP" ]
  do
    title 'Environment > Keymap Setup'
    wprintf '[+] Available keymap options:'
    printf "\n
  1. Set a keymap
  2. List available keymaps\n\n"
    wprintf '[?] Make a choice: '
    read -r keymap_opt

    if [ "$keymap_opt" = "$SET_KEYMAP" ]
    then
      break
    elif [ "$keymap_opt" = "$LIST_KEYMAP" ]
    then
      localectl list-keymaps
      echo
    else
      clear
      continue
    fi
    clear
  done

  clear

  return $SUCCESS
}


# set keymap to use
set_keymap()
{
  title 'Environment > Keymap Setup'
  wprintf '[?] Set keymap [us]: '
  read -r KEYMAP

  # default keymap
  if [ -z "$KEYMAP" ]
  then
    echo
    warn 'Setting default keymap: us'
    sleep 1
    KEYMAP='us'
  fi
  localectl set-keymap --no-convert "$KEYMAP"
  loadkeys "$KEYMAP" > $VERBOSE 2>&1
  check $? 'setting keymap'

  return $SUCCESS
}


# enable multilib in pacman.conf if x86_64 present
enable_pacman_multilib()
{
  path="$1"

  if [ "$path" = 'chroot' ]
  then
    path="$CHROOT"
  else
    path=""
  fi

  title 'Pacman Setup > Multilib'

  if [ "$(uname -m)" = "x86_64" ]
  then
    wprintf '[+] Enabling multilib support'
    printf "\n\n"
    if grep -q "#\[multilib\]" "$path/etc/pacman.conf"
    then
      # it exists but commented
      sed -i '/\[multilib\]/{ s/^#//; n; s/^#//; }' "$path/etc/pacman.conf"
    elif ! grep -q "\[multilib\]" "$path/etc/pacman.conf"
    then
      # it does not exist at all
      printf "[multilib]\nInclude = /etc/pacman.d/mirrorlist\n" \
        >> "$path/etc/pacman.conf"
    fi
  fi

  return $SUCCESS
}


# enable color mode in pacman.conf
enable_pacman_color()
{
  path="$1"

  if [ "$path" = 'chroot' ]
  then
    path="$CHROOT"
  else
    path=""
  fi

  title 'Pacman Setup > Color'

  wprintf '[+] Enabling color mode'
  printf "\n\n"

  sed -i 's/^#Color/Color/' "$path/etc/pacman.conf"

  return $SUCCESS
}


# enable misc options in pacman.conf
enable_pacman_misc()
{
  path="$1"

  if [ "$path" = 'chroot' ]
  then
    path="$CHROOT"
  else
    path=""
  fi

  title 'Pacman Setup > Misc Options'

  wprintf '[+] Enabling DisableDownloadTimeout'
  printf "\n\n"
  sed -i '37a DisableDownloadTimeout' "$path/etc/pacman.conf"

  # put here more misc options if necessary

  return $SUCCESS
}


# update pacman package database
update_pkg_database()
{
  title 'Pacman Setup > Package Database'

  wprintf '[+] Updating pacman database'
  printf "\n\n"

  pacman -Syy --noconfirm > $VERBOSE 2>&1

  return $SUCCESS
}


# update pacman.conf and database
update_pacman()
{
  enable_pacman_multilib
  sleep_clear 1

  enable_pacman_color
  sleep_clear 1

  enable_pacman_misc
  sleep_clear 1

  update_pkg_database
  sleep_clear 1

  return $SUCCESS
}


# ask user for hostname
ask_hostname()
{
  while [ -z "$HOST_NAME" ]
  do
    title 'Network Setup > Hostname'
    wprintf '[?] Set your hostname: '
    read -r HOST_NAME
  done

  return $SUCCESS
}

# get available network interfaces
get_net_ifs()
{
  NET_IFS="$(ip -o link show | awk -F': ' '{print $2}' |grep -v 'lo')"

  return $SUCCESS
}


# ask user for network interface
ask_net_if()
{
  while true
  do
    title 'Network Setup > Network Interface'
    wprintf '[+] Available network interfaces:'
    printf "\n\n"
    for i in $NET_IFS
    do
      echo "    > $i"
    done
    echo
    wprintf '[?] Please choose a network interface: '
    read -r NET_IF
    if echo "$NET_IFS" | grep "\<$NET_IF\>" > /dev/null
    then
      clear
      break
    fi
    clear
  done

  return $SUCCESS
}


# ask for networking configuration mode
ask_net_conf_mode()
{
  while [ "$NET_CONF_MODE" != "$NET_CONF_AUTO" ] && \
    [ "$NET_CONF_MODE" != "$NET_CONF_WLAN" ] && \
    [ "$NET_CONF_MODE" != "$NET_CONF_MANUAL" ] && \
    [ "$NET_CONF_MODE" != "$NET_CONF_SKIP" ]
  do
    title 'Network Setup > Network Interface'
    wprintf '[+] Network interface configuration:'
    printf "\n
  1. Auto DHCP (use this for auto connect via dhcp on selected interface)
  2. WiFi WPA Setup (use if you need to connect to a wlan before)
  3. Manual (use this if you are 1337)
  4. Skip (use this if you are already connected)\n\n"
    wprintf "[?] Please choose a mode: "
    read -r NET_CONF_MODE
    clear
  done

  return $SUCCESS
}


# ask for network addresses
ask_net_addr()
{
  while [ "$HOST_IPV4" = "" ] || \
    [ "$GATEWAY" = "" ] || [ "$SUBNETMASK" = "" ] || \
    [ "$BROADCAST" = "" ] || [ "$NAMESERVER" = "" ]
  do
    title 'Network Setup > Network Configuration (manual)'
    wprintf "[+] Configuring network interface $NET_IF via USER: "
    printf "\n
  > Host ipv4
  > Gateway ipv4
  > Subnetmask
  > Broadcast
  > Nameserver
    \n"
    wprintf '[?] Host IPv4: '
    read -r HOST_IPV4
    wprintf '[?] Gateway IPv4: '
    read -r GATEWAY
    wprintf '[?] Subnetmask: '
    read -r SUBNETMASK
    wprintf '[?] Broadcast: '
    read -r BROADCAST
    wprintf '[?] Nameserver: '
    read -r NAMESERVER
    clear
  done

  return $SUCCESS
}


# manual network interface configuration
net_conf_manual()
{
  title 'Network Setup > Network Configuration (manual)'
  wprintf "[+] Configuring network interface '$NET_IF' manually: "
  printf "\n\n"

  ip addr flush dev "$NET_IF"
  ip link set "$NET_IF" up
  ip addr add "$HOST_IPV4/$SUBNETMASK" broadcast "$BROADCAST" dev "$NET_IF"
  ip route add default via "$GATEWAY"
  echo "nameserver $NAMESERVER" > /etc/resolv.conf

  return $SUCCESS
}


# auto (dhcp) network interface configuration
net_conf_auto()
{
  opts='-h noleak -i noleak -v ,noleak -I noleak -t 10'

  title 'Network Setup > Network Configuration (auto)'
  wprintf "[+] Configuring network interface '$NET_IF' via DHCP: "
  printf "\n\n"

  dhcpcd "$opts" -i "$NET_IF" > $VERBOSE 2>&1

  sleep 10

  return $SUCCESS
}


# ask for wlan data (ssid, wpa passphrase, etc.)
ask_wlan_data()
{
  while [ "$WLAN_SSID" = "" ] || [ "$WLAN_PASSPHRASE" = "" ]
  do
    title 'Network Setup > Network Configuration (WiFi)'
    wprintf "[+] Configuring network interface $NET_IF via W-LAN + DHCP: "
    printf "\n
  > W-LAN SSID
  > WPA Passphrase (will not echo)
    \n"
    wprintf "[?] W-LAN SSID: "
    read -r WLAN_SSID
    wprintf "[?] WPA Passphrase: "
    read -rs WLAN_PASSPHRASE
    clear
  done

  return $SUCCESS
}


# wifi and auto dhcp network interface configuration
net_conf_wlan()
{
  wpasup="$(mktemp)"
  dhcp_opts='-h noleak -i noleak -v ,noleak -I noleak -t 10'

  title 'Network Setup > Network Configuration (WiFi)'
  wprintf "[+] Configuring network interface $NET_IF via W-LAN + DHCP: "
  printf "\n\n"

  wpa_passphrase "$WLAN_SSID" "$WLAN_PASSPHRASE" > "$wpasup"
  wpa_supplicant -B -c "$wpasup" -i "$NET_IF" > $VERBOSE 2>&1

  warn 'We need to wait a bit for wpa_supplicant and dhcpcd'

  sleep 10

  dhcpcd "$dhcp_opts" -i "$NET_IF" > $VERBOSE 2>&1

  sleep 10

  return $SUCCESS
}


# check for internet connection
check_inet_conn()
{
  title 'Network Setup > Connection Check'
  wprintf '[+] Checking for Internet connection...'

  if ! curl -s http://www.yahoo.com/ > $VERBOSE
  then
    err 'No Internet connection! Check your network (settings).'
    exit $FAILURE
  fi

}


# ask user for dualboot install
ask_dualboot()
{
  while [ "$DUALBOOT" = '' ]
  do
    if confirm 'Hard Drive Setup > DualBoot' '[?] Install BlackArch Linux with Windows/Other OS [y/n]: '
    then
      DUALBOOT=$TRUE
    else
      DUALBOOT=$FALSE
    fi
  done
  return $SUCCESS
}


# ask user for luks encrypted partition
ask_luks()
{
  while [ "$LUKS" = '' ]
  do
    if confirm 'Hard Drive Setup > Crypto' '[?] Full encrypted root [y/n]: '
    then
      LUKS=$TRUE
    else
      LUKS=$FALSE
      echo
      warn 'The root partition will NOT be encrypted'
    fi
    sleep_clear 2
  done
  return $SUCCESS
}


# get available hard disks
get_hd_devs()
{
  HD_DEVS="$(lsblk | grep disk | awk '{print $1}')"

  return $SUCCESS
}


# ask user for device to format and setup
ask_hd_dev()
{
  while true
  do
    title 'Hard Drive Setup'

    wprintf '[+] Available hard drives for installation:'
    printf "\n\n"

    for i in $HD_DEVS
    do
      echo "    > ${i}"
    done
    echo
    wprintf '[?] Please choose a device: '
    read -r HD_DEV
    if echo "$HD_DEVS" | grep "\<$HD_DEV\>" > /dev/null
    then
      HD_DEV="/dev/$HD_DEV"
      clear
      break
    fi
    clear
  done


  return $SUCCESS
}

# get available partitions on hard drive
get_partitions()
{
  PARTITIONS=$(fdisk -l "${HD_DEV}" -o device,size,type | \
    grep "${HD_DEV}[[:alnum:]]" |awk '{print $1;}')

  return $SUCCESS
}


# ask user to create partitions using cfdisk
ask_cfdisk()
{
  if confirm 'Hard Drive Setup > Partitions' '[?] Create partitions with cfdisk (root and boot, optional swap) [y/n]: '
  then
    clear
    zero_part
  else
    echo
    warn 'No partitions chosed? Make sure you have them already configured.'
    get_partitions
  fi

  return $SUCCESS
}


# zero out partition if needed/chosen
zero_part()
{
  if confirm 'Hard Drive Setup' '[?] Start with an in-memory zeroed partition table [y/n]: '
  then
    cfdisk -z "$HD_DEV"
    sync
  else
    cfdisk "$HD_DEV"
    sync
  fi
  get_partitions
  if [ -z "$PARTITIONS" ] ; then
    err 'You have not created partitions on your disk, make sure to write your changes before quitting cfdisk. Trying again...'
    zero_part
  fi
  if [ "$BOOT_MODE" = 'uefi' ] && ! fdisk -l "$HD_DEV" -o type | grep -qi 'EFI' ; then
    err 'You are booting in UEFI mode but not EFI partition was created, make sure you select the "EFI System" type for your EFI partition.'
    zero_part
  fi
  return $SUCCESS
}


# get partition label
get_partition_label()
{
  PART_LABEL="$(fdisk -l "$HD_DEV" |grep "Disklabel" | awk '{print $3;}')"

  return $SUCCESS
}


# turn user input like "p1", "1", "nvme0n1p1" or "/dev/nvme0n1p1" into a full
# device path. prints the normalized path (may still be invalid).
normalize_part()
{
  local input="$1"

  case "$input" in
    /dev/*) echo "$input" ;;
    "${HD_DEV#/dev/}"*) echo "/dev/$input" ;;
    *)
      # nvme/mmc/loop devices use a "p" separator (nvme0n1p1), sdX does not
      if [[ "$HD_DEV" =~ [0-9]$ ]] && [[ "$input" =~ ^[0-9]+$ ]]
      then
        echo "${HD_DEV}p${input}"
      else
        echo "${HD_DEV}${input}"
      fi
      ;;
  esac
}


# check that a full partition path is exactly one of the partitions on HD_DEV
is_valid_part()
{
  [ -n "$1" ] && printf '%s\n' "$PARTITIONS" | grep -qxF -- "$1"
}


# ask for one partition until the answer is valid, result in $REPLY_PART
read_part()
{
  local prompt="$1"
  local allow_empty="$2"
  local input=''

  REPLY_PART=''
  while true
  do
    wprintf "[?] $prompt (${HD_DEV}X): "
    read -r input
    if [ -z "$input" ] && [ "$allow_empty" = 'empty' ]
    then
      return $SUCCESS
    fi
    REPLY_PART="$(normalize_part "$input")"
    if is_valid_part "$REPLY_PART"
    then
      return $SUCCESS
    fi
    wprintf "[!] $REPLY_PART is not a partition on $HD_DEV. Valid choices:\n"
    printf '%s\n' "$PARTITIONS" | sed 's/^/    > /'
  done
}


# get partitions
ask_partitions()
{
  while [ "$BOOT_PART" = '' ] || \
    [ "$ROOT_PART" = '' ] || \
    [ "$BOOT_FS_TYPE" = '' ] || \
    [ "$ROOT_FS_TYPE" = '' ]
  do
    title 'Hard Drive Setup > Partitions'
    wprintf '[+] Created partitions:'
    printf "\n\n"

    fdisk -l "${HD_DEV}" -o device,size,type |grep "${HD_DEV}[[:alnum:]]"

    echo

    get_partitions

    # nothing to choose from (e.g. "no" to cfdisk on an empty disk):
    # read_part could never accept an answer, so go create partitions
    if [ -z "$PARTITIONS" ]
    then
      err "No partitions found on $HD_DEV, opening cfdisk to create them..."
      sleep_clear 3
      zero_part
      get_partition_label
      continue
    fi

    if [ "$BOOT_MODE" = 'uefi' ]  && [ "$PART_LABEL" = 'gpt' ]
    then
      read_part 'EFI System partition'
      BOOT_PART="$REPLY_PART"
      BOOT_FS_TYPE="fat32"
    else
      read_part 'Boot partition'
      BOOT_PART="$REPLY_PART"
      wprintf '[?] Choose a filesystem to use in your boot partition (ext2, ext3, ext4)? (default: ext4): '
      read -r BOOT_FS_TYPE
      if [ -z "$BOOT_FS_TYPE" ]; then
        BOOT_FS_TYPE="ext4"
      fi
    fi
    read_part 'Root partition'
    ROOT_PART="$REPLY_PART"
    if [ "$ROOT_PART" = "$BOOT_PART" ]
    then
      err 'Root and boot partition must be different.'
      ROOT_PART=''
      sleep_clear 2
      continue
    fi
    wprintf '[?] Choose a filesystem to use in your root partition (ext2, ext3, ext4, btrfs)? (default: ext4): '
    read -r ROOT_FS_TYPE
    if [ -z "$ROOT_FS_TYPE" ]; then
      ROOT_FS_TYPE="ext4"
    fi
    read_part 'Swap partition - empty for none' 'empty'
    while [ -n "$REPLY_PART" ] &&
      { [ "$REPLY_PART" = "$BOOT_PART" ] || [ "$REPLY_PART" = "$ROOT_PART" ]; }
    do
      err 'Swap, root and boot partitions must be different.'
      read_part 'Swap partition - empty for none' 'empty'
    done
    SWAP_PART="$REPLY_PART"

    if [ "$SWAP_PART" = '' ]
    then
      SWAP_PART='none'
    fi
    clear
  done

  return $SUCCESS
}


# print partitions and ask for confirmation
print_partitions()
{
  i=""

  while true
  do
    title 'Hard Drive Setup > Partitions'
    wprintf '[+] Current Partition table'
    printf "\n
  > /boot   : %s (%s)
  > /       : %s (%s)
  > swap    : %s (swap)
  \n" "$BOOT_PART" "$BOOT_FS_TYPE" \
      "$ROOT_PART" "$ROOT_FS_TYPE" \
      "$SWAP_PART"
    wprintf '[?] Partition table correct [y/n]: '
    read -r i
    if [ "$i" = 'y' ] || [ "$i" = 'Y' ]
    then
      clear
      break
    elif [ "$i" = 'n' ] || [ "$i" = 'N' ]
    then
      echo
      err 'Hard Drive Setup aborted.'
      exit $FAILURE
    else
      clear
      continue
    fi
    clear
  done

  return $SUCCESS
}


# ask user and get confirmation for formatting
ask_formatting()
{
  if confirm 'Hard Drive Setup > Partition Formatting' '[?] Formatting partitions. Are you sure? No crying afterwards? [y/n]: '
  then
    return $SUCCESS
  else
    echo
    err 'Seriously? No formatting no fun! Please format to continue or CTRL + c to cancel...'
    ask_formatting
  fi

}


# create LUKS encrypted partition
make_luks_partition()
{
  part="$1"

  title 'Hard Drive Setup > Partition Creation (crypto)'

  wprintf '[+] Creating LUKS partition'
  printf "\n\n"

  # a mapping left over from an earlier run keeps the partition busy and
  # makes luksFormat fail no matter which passphrase is typed (issue #104)
  if [ -e "/dev/mapper/$CRYPT_ROOT" ]
  then
    umount -R "/dev/mapper/$CRYPT_ROOT" > /dev/null 2>&1
    cryptsetup close "$CRYPT_ROOT" > /dev/null 2>&1
  fi

  # stderr is shown on purpose: it holds the real reason for a failure
  # ("Passphrases do not match", "device in use", ...). the passphrase
  # prompts themselves go to /dev/tty
  until cryptsetup -q -y -v luksFormat "$part" > $VERBOSE
  do
    echo
    err 'Could not LUKS format (see the cryptsetup message above).'
    if ! confirm 'Hard Drive Setup > Partition Creation (crypto)' \
      '[?] Try again [y/n]: '
    then
      exit $FAILURE
    fi
  done

  return $SUCCESS
}


# open LUKS partition
open_luks_partition()
{
  part="$1"
  name="$2"

  title 'Hard Drive Setup > Partition Creation (crypto)'

  wprintf '[+] Opening LUKS partition'
  printf "\n\n"
  until cryptsetup open "$part" "$name" > $VERBOSE
  do
    echo
    err 'Could not open LUKS device (see the cryptsetup message above).'
    if ! confirm 'Hard Drive Setup > Partition Creation (crypto)' \
      '[?] Try again [y/n]: '
    then
      exit $FAILURE
    fi
  done

  return $SUCCESS
}


# create swap partition
make_swap_partition()
{
  title 'Hard Drive Setup > Partition Creation (swap)'

  wprintf '[+] Creating SWAP partition'
  printf "\n\n"
  mkswap $SWAP_PART > $VERBOSE 2>&1 || { err 'Could not create filesystem'; exit $FAILURE; }

}


# make and format root partition
make_root_partition()
{
  if [ $LUKS = $TRUE ]
  then
    make_luks_partition "$ROOT_PART"
    sleep_clear 1
    open_luks_partition "$ROOT_PART" "$CRYPT_ROOT"
    sleep_clear 1
    title 'Hard Drive Setup > Partition Creation (root crypto)'
    wprintf '[+] Creating encrypted ROOT partition'
    printf "\n\n"
    if [ "$ROOT_FS_TYPE" = 'btrfs' ]
    then
      mkfs.$ROOT_FS_TYPE -f "/dev/mapper/$CRYPT_ROOT" > $VERBOSE 2>&1 ||
        { err 'Could not create filesystem'; exit $FAILURE; }
    else
      mkfs.$ROOT_FS_TYPE -F "/dev/mapper/$CRYPT_ROOT" > $VERBOSE 2>&1 ||
        { err 'Could not create filesystem'; exit $FAILURE; }
    fi
    sleep_clear 1
  else
    title 'Hard Drive Setup > Partition Creation (root)'
    wprintf '[+] Creating ROOT partition'
    printf "\n\n"
    if [ "$ROOT_FS_TYPE" = 'btrfs' ]
    then
      mkfs.$ROOT_FS_TYPE -f "$ROOT_PART" > $VERBOSE 2>&1 ||
        { err 'Could not create filesystem'; exit $FAILURE; }
    else
      mkfs.$ROOT_FS_TYPE -F "$ROOT_PART" > $VERBOSE 2>&1 ||
        { err 'Could not create filesystem'; exit $FAILURE; }
    fi
    sleep_clear 1
  fi

  return $SUCCESS
}


# make and format boot partition
make_boot_partition()
{
  if [ "$BOOT_MODE" = 'uefi' ] && [ "$PART_LABEL" = 'gpt' ] && [ $DUALBOOT = $TRUE ]
  then
    return $SUCCESS
  fi

  title 'Hard Drive Setup > Partition Creation (boot)'

  wprintf '[+] Creating BOOT partition'
  printf "\n\n"
  if [ "$BOOT_MODE" = 'uefi' ] && [ "$PART_LABEL" = 'gpt' ]
  then
    mkfs.fat -F32 "$BOOT_PART" > $VERBOSE 2>&1 ||
      { err 'Could not create filesystem'; exit $FAILURE; }
  else
    mkfs.$BOOT_FS_TYPE -F "$BOOT_PART" > $VERBOSE 2>&1 ||
      { err 'Could not create filesystem'; exit $FAILURE; }
  fi

  return $SUCCESS
}


# make and format partitions
make_partitions()
{
  make_boot_partition
  sleep_clear 1

  make_root_partition
  sleep_clear 1

  if [ "$SWAP_PART" != "none" ]
  then
    make_swap_partition
    sleep_clear 1
  fi

  return $SUCCESS
}


# mount filesystems
mount_filesystems()
{
  title 'Hard Drive Setup > Mount'

  wprintf '[+] Mounting filesystems'
  printf "\n\n"

  # ROOT
  if [ $LUKS = $TRUE ]; then
    if ! mount "/dev/mapper/$CRYPT_ROOT" $CHROOT; then
      err "Error mounting root filesystem, leaving."
      exit $FAILURE
    fi
  else
    if ! mount "$ROOT_PART" $CHROOT; then
      err "Error mounting root filesystem, leaving."
      exit $FAILURE
    fi
  fi

  # BOOT
  mkdir "$CHROOT/boot" > $VERBOSE 2>&1
  if ! mount "$BOOT_PART" "$CHROOT/boot"; then
    err "Error mounting boot partition, leaving."
    exit $FAILURE
  fi

  # SWAP
  if [ "$SWAP_PART" != "none" ]
  then
    swapon $SWAP_PART > $VERBOSE 2>&1
  fi

  return $SUCCESS
}


# unmount filesystems
umount_filesystems()
{
  routine="$1"

  if [ "$routine" = 'harddrive' ]
  then
    title 'Hard Drive Setup > Unmount'

    wprintf '[+] Unmounting filesystems'
    printf "\n\n"

    umount -Rf /mnt > /dev/null 2>&1; \
    umount -Rf "$HD_DEV"{1..128} "${HD_DEV}p"{1..128} > /dev/null 2>&1 # gpt max - 128
    cryptsetup close "$CRYPT_ROOT" > /dev/null 2>&1
    swapoff "$HD_DEV"?* > /dev/null 2>&1
  else
    title 'Game Over'

    wprintf '[+] Unmounting filesystems'
    printf "\n\n"

    umount -Rf $CHROOT > /dev/null 2>&1
    cryptsetup luksClose "$CRYPT_ROOT" > /dev/null 2>&1
    swapoff $SWAP_PART > /dev/null 2>&1
  fi

  return $SUCCESS
}


# check for necessary space
check_space()
{
  if [ $LUKS -eq $TRUE ]
  then
    avail_space=$(df -m | grep "/dev/mapper/$CRYPT_ROOT" | awk '{print $4}')
  else
    avail_space=$(df -m | grep "$ROOT_PART" | awk '{print $4}')
  fi

  if [ "$avail_space" -le 40960 ]
  then
    warn 'BlackArch Linux requires at least 40 GB of free space to install!'
  fi

  return $SUCCESS
}


# install ArchLinux base and base-devel packages
install_base_packages()
{
  title 'Base System Setup > ArchLinux Packages'

  wprintf '[+] Installing ArchLinux base packages'
  printf "\n\n"
  warn 'This can take a while, please wait...'
  printf "\n"

  if ! pacstrap $CHROOT base base-devel btrfs-progs linux linux-firmware \
    terminus-font terminus-font-ttf > $VERBOSE 2>&1
  then
    echo
    err 'pacstrap failed, the base system was NOT installed.'
    warn 'Common causes: no network, bad mirror or outdated keyring.'
    warn 'Try "pacman -Sy archlinux-keyring" and restart the installer, or use verbose output mode.'
    exit $FAILURE
  fi
  chroot $CHROOT pacman -Syy --noconfirm --overwrite='*' > $VERBOSE 2>&1

  return $SUCCESS
}


# setup /etc/resolv.conf
setup_resolvconf()
{
  title 'Base System Setup > Etc'

  wprintf '[+] Setting up /etc/resolv.conf'
  printf "\n\n"

  mkdir -p "$CHROOT/etc/" > $VERBOSE 2>&1
  cp -L /etc/resolv.conf "$CHROOT/etc/resolv.conf" > $VERBOSE 2>&1

  return $SUCCESS
}


# setup fstab
setup_fstab()
{
  title 'Base System Setup > Etc'

  wprintf '[+] Setting up /etc/fstab'
  printf "\n\n"

  if [ "$PART_LABEL" = "gpt" ]
  then
    genfstab -U $CHROOT >> "$CHROOT/etc/fstab"
  else
    genfstab -L $CHROOT >> "$CHROOT/etc/fstab"
  fi

  sed 's/relatime/noatime/g' -i "$CHROOT/etc/fstab"

  return $SUCCESS
}


# setup locale and keymap
setup_locale()
{
  title 'Base System Setup > Locale'

  wprintf "[+] Setting up $LOCALE locale"
  printf "\n\n"
  sed -i "s/^#en_US.UTF-8/en_US.UTF-8/" "$CHROOT/etc/locale.gen"
  sed -i "s/^#$LOCALE/$LOCALE/" "$CHROOT/etc/locale.gen"
  chroot $CHROOT locale-gen > $VERBOSE 2>&1
  echo "LANG=$LOCALE" > "$CHROOT/etc/locale.conf"
  echo "KEYMAP=$KEYMAP" > "$CHROOT/etc/vconsole.conf"

  return $SUCCESS
}


# setup timezone
setup_time()
{
  if confirm 'Base System Setup > Timezone' '[?] Default: UTC. Choose other timezone [y/n]: '
  then
    for t in $(timedatectl list-timezones)
    do
      echo "    > $t"
    done

    wprintf "\n[?] What is your (Zone/SubZone): "
    read -r timezone
    chroot $CHROOT ln -sf "/usr/share/zoneinfo/$timezone" /etc/localtime \
      > $VERBOSE 2>&1

    if [ $? -eq 1 ]
    then
      warn 'Do you live on Mars? Setting default time zone...'
      sleep 1
      default_time
    else
      wprintf "\n[+] Time zone setup correctly\n"
    fi
  else
    wprintf "\n[+] Setting up default time and timezone\n"
    sleep 2
    default_time
  fi

  printf "\n"

  return $SUCCESS
}


# default time and timezone
default_time()
{
  echo
  warn 'Setting up default time and timezone: UTC'
  printf "\n\n"
  chroot $CHROOT ln -sf /usr/share/zoneinfo/UTC /etc/localtime > $VERBOSE 2>&1

  return $SUCCESS
}


# setup initramfs
setup_initramfs()
{
  title 'Base System Setup > InitramFS'

  wprintf '[+] Setting up InitramFS'
  printf "\n\n"

  cp -f "$BI_PATH/data/etc/mkinitcpio.conf" "$CHROOT/etc/mkinitcpio.conf"
  cp -fr "$BI_PATH/data/etc/mkinitcpio.d" "$CHROOT/etc/"

  if [ "$INSTALL_MODE" = "$INSTALL_FULL_ISO" ]
  then
    cp /run/archiso/bootmnt/blackarch/boot/x86_64/vmlinuz-linux \
      "$CHROOT/boot/vmlinuz-linux"
  fi

  # terminus font
  sed -i 's/keyboard fsck/keyboard fsck consolefont/g' \
    "$CHROOT/etc/mkinitcpio.conf"
  echo 'FONT=ter-114n' >> "$CHROOT/etc/vconsole.conf"

  if [ $LUKS = $TRUE ]
  then
    sed -i 's/block filesystems/block keymap encrypt filesystems/g' \
      "$CHROOT/etc/mkinitcpio.conf"
  fi

  warn 'This can take a while, please wait...'
  printf "\n"
  chroot $CHROOT mkinitcpio -P > $VERBOSE 2>&1

  return $SUCCESS
}


# mount /proc, /sys and /dev
setup_proc_sys_dev()
{
  title 'Base System Setup > Proc Sys Dev'

  wprintf '[+] Setting up /proc, /sys and /dev'
  printf "\n\n"

  mkdir -p "${CHROOT}/"{proc,sys,dev} > $VERBOSE 2>&1

  mount -t proc proc "$CHROOT/proc" > $VERBOSE 2>&1
  mount --rbind /sys "$CHROOT/sys" > $VERBOSE 2>&1
  mount --make-rslave "$CHROOT/sys" > $VERBOSE 2>&1
  mount --rbind /dev "$CHROOT/dev" > $VERBOSE 2>&1
  mount --make-rslave "$CHROOT/dev" > $VERBOSE 2>&1

  return $SUCCESS
}


# setup hostname
setup_hostname()
{
  title 'Base System Setup > Hostname'

  wprintf '[+] Setting up hostname'
  printf "\n\n"

  echo "$HOST_NAME" > "$CHROOT/etc/hostname"

  return $SUCCESS
}


# print the graphics related parameters the live system was booted with, so
# the installed system boots the same way. without this, a machine that only
# comes up with e.g. "nomodeset" or "nouveau.modeset=0" gets a black screen
# on the first boot of the installed system.
get_live_gfx_params()
{
  local -a words
  local w out=''

  read -r -a words < /proc/cmdline 2> /dev/null

  for w in "${words[@]}"
  do
    case "$w" in
      nomodeset|modprobe.blacklist=*|video=*)
        out="$out $w"
        ;;
      *=*)
        # <module>.modeset=<value>, e.g. i915.modeset=0 or nvidia-drm.modeset=1
        case "${w%%=*}" in
          *.modeset) out="$out $w" ;;
        esac
        ;;
    esac
  done

  merge_kernel_params '' "$out"
}


# merge two lists of kernel parameters, dropping duplicates and quotes
merge_kernel_params()
{
  local -
  local w out=''

  set -f
  for w in $(printf '%s %s' "$1" "$2" | tr -d "\"'")
  do
    case " $out " in
      *" $w "*) ;;
      *) out="${out:+$out }$w" ;;
    esac
  done

  echo "$out"
}


# add the live graphics parameters to GRUB_CMDLINE_LINUX_DEFAULT
add_gfx_params_grub()
{
  local gfx grub_def="$CHROOT/etc/default/grub"

  gfx="$(get_live_gfx_params)"

  if [ -z "$gfx" ] || [ ! -f "$grub_def" ]
  then
    return $SUCCESS
  fi

  GFX="$gfx" awk -v F="$grub_def" '
    function merge(cur, extra,    t, n, i, r) {
      gsub(/["\047]/, "", cur)
      n = split(cur " " extra, t, " ")
      r = ""
      for (i = 1; i <= n; i++)
        if (t[i] != "" && index(" " r " ", " " t[i] " ") == 0)
          r = (r == "" ? t[i] : r " " t[i])
      return r
    }
    BEGIN {
      add = ENVIRON["GFX"]
      out = ""
      done = 0
      while ((getline l < F) > 0) {
        if (!done && l ~ /^GRUB_CMDLINE_LINUX_DEFAULT=/) {
          l = "GRUB_CMDLINE_LINUX_DEFAULT=\"" merge(substr(l, 28), add) "\""
          done = 1
        }
        out = out l "\n"
      }
      close(F)
      if (!done)
        out = out "GRUB_CMDLINE_LINUX_DEFAULT=\"" merge("", add) "\"\n"
      printf "%s", out > F
      close(F)
    }'

  wprintf "[+] Kept graphics boot parameters from the live system: $gfx"
  printf "\n\n"

  return $SUCCESS
}


# list the PARTUUIDs of all partitions currently present (lowercase)
get_present_partuuids()
{
  {
    lsblk -rno PARTUUID 2> /dev/null
    blkid -s PARTUUID -o value 2> /dev/null
  } | tr '[:upper:]' '[:lower:]' | sed '/^$/d' | sort -u
}


# run efibootmgr from the live system, or from the new system as fallback
run_efibootmgr()
{
  if command -v efibootmgr > /dev/null 2>&1
  then
    efibootmgr "$@"
  elif [ -x "$CHROOT/usr/bin/efibootmgr" ]
  then
    chroot $CHROOT efibootmgr "$@"
  else
    return $FAILURE
  fi
}


# remove systemd-boot NVRAM entries that point to partitions which do not
# exist anymore. every reinstall recreates the ESP with a new PARTUUID and
# "bootctl install" registers a new "Linux Boot Manager" (plus "Fallback
# Linux Boot Manager") entry, while the old ones stay in the firmware
# forever. entries of other boot loaders and of existing partitions are left
# alone.
clean_stale_efi_entries()
{
  local present entries line num label guid removed=''

  if ! entries="$(run_efibootmgr -v 2> /dev/null)"
  then
    warn 'efibootmgr not available, cannot clean up old EFI boot entries.'
    printf "\n"
    return $SUCCESS
  fi

  present="$(get_present_partuuids)"

  # without a list of partitions we cannot tell stale from valid entries
  if [ -z "$present" ]
  then
    return $SUCCESS
  fi

  while IFS= read -r line
  do
    num="$(printf '%s\n' "$line" |
      sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\).*/\1/p')"
    [ -z "$num" ] && continue

    label="$(printf '%s\n' "$line" |
      sed 's/^Boot[0-9A-Fa-f]\{4\}[* ] *//' |
      sed 's/[[:space:]]*\(HD\|PciRoot\|VenHw\|VenMsg\|BBS\)(.*//')"
    case "$label" in
      'Linux Boot Manager'|'Fallback Linux Boot Manager') ;;
      *) continue ;;
    esac

    # only touch entries that start systemd-boot
    printf '%s\n' "$line" | grep -qi 'systemd' || continue

    guid="$(printf '%s\n' "$line" |
      grep -oiE 'HD\([0-9]+,GPT,[0-9a-f-]{36}' | head -1 | cut -d ',' -f 3 |
      tr '[:upper:]' '[:lower:]')"
    [ -z "$guid" ] && continue

    if ! printf '%s\n' "$present" | grep -qxF "$guid"
    then
      if run_efibootmgr -q -b "$num" -B > $VERBOSE 2>&1
      then
        removed="$removed Boot$num"
      fi
    fi
  done << EOT
$entries
EOT

  if [ -n "$removed" ]
  then
    wprintf "[+] Removed stale EFI boot entries:$removed"
    printf "\n\n"
  fi

  return $SUCCESS
}


# setup boot loader for UEFI/GPT or BIOS/MBR
setup_bootloader()
{
  local gfx opts

  title 'Base System Setup > Boot Loader'

  if [ "$BOOT_MODE" = 'uefi' ] && [ "$PART_LABEL" = 'gpt' ]
  then
    wprintf '[+] Setting up EFI boot loader'
    printf "\n\n"

    clean_stale_efi_entries

    chroot $CHROOT bootctl install > $VERBOSE 2>&1
    uuid="$(blkid "$ROOT_PART" | cut -d ' ' -f 2 | cut -d '"' -f 2)"

    if [ $LUKS = $TRUE ]
    then
      opts="cryptdevice=UUID=$uuid:$CRYPT_ROOT root=/dev/mapper/$CRYPT_ROOT rw"
    else
      opts="root=UUID=$uuid rw"
    fi

    gfx="$(get_live_gfx_params)"
    if [ -n "$gfx" ]
    then
      opts="$(merge_kernel_params "$opts" "$gfx")"
      wprintf "[+] Kept graphics boot parameters from the live system: $gfx"
      printf "\n\n"
    fi

    # overwrite, don't append: a reused ESP (dualboot) would otherwise end up
    # with the entry duplicated inside the same file
    mkdir -p "$CHROOT/boot/loader/entries"
    cat > "$CHROOT/boot/loader/entries/arch.conf" << EOF
title   BlackArch Linux
linux   /vmlinuz-linux
initrd    /initramfs-linux.img
options   $opts
EOF
  else
    wprintf '[+] Setting up GRUB boot loader'
    printf "\n\n"

    uuid="$(lsblk -o UUID "$ROOT_PART" | sed -n 2p)"

    if [ "$INSTALL_MODE" != "$INSTALL_FULL_ISO" ]
    then
      chroot $CHROOT pacman -S grub --noconfirm --overwrite='*' --needed \
        > $VERBOSE 2>&1
    else
      mkdir -p "$CHROOT/boot/grub"
    fi

    if [ $DUALBOOT = $TRUE ]
    then
      chroot $CHROOT pacman -S os-prober --noconfirm --overwrite='*' --needed \
        > $VERBOSE 2>&1
    fi

    if [ $LUKS = $TRUE ]
    then
      sed -i "s|quiet|cryptdevice=UUID=$uuid:$CRYPT_ROOT root=/dev/mapper/$CRYPT_ROOT quiet|" \
        "$CHROOT/etc/default/grub"
    fi
    sed -i 's/Arch/BlackArch/g' "$CHROOT/etc/default/grub"
    echo "GRUB_BACKGROUND=\"/boot/grub/splash.png\"" >> \
      "$CHROOT/etc/default/grub"

    sed -i 's/#GRUB_COLOR_/GRUB_COLOR_/g' "$CHROOT/etc/default/grub"

    add_gfx_params_grub

    chroot $CHROOT grub-install --target=i386-pc "$HD_DEV" > $VERBOSE 2>&1

    cp -f "$BI_PATH/data/boot/grub/splash.png" "$CHROOT/boot/grub/splash.png"

    chroot $CHROOT grub-mkconfig -o /boot/grub/grub.cfg > $VERBOSE 2>&1

  fi

  return $SUCCESS
}


# ask for normal user account to setup
ask_user_account()
{
  if confirm 'Base System Setup > User' '[?] Setup a normal user account [y/n]: '
  then
    wprintf '[?] User name: '
    read -r NORMAL_USER
  fi

  return $SUCCESS
}


# setup blackarch test user (not active + lxdm issue)
setup_testuser()
{
  title 'Base System Setup > Test User'

  wprintf '[+] Setting up test user blackarchtest account'
  printf "\n\n"
  warn 'Remove this user after you added a normal system user account'
  printf "\n"

  chroot $CHROOT groupadd blackarchtest > $VERBOSE 2>&1
  chroot $CHROOT useradd -g blackarchtest -d /home/blackarchtest/ \
    -s /sbin/nologin -m blackarchtest > $VERBOSE 2>&1
}


# turn user input into a valid user name
sanitize_username()
{
  echo "$1" | tr -dc '[:alnum:]_' | tr '[:upper:]' '[:lower:]' | cut -c 1-32
}


# setup user account, password and environment
setup_user()
{
  user="$(sanitize_username "$1")"

  title 'Base System Setup > User'

  wprintf "[+] Setting up $user account"
  printf "\n\n"

  # dotfiles (bash, vim, X11, i3) come from the blackarch-config-* packages,
  # see setup_dotfiles(), setup_display_manager() and setup_wm_i3()
  if [ "$user" = 'root' ]
  then
    # root environment (helper scripts)
    cp -r "$BI_PATH/data/root/." "$CHROOT/root/." > $VERBOSE 2>&1
  else
    # normal user
    chroot $CHROOT groupadd "$user" > $VERBOSE 2>&1
    chroot $CHROOT useradd -g "$user" -d "/home/$user" -s "/bin/bash" \
      -G "$user,wheel,users,video,audio" -m "$user" > $VERBOSE 2>&1
    chroot $CHROOT chown -R "$user":"$user" "/home/$user" > $VERBOSE 2>&1
    wprintf "[+] Added user: $user"
    printf "\n\n"
  fi

  # password
  res=1337
  wprintf "[?] Set password for $user: "
  printf "\n\n"
  while [ $res -ne 0 ]
  do
    if [ "$user" = "root" ]
    then
      chroot $CHROOT passwd
    else
      chroot $CHROOT passwd "$user"
    fi
    res=$?
    # 125-127: chroot or passwd could not run at all (the base system is
    # missing or broken), retrying would loop forever (issue #104)
    if [ $res -ge 125 ] && [ $res -le 127 ]
    then
      echo
      err "Cannot run passwd inside $CHROOT, the base system looks incomplete."
      exit $FAILURE
    fi
  done

  return $SUCCESS
}

reinitialize_keyring()
{
  title 'Base System Setup > Keyring Reinitialization'

  wprintf '[+] Reinitializing keyrings'
  printf "\n"
  sleep 2

  chroot $CHROOT pacman -S --overwrite='*' --noconfirm archlinux-keyring \
    > $VERBOSE 2>&1

  return $SUCCESS
}


chroot_install()
{
  local pkgs='' missing='' p

  for p in "$@"
  do
    if chroot $CHROOT pacman -Sp "$p" > /dev/null 2>&1
    then
      pkgs="$pkgs $p"
    else
      missing="$missing $p"
    fi
  done

  if [ -n "$missing" ]
  then
    warn "Skipping packages not found in the repos:$missing"
    printf "\n"
  fi

  if [ -z "$pkgs" ]
  then
    return $FAILURE
  fi

  # shellcheck disable=SC2086
  if ! chroot $CHROOT pacman -S --needed --overwrite='*' --noconfirm $pkgs \
    > $VERBOSE 2>&1
  then
    warn 'Some packages failed to install.'
    return $FAILURE
  fi

  return $SUCCESS
}


# the blackarch-config-* packages only stage their files (mostly below
# $BA_CONFIG, lxdm below /etc/lxdm-blackarch and /usr/share/*-blackarch);
# nothing is copied into place by pacman, so the installer does that step.
# $1 is the staged file or directory and $2 the destination, both paths inside
# $CHROOT. a directory is merged into the destination directory, a file
# replaces the destination file.
copy_ba_config()
{
  local src="$CHROOT$1" dst="$CHROOT$2"

  if [ ! -e "$src" ]
  then
    warn "Missing $1, is its blackarch-config package installed?"
    printf "\n"
    return $FAILURE
  fi

  if [ -d "$src" ]
  then
    mkdir -p "$dst"
    cp -a "$src/." "$dst/." > $VERBOSE 2>&1
  else
    mkdir -p "$(dirname "$dst")"
    cp -af "$src" "$dst" > $VERBOSE 2>&1
  fi
}


# copy a staged config into the home directory of root and of the normal user
# $1: source, relative to $BA_CONFIG
# $2: destination, relative to the home directory (e.g. .config/i3/config)
copy_ba_home_config()
{
  local src="$BA_CONFIG/$1" dst="$2" u home users='root'

  if [ -n "$NORMAL_USER" ]
  then
    users="$users $(sanitize_username "$NORMAL_USER")"
  fi

  for u in $users
  do
    home="$(chroot $CHROOT getent passwd "$u" | cut -d ':' -f 6)"
    if [ -z "$home" ] || [ ! -d "$CHROOT$home" ]
    then
      continue
    fi

    copy_ba_config "$src" "$home/$dst" || return $FAILURE

    # chown the top-level entry, so that e.g. ~/.config is owned by the user
    chroot $CHROOT chown -R "$u:" "$home/${dst%%/*}" > $VERBOSE 2>&1
  done

  return $SUCCESS
}


# install extra (missing) packages
setup_extra_packages()
{
  arch='arch-install-scripts pkgfile'

  bluetooth='bluez bluez-hid2hci bluez-tools bluez-utils'

  browser='chromium elinks firefox'

  editor='hexedit nano vim'

  filesystem='cifs-utils dmraid dosfstools exfat-utils f2fs-tools
  gpart gptfdisk mtools nilfs-utils ntfs-3g partclone parted partimage'

  fonts='ttf-dejavu ttf-indic-otf ttf-liberation xorg-fonts-misc'

  hardware='amd-ucode efibootmgr intel-ucode'

  kernel='linux-headers'

  misc='acpi alsa-utils b43-fwcutter bash-completion bc cmake ctags expac
  feh git gpm haveged hdparm htop inotify-tools ipython irssi
  linux-atm lsof mercurial mesa mlocate moreutils mpv p7zip rsync
  rtorrent screen scrot smartmontools strace tmux udisks2 unace unrar
  unzip upower usb_modeswitch usbutils zip zsh'

  network='atftp bind-tools bridge-utils curl darkhttpd dhclient dhcpcd dialog
  dnscrypt-proxy dnsmasq dnsutils fwbuilder iw  iwd lftp nfs-utils
  ntp openbsd-netcat openconnect openssh openvpn ppp pptpclient rfkill
  rp-pppoe socat vpnc wget wireless_tools wpa_supplicant wvdial xl2tpd'

  xorg='rxvt-unicode xf86-video-amdgpu xf86-video-ati
  xf86-video-dummy xf86-video-fbdev xf86-video-intel xf86-video-nouveau
  xf86-video-sisusb xf86-video-vesa
  xf86-video-voodoo xorg-server xorg-xbacklight xorg-xinit xterm'

  all="$arch $bluetooth $browser $editor $filesystem $fonts $hardware $kernel"
  all="$all $misc $network $xorg"

  title 'Base System Setup > Extra Packages'

  wprintf '[+] Installing extra packages'
  printf "\n"

  printf "
  > ArchLinux   : $(echo "$arch" | wc -w) packages
  > Browser     : $(echo "$browser" | wc -w) packages
  > Bluetooth   : $(echo "$bluetooth" | wc -w) packages
  > Editor      : $(echo "$editor" | wc -w) packages
  > Filesystem  : $(echo "$filesystem" | wc -w) packages
  > Fonts       : $(echo "$fonts" | wc -w) packages
  > Hardware    : $(echo "$hardware" | wc -w) packages
  > Kernel      : $(echo "$kernel" | wc -w) packages
  > Misc        : $(echo "$misc" | wc -w) packages
  > Network     : $(echo "$network" | wc -w) packages
  > Xorg        : $(echo "$xorg" | wc -w) packages
  \n"

  warn 'This can take a while, please wait...'
  printf "\n"
  sleep 2

  chroot_install $all

  return $SUCCESS
}


# perform system base setup/configurations
setup_base_system()
{
  if [ "$INSTALL_MODE" = "$INSTALL_FULL_ISO" ]
  then
    dump_full_iso
    sleep_clear 1
  fi

  if [ "$INSTALL_MODE" != "$INSTALL_FULL_ISO" ]
  then
    pass_mirror_conf # copy mirror list to chroot env

    setup_resolvconf
    sleep_clear 1

    install_base_packages
    sleep_clear 1

    setup_resolvconf
    sleep_clear 1
  fi

  setup_fstab
  sleep_clear 1

  setup_proc_sys_dev
  sleep_clear 1

  setup_locale
  sleep_clear 1

  setup_initramfs
  sleep_clear 1

  setup_hostname
  sleep_clear 1

  setup_user "root"
  sleep_clear 1

  ask_user_account
  sleep_clear 1

  if [ -n "$NORMAL_USER" ]
  then
    setup_user "$NORMAL_USER"
    sleep_clear 1
  else
    setup_testuser
    sleep_clear 2
  fi

  if [ "$INSTALL_MODE" != "$INSTALL_FULL_ISO" ]
  then
    reinitialize_keyring
    sleep_clear 1
    setup_extra_packages
    sleep_clear 1
  fi

  setup_bootloader
  sleep_clear 1

  return $SUCCESS
}


# enable systemd-networkd services
enable_iwd_networkd()
{
  title 'BlackArch Linux Setup > Network'

  wprintf '[+] Enabling Networkd, Resolved and Iwd'
  printf "\n\n"

  for svc in systemd-networkd systemd-resolved iwd
  do
    chroot $CHROOT systemctl enable "$svc" > $VERBOSE 2>&1 ||
      warn "Could not enable $svc"
  done

  return $SUCCESS
}


# persist wifi credentials into the installed system (iwd format)
setup_wifi_persist()
{
  title 'BlackArch Linux Setup > Network'

  wprintf '[+] Saving WiFi settings to the installed system'
  printf "\n\n"

  mkdir -p "$CHROOT/var/lib/iwd" > $VERBOSE 2>&1
  chmod 700 "$CHROOT/var/lib/iwd"

  # networks the live ISO already knows (e.g. connected with iwctl)
  if [ -d /var/lib/iwd ]
  then
    cp -a /var/lib/iwd/. "$CHROOT/var/lib/iwd/" > $VERBOSE 2>&1
  fi

  # network entered in the installer's "WiFi WPA Setup" mode
  if [ -n "$WLAN_SSID" ] && [ -n "$WLAN_PASSPHRASE" ]
  then
    # iwd uses the plain SSID as file name only for [A-Za-z0-9_-],
    # otherwise '=' followed by the hex-encoded SSID
    if printf '%s' "$WLAN_SSID" | grep -Eq '^[A-Za-z0-9_-]+$'
    then
      name="$WLAN_SSID"
    else
      name="=$(printf '%s' "$WLAN_SSID" | od -An -tx1 | tr -d ' \n')"
    fi
    printf '[Security]\nPassphrase=%s\n' "$WLAN_PASSPHRASE" \
      > "$CHROOT/var/lib/iwd/$name.psk"
    chmod 600 "$CHROOT/var/lib/iwd/$name.psk"
  fi

  return $SUCCESS
}


# update /etc files and set up iptables
update_etc()
{
  title 'BlackArch Linux Setup > Etc files'

  wprintf '[+] Updating /etc files'
  printf "\n\n"

  # /etc/*
  cp -r "$BI_PATH/data/etc/"{arch-release,issue,motd,\
os-release,sysctl.d,systemd} "$CHROOT/etc/." > $VERBOSE 2>&1

  return $SUCCESS
}


# ask for blackarch linux mirror
ask_mirror()
{
  title 'BlackArch Linux Setup > BlackArch Mirror'

  local IFS='|'
  count=1
  mirror_url='https://raw.githubusercontent.com/BlackArch/blackarch/master/mirror/mirror.lst'
  mirror_file='/tmp/mirror.lst'

  wprintf '[+] Fetching mirror list'
  printf "\n\n"
  curl -s -o $mirror_file $mirror_url > $VERBOSE

  while read -r country url mirror_name
  do
    wprintf " %s. %s - %s" "$count" "$country" "$mirror_name"
    printf "\n"
    wprintf "   * %s" "$url"
    printf "\n"
    count=$((count + 1))
  done < "$mirror_file"

  printf "\n"
  wprintf '[?] Select a mirror number (enter for default): '
  read -r a
  printf "\n"

  # bugfix: detected chars added sometimes - clear chars
  _a=$(printf "%s" "$a" | sed 's/[a-z]//Ig' 2> /dev/null)

  if [ -z "$_a" ]
  then
    wprintf "[+] Choosing default mirror: %s " $BA_REPO_URL
  else
    BA_REPO_URL=$(sed -n "${_a}p" $mirror_file | cut -d "|" -f 2)
    wprintf "[+] Mirror from '%s' selected" \
      "$(sed -n "${_a}p" $mirror_file | cut -d "|" -f 3)"
    printf "\n\n"
  fi

  rm -f $mirror_file

  return $SUCCESS
}

# ask for archlinux server
ask_mirror_arch()
{
  local mirrold='cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.backup'

  if confirm 'Pacman Setup > ArchLinux Mirrorlist' \
    "[+] Worldwide mirror will be used\n\n[?] Look for the best server [y/n]: "
  then
    printf "\n"
    warn 'This may take time depending on your connection'
    printf "\n"
    $mirrold
    pacman -Sy --noconfirm > $VERBOSE 2>&1
    pacman -S --needed --noconfirm reflector > $VERBOSE 2>&1
    yes | pacman -Scc > $VERBOSE 2>&1
    reflector --verbose --latest 5 --protocol https --sort rate \
      --save /etc/pacman.d/mirrorlist > $VERBOSE 2>&1
  else
    printf "\n"
    warn 'Using Worldwide mirror server'
    $mirrold
    echo -e "## Arch Linux repository Worldwide mirrorlist\n\n" \
      > /etc/pacman.d/mirrorlist

    for wore in $AR_REPO_URL
    do
      echo "Server = $wore" >> /etc/pacman.d/mirrorlist
    done
  fi

}

# pass correct config
pass_mirror_conf()
{
  mkdir -p "$CHROOT/etc/pacman.d/" > $VERBOSE 2>&1
  cp -f /etc/pacman.d/mirrorlist "$CHROOT/etc/pacman.d/mirrorlist" \
    > $VERBOSE 2>&1
}


# run strap.sh
run_strap_sh()
{
  strap_sh='/tmp/strap.sh'
  orig_sha1="$(curl -s https://blackarch.org/checksums/strap | awk '{print $1}')"

  title 'BlackArch Linux Setup > Strap'

  wprintf '[+] Downloading and executing strap.sh'
  printf "\n\n"
  warn 'This can take a while, please wait...'
  printf "\n"

  curl -s -o $strap_sh 'https://www.blackarch.org/strap.sh' > $VERBOSE 2>&1
  sha1="$(sha1sum $strap_sh | awk '{print $1}')"

  if [ "$sha1" = "$orig_sha1" ]
  then
    mv $strap_sh "${CHROOT}${strap_sh}"
    chmod a+x "${CHROOT}${strap_sh}"
    # run strap.sh inside the new system (before, only "echo" ran in the
    # chroot and strap.sh itself ran on the live ISO)
    echo "$BA_REPO_URL" | chroot $CHROOT sh "$strap_sh" > $VERBOSE 2>&1 ||
      warn 'strap.sh reported an error, BlackArch packages may fail to install.'
  else
    { err "Wrong SHA1 sum for strap.sh: $sha1 (orig: $orig_sha1). Aborting!"; exit $FAILURE; }
  fi

  # add blackarch linux mirror if we are in chroot
  if ! grep -q 'blackarch' "$CHROOT/etc/pacman.conf"
  then
    printf '[blackarch]\nServer = %s\n' "$BA_REPO_URL" \
      >> "$CHROOT/etc/pacman.conf"
  else
    sed -i "/\[blackarch\]/{ n;s?Server.*?Server = $BA_REPO_URL?; }" \
      "$CHROOT/etc/pacman.conf"
  fi

  return $SUCCESS
}


# ask user for X (display + window manager) setup
ask_x_setup()
{
  if confirm 'BlackArch Linux Setup > X11' '[?] Setup X11 + window managers [y/n]: '
  then
    X_SETUP=$TRUE
    printf "\n"
    printf "${BLINK}NOOB! NOOB! NOOB! NOOB! NOOB! NOOB! NOOB!${NC}\n\n"
  fi

  return $SUCCESS
}


# setup display manager
setup_display_manager()
{
  title 'BlackArch Linux Setup > Display Manager'

  wprintf '[+] Setting up LXDM'
  printf "\n\n"

  # install lxdm packages
  chroot $CHROOT pacman -S lxdm --needed --overwrite='*' --noconfirm \
    > $VERBOSE 2>&1

  # config packages. blackarch-config-lxdm also ships the xsession files used
  # by setup_window_managers()
  chroot_install blackarch-config-lxdm blackarch-config-gtk \
    blackarch-config-x11

  # config files. xinitrc and xorg.conf.d are not packaged
  cp -r "$BI_PATH/data/etc/X11" "$CHROOT/etc/."
  copy_ba_config "$BA_CONFIG/x11/xprofile" /etc/xprofile
  copy_ba_home_config x11/Xdefaults .Xdefaults
  copy_ba_home_config x11/Xresources .Xresources
  copy_ba_config /etc/lxdm-blackarch /etc/lxdm
  copy_ba_config /usr/share/lxdm-blackarch /usr/share/lxdm
  # the package stages gtk-2.0/gtk-2.0/, not gtk-2.0/ as its .install says
  copy_ba_config "$BA_CONFIG/gtk-2.0/gtk-2.0" /usr/share/gtk-2.0
  mkdir -p "$CHROOT/usr/share/xsessions"

  # enable in systemd
  chroot $CHROOT systemctl enable lxdm > $VERBOSE 2>&1

  return $SUCCESS
}


# setup wallpaper. /usr/share/blackarch/wallpaper.{png,jpg} are owned by the
# blackarch-wallpaper package, which most blackarch-config-* packages depend
# on.
setup_wallpaper()
{
  if ! chroot_install blackarch-wallpaper
  then
    warn 'Could not install blackarch-wallpaper, no wallpaper will be set.'
    printf "\n"
  fi

  return $SUCCESS
}


# install the xsession file of a window manager. $1: window manager name.
# the files are shipped by blackarch-config-lxdm (see setup_display_manager)
setup_xsession()
{
  copy_ba_config "/usr/share/xsessions-blackarch/$1.desktop" \
    "/usr/share/xsessions/$1.desktop"
}


# awesome window manager
setup_wm_awesome()
{
  chroot_install awesome rxvt-unicode feh blackarch-config-awesome
  copy_ba_config "$BA_CONFIG/awesome/etc/xdg/awesome/rc.lua.blackarch" \
    /etc/xdg/awesome/rc.lua
  copy_ba_config "$BA_CONFIG/awesome/usr/share/awesome/themes/blackarch" \
    /usr/share/awesome/themes/blackarch
  # fix bullshit exit() issue
  sed -i 's|local visible, action = cmd(item, self)|local visible, action = cmd(0, 0)|' \
    "$CHROOT/usr/share/awesome/lib/awful/menu.lua"
  setup_xsession awesome
}


# fluxbox window manager
setup_wm_fluxbox()
{
  chroot_install fluxbox rxvt-unicode feh blackarch-config-fluxbox
  copy_ba_config "$BA_CONFIG/fluxbox" /usr/share/fluxbox
  setup_xsession fluxbox
}


# i3 window manager. blackarch-config-i3 depends on i3-gaps, which was merged
# into i3-wm; if that dependency cannot be resolved, the package is skipped
# and copy_ba_home_config() warns about the missing config.
setup_wm_i3()
{
  chroot_install i3-wm i3status i3lock i3blocks dmenu rofi rxvt-unicode
  chroot_install blackarch-config-i3
  copy_ba_home_config i3/config .config/i3/config
  copy_ba_home_config i3/i3status.conf .i3status.conf
  setup_xsession i3
}


# openbox window manager
setup_wm_openbox()
{
  chroot_install openbox rxvt-unicode feh blackarch-config-openbox
  copy_ba_config "$BA_CONFIG/openbox/etc/xdg/openbox" /etc/xdg/openbox
  copy_ba_config "$BA_CONFIG/openbox/usr/share/themes/blackarch" \
    /usr/share/themes/blackarch
  setup_xsession openbox
}


# spectrwm window manager. the config uses urxvt as terminal, dmenu as menu
# and xlock (xlockmore) as screen locker, so all of them must be present or
# the key bindings silently do nothing.
setup_wm_spectrwm()
{
  local conf="$CHROOT/etc/spectrwm.conf"

  chroot_install spectrwm dmenu xlockmore rxvt-unicode feh \
    blackarch-config-spectrwm

  if copy_ba_config "$BA_CONFIG/spectrwm/spectrwm.conf" /etc/spectrwm.conf
  then
    # installer overrides: the packaged config disables the lock key
    # (program[lock] = /bin/false) and uses the default modkey (Mod1)
    sed -i 's|^program\[lock\].*|program[lock] = xlock -mode blank|' "$conf"
    if ! grep -q '^modkey' "$conf"
    then
      echo 'modkey = Mod4' >> "$conf"
    fi
  fi

  setup_xsession spectrwm
}


# setup window managers
setup_window_managers()
{
  title 'BlackArch Linux Setup > Window Managers'

  wprintf '[+] Setting up window managers'
  printf "\n\n"

  printf "
  1. Awesome
  2. Fluxbox
  3. I3-wm
  4. Openbox
  5. Spectrwm
  6. All of the above
  \n"
  wprintf '[?] Choose an option [6]: '
  read -r choice
  echo

  mkdir -p "$CHROOT/usr/share/xsessions"

  # wallpaper first, it is used by every window manager config
  setup_wallpaper

  case $choice in
    1) setup_wm_awesome ;;
    2) setup_wm_fluxbox ;;
    3) setup_wm_i3 ;;
    4) setup_wm_openbox ;;
    5) setup_wm_spectrwm ;;
    *)
      setup_wm_awesome
      setup_wm_fluxbox
      setup_wm_i3
      setup_wm_openbox
      setup_wm_spectrwm
      ;;
  esac

  # remove wrong xsession entries
  chroot $CHROOT rm -f /usr/share/xsessions/openbox-kde.desktop > $VERBOSE 2>&1
  chroot $CHROOT rm -f /usr/share/xsessions/i3-with-shmlog.desktop > $VERBOSE 2>&1

  return $SUCCESS
}


# ask user for VirtualBox modules+utils setup
ask_vbox_setup()
{
  if confirm 'BlackArch Linux Setup > VirtualBox' '[?] Setup VirtualBox modules [y/n]: '
  then
    VBOX_SETUP=$TRUE
  fi

  return $SUCCESS
}


# setup virtualbox utils
setup_vbox_utils()
{
  title 'BlackArch Linux Setup > VirtualBox'

  wprintf '[+] Setting up VirtualBox utils'
  printf "\n\n"

  chroot $CHROOT pacman -S virtualbox-guest-utils --overwrite='*' --needed \
    --noconfirm > $VERBOSE 2>&1

  chroot $CHROOT systemctl enable vboxservice > $VERBOSE 2>&1

  #printf "vboxguest\nvboxsf\nvboxvideo\n" \
  #  > "$CHROOT/etc/modules-load.d/vbox.conf"

  cp -r "$BI_PATH/data/etc/xdg/autostart/vboxclient.desktop" \
    "$CHROOT/etc/xdg/autostart/." > $VERBOSE 2>&1

  return $SUCCESS
}


# ask user for VirtualBox modules+utils setup
ask_vmware_setup()
{
  if confirm 'BlackArch Linux Setup > VMware' '[?] Setup VMware modules [y/n]: '
  then
    VMWARE_SETUP=$TRUE
  fi

  return $SUCCESS
}


# setup vmware utils
setup_vmware_utils()
{
  title 'BlackArch Linux Setup > VMware'

  wprintf '[+] Setting up VMware utils'
  printf "\n\n"

  chroot_install open-vm-tools xf86-input-vmmouse

  chroot $CHROOT systemctl enable vmware-vmblock-fuse.service > $VERBOSE 2>&1
  chroot $CHROOT systemctl enable vmtoolsd.service > $VERBOSE 2>&1

  return $SUCCESS
}


# ask user for BlackArch tools setup
ask_ba_tools_setup()
{
  if confirm 'BlackArch Linux Setup > Tools' '[?] Setup BlackArch Linux tools [y/n]: '
  then
    BA_TOOLS_SETUP=$TRUE
  fi

  return $SUCCESS
}


# setup blackarch tools from repository (binary) or via blackman (source)
setup_blackarch_tools()
{
  foo=5

  if [ "$VERBOSE" = '/dev/null' ]
  then
    noconfirm='--noconfirm'
  fi

  title 'BlackArch Linux Setup > Tools'

  wprintf '[+] Installing BlackArch Linux packages (grab a coffee)'
  printf "\n\n"

  if [ "$INSTALL_MODE" = $INSTALL_REPO ]
  then
    wprintf "[+] All available BlackArch tools groups:\n\n"
    printf "    > blackarch blackarch-anti-forensic blackarch-automation
    > blackarch-backdoor blackarch-binary blackarch-bluetooth blackarch-code-audit
    > blackarch-cracker blackarch-crypto blackarch-database blackarch-debugger
    > blackarch-decompiler blackarch-defensive blackarch-disassembler
    > blackarch-dos blackarch-drone blackarch-exploitation blackarch-fingerprint
    > blackarch-firmware blackarch-forensic blackarch-fuzzer blackarch-hardware
    > blackarch-honeypot blackarch-ids blackarch-keylogger blackarch-malware
    > blackarch-misc blackarch-mobile blackarch-networking blackarch-nfc
    > blackarch-packer blackarch-proxy blackarch-recon blackarch-reversing
    > blackarch-scanner blackarch-sniffer blackarch-social blackarch-spoof
    > blackarch-threat-model blackarch-tunnel blackarch-unpacker blackarch-voip
    > blackarch-webapp blackarch-windows blackarch-wireless \n\n"
    wprintf "[?] BlackArch groups to install (space for multiple) [blackarch]: "
    read -r BA_GROUPS
    printf "\n"
    warn 'This can take a while, please wait...'
    if [ -z "$BA_GROUPS" ]
    then
      printf "\n"
      check_space
      printf "\n\n"
      chroot $CHROOT pacman -S --needed --noconfirm --overwrite='*' blackarch \
        > $VERBOSE 2>&1
    else
      chroot $CHROOT pacman -S --needed --noconfirm --overwrite='*' "$BA_GROUPS" \
        > $VERBOSE 2>&1
    fi
  else
    warn 'Installing all tools from source via blackman can take hours'
    printf "\n"
    wprintf '[+] <Control-c> to abort ... '
    while [ $foo -gt 0 ]
    do
      wprintf "$foo "
      sleep 1
      foo=$((foo - 1))
    done
    printf "\n"
    chroot $CHROOT pacman -S --needed --overwrite='*' $noconfirm blackman \
      > $VERBOSE 2>&1
    chroot $CHROOT blackman -a > $VERBOSE 2>&1
  fi

  return $SUCCESS
}


# add user to newly created groups
update_user_groups()
{
  title 'BlackArch Linux Setup > User'

  wprintf "[+] Adding user $user to groups and sudoers"
  printf "\n\n"

  # TODO: more to add here
  if [ $VBOX_SETUP -eq $TRUE ]
  then
    chroot $CHROOT usermod -aG 'vboxsf,audio,video' "$user" > $VERBOSE 2>&1
  fi

  # sudoers
  echo "$user ALL=(ALL) ALL" >> $CHROOT/etc/sudoers > $VERBOSE 2>&1

  return $SUCCESS
}


# dump data from the full-iso
dump_full_iso()
{
  full_dirs='/bin /sbin /etc /home /lib /lib64 /opt /root /srv /usr /var /tmp'
  total_size=0 # no cheat

  title 'BlackArch Linux Setup'

  wprintf '[+] Dumping data from Full-ISO. Grab a coffee and pop shells!'
  printf "\n\n"

  wprintf '[+] Fetching total size to transfer, please wait...'
  printf "\n"

  for d in $full_dirs
  do
    part_size=$(du -sm "$d" 2> /dev/null | awk '{print $1}')
    ((total_size+=part_size))
    printf "
  > $d $part_size MB"
  done
  printf "\n
  [ Total size = $total_size MB ]
  \n\n"

  check_space

  wprintf '[+] Installing the backdoors to /'
  printf "\n\n"
  warn 'This can take a while, please wait...'
  printf "\n"
  rsync -aWx --human-readable --info=progress2 / $CHROOT > $VERBOSE 2>&1
  wprintf "[+] Installation done!\n"

  # clean up files
  wprintf '[+] Cleaning Full Environment files, please wait...'
  sed -i 's/Storage=volatile/#Storage=auto/' ${CHROOT}/etc/systemd/journald.conf
  rm -rf "$CHROOT/etc/udev/rules.d/81-dhcpcd.rules"
  rm -rf "$CHROOT/etc/systemd/system/"{choose-mirror.service,pacman-init.service,etc-pacman.d-gnupg.mount,getty@tty1.service.d}
  rm -rf "$CHROOT/etc/systemd/scripts/choose-mirror"
  rm -rf "$CHROOT/etc/systemd/system/getty@tty1.service.d/autologin.conf"
  rm -rf "$CHROOT/root/"{.automated_script.sh,.zlogin}
  rm -rf "$CHROOT/etc/mkinitcpio-archiso.conf"
  rm -rf "$CHROOT/etc/initcpio"
  #rm -rf ${CHROOT}/etc/{group*,passwd*,shadow*,gshadow*}
  wprintf "done\n"

  return $SUCCESS
}


# install the BlackArch bash and vim configs into the home of root and of the
# normal user. this must run after run_strap_sh() (BlackArch repo). in
# Full-ISO mode nothing is installed: the configs come with the live system.
setup_dotfiles()
{
  title 'BlackArch Linux Setup > Dotfiles'

  wprintf '[+] Setting up bash and vim configs'
  printf "\n\n"

  if [ "$INSTALL_MODE" != "$INSTALL_FULL_ISO" ]
  then
    chroot_install blackarch-config-bash blackarch-config-vim
  fi

  copy_ba_home_config bash/bashrc .bashrc
  copy_ba_home_config bash/bash_profile .bash_profile
  copy_ba_home_config vim/vimrc .vimrc
  copy_ba_home_config vim/vim .vim

  # Full-ISO mode skips the X11 and window manager setup, so their per-user
  # configs are copied here, if the live system has them
  if [ "$INSTALL_MODE" = "$INSTALL_FULL_ISO" ]
  then
    if [ -d "$CHROOT$BA_CONFIG/x11" ]
    then
      copy_ba_home_config x11/Xdefaults .Xdefaults
      copy_ba_home_config x11/Xresources .Xresources
    fi
    if [ -d "$CHROOT$BA_CONFIG/i3" ]
    then
      copy_ba_home_config i3/config .config/i3/config
      copy_ba_home_config i3/i3status.conf .i3status.conf
    fi
  fi

  return $SUCCESS
}


# make zsh the default login shell of root and the normal user. the
# BlackArch zshrc (blackarch-config-zsh) is copied as ~/.zshrc; without any
# ~/.zshrc zsh would greet every new user with its setup wizard.
setup_default_shell()
{
  local zshrc='/usr/share/blackarch/config/zsh/zshrc' u home users='root'

  title 'BlackArch Linux Setup > Shell'

  wprintf '[+] Setting up zsh as default shell'
  printf "\n\n"

  if [ "$INSTALL_MODE" != "$INSTALL_FULL_ISO" ]
  then
    chroot_install zsh
    chroot_install blackarch-config-zsh
  fi

  if [ ! -x "$CHROOT/usr/bin/zsh" ]
  then
    warn 'zsh is not installed, keeping bash as default shell.'
    return $SUCCESS
  fi

  if [ -n "$NORMAL_USER" ]
  then
    users="$users $(sanitize_username "$NORMAL_USER")"
  fi

  for u in $users
  do
    if ! chroot $CHROOT usermod -s /usr/bin/zsh "$u" > $VERBOSE 2>&1
    then
      warn "Could not change the shell of $u."
      continue
    fi

    home="$(chroot $CHROOT getent passwd "$u" | cut -d ':' -f 6)"
    if [ -n "$home" ] && [ -d "$CHROOT$home" ] && [ ! -e "$CHROOT$home/.zshrc" ]
    then
      if [ -f "$CHROOT$zshrc" ]
      then
        cp "$CHROOT$zshrc" "$CHROOT$home/.zshrc"
      else
        echo '# ~/.zshrc' > "$CHROOT$home/.zshrc"
      fi
      chroot $CHROOT chown "$u:" "$home/.zshrc" > $VERBOSE 2>&1
    fi

    wprintf "[+] Default shell of $u: /usr/bin/zsh"
    printf "\n"
  done
  printf "\n"

  return $SUCCESS
}


# setup blackarch related stuff
setup_blackarch()
{
  update_etc
  sleep_clear 1

  enable_iwd_networkd
  sleep_clear 1

  setup_wifi_persist
  sleep_clear 1

  ask_mirror
  sleep_clear 1

  run_strap_sh
  sleep_clear 1

  setup_dotfiles
  sleep_clear 1

  setup_default_shell
  sleep_clear 1

  ask_x_setup
  sleep_clear 3

  if [ $X_SETUP -eq $TRUE ]
  then
    setup_display_manager
    sleep_clear 1
    setup_window_managers
    sleep_clear 1
  fi

  ask_vbox_setup
  sleep_clear 1

  if [ $VBOX_SETUP -eq $TRUE ]
  then
    setup_vbox_utils
    sleep_clear 1
  fi

  ask_vmware_setup
  sleep_clear 1

  if [ $VMWARE_SETUP -eq $TRUE ]
  then
    setup_vmware_utils
    sleep_clear 1
  fi

  sleep_clear 1

  enable_pacman_multilib 'chroot'
  sleep_clear 1

  enable_pacman_color 'chroot'
  sleep_clear 1

  ask_ba_tools_setup
  sleep_clear 1

  if [ $BA_TOOLS_SETUP -eq $TRUE ]
  then
    setup_blackarch_tools
    sleep_clear 1
  fi

  if [ -n "$NORMAL_USER" ]
  then
    update_user_groups
    sleep_clear 1
  fi

  return $SUCCESS
}


# for fun and lulz
easter_backdoor()
{
  bar=0

  title 'Game Over'

  wprintf '[+] BlackArch Linux installation successfull!'
  printf "\n\n"

  wprintf 'Yo n00b, b4ckd00r1ng y0ur sy5t3m n0w '
  while [ $bar -ne 5 ]
  do
    wprintf "."
    sleep 1
    bar=$((bar + 1))
  done
  printf " >> ${BLINK}${WHITE}HACK THE PLANET! D00R THE PLANET!${NC} <<"
  printf "\n\n"

  return $SUCCESS
}


# perform sync
sync_disk()
{
  title 'Game Over'

  wprintf '[+] Syncing disk'
  printf "\n\n"

  sync

  return $SUCCESS
}


# check if new version available. perform self-update and exit
self_updater()
{
  title 'Self Updater'
  wprintf '[+] Checking for a new version of myself...'
  printf "\n\n"

  pacman -Syy > $VERBOSE 2>&1
  repo="$(pacman -Ss '^blackarch-installer$' | head -1 | cut -d ' ' -f 2 |
    cut -d '-' -f 1)"

  if [ -n "$repo" ] && [ "$(vercmp "$VERSION" "$repo")" -lt 0 ]
  then
    printf "\n\n"
    warn 'A new version is available! Going to fuck, err, update myself.'
    pacman -S --overwrite='*' --noconfirm blackarch-installer > $VERBOSE 2>&1
    yes | pacman -Scc > $VERBOSE 2>&1
    wprintf "\n[+] Updated successfully. Please restart the installer now!\n"
    chmod +x /usr/share/blackarch-installer/blackarch-install
    exit $SUCCESS
  fi

  return $SUCCESS
}


# controller and program flow
main()
{
  # do some ENV checks
  sleep_clear 0
  check_uid
  check_env
  check_boot_mode
  check_iso_type

  # install mode
  ask_install_mode

  # output mode
  ask_output_mode
  sleep_clear 0

  # locale
  ask_locale
  set_locale
  sleep_clear 0

  # keymap
  ask_keymap
  set_keymap
  sleep_clear 0

  # network
  ask_hostname
  sleep_clear 0

  if [ "$INSTALL_MODE" != "$INSTALL_FULL_ISO" ]
  then
    get_net_ifs
    ask_net_conf_mode
    if [ "$NET_CONF_MODE" != "$NET_CONF_SKIP" ]
    then
      ask_net_if
    fi
    case "$NET_CONF_MODE" in
      "$NET_CONF_AUTO")
        net_conf_auto
        ;;
      "$NET_CONF_WLAN")
        ask_wlan_data
        net_conf_wlan
        ;;
      "$NET_CONF_MANUAL")
        ask_net_addr
        net_conf_manual
        ;;
      "$NET_CONF_SKIP")
        ;;
      *)
        ;;
    esac
    sleep_clear 1
    check_inet_conn
    sleep_clear 1

    # Update keyrings
    install_keyrings
    sleep_clear 1

    # self updater
    self_updater
    sleep_clear 1

    # pacman
    ask_mirror_arch
    sleep_clear 1
    update_pacman
  fi

  # hard drive
  get_hd_devs
  ask_hd_dev
  ask_dualboot
  sleep_clear 1
  umount_filesystems 'harddrive'
  sleep_clear 1
  ask_cfdisk
  sleep_clear 3
  ask_luks
  get_partition_label
  ask_partitions
  print_partitions
  ask_formatting
  clear
  make_partitions
  clear
  mount_filesystems
  sleep_clear 1

  # arch linux
  setup_base_system
  sleep_clear 1
  setup_time
  sleep_clear 1

  # blackarch Linux
  if [ "$INSTALL_MODE" != "$INSTALL_FULL_ISO" ]
  then
    setup_blackarch
    sleep_clear 1
    ln -sf /run/systemd/resolve/stub-resolv.conf "$CHROOT/etc/resolv.conf"
  else
    setup_dotfiles
    sleep_clear 1
    setup_default_shell
    sleep_clear 1
    setup_wifi_persist
    sleep_clear 1
  fi

  # epilog
  umount_filesystems
  sleep_clear 1
  sync_disk
  sleep_clear 1
  easter_backdoor

  return $SUCCESS
}


# we start here
main "$@"


# EOF
