#!/bin/sh
# shellcheck disable=SC3043 disable=SC2086 disable=SC2059 disable=SC2039 disable=SC2034 disable=SC2317
#------------------------------------------------------------------------------
# Utility function library for installation scripts
# slib v1.4.0 (https://github.com/virtualmin/slib)
# Copyright 2017-2026 The Virtualmin Developers
# slog logging library Copyright Fred Palmer and Joe Cooper
# Licensed under the BSD 3 clause license
#------------------------------------------------------------------------------

restore_cursor () {
  tput cnorm
}

cleanup () {
  exit_code=$1
  stty echo 1>/dev/null 2>&1
  echo
  # Make super duper sure we reap all the spinners
  # This is ridiculous, and I still don't know why spinners stick around.
  if [ -n "$allpids" ]; then
    for pid in $allpids; do
      kill "$pid" 1>/dev/null 2>&1
    done
    tput sgr0
  fi
  restore_cursor
  # Clean any env dirs
  env | grep '_INSTALL_TEMPDIR=' | while IFS='=' read -r var temp_dir; do
    [ -z "$temp_dir" ] && continue
    prefix="${var%%_INSTALL_TEMPDIR}"
    if [ -d "$temp_dir" ] && echo "$temp_dir" | grep -iq "${prefix}-"; then
      rm -rf "$temp_dir"
    fi
  done

  if [ "$exit_code" -ne 0 ]; then
    echo
  fi

  exit $exit_code
}

# Check for interactive shell
INTERACTIVE_MODE="on"
[ -z "${NONINTERACTIVE-}" ] && NONINTERACTIVE=0      # Set only if unset
if [ ! -t 0 ] && [ -z "${PS1-}" ]; then
    INTERACTIVE_MODE="off"
    [ -z "${NONINTERACTIVE-}" ] && NONINTERACTIVE=1  # Only set if unset
fi

# This tries to catch any exit, whether normal or forced (e.g. Ctrl-C)
if [ "$INTERACTIVE_MODE" != "off" ]; then
  trap 'cleanup 2' INT
  trap 'cleanup 3' QUIT
  trap 'cleanup 15' TERM
  trap 'cleanup 0' EXIT
fi

# scolors - Color constants
# canonical source http://github.com/swelljoe/scolors

# Check if terminal is supported and set TERM to a supported one if not
is_term_supported () {
  term=$1
  [ -n "$term" ] || term=dumb            # avoid empty TERM
  # Tput probe
  if command -pv tput >/dev/null 2>&1; then
    tput -T "$term" cols >/dev/null 2>&1 && return 0 || return 1
  fi
  # Infocmp probe
  if command -pv infocmp >/dev/null 2>&1; then
    infocmp "$term" >/dev/null 2>&1 && return 0 || return 1
  fi
  return 1
}
FALLBACK_TERMS='xterm-256color xterm-color xterm vt220 ansi dumb'
if ! is_term_supported "$TERM"; then
  OLDTERM=$TERM
  for alt in $FALLBACK_TERMS; do
    if is_term_supported "$alt"; then
      TERM=$alt
      export TERM
      echo "[INFO] Terminal type '$OLDTERM' not supported; switched to '$TERM'"
      break
    fi
  done
fi

# do we have tput?
if command -pv 'tput' > /dev/null; then
  # do we have a terminal?
  if [ -t 1 ]; then
    # does the terminal have colors?
    ncolors=$(tput colors)
    if [ "$ncolors" -ge 8 ]; then
      BLACK="$(tput setaf 0)"
      RED=$(tput setaf 1)
      GREEN=$(tput setaf 2)
      YELLOW=$(tput setaf 3)
      ORANGE=$(tput setaf 3)
      BLUE=$(tput setaf 4)
      MAGENTA=$(tput setaf 5)
      CYAN=$(tput setaf 6)
      WHITE=$(tput setaf 7)
      REDBG=$(tput setab 1)
      GREENBG=$(tput setab 2)
      YELLOWBG=$(tput setab 3)
      ORANGEBG=$(tput setab 3)
      BLUEBG=$(tput setab 4)
      MAGENTABG=$(tput setab 5)
      CYANBG=$(tput setab 6)
      WHITEBG=$(tput setab 7)

      # Do we have support for bright colors?
      if [ "$ncolors" -ge 16 ]; then
        WHITE=$(tput setaf 15)
        WHITEBG=$(tput setab 15)
      fi

      # Do we have support for 256 colors to make it more readable?
      if [ "$ncolors" -ge 256 ]; then
        RED=$(tput setaf 124)
        GREEN=$(tput setaf 34)
        YELLOW=$(tput setaf 186)
        BLUE=$(tput setaf 25)
        ORANGE=$(tput setaf 202)
        MAGENTA=$(tput setaf 90)
        CYAN=$(tput setaf 45)
        WHITE=$(tput setaf 255)
        REDBG=$(tput setab 160)
        YELLOWBG=$(tput setab 186)
        ORANGEBG=$(tput setab 166)
        BLUEBG=$(tput setab 25)
        MAGENTABG=$(tput setab 90)
        CYANBG=$(tput setab 45)
      fi

      BOLD=$(tput bold)
      UNDERLINE=$(tput smul) # Many terminals don't support this
      NORMAL=$(tput sgr0)
    fi
  fi
else
  echo "tput not found, colorized output disabled."
  BLACK=''
  RED=''
  GREEN=''
  YELLOW=''
  ORANGE=''
  BLUE=''
  MAGENTA=''
  CYAN=''
  WHITE=''
  REDBG=''
  GREENBG=''
  YELLOWBG=''
  ORANGEBG=''
  BLUEBG=''
  MAGENTABG=''
  CYANBG=''

  BOLD=''
  UNDERLINE=''
  NORMAL=''
fi

# slog - logging library
# canonical source http://github.com/swelljoe/slog

# LOG_PATH - Define $LOG_PATH in your script to log to a file, otherwise
# just writes to STDOUT.

# LOG_LEVEL_STDOUT - Define to determine above which level goes to STDOUT.
# By default, all log levels will be written to STDOUT.
LOG_LEVEL_STDOUT="INFO"

# LOG_LEVEL_LOG - Define to determine which level goes to LOG_PATH.
# By default all log levels will be written to LOG_PATH.
LOG_LEVEL_LOG="INFO"

# Useful global variables that users may wish to reference
SCRIPT_ARGS="$*"
SCRIPT_NAME="$0"
SCRIPT_NAME="${SCRIPT_NAME#\./}"
SCRIPT_NAME="${SCRIPT_NAME##/*/}"

#--------------------------------------------------------------------------------------------------
# Begin Logging Section
if [ "$INTERACTIVE_MODE" = "off" ]
then
    # Then we don't care about log colors
    LOG_DEFAULT_COLOR=""
    LOG_ERROR_COLOR=""
    LOG_INFO_COLOR=""
    LOG_SUCCESS_COLOR=""
    LOG_WARN_COLOR=""
    LOG_DEBUG_COLOR=""
else
    LOG_DEFAULT_COLOR=$(tput sgr0)
    LOG_ERROR_COLOR=$(tput setaf 1)
    LOG_INFO_COLOR=$(tput setaf 6)
    LOG_SUCCESS_COLOR=$(tput setaf 2)
    LOG_WARN_COLOR=$(tput setaf 3)
    LOG_DEBUG_COLOR=$(tput setaf 4)
fi

# This function scrubs the output of any control characters used in colorized output
# It's designed to be piped through with text that needs scrubbing.  The scrubbed
# text will come out the other side!
prepare_log_for_nonterminal () {
    # Essentially this strips all the control characters for log colors
    sed -E 's/\x1B\[[0-9;]*[mK]//g; s/\x1B\([A-Za-z]//g' | tr -d '[:cntrl:]'
}

log_date () {
  local log_date_level="$1"
  echo "[$(date +"%Y-%m-%d %H:%M:%S %Z")] [$log_date_level] "
}

log () {
  local log_text="$1"
  local log_level="$2"
  local log_color="$3"

  # Levels for comparing against LOG_LEVEL_STDOUT and LOG_LEVEL_LOG
  local LOG_LEVEL_DEBUG=0
  local LOG_LEVEL_INFO=1
  local LOG_LEVEL_SUCCESS=2
  local LOG_LEVEL_WARNING=3
  local LOG_LEVEL_ERROR=4

  # Default level to "info"
  [ -z "${log_level}" ] && log_level="INFO";
  [ -z "${log_color}" ] && log_color="${LOG_INFO_COLOR}";

  # Validate LOG_LEVEL_STDOUT and LOG_LEVEL_LOG since they'll be eval-ed.
  case $LOG_LEVEL_STDOUT in
    DEBUG|INFO|SUCCESS|WARNING|ERROR)
      ;;
    *)
      LOG_LEVEL_STDOUT=INFO
      ;;
  esac
  case $LOG_LEVEL_LOG in
    DEBUG|INFO|SUCCESS|WARNING|ERROR)
      ;;
    *)
      LOG_LEVEL_LOG=INFO
      ;;
  esac

  # Check LOG_LEVEL_STDOUT to see if this level of entry goes to STDOUT.
  # XXX This is the horror that happens when your language doesn't have a hash data struct.
  eval log_level_int="\$LOG_LEVEL_${log_level}";
  eval log_level_stdout="\$LOG_LEVEL_${LOG_LEVEL_STDOUT}"
  # shellcheck disable=SC2154
  if [ "$log_level_stdout" -le "$log_level_int" ]; then
    # STDOUT
    printf "%s[%s]%s %s\\n" "$log_color" "$log_level" "$LOG_DEFAULT_COLOR" "$log_text";
  fi
  # This is all very tricky; figures out a numeric value to compare.
  eval log_level_log="\$LOG_LEVEL_${LOG_LEVEL_LOG}"
  # Check LOG_LEVEL_LOG to see if this level of entry goes to LOG_PATH
  # shellcheck disable=SC2154
  if [ "$log_level_log" -le "$log_level_int" ]; then
    # LOG_PATH minus fancypants colors
    if [ -n "$LOG_PATH" ]; then
      today=$(date +"%Y-%m-%d %H:%M:%S %Z")
      printf "[%s] [%s] %s\\n" "$today" "$log_level" "$log_text" >> "$LOG_PATH"
    fi
  fi

  return 0;
}

log_info()      { log "$@"; }
log_success()   { log "$1" "SUCCESS" "${LOG_SUCCESS_COLOR}"; }
log_error()     { log "$1" "ERROR" "${LOG_ERROR_COLOR}"; }
log_warning()   { log "$1" "WARNING" "${LOG_WARN_COLOR}"; }
log_debug()     { log "$1" "DEBUG" "${LOG_DEBUG_COLOR}"; }

# End Logging Section
#--------------------------------------------------------------------------------------------------

# spinner - Log to provide spinners when long-running tasks happen
# Canonical source http://github.com/swelljoe/spinner

# Config variables, set these after sourcing to change behavior.
SPINNER_COLORNUM=2 # What color? Irrelevent if COLORCYCLE=1.
SPINNER_COLORCYCLE=1 # Does the color cycle?
SPINNER_DONEFILE="stopspinning" # Path/name of file to exit on.
SPINNER_SYMBOLS="WIDE_ASCII_PROG" # Name of the variable containing the symbols.
SPINNER_CLEAR=1 # Blank the line when done.

spinner () {
  # Add this trap to make sure the spinner is terminated and the cursor
  # is restored, when the script is either finished or killed.
  trap 'restore_cursor; exit' INT QUIT TERM EXIT
  # Safest option are one of these. Doesn't need Unicode, at all.
  local WIDE_ASCII_PROG="[>-] [->] [--] [--]"
  local WIDE_UNI_GREYSCALE2="▒▒▒ █▒▒ ██▒ ███ ▒██ ▒▒█ ▒▒▒"

  local SPINNER_NORMAL
  SPINNER_NORMAL=$(tput sgr0)

  eval SYMBOLS=\$${SPINNER_SYMBOLS}

  # Get the parent PID
  SPINNER_PPID=$(ps -p "$$" -o ppid=)
  while :; do
    tput civis
    for c in ${SYMBOLS}; do
      if [ $SPINNER_COLORCYCLE -eq 1 ]; then
        if [ $SPINNER_COLORNUM -eq 7 ]; then
          SPINNER_COLORNUM=1
        else
          SPINNER_COLORNUM=$((SPINNER_COLORNUM+1))
        fi
      fi
      local SPINNER_COLOR
      SPINNER_COLOR=$(tput setaf ${SPINNER_COLORNUM})
      printf "\033[77G"  # Move to column 77
      env printf "${SPINNER_COLOR}${c}${SPINNER_NORMAL}"
      if [ -f "${SPINNER_DONEFILE}" ]; then
        if [ ${SPINNER_CLEAR} -eq 1 ]; then
          tput el
        fi
	      rm -f ${SPINNER_DONEFILE}
	      break 2
      fi
      # This is questionable. sleep with fractional seconds is not
      # always available, but seems to not break things, when not.
      env sleep .2
      # Check to be sure parent is still going; handles sighup/kill
      if [ -n "$SPINNER_PPID" ]; then
        # This is ridiculous. ps prepends a space in the ppid call, which breaks
        # this ps with a "garbage option" error.
        # XXX Potential gotcha if ps produces weird output.
        # shellcheck disable=SC2086
        SPINNER_PARENTUP=$(ps --no-headers $SPINNER_PPID)
        if [ -z "$SPINNER_PARENTUP" ]; then
          break 2
        fi
      fi
    done
  done
  restore_cursor
  return 0
}

# run_ok - function to run a command or function, start a spinner and print a confirmation
# indicator when done.
# Canonical source - http://github.com/swelljoe/run_ok
RUN_LOG="run.log"

# Check for unicode support in the shell
# This is a weird function, but seems to work. Checks to see if a unicode char can be
# written to a file and can be read back.
shell_has_unicode () {
  # Write a unicode character to a file...read it back and see if it's handled right.
  env printf "\\u2714"> unitest.txt

  read -r unitest < unitest.txt
  rm -f unitest.txt
  if [ ${#unitest} -le 3 ]; then
    return 0
  else
    return 1
  fi
}

# Setup spinner with our prefs.
SPINNER_COLORCYCLE=0
SPINNER_COLORNUM=6
if shell_has_unicode; then
  SPINNER_SYMBOLS="WIDE_UNI_GREYSCALE2"
else
  SPINNER_SYMBOLS="WIDE_ASCII_PROG"
fi
SPINNER_CLEAR=0 # Don't blank the line, so our check/x can simply overwrite it.

# Perform an action, log it, and print a colorful checkmark or X if failed
# Returns 0 if successful, $? if failed.
run_ok () {
  # Shell is really clumsy with passing strings around.
  # This passes the unexpanded $1 and $2, so subsequent users get the
  # whole thing.
  local cmd="${1}"
  local msg="${2}"
  local log_pref
  log_pref="$(log_date "INFO")"

  printf "%s" "$2"
  printf "\033[K"      # Clear to end of line
  printf "\033[77G"    # Move cursor to column 77

  CHECK='\u2714'
  BALLOT_X='\u2718'
  if [ "$INTERACTIVE_MODE" != "off" ];then
    stty -echo 1>/dev/null 2>&1
    spinner &
    spinpid=$!
    allpids="$allpids $spinpid"
    echo "$log_pref Spin pid is: $spinpid" >> ${RUN_LOG}
  fi
  eval "${cmd}" 1>> ${RUN_LOG} 2>&1
  local res=$?
  touch ${SPINNER_DONEFILE}
  env sleep .4 # It's possible to have a race for stdout and spinner clobbering the next bit
  # Just in case the spinner survived somehow, kill it.
  if [ "$INTERACTIVE_MODE" != "off" ];then
    stty echo 1>/dev/null 2>&1
    pidcheck=$(ps --no-headers ${spinpid})
    if [ -n "$pidcheck" ]; then
      echo "$log_pref Made it here...why?" >> ${RUN_LOG}
      kill $spinpid 2>/dev/null
      rm -rf ${SPINNER_DONEFILE} 2>/dev/null 2>&1
      restore_cursor
    fi
  fi
  # Log what we were supposed to be running
  msg_safe=$(echo "$msg" | prepare_log_for_nonterminal)
  printf "$log_pref ${msg_safe}: " >> ${RUN_LOG}
  if shell_has_unicode; then
    if [ $res -eq 0 ]; then
      printf "$log_pref Success.\\n" >> ${RUN_LOG}
      printf "\033[77G\033[K"  # Position and clear
      env printf "${GREENBG}${WHITE} ${CHECK} ${NORMAL}\\n"
      return 0
    else
      printf "$log_pref Failed with error: ${res}\\n" >> ${RUN_LOG}
      printf "\033[77G\033[K"  # Position and clear
      env printf "${REDBG}${WHITE} ${BALLOT_X} ${NORMAL}\\n"
      if [ "$RUN_ERRORS_FATAL" ]; then
        echo
        log_fatal "Something went wrong. Exiting."
        log_fatal "The last few log entries were:"
        tail -17 "${RUN_LOG}" | head -15
        exit 1
      fi
      return ${res}
    fi
  else
    if [ $res -eq 0 ]; then
      printf "$log_pref Success.\\n" >> ${RUN_LOG}
      printf "\033[77G\033[K"
      env printf "${GREENBG} OK ${NORMAL}\\n"
      return 0
    else
      printf "$log_pref Failed with error: ${res}\\n" >> ${RUN_LOG}
      printf "\033[77G\033[K"
      env printf "${REDBG} ER ${NORMAL}\\n"
      if [ "$RUN_ERRORS_FATAL" ]; then
        log_fatal "Something went wrong with the previous command. Exiting."
        exit 1
      fi
      return ${res}
    fi
  fi
}

# Ask a yes or no question
# if $skipyesno is 1, always Y
# if NONINTERACTIVE environment variable is 1, always N, and print error message to use --force
yesno () {
  # XXX skipyesno is a global set in the calling script
  # shellcheck disable=SC2154
  if [ "$skipyesno" = "1" ]; then
    return 0
  fi
  if [ "$NONINTERACTIVE" = "1" ]; then
    echo "Non-interactive shell detected. Cannot continue, as the script may need to ask questions."
    echo "If you're running this from a script and want to install with default options, use '--force'."
    return 1
  fi
  stty echo 1>/dev/null 2>&1
  while read -r line; do
    stty -echo 1>/dev/null 2>&1
    case $line in
      y|Y|Yes|YES|yes|yES|yEs|YeS|yeS) return 0
      ;;
      n|N|No|NO|no|nO) return 1
      ;;
      *)
      stty echo 1>/dev/null 2>&1
      printf "\\n${YELLOW}Please enter ${CYAN}[y]${YELLOW} or ${CYAN}[n]${YELLOW}:${NORMAL} "
      ;;
    esac
  done
  stty -echo 1>/dev/null 2>&1
}

# mkdir if it doesn't exist
testmkdir () {
  if [ ! -d "$1" ]; then
    mkdir -p "$1"
  fi
}

# Copy a file if the destination doesn't exist
testcp () {
  if [ ! -e "$2" ]; then
    cp "$1" "$2"
  fi
}

# Set a Webmin directive or add it if it doesn't exist
setconfig () {
  sc_config="$2"
  sc_value="$1"
  sc_directive=$(echo "$sc_value" | cut -d'=' -f1)
  if grep -q "$sc_directive $2"; then
    sed -i -e "s#$sc_directive.*#$sc_value#" "$sc_config"
  else
    echo "$1" >> "$2"
  fi
}

# Detect the primary IP address
# works across most Linux and FreeBSD (maybe)
detect_ip () {
  # Interface detection
  defaultdev=$(ip ro ls 2>>"${RUN_LOG}" | grep default | head -1 | sed -e 's/.*\sdev\s//g' | awk '{print $1}')
  # IPv6 only?
  if [ -z "$defaultdev" ]; then
    defaultdev=$(ip -6 ro ls 2>>"${RUN_LOG}" | grep default | head -1 | sed -e 's/.*\sdev\s//g' | awk '{print $1}')
  fi
  # No default route at all: isolated or internal-only system?
  if [ -z "$defaultdev" ]; then
    log_warning "No default route detected. Cannot determine primary interface."
    log_warning "Extracting the name of the first active network interface that is not the loopback!"
    defaultdev=$(ip -o link show 2>>"${RUN_LOG}" | awk -F': ' '/state UP/ && !/LOOPBACK/ {print $2}' | head -1)
  fi
  # IPv4
  primaryaddr=$(ip -f inet addr show dev "$defaultdev" 2>>"${RUN_LOG}" | grep 'inet ' | awk '{print $2}' | head -1 | cut -d"/" -f1 | cut -f1)
  # IPv6 only?
  if [ -z "$primaryaddr" ]; then
      primaryaddr=$(ip -f inet6 addr show dev "$defaultdev" 2>>"${RUN_LOG}" | grep 'inet6 ' | awk '{print $2}' | head -1 | cut -d"/" -f1 | cut -f1)
  fi
  if [ "$primaryaddr" ]; then
    log_debug "Primary address detected as $primaryaddr"
    address=$primaryaddr
    return 0
  else
    log_warning "Unable to determine IP address of primary interface."
    echo "Please enter the name of your primary network interface: "
    stty echo 1>/dev/null 2>&1
    read -r primaryinterface
    stty -echo 1>/dev/null 2>&1
    # IPv4
    primaryaddr=$(/sbin/ip -f inet -o -d addr show dev "$primaryinterface" 2>>"${RUN_LOG}" | head -1 | awk '{print $4}' | head -1 | cut -d"/" -f1)
    # IPv6 only?
    if [ -z "$primaryaddr" ]; then
      primaryaddr=$(/sbin/ip -f inet6 -o -d addr show dev "$primaryinterface" 2>>"${RUN_LOG}" | head -1 | awk '{print $4}' | head -1 | cut -d"/" -f1)
    fi
    if [ "$primaryaddr" = "" ]; then
      # FreeBSD (IPv4)
      primaryaddr=$(/sbin/ifconfig "$primaryinterface" 2>>"${RUN_LOG}" | grep 'inet' | awk '{ print $2 }')
      # FreeBSD IPv6 only?
      if [ -z "$primaryaddr" ]; then
        primaryaddr=$(/sbin/ifconfig "$primaryinterface" 2>>"${RUN_LOG}" | grep 'inet6' | awk '{ print $2 }')
      fi
    fi
    if [ "$primaryaddr" ]; then
      log_debug "Primary address detected as $primaryaddr"
      address=$primaryaddr
    else
      fatal "Unable to determine IP address of selected interface.  Cannot continue."
    fi
    return 0
  fi
}

# Set the hostname in cloud-init
set_hostname_cloud () {
  # If cloud-init is installed, preserve the hostname
  if [ -f "/etc/cloud/cloud.cfg" ]; then
    if grep "^preserve_hostname: false" /etc/cloud/cloud.cfg >/dev/null; then
      log_debug "Setting preserve_hostname to true in /etc/cloud/cloud.cfg"
      sed -i "s/^preserve_hostname: false/preserve_hostname: true/" /etc/cloud/cloud.cfg
    fi
  fi
}

# Set the hostname
set_hostname () {
  local i=0
  local forcehostname
  if [ -n "$1" ]; then
    forcehostname=$1
  fi
  while [ $i -le 3 ]; do
    if [ -z "$forcehostname" ]; then
      local name
      name=$(hostname -f)
      log_error "Your system hostname $name is not fully qualified."
      printf "Please enter a fully qualified hostname (e.g.: host.example.com): "
      stty echo 1>/dev/null 2>&1
      read -r line
      stty -echo 1>/dev/null 2>&1
    else
      log_debug "Setting hostname to $forcehostname"
      line=$forcehostname
    fi
    if ! is_fully_qualified "$line"; then
      i=$((i + 1))
      log_warning "Hostname $line is not fully qualified."
      if [ "$i" = "4" ]; then
        fatal "Unable to set fully qualified hostname."
      fi
    else
      hostname "$line"
      echo "$line" > /etc/hostname
      hostnamectl set-hostname "$line" 1>/dev/null 2>&1
      set_hostname_cloud
      detect_ip
      shortname=$(echo "$line" | cut -d"." -f1)
      if grep "^$address" /etc/hosts >/dev/null; then
        log_debug "Entry for IP $address exists in /etc/hosts."
        log_debug "Updating with new hostname."
        sed -i "s/^$address.*/$address $line $shortname/" /etc/hosts
      else
        log_debug "Adding new entry for hostname $line on $address to /etc/hosts."
        printf "%s\\t%s\\t%s\\n" "$address" "$line" "$shortname" >> /etc/hosts
      fi
      i=4
    fi
  done
}

is_fully_qualified () {
  case $1 in
    localhost.localdomain)
      log_warning "Hostname cannot be localhost.localdomain."
      return 1
      ;;
    *.localdomain)
      log_warning "Hostname cannot be *.localdomain."
      return 1
      ;;
    *.internal)
      log_warning "Hostname cannot be *.internal."
      return 1
      ;;
    *.*)
      log_debug "Hostname is fully qualified as $1"
      return 0
      ;;
  esac
  return 1
}

# sets up distro version globals os_type, os_version, os_major_version, os_real
# returns 1 if something fails.
get_distro () {
  os=$(uname -o)
  # Make sure we're Linux
  if echo "$os" | grep -iq linux; then
    if [ -f /etc/cloudlinux-release ]; then # Oracle
      local os_string
      os_string=$(cat /etc/cloudlinux-release)
      os_real='CloudLinux'
      os_pretty=$os_string
      os_type='cloudlinux'
      os_version=$(echo "$os_string" | grep -o '[0-9\.]*')
      os_major_version=$(echo "$os_version" | cut -d '.' -f1)
    elif [ -f /etc/oracle-release ]; then # Oracle
      local os_string
      os_string=$(cat /etc/oracle-release)
      os_real='Oracle Linux'
      os_pretty=$os_string
      os_type='ol'
      os_version=$(echo "$os_string" | grep -o '[0-9\.]*')
      os_major_version=$(echo "$os_version" | cut -d '.' -f1)
    elif [ -f /etc/redhat-release ]; then # RHEL/CentOS/Alma/Rocky
      local os_string
      os_string=$(cat /etc/redhat-release)
      isrhel=$(echo "$os_string" | grep 'Red Hat')
      iscentosstream=$(echo "$os_string" | grep 'CentOS Stream')
      if [ -n "$isrhel" ]; then
        os_real='RHEL'
      elif [ -n "$iscentosstream" ]; then
        os_real='CentOS Stream'
      else
        os_real=$(echo "$os_string" | cut -d' ' -f1) # Doesn't work for Scientific
      fi
      os_pretty=$os_string
      os_type=$(echo "$os_real" | tr '[:upper:]' '[:lower:]' | tr ' ' '_')
      os_version=$(echo "$os_string" | grep -o '[0-9\.]*')
      os_major_version=$(echo "$os_version" | cut -d '.' -f1)
    elif [ -f /etc/os-release ]; then # Debian/Ubuntu
      # Source it, so we can check VERSION_ID
      # shellcheck disable=SC1091
      . /etc/os-release
      # Not technically correct, but os-release does not have 7.xxx for centos
      # shellcheck disable=SC2153
      os_real=$NAME
      os_pretty=$PRETTY_NAME
      os_type=$ID
      os_version=$VERSION_ID
      os_major_version=$(echo "${os_version}" | cut -d'.' -f1)
    else
      printf "${RED}No /etc/*-release file found, this OS is probably not supported.${NORMAL}\\n"
      return 1
    fi
  else
    printf "${RED}Failed to detect a supported operating system.${NORMAL}\\n"
    return 1
  fi
  if [ -n "$1" ]; then
    case $1 in
      real)
        echo "$os_real"
        ;;
      type)
        echo "$os_type"
        ;;
      version)
        echo "$os_version"
        ;;
      major)
        echo "$os_major_version"
        ;;
      *)
        printf "${RED}Unknown argument${NORMAL}\\n"
        return 1
        ;;
    esac
  fi
  return 0
}

# memory_ok - Function to check for enough memory. Will fix it, if not, by
# adding a swap file.
memory_ok () {
  min_mem=$1
  disk_space_required=$2
  # If swap hasn't been setup yet, try doing it
  is_swap=$(swapon -s|grep /swap.vm)
  if [ -n "$is_swap" ]; then
    if [ -z "$min_mem" ]; then
      min_mem=1048576
    fi
    # Check the available RAM and swap
    mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
    swap_total=$(awk '/SwapTotal/ {print $2}' /proc/meminfo)
    all_mem=$((mem_total + swap_total))
    swap_min=$(( 1286144 - all_mem ))

    if [ "$swap_min" -lt '262144' ]; then
      swap_min=262144
    fi

    min_mem_h=$((min_mem / 1024))
    if [ "$all_mem" -gt "$min_mem" ]; then
      log_debug "Memory is greater than ${min_mem_h} MB, which should be sufficient."
      return 0
    else
      log_error "Memory is below ${min_mem_h} MB. A full installation may not be possible."
    fi

    # We'll need swap, so ask and turn some on.
    swap_min_h=$((swap_min / 1024))
    echo
    echo "  Your system has less than ${min_mem_h} MB of available memory and swap."
    echo "  Installation is likely to fail, especially on Debian/Ubuntu systems (apt-get"
    echo "  grows very large when installing large lists of packages). You could exit"
    echo "  and re-install with the --minimal flag to install a more compact selection"
    echo "  of packages, or we can try to create a swap file for you. To create a swap"
    echo "  file, you'll need ${swap_min_h} MB free disk space, in addition to $disk_space_required GB of free space"
    echo "  for packages installation."
    echo
    echo "  Would you like to continue? If you continue, you will be given the option to" 
    printf "  create a swap file. (y/n) "
    if ! yesno; then
      return 1 # Should exit when this function returns 1
    fi
    echo
    echo "  Would you like for me to try to create a swap file? This will require" 
    echo "   at least ${swap_min_h} MB of free space, in addition to $disk_space_required GB for the"

    printf "  installation. (y/n) "
    if ! yesno; then
      log_warning "Proceeding without creating a swap file. Installation may fail."
      return 0
    fi

    # Check for btrfs, because it can't host a swap file safely.
    root_fs_type=$(grep -v "^$\\|^\\s*#" /etc/fstab | awk '{print $2 " " $3}' | grep "/ " | cut -d' ' -f2)
    if [ "$root_fs_type" = "btrfs" ]; then
      log_fatal "Your root filesystem appears to be running btrfs. It is unsafe to create"
      log_fatal "a swap file on a btrfs filesystem. You'll either need to use the --minimal"
      log_fatal "installation or create a swap file manually (on some other filesystem)."
      return 2
    fi

    # Check for enough space.
    root_fs_avail=$(df /|grep -v Filesystem|awk '{print $4}')
    if [ "$root_fs_avail" -lt $((swap_min + 358400)) ]; then
      root_fs_avail_h=$((root_fs_avail / 1024))
      log_fatal "Root filesystem only has $root_fs_avail_h MB available, which is too small."
      log_fatal "You'll either need to use the --minimal installation of add more space to '/'."
      return 3
    fi

    # Create a new file
    if ! dd if=/dev/zero of=/swap.vm bs=1024 count=$swap_min 1>>${RUN_LOG} 2>&1; then
      log_fatal "Creating swap file /swap.vm failed."
      return 4
    fi
    chmod 0600 /swap.vm 1>>${RUN_LOG} 2>&1
    mkswap /swap.vm 1>>${RUN_LOG} 2>&1
    if ! swapon /swap.vm 1>>${RUN_LOG} 2>&1; then
      log_fatal "Enabling swap file failed. If this is a VM, it may be prohibited by your provider."
      return 5
    fi
    echo "/swap.vm          swap            swap    defaults        0 0" >> /etc/fstab
  fi
  return 0
}

# serial_ok $serial $key
# Does the serial number and licnese key look correct?
serial_ok () {
  serial_num=$1
  license_key=$2
  i=0
  while [ $i -eq 0 ]; do
    if res=$(echo "$serial_num" |grep "[^a-z^A-Z^0-9]"); then
      printf "Serial number ${RED}$serial_num${NORMAL} contains invalid characters.\\n"
      get_serial
    elif [ -z "$serial_num" ]; then
      printf "${RED}Serial number cannot be blank.${NORMAL}\\n"
      get_serial
    elif res=$(echo "$license_key" |grep "[^a-z^A-Z^0-9]"); then
      printf "License key ${RED}$license_key${NORMAL} contains invalid characters.\\n"
      get_serial
    elif [ -z "$license_key" ]; then
      printf "${RED}License key cannot be blank.${NORMAL}\\n"
      get_serial
    else
      i=1
    fi
  done
  export SERIAL=$serial_num
  export KEY=$license_key
}

# Ask the user for a new serial number and license key
get_serial () {
  printf "${YELLOW}Please enter your serial number or 'GPL': ${NORMAL}"
  stty echo 1>/dev/null 2>&1
  read -r serial_num
  stty -echo 1>/dev/null 2>&1
  printf "${YELLOW}Please enter your license key or 'GPL': ${NORMAL}"
  stty echo 1>/dev/null 2>&1
  read -r license_key
  stty -echo 1>/dev/null 2>&1
}
