#!/bin/bash
# Copyright 2020 Quobyte Inc.
# Installation script for Quobyte software.

SLE_NAME='SLE'
CENTOS_NAME='CentOS'
CENTOSSTREAM_NAME='CentOS_Stream'
FEDORA_NAME='Fedora'
REDHAT_NAME='RHEL'
ROCKYLINUX_NAME='RockyLinux'
UBUNTU_NAME='Ubuntu'
DEBIAN_NAME='Debian'
AMAZON_LINUX_NAME='AmazonLinux'
AUTO_CONFIRM='yes' # Default auto confirm is yes. i.e; Agree to all the installation prompts.
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
COMMAND=''
# Default non-verbose mode, if -v given, show all the command output.
# With --interactive, sets it to yes and -v doesn't sever any purpose in this case.
EXTRA_VERBOSE='no'
POST_INSTALL_MSG='yes'
NTP_PACKAGE_NAME=''
FORCE_UNINSTALL='no'
CLIENT_MOUNT_POINT='/quobyte' # default /quobyte
SERVICES='/registry/metadata/data/webconsole/api/s3/nfs/' # Supported services.
# Some RHEL derivatives are supported - see adjust_OS_name()
SUPPORTED_OS='/Debian_11/Debian_12/Debian_13/RHEL_8/RHEL_9/RHEL_10/xUbuntu_22.04/xUbuntu_24.04/SLE_15/AmazonLinux_2023/'
DOCUMENTATION_URL_BASE="https://support.quobyte.com/docs/16/"
QUOBYTE_INSTALL_DOC="${DOCUMENTATION_URL_BASE}latest/installation_packages.html"
# Bootstrap registry is appended with "-bootstrap"
DEFAULT_REGISTRY_LOCATION='/var/lib/quobyte/devices/registry'
TEMP_REGISTRY_MOUNT='/mnt/quobyte-registry'
APT_GPG_KEY_LOCATION="/etc/apt/trusted.gpg.d/quobyte.gpg"

QNS_ENDPOINT='gk6z7wszg1.execute-api.eu-central-1.amazonaws.com'
SSH_PASS_AUTH='false'
########################################################################################
# ATTENTION:                                                                           #
# Any new optoion/command should be added to below variables,                          #
# otherwise script may reads missing options/values as remote host                     #
########################################################################################
SUPPORTED_COMMANDS='/uninstall/bootstrap/configure/add/add-client/'
NO_VAL_OPTIONS='/-f/-v/--no-create-registry-device/--interactive/-p/' # options that don't need any value.
VAL_OPTIONS='/--qns-id/--registry-endpoints/--services/--user/--password/--licensekey/--email/--script-host-path/--currenthost/-i/--mount-point/--registry-device' # options that need a value.
create_emtpy_registry='yes'
SSH_CONNECT_TIMEOUT='10'
HTTP_CONNECTIVITY_CHECK_TIMEOUT_SEC='60'
APT_LOCK_TIMEOUT=${APT_LOCK_TIMEOUT:-60}
ZYPPER_LOCK_TIMEOUT=${ZYPP_LOCK_TIMEOUT:-60}
DAEMON_TYPE=${DAEMON_TYPE:-}
DISABLE_REGISTRY_RESOLUTION=${DISABLE_REGISTRY_RESOLUTION:-'no'}
BOOTSTRAP_NODE_SERVICES="registry,data,metadata,webconsole,api"
REPO_URL=${REPO_URL:-https://packages.quobyte.com/repo/current}
TRACE_ON=${TRACE_ON:-""}
QNS_SUFFIX=".myquobyte.net"

# Keep java version checks in sync with ../bin/quobyte-service
MIN_SUPPORTED_JAVA_VERSION=17
MAX_SUPPORTED_JAVA_VERSION=25
PREFERRED_JAVA_VERSION=21

if [[ -n "${TRACE_ON}" ]]; then
  set -x
fi

print_java_not_found() {
    script_verbose_message 'Unable to install the Java JRE for your Linux distribution.' 'notify'
    script_verbose_message 'Please contact Quobyte support with the Linux distribution name and version.' 'notify'
    script_verbose_message "Alternatively, you can manually install JRE ${MIN_SUPPORTED_JAVA_VERSION} to JRE ${MAX_SUPPORTED_JAVA_VERSION}" 'notify'
    script_verbose_message '  on the node and repeat the installer command.' 'notify'
}

print_support_contact_message() {
  echo
  echo "If you encounter problems or have questions, don't hesitate to reach out to support@quobyte.com"
}

exit_on_error_with_support_contact_message() {
  print_support_contact_message
  exit 1
}

verify_java() {
  # Pick a JVM installation, prefer 'alternatives' over Java 11 over Java 1.8
  # Can be overriden for example with setting JAVA_HOME in a systemd override:
  #    Environment=JAVA_HOME=/usr/lib/jvm/jre-1.8.0
  JAVA_BIN="/usr/bin/java"
  if [ -n "$JAVA_HOME" ]; then
    JAVA_BIN="${JAVA_HOME}/bin/java"
  elif [ -x /etc/alternatives/java ]; then
    JAVA_BIN=/etc/alternatives/java
  fi

  if [ ! -x ${JAVA_BIN} ]; then
    print_java_not_found
    exit_on_error_with_support_contact_message
  fi

  # E.g. 1.8.0_191 or 11.0.1.
  JAVA_MAJOR_VERSION=$($JAVA_BIN -version 2>&1 | \
    awk '/version/ { gsub(/"/, "", $3); split($3, a, "."); \
      if (a[1] == 1) { print int(a[2]) } else { print int(a[1]); } }')

  if [ -z "$JAVA_MAJOR_VERSION" ] || \
    [ $JAVA_MAJOR_VERSION -lt $MIN_SUPPORTED_JAVA_VERSION ] || \
    [ $JAVA_MAJOR_VERSION -gt $MAX_SUPPORTED_JAVA_VERSION ]; then
    script_verbose_message "Java Runtime ($JAVA_BIN) has version $JAVA_MAJOR_VERSION which is not \
      supported. Please use version $PREFERRED_JAVA_VERSION. \
      Please select a supported Java version using 'update-alternatives --config java' \
      and restart the installation." 'notify'
    exit_on_error_with_support_contact_message
  fi
  script_verbose_message "Found Java version $JAVA_MAJOR_VERSION" 'info'
}

extract_repo_host() {
  if [[ "${REPO_URL}" != http*://* ]]; then
    echo "Invalid Quobyte REPO_URL '$REPO_URL'. REPO_URL must start with http(s)://"
    exit_on_error_with_support_contact_message
  fi
  QUOBYTE_PACKAGES_HOST="$(echo ${REPO_URL} | cut -d'/' -f3)"
}

extract_repo_host

if [ ! "$BASH_VERSION" ] ; then
    echo 'Script requires to be executed with bash.'
    echo "Please run the script with /bin/bash $0"
    echo " or as a binary (requires executable permissions)"
    exit_on_error_with_support_contact_message
fi

confirmation() {
    while [ 1 ]
    do
      echo -n $1
      read -n 1 RESPONSE
      echo
      case "$RESPONSE" in
        "") yesno=$2; return;;
        y|Y|"" ) yesno=1; return;;
        n|N ) yesno=0; return;;
        * ) echo; echo "'$RESPONSE' is not a valid response.";;
      esac
    done
 }

########################################################################################
# Interactive installer, executed when no command is given                             #
# The function is executed in a subshell to keep the variable and functions local      #
########################################################################################
interactive_install() (
  QBREPO="${REPO_URL}"
  STEPS=6
  CLEOL=$'\x1B[K'
  RED=$'\x1B[91m'
  YELLOW=$'\x1B[93m'
  WARNING=$'\x1B[103m\x1B[30mWARNING:\x1B[27m\x1B[0m'
  GREEN=$'\x1B[32m'
  RESETALL=$'\x1B[0m'

  MINRAM=16
  MINCORES=8

  header() {
    echo -e '\x1Bc'
    echo -e "\x1B[97m\x1B[44m Quobyte Installer - $1 ${CLEOL}"
    echo -e "\x1B[0m"
    echo
  }

  input_hosts() {
    hosts=""
    confirmation "Do you have a file with $1 FQDNs or IPs (one machine per line)? [y/N]" 0
    if [ $yesno = 1 ]
    then
      while [ 1 ]
      do
        read -e -p "Please enter the file name (including full path) here: " hostfile
        echo
        if [ ! -f $hostfile ]; then
          echo
          echo "${RED}File $hostfile does not exist or cannot be read.${RESETALL}"
          continue
        fi
        while read -r line || [[ -n "$line" ]]; do
          # Skip comment lines in host file
          case "$line" in \#*) continue ;; esac
          hosts=$hosts" "$line
        done < $hostfile
        break
      done
    else
      while [ 1 ]
      do
        read -e -p "FQDN or Machine IP address, or press <enter> to finish: " host
        if [ "$host" == "" ]
        then
          break;
        fi
        host=$(echo ${host} | tr -d '[:space:]')
        hosts=$hosts" "$host
      done
    fi
  }

  verify_hosts() {
    errors=0
    hosts=$1
    for server in ${hosts[@]}
    do
      echo -n "Machine: $server..."
      SSHOUT="$($sshcmd -tt $server "sudo -n -l >> /dev/null && free --si -g | awk  '/Mem:/{printf \"%s \",\$2}' && nproc --all")"
      if [ $? != 0 ]
      then
        echo -e "${RED}FAILED!"
        echo -e "Could not connect or execute sudo on machine $server: $SSHOUT${RESETALL}"
        errors=1
      elif [ "$2" != "" ]
      then
        elements=($(echo $SSHOUT | tr -d "\r"))
        result=""
        if ! [[ ${elements[0]} =~ ^[0-9]+$ ]]
        then
          result+="${WARNING} Could not parse amount of RAM from $server '${elements[0]}'\n"
        else
          if [ ${elements[0]} -lt ${MINRAM} ]
          then
            result+="${WARNING} $server has only ${elements[0]} GB RAM, which is less than the recommended ${MINRAM}GB.\n"
          fi
        fi

        if ! [[ ${elements[1]} =~ ^[0-9]+$ ]]
        then
          result+="${WARNING} Could not parse number of cores from $server '${elements[1]}'\n"
        else
          if [ ${elements[1]} -lt ${MINCORES} ]
          then
            result+="${WARNING} $server has only ${elements[1]} cores, which is less than the recommended minimum of ${MINCORES} cores.\n"
          fi
        fi
        if [ "$result" == "" ]
        then
          result="${GREEN}OK${RESETALL}"
        fi
        echo -e -n $result
      else
        echo -e "${GREEN}OK${RESETALL}"
      fi

    done
  }

  if [[ ! `uname` == "Linux" ]]
  then
    echo "This script works only with Linux at this time."
    exit_on_error_with_support_contact_message
  fi

  trap 'reset; exit' SIGINT
  header "Welcome"
  echo "Welcome to the interactive Quobyte installation."
  echo " "
  echo "This installer can be used to install Quobyte software on local or"
  echo "remote machines. For convenience, we recommend that you use pre-shared"
  echo "SSH keys for password-less login to the remote machines. Otherwise"
  echo "you will have to enter your password multiple times for each machine."
  echo ""
  echo "The installer will setup your installation to use QNS,"
  echo "the Quobyte Naming Service. This service is provided by Quobyte"
  echo "and requires internet connectivity from all participating machines."
  echo "QNS is provided free of charge on a best-effort basis."
  echo ""
  confirmation "Would you like to continue with the installation? [Y/n] " 1
  if [ $yesno = 0 ]
  then
    exit_on_error_with_support_contact_message
  fi

  header "Step 1 of $STEPS: Configure SSH settings"
  echo "Please enter the username to use for the SSH connection to the target machines."
  read -e -i $USER -p "Username: " installusr
  echo
  echo  "If you want to use another private key please specify the file name"
  echo  "here or leave empty for SSH defaults."

  while [ 1 ]
  do
    read -e -p "Path to keyfile, or empty: " keyfile
    echo
    sshcmd="ssh -n -l $installusr -o ConnectTimeout=${SSH_CONNECT_TIMEOUT}"
    ssh_id="$installusr@"
    if [ "$keyfile" != "" ]
    then
      if [ ! -f $keyfile ]
      then
        echo "${RED}Keyfile $keyfile does not exist or cannot be read.${RESETALL}"
        continue
      fi
      sshcmd+=" -i $keyfile "
      ssh_id=" -i ${keyfile} ${ssh_id}"
      break
    else
      break
    fi
  done

  while [ 1 ]
  do
    header "Step 2 of $STEPS: Add Servers"
    echo "Now you need to designate the servers on which to install the Quobyte services."
    echo "These servers will provide the storage for your Quobyte installation. "
    echo " "
    echo "Each server needs at least:"
    echo "* Two or more unformatted storage devices (NVMe, SSD or HDD)"
    echo "* ${MINRAM}GB of RAM or more"
    echo "* ${MINCORES} CPU cores or more"
    echo " "
    echo "IMPORTANT: If the devices have been used before, please make sure to clean them"
    echo "properly, otherwise the Quobyte device manager will not detect them. "
    echo "See ${DOCUMENTATION_URL_BASE}latest/ for details."
    input_hosts "server"
    servers=$hosts
    if [ "$hosts" == "" ]
    then
      echo "${RED}You did not specify any servers to install Quobyte on."
      echo -e "Press <enter> to repeat this step."
      read -n 1
      continue
    fi

    hostsArray=( $hosts )
    if (( ${#hostsArray[@]} < 4 ))
    then
      echo
      echo "${WARNING} The minimum recommended Quobyte cluster size is four!"
      echo "You won't be able to use all Quobyte functionality."
      echo
      echo "Press any key to continue..."
      read -n 1
    fi
    header "Step 3 of $STEPS: Verifying SSH connectivity to server machines"
    verify_hosts "$servers" "validate"
    if [ $errors -ne 0 ]
    then
      confirmation "At least one server had errors. Would you like to retry? [Y/n]" 0
      if [ $yesno -eq 0 ]
      then
        exit_on_error_with_support_contact_message
      fi
    else
      echo
      echo -e "All server machines verified successfully. Press any key to continue..."
      read -n 1
      break;
    fi
  done

  while [ 1 ]
  do
    header "Step 4 of $STEPS: Add Clients"
    echo "Please add all of the machines that should run a Quobyte client,"
    echo "i.e. be able to access the files on the Quobyte cluster."
    echo " "
    echo "NOTE: This installer only works for Linux clients. Please refer to the Quobyte"
    echo "manual for Windows and macOS installation steps:"
    echo "${DOCUMENTATION_URL_BASE}latest/user_tools.html"
    echo " "
    echo "Please add the FQDN or IP address for each machine:"
    echo
    input_hosts "client"
    clients=$hosts

    if [ "$hosts" == "" ]
    then
      echo
      echo "${RED}You did not specify any clients to install Quobyte on.${RESETALL}"
      confirmation "Press N to install without clients or Y to add clients? [y/N]" 0
      if [ $yesno -eq 1 ]
      then
        continue
      fi
    fi

    header "Step 5 of $STEPS: Verifying SSH connectivity to client machines"
    verify_hosts "$clients"
    if [ $errors -ne 0 ]
    then
      confirmation "At least one client had errors. Would you like to retry? [Y/n]" 0
      if [ $yesno -eq 0 ]
      then
        exit_on_error_with_support_contact_message
      fi
    else
      echo
      break;
    fi
  done

  echo
  echo "You've answered all of the necessary questions and everything has"
  echo -n "been configured. "
  confirmation "Do you want to start the installation? [Y/n]" 1
  if [ $yesno -eq 0 ]
  then
    echo
    echo "Aborting Quobyte installation. Goodbye."
    exit_on_error_with_support_contact_message
  fi
  echo

  header "Step 6 of $STEPS: Installing Quobyte..."
  generated_qns_id="$(head -c 8 /dev/urandom | base64 | tr -d '/+=' | tr '[:upper:]' '[:lower:]')"

  instlog=$(mktemp /tmp/quobyte-install-log.XXXXXX)
  counter=0
  bootstrap_node=""
  set -o pipefail
  for server in ${servers[@]}
  do
    echo "Starting install on server ${server}..." >> ${instlog}
    if [ $counter -eq 0 ]
    then
      # bootstrap
      bootstrap_node=$server
      /bin/bash $0 bootstrap -p --no-post-msg --qns-id ${generated_qns_id} --services registry,data,metadata,webconsole,api,s3 ${ssh_id}${server} | tee -a ${instlog}
    elif [ $counter -le 4 ]
    then
       /bin/bash $0 add -p --qns-id ${generated_qns_id} --services registry,data,metadata,s3 ${ssh_id}${server} | tee -a ${instlog}
    else
       /bin/bash $0 add -p --qns-id ${generated_qns_id} --services data,metadata,s3 ${ssh_id}${server} | tee -a ${instlog}
    fi
    if [ ! "${PIPESTATUS[0]}" -eq 0 ]
    then
      echo "${RED}Installation of software on ${server} failed. Aborting Quobyte installation.${RESETALL}"
      echo "${RED}Address the reported errors, then run the uninstall command on all nodes before retrying the installation.${RESETALL}"
      exit_on_error_with_support_contact_message
    fi
    echo "---" >> ${instlog}
    ((counter += 1))
  done

  errors=0
  for client in ${clients[@]}
  do
    echo "Starting install on client ${client}..." >> ${instlog}
    /bin/bash $0 add-client -p --qns-id ${generated_qns_id} ${ssh_id}${client} | tee -a ${instlog}
    if [ ! $? -eq 0 ]
    then
      echo "${WARNING}Installation of client on ${server} failed."
      echo "${WARNING}Installation of client on ${server} failed." >> ${instlog}
      errors=1
    fi
  done

  tmpfile=$(mktemp /tmp/quobyte-install-results.XXXXXX)
  echo
  cat > $tmpfile <<EOL
* Installation successful *

The system is starting and the Quobyte web console should be up
within 60 seconds. Please point your web browser to
http://console.${generated_qns_id}${QNS_SUFFIX}:8080 to complete the setup.
If your servers are not accessible from the outside via their primary ip use
http://${bootstrap_node}:8080

Your Quobyte API service is available as api.${generated_qns_id}${QNS_SUFFIX}
Use the following command to show the list of devices from the command line:
qmgmt -u http://api.${generated_qns_id}${QNS_SUFFIX} device list

The S3 proxies are reachable via s3.${generated_qns_id}. Once you create buckets you
can access them via <bucket-name>.s3.${generated_qns_id}${QNS_SUFFIX}

You can install additional Quobyte server machines with the following command:
$0 add --services registry,data,metadata --qns-id ${generated_qns_id} ${ssh_id}remote-host

and additional client machines with:
$0 add-client --qns-id ${generated_qns_id} ${ssh_id}remote-host

This information has been written to $tmpfile for future reference.
EOL

  if [ ! $errors -eq 0 ]
  then
    echo -e "\nThe installation failed on at least one client. Please check $instlog for details." >> $tmpfile
  else
    echo -e "\nYou can find the complete install log in $instlog." >> $tmpfile
  fi

  header "Quobyte installation finished"
  more $tmpfile
)

########################################################################################
# Set QUOBYTE_INSTALL_DEBUG to 1 to print debug messages                               #
########################################################################################
debug_message(){
  if [[ "${QUOBYTE_INSTALL_DEBUG}" = '1' ]] ; then
    echo -e "${RED}DEBUG:${NC} ${1}"
  fi
}

print_help(){
  echo ''
  echo 'Synopsis: This script helps to install Quobyte on a server or'
  echo '          in container environment.'
  echo "  You can use it to install Quobyte on your local machine (by omitting"
  echo "    'remote-host'), or on the remote host 'remote-host' (script uses"
  echo '    SSH to execute commands on the remote host).'
  echo '  Script requires root privileges:'
  echo '    - For remote host installation, the given user must have sudo'
  echo '      capabilities or must be the root user on remote host'
  echo '    - For local installation, the script must be run with sudo or as'
  echo '      root user'
  echo ''
  echo 'Usage: ./install_quobyte Command [Options] [[user@]remote-host]'
  echo ''
  echo 'General Options'
  echo '  --interactive Runs script in interactive mode.'
  echo "                Default is non-interactive mode, i.e, assumes 'yes' for"
  echo "                all the 'yes/no' prompts"
  echo '  -v            Verbose mode, outputs command execution messages'
  echo '                    default is non-verbose mode. Use this option to see command'
  echo '                    execution messages in non-interactive mode.'
  echo '                  --interactive runs in verbose mode, hence -v can be omitted with it'
  echo '  -i arg        Host authentication key'
  echo '  -p            Uses publickey, password authentication (in that order) for SSH and'
  echo '                     disables batch mode (default is batch mode i.e, no password prompts).'
  echo '                User should have sudo access on remote node without password prompts.'
  echo '  --ssh-connect-timeout arg_seconds Ssh connection timeout.'
  echo '                                    Default connection timeout is 10 seconds.'
  echo ''
  echo 'Commands:'
  echo '  bootstrap  Installs the first node of new cluster'
  echo '    note: If --registry-endpoints not given, the script'
  echo "          generates a qns-id. 'qns-id' should be used with 'add' while"
  echo '          adding additional nodes.'
  echo '    options:'
  echo '      --registry-device arg    Unformatted Registry device.'
  echo '      --registry-endpoints arg Srv record or comma separated list'
  echo '                               without spaces, disables QNS.'
  echo '      --services arg           Comma separated list without spaces. On'
  echo "                               'bootstrap' node, script installs"
  echo "                               registry,data,metadata,webconsole,api"
  echo '                               services by default. Additional services'
  echo '                               can be installed with --services'
  echo '                               <service-list>. See'
  echo "                               'Services option notes' at the bottom."
  echo ''
  echo '  add  Adds an additional node'
  echo '    note: One of --qns-id or --registry-endpoints must be given'
  echo '          but not both. The same method (QNS/registry-endpoints)'
  echo "          for 'add', that is used with 'bootstrap'."
  echo '    options:'
  echo '      --registry-device arg       Unformatted Registry device.'
  echo '      --services arg              Comma separated list without spaces. On'
  echo "                                  'add' node, script does not install any"
  echo "                                  service. All required services can be"
  echo '                                  installed with --services <service-list>'
  echo "                                  See 'Services option notes' at the bottom."
  echo '      --no-create-registry-device Does not create registry device.'
  echo '      --qns-id arg                Can be found in'
  echo '                                  /etc/quobyte/registry.cfg on'
  echo '                                  Quobyte bootstrap node.'
  echo '         or'
  echo '      --registry-endpoints arg    Srv record or comma separated list'
  echo '                                  without spaces.'
  echo ''
  echo '  configure  Configures initial management user'
  echo '    note: Requires quobyte-server package and python'
  echo '    options:'
  echo '      --user arg'
  echo '      --password arg'
  echo '      --email arg'
  echo ''
  echo '  add-client  Installs the Quobyte client'
  echo '    options:'
  echo '      --mount-point arg        Mount point (should not have spaces).'
  echo '                               Multiple mount points are not supported'
  echo '      --qns-id arg             Can be found in /etc/quobyte/registry.cfg on'
  echo '                               Quobyte bootstrap node.'
  echo '         or'
  echo '      --registry-endpoints arg Srv record or comma separated list'
  echo '                               without spaces.'
  echo ''
  echo '  uninstall  Uninstalls Quobyte'
  echo '    options:'
  echo '      -f  Clean uninstall:'
  echo '            Removes local Registry device (/var/lib/quobyte) and'
  echo '            configuration (/etc/quobyte).'
  echo ''
  echo 'Services option notes:'
  echo '  Supported services: registry,data,metadata,api,s3,webconsole,nfs'
  echo ''
  echo '  Some services are only supported on particular OS distributions.'
  echo "  See ${QUOBYTE_INSTALL_DOC} for details."
  print_support_contact_message
}

if [[ ${1} = '' ]]; then
  interactive_install
  exit 0
fi

if [[ ${1} = '-h' || ${1} = '--help' || ${1} = '' ]]; then
  print_help
  exit 0
fi

create_new_log(){
  INSTALLATION_LOG=$(mktemp /tmp/quobyte-install-log.XXXXXX)
  echo "Log file created by install_quobyte.sh on $(date) by $USER"> ${INSTALLATION_LOG}
}

# @Execute
create_new_log

########################################################################################
# Verbose mode for script execution                                                    #
#   If -v supplied, write echo messages to to STDOUT else to log file                  #
# Usage: script_verbose_message 'message' ['info'/'notify'/'non-critical']             #
#  default- white colored output                                                       #
#           'info' - green colored output on verbose mode (-v)                         #
#           'notify'- red colored and displayed always irrespective of -v              #
#           'non-critical' - yellow colored and displayed always irrespective of -v    #
#           'user_action'- message for user prompts( ex: overriding files etc )        #
########################################################################################
script_verbose_message(){
  if [[ "${2}" = 'notify' ]]; then
    echo -e ${1} >> ${INSTALLATION_LOG}
    echo -e ${RED}${1}${NC}
  elif [[ "${2}" = 'non-critical' ]]; then
    echo ${1} >> ${INSTALLATION_LOG}
    echo -e ${YELLOW}${1}${NC}
  elif [[ "${2}" = 'user_action' ]]; then
    echo -e "${1}"
  else
     echo -e ${1} >> ${INSTALLATION_LOG}
    if [[ "${2}" = 'info' ]]; then
      echo -e ${GREEN}${1}${NC}
    else
      echo -e ${1}
    fi
  fi
}

########################################################################################
# Usage: is_supported_option 'option' 'command'                                        #
#   echos "supported" if operation is supported by given command else nothing         #
########################################################################################
is_supported_option(){
  case $1 in
    --interactive | -v | -i | -p | --no-post-msg | --ssh-connect-timeout ) echo "supported" ; return;;
  esac
  if [[ ! -z "$2" ]]; then
    case "$2" in
      bootstrap )
        case "$1" in
          --registry-endpoints | --services | --qns-id | --registry-device) echo "supported" ; return ;;
          * ) return ;;
        esac
      ;;
      add )
        case "$1" in
          --registry-endpoints | --qns-id | --services | --registry-device ) echo "supported" ; return ;;
          * ) return ;;
        esac
      ;;
      uninstall )
        case "$1" in
          -f ) echo "supported" ; return ;;
          * ) return ;;
        esac
      ;;
      configure)
        case "$1" in
          --user | --password | --licensekey| --email) echo "supported" ; return ;;
          * ) return ;;
        esac
      ;;
      add-client )
       case "$1" in
          --mount-point | --registry-endpoints | --qns-id ) echo "supported" ; return ;;
          * ) return ;;
        esac
      ;;
    esac
  fi
}

########################################################################################
# This validation ensures services are given without mistakes, as script currently     #
# doesn't support adding additional service after installation, it would be better to  #
# validate and stop on erred service names.                                            #
########################################################################################
validate_opt_services(){
  IFS=',' read -r -a services <<< "${services_string}"
  for service in "${services[@]}"
  do
    if [[ -z "$(echo "${SERVICES}" | grep  "/$service/")" ]]; then
      script_verbose_message "Invalid service ${service}." 'notify'
      exit_on_error_with_support_contact_message
    fi
  done
}

########################################################################################
# This validation ensures 'add' given with required registry value                     #
#      --qns-id or --registry-endpoints                                                #
########################################################################################
validate_registry_opts(){
   if [[ (${COMMAND} = 'add' || ${COMMAND} = 'add-client' ) && ( -z  "${registry_endpoints}") && ( -z "${qns_id}" ) ]]; then
      script_verbose_message "Requires --qns-id or --registry-endpoints for ${COMMAND}." 'notify'
      exit_on_error_with_support_contact_message
   fi

   if [[ ( ! -z  "${registry_endpoints}") && ( ! -z "${qns_id}" ) ]]; then
     script_verbose_message "Only one of --qns-id or --registry-endpoints for ${COMMAND} but not both." 'notify'
     exit_on_error_with_support_contact_message
   fi
   if [[ ! -z "${registry_endpoints}" && ( "${registry_endpoints}" == *"localhost"* || "${registry_endpoints}" == *"127.0.0.1"* ) ]]; then
     script_verbose_message "127.0.0.1/localhost is not allowed as registry endpoints. Given --registry-endpoints ${registry_endpoints}." 'notify'
     exit_on_error_with_support_contact_message
   fi
 }

validate_configure_opts(){
  if [[ (${COMMAND} = 'configure') &&  ( -z "${config_user}" || -z "${config_passwd}" || -z "${email}" ) ]]; then
    script_verbose_message "Found empty value for --user/--password/--email. All these are required for 'configure'." 'notify'
    exit_on_error_with_support_contact_message
  fi
}

########################################################################################
# All required option validations should be validated before intiating command         #
########################################################################################
validate_opt_values(){
  validate_opt_services
  validate_registry_opts
  validate_configure_opts
}

parse_opts(){
    eval set -- "${valid_opts}"
    while true; do
      case "$1" in
        --no-post-msg ) POST_INSTALL_MSG='no'; shift ;;
        --ssh-connect-timeout) shift ; SSH_CONNECT_TIMEOUT="${1}"; shift ;;
        -v ) EXTRA_VERBOSE='yes'   ; shift ;;
        -i ) shift ; REMOTE_HOST_KEY="${1}" ; shift ;;
        -p ) SSH_PASS_AUTH='true' ; shift ;;
        --registry-device ) shift; REGISTRY_DEV="${1}";shift;;
        --interactive ) AUTO_CONFIRM='no' ; shift ;;
        --registry-endpoints ) shift; registry_endpoints="${1}" ; shift ;;
        --qns-id ) shift ; qns_id="${1}" ; shift ;;
        --no-create-registry-device )  create_emtpy_registry='no'; shift ;;
        --services ) shift ; services_string="$1" ; shift ;;
        -f ) FORCE_UNINSTALL='yes' ; shift ;;
        --user ) shift ; config_user="$1" ; shift ;;
        --password ) shift ; config_passwd="$1" ; shift ;;
        --email ) shift ; email="$1" ; shift ;;
        --license) shift ; licensekey="$1" ; shift ;;  # TODO: add licensekey functionality.
        --mount-point) shift; CLIENT_MOUNT_POINT="${1}" ; shift ;;
        -- ) shift ; break ;;
        * ) shift ; break ;;
      esac
    done
}

#########################################################################################################
# GNU getopt doesn't enforce missing values with options and doesn't stop execution on invalid options  #
#   validate_opts                                                                                       #
#      1) enforces the missing values, if the supported option is given for command                     #
#      2) makes sure unsupported options are not given                                                  #
#########################################################################################################
validate_opts(){
  local opt_end_index=$(echo "${valid_opts}" | grep -b -o -- "-- '"$COMMAND"'" | cut -d: -f1)
  local valid_opts_parsed="${valid_opts:0:$opt_end_index}" # cannonicalized options from getopt, by removing options that did not have -/--
  local opts=($@) # actual options given to script
  for opt in "${opts[@]}"
  do
    if [[ ! -z $(echo "$opt" | grep -- "^-") ]]; then
      if [[ -z $(echo $valid_opts_parsed | grep -w -- "$opt") ]]; then # Given option with command not cannonicalized by getopt
        if [[ ! -z $(is_supported_option "$opt" "$COMMAND") ]]; then # If it is supported by command, that mean value not given for the option.
          script_verbose_message "Value required for '$opt' with '$COMMAND'" 'notify'
          exit_on_error_with_support_contact_message
        else
          script_verbose_message "'$opt' is not supported by '$COMMAND'" 'notify'
          exit_on_error_with_support_contact_message
        fi
      fi
    fi
  done
}

######################################################################################################
# Check for remote options                                                                           #
#    Checks last three arguments and determines whether script execution is on remote/local machine  #
######################################################################################################
read_remote_options(){
  local skipNextVale='n'
  if [[ $# -gt 3 ]]; then
    last_3_options=(${@: -3})
  else
    last_3_options=(${@})
  fi
  for option in "${last_3_options[@]}"
  do
    if [[ ${skipNextVale} != 'n' ]]; then
      skipNextVale='n'
      continue
    fi
      if [[ ! -z "$(echo "${SUPPORTED_COMMANDS}" | grep  "/${option}/")" ]]; then # Command, so skip it
        REMOTE_HOST=''
        continue
      elif [[ ! -z "$(echo "${VAL_OPTIONS}" | grep  "/${option}/")" ]]; then # Value option, so skip value that is in next position
        REMOTE_HOST=''
        skipNextVale='y'
        continue
      elif [[ ! -z "$(echo "${NO_VAL_OPTIONS}" | grep  "/${option}/")" ]];then # Flag like -v that doesn't take any value.
        REMOTE_HOST=''
        continue
      else
        REMOTE_HOST=${option}
      fi
  done
  if [[ ! -z "$(echo "${SUPPORTED_COMMANDS}${VAL_OPTIONS}${NO_VAL_OPTIONS}" | grep  "/${REMOTE_HOST}/")" || ( ! -z $(echo $REMOTE_HOST | grep -- "^-") ) ]]; then
    REMOTE_HOST=''
  fi
  debug_message "Identified Host:${REMOTE_HOST}. If host is empty, that means script is executing on local machine."
}

process_command_options(){
  if [[ ! -z $(getopt &> /dev/null | grep 'command not found' ) ]] ; then
    script_verbose_message 'Quobyte installer requires GNU getopt utility.' 'notify'
    exit_on_error_with_support_contact_message
  fi
  if [[ "${1}" = '--currenthost' ]]; then
    shift ; HOST_NAME="${1}" ; shift; # Internal, used to identity installer path on host for print_post_bootstrap_instructions.
  fi
  if [[ "${1}" = '--script-host-path' ]];then
    shift ; SCRIPT_HOST_PATH="${1}" ; shift; # Internal, used to identity user supplied host name for remote execution.
  fi
  COMMAND="${1}" ; # shift ;
  if [[ "${COMMAND}" = 'bootstrap' ]]; then
    valid_opts=`getopt -q -o vpi: --longoptions ssh-connect-timeout:,interactive,no-post-msg,registry-endpoints:,qns-id:,services:,registry-device: -n 'install_quobyte' -- "$@"`
  elif [[ "${COMMAND}" = 'add' ]]; then
    valid_opts=`getopt -q -o vpi: --longoptions ssh-connect-timeout:,interactive,no-create-registry-device,registry-device:,registry-endpoints:,qns-id:,services: -n 'install_quobyte' -- "$@"`
  elif [[ "${COMMAND}" = 'uninstall' ]]; then
    valid_opts=`getopt -q -o vfpi: --long ssh-connect-timeout:,interactive -n 'install_quobyte' -- "$@"`
  elif [[ "${COMMAND}" = 'configure' ]]; then
    valid_opts=`getopt -q -o vpi: --long ssh-connect-timeout:,interactive,user:,password:,email:,license: -n 'install_quobyte' -- "$@"`
  elif [[ "${COMMAND}" = 'add-client' ]]; then
    valid_opts=`getopt -q -o vpi: --long ssh-connect-timeout:,interactive,registry-endpoints:,qns-id:,mount-point: -n 'install_quobyte' -- "$@"`
  else
    script_verbose_message "${1} is not a valid Quobyte installer command" 'notify'
    exit_on_error_with_support_contact_message
  fi
  shift; # It must be exactly after command identification and before command validating options.
  if [[  -z "$SCRIPT_HOST_PATH" ]] ;then # options are already validate on local machine before intiating execution on remote, so can be skipped.
    read_remote_options "$@"
    validate_opts "$@"
  fi

  parse_opts $@
  validate_opt_values
}


########################################################################################
# @Execute Read options specified                                                      #
#  Having it here makes script bit faster and exits early if invalid option is given   #
########################################################################################
options=($@)
process_command_options "$@"

########################################################################################
# @Execute Quobyte Command execution starts from here                                  #
#   If REMOTE_HOST identified during execution, script copies itself                   #
#     and starts exectuion on remote machine.                                          #
########################################################################################

if [[ ! -z ${REMOTE_HOST} ]]; then
  # Strip remote options and then execute same command on remote machine.
  if [[ ! -z "$( echo ${REMOTE_HOST} | grep '@')" ]]; then
     host="$( echo ${REMOTE_HOST} | cut -d@ -f2)"
  else
     host=${REMOTE_HOST}
  fi
  len=${#options[@]}
  options=${options[@]:0:$len-1}
  # Run script only if the remote machine does not require password to sudo.
  # This behavior is added to avoid hanging of the script for user input.
  # SSH requirement is stated in the documentation.
  script_verbose_message "Executing Quobyte installer command on ${host}" 'info'
  remote_command="sudo -l -n >> /dev/null || {
      echo 'ERROR: Script cannot run on remote machine that requires password to sudo.'
      echo '  The script running user should have sudo access on remote node without password prompts.';
      exit 1 ; } && sudo chmod 777 /tmp/install_quobyte &&
      sudo REPO_URL=${REPO_URL} /tmp/install_quobyte --currenthost ${host} --script-host-path ${0} ${options}"
  if [[ "${SSH_PASS_AUTH}" = 'false' ]]; then
    if [[ ! -z ${REMOTE_HOST_KEY} ]]; then
      debug_message "Installation started on ${REMOTE_HOST} with keyfile ${REMOTE_HOST_KEY}"
      scp -o BatchMode=Yes -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_CONNECT_TIMEOUT} -i  \
        ${REMOTE_HOST_KEY} $(dirname $0)/$(basename $0)  \
        ${REMOTE_HOST}:/tmp/install_quobyte  >> /dev/null && \
      ssh -o BatchMode=Yes -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_CONNECT_TIMEOUT} -tt \
        -i ${REMOTE_HOST_KEY} ${REMOTE_HOST} ${remote_command}
    else
      debug_message "Installation started on ${REMOTE_HOST} without keyfile"
      scp -o BatchMode=Yes -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_CONNECT_TIMEOUT}  \
        $(dirname $0)/$(basename $0) ${REMOTE_HOST}:/tmp/install_quobyte \
        >> /dev/null  && \
      ssh -o BatchMode=Yes -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_CONNECT_TIMEOUT} \
       -tt ${REMOTE_HOST} ${remote_command}
    fi
    if [ ! $? -eq 0 ]
    then
      echo -e "${RED}Please resolve reported issues and re-run the command.${NC}"
      exit_on_error_with_support_contact_message
    fi
  else
    # https://stackoverflow.com/questions/41786904/authenticating-with-user-password-once-for-multiple-commands-session-multipl
    tmpConnectDir=$(mktemp -u ~/quobyteXXXXXXXXXX)
    scp -o BatchMode=No -o PreferredAuthentications=publickey,password -o ControlMaster=Yes \
      -o ControlPersist=yes -o ControlPath=$tmpConnectDir \
      -o ConnectTimeout=${SSH_CONNECT_TIMEOUT} $(dirname $0)/$(basename $0) \
      ${REMOTE_HOST}:/tmp/install_quobyte >> /dev/null && \
    ssh -o PreferredAuthentications=publickey,password -tt -o ControlMaster=No \
      -o BatchMode=No -o ControlPath=$tmpConnectDir \
      -o ConnectTimeout=${SSH_CONNECT_TIMEOUT} ${REMOTE_HOST} ${remote_command}
    script_exist_status=$?
    if [[ -d $tmpConnectDir ]]; then
      ssh -o ControlPath=$tmpConnectDir -O exit ${REMOTE_HOST}
      rm -rf $tmpConnectDir
    fi
    if [ ! $script_exist_status -eq 0 ]
    then
      echo -e "${RED}Please resolve reported issues and re-run the command.${NC}"
      exit_on_error_with_support_contact_message
    fi
  fi
  debug_message "Script excuted on ${host} with options : ${options}"
else
  if [[ -z "$SCRIPT_HOST_PATH" ]]; then
    script_verbose_message 'Executing Quobyte installer command on localhost' 'info'
  fi
  if [[ $EUID > 0 ]]; then
    if [[ ! -z "$SCRIPT_HOST_PATH" ]]; then
       echo -e "${RED}The user executing this script must be able to sudo or root user on remote machine${NC}"
    else # Empty SCRIPT_HOST_PATH indicates that the script is running on local machine
       echo -e "${RED}install_quobyte must be run with sudo or as root user${NC}"
    fi
    exit_on_error_with_support_contact_message
  fi

  ###################################################################################
  #  ATTENTION: everthing that needs to be executed on remote when remote given     #
  #         should go below                                                         #
  ###################################################################################

########################################################################################
#  ATTENTION:  CARE sending passwords as plain text                                    #
#   logs commands to /tmp/quobyte_installation.log for non-interactive mode            #
#   (execution without --interactive),                                                 #
#  Handle auto-confirm and --vv options  before executing command                      #
#  If -yq NOT supplied, commands are modified to run in interactive mode               #
#  If -vv supplied, output command execution messages                                  #
#  Returns command status code                                                         #
########################################################################################
adjust_command_to_user_options(){
  local status=1
  if [[ "${AUTO_CONFIRM}" = 'yes' ]]; then
    command_str="${1}"
    echo "Command: ${command_str}" >>  ${INSTALLATION_LOG}
    if [[ "${EXTRA_VERBOSE}" = 'yes' ]]; then
      bash -c "${command_str}"  | tee -a ${INSTALLATION_LOG}
      status=${PIPESTATUS[0]}
    else
      bash -c "${command_str}" >> ${INSTALLATION_LOG} 2>&1
      status=${PIPESTATUS[0]}
    fi
  else # remove auto yes flags and then run commands in interactive mode
    main_string="${1}"
    interactive_command=${main_string/-yq/ }
    interactive_command=${interactive_command/--non-interactive/ }
    interactive_command=${interactive_command/--force-yes/ }
    interactive_command=${interactive_command/DEBIAN_FRONTEND=noninteractive /}
    echo "Command: ${interactive_command}" >>  ${INSTALLATION_LOG}
    # Cannot write to log file due to tee hang on input dialog https://stackoverflow.com/questions/30687504/redirected-output-hangs-when-using-tee
    bash -c "${interactive_command}"
    status=$?
  fi
  return $status
}

determine_OS_details(){
  script_verbose_message 'Determining OS details' 'info'
  if [[ -f /etc/os-release ]]; then
    OSName="$(grep ^NAME /etc/os-release | cut -d= -f2 | cut -d\" -f2)"
    OSVersion="$(grep VERSION_ID /etc/os-release | cut -d= -f2 | cut -d\" -f2)"
  else
    script_verbose_message 'Could not Identity OS details.' 'notify'
    script_verbose_message 'Make sure /etc/os-release exists on the machine.' 'notify'
    exit_on_error_with_support_contact_message
  fi
  script_verbose_message "Identified OS: ${OSName}" "info"
  script_verbose_message "Identified Version id: ${OSVersion}" "info"
  adjust_OS_name
}

adjust_OS_name(){
  local original_osname=${OSName}
  local original_os_version=${OSVersion}
  # NOTE(kaisers): match for stream first as centos is a subset of centos stream
  if [[ ! -z "$(echo "${OSName}" | grep -iE \
     "Red Hat Enterprise Linux|Oracle Linux Server|centos stream|rocky linux|AlmaLinux")" ]]; then
    OSName=${REDHAT_NAME}
    if [[ ${OSVersion} = 8.* ]]; then
      OSVersion='8';
    elif [[ ${OSVersion} = 9.* ]]; then
      OSVersion='9';
    elif [[ ${OSVersion} = 10.* ]]; then
      OSVersion='10';
    fi
  elif [[ ! -z "$(echo "${OSName}" | grep -i "opensuse")" ]] ; then
    OSName=${SLE_NAME}
    if [[ "${OSVersion}" = 15.* ]]; then
      OSVersion='15'
    fi
  elif [[ ! -z "$(echo "${OSName}" | grep -i "ubuntu" )" ]] ; then
    OSName=${UBUNTU_NAME}
  elif [[ ! -z "$(echo "${OSName}" | grep -i "debian" )" ]] ; then
    OSName=${DEBIAN_NAME}
  elif [[ "${OSName}" = 'SLES' ]] ; then
    OSName=${SLE_NAME}
    if [[ "${OSVersion}" = 15.* ]] ; then
      OSVersion='15'
    fi
  elif [[ "${OSName}" = "Amazon Linux" ]]; then
    OSName=${AMAZON_LINUX_NAME}
  else
    script_verbose_message "${original_osname} is not supported. See ${QUOBYTE_INSTALL_DOC}#supported-linux-distributions" 'notify'
    exit_on_error_with_support_contact_message
  fi
  if [[  -z "$(echo "${SUPPORTED_OS}" | grep -i ${OSName}_${OSVersion})" ]]; then
    script_verbose_message "${original_osname} version ${original_os_version} is not supported. See ${QUOBYTE_INSTALL_DOC}#supported-linux-distributions" 'notify'
    exit_on_error_with_support_contact_message
  fi
  script_verbose_message "Will use compatible packages of ${OSName} ${OSVersion}" 'info'
}

get_time_service_status(){
 if [[ "${DAEMON_TYPE}" = 'systemd' ]]; then
   sh -c "(((sudo systemctl status ${1} || sudo systemctl status ${1}d ) 2>&1 )  |grep -w 'Active:' |  sed 's/.*Active: \?\([a-zA-Z0-9]* [(a-zA-Z0-9)-]*\).*/\1/' )"
 else
   sh -c "(sudo /etc/init.d/${1} status 2>&1)"
 fi
}

install_time_service(){
  local is_already_installed='no'
  local running_package=''
  if [[ "$(get_time_service_status 'chrony')" = 'active (running)' ]]; then
    running_package='chrony'
  elif [[   "$(get_time_service_status 'ntp')" = 'active (running)' ]];then
    running_package='ntp'
  fi
  if [[ ! -z "${running_package}" ]]; then
    is_already_installed='yes'
    NTP_PACKAGE_NAME=${running_package}
  else
    if [[ $(is_software_installed 'chrony') -eq 1 ]]; then
      if [[ $(is_software_installed 'ntp') -eq 1  ]]; then
        case "${OSName}" in
          ${REDHAT_NAME} | ${AMAZON_LINUX_NAME})
            adjust_command_to_user_options 'sudo dnf -yq install chrony'
          ;;
          ${SLE_NAME})
            adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive refresh"
            adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive install chrony"
          ;;
          ${UBUNTU_NAME} | ${DEBIAN_NAME})
            adjust_command_to_user_options "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq install chrony"
          ;;
        esac
        if [[ $(is_software_installed 'chrony') -eq 1  ]]; then #if chrony not installed yet, install ntp.
          case "${OSName}" in
            ${REDHAT_NAME} | ${AMAZON_LINUX_NAME})
              adjust_command_to_user_options 'sudo dnf -yq install ntp'
            ;;
            ${SLE_NAME})
              if [[ "${OSName}" = ${SLE_NAME} ]]; then
                adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive refresh"
                adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive install ntp"
              else
                adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive install ntp"
              fi
            ;;
            ${UBUNTU_NAME} | ${DEBIAN_NAME})
             adjust_command_to_user_options "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq install chrony"
            ;;
          esac
          NTP_PACKAGE_NAME='ntp'
        else
          NTP_PACKAGE_NAME='chrony'
        fi
    else
      is_already_installed='yes'
      NTP_PACKAGE_NAME='ntp'
    fi
  else
     is_already_installed='yes'
     NTP_PACKAGE_NAME='chrony'
   fi
 fi
  if [[ ${is_already_installed} = 'yes' ]]; then
    script_verbose_message "Found existing ${NTP_PACKAGE_NAME} package" 'info'
  else
    script_verbose_message "Installed ${NTP_PACKAGE_NAME}" 'info'
  fi
}

install_certificates_if_required() {
  curl -IsL --connect-timeout "${HTTP_CONNECTIVITY_CHECK_TIMEOUT_SEC}" \
    "${QUOBYTE_PACKAGES_HOST}" 2>&1 | \
    grep -iq "SSL certificate problem\|certificate has expired\|CN mismatch"
  if [[ "$?" != 0 ]]; then
    script_verbose_message 'Installing required ca-certificates package' 'non-critical'
    adjust_command_to_user_options 'sudo dnf -y -q install ca-certificates'
  fi
}

install_curl() {
  # check before installation as curl and curl-minimal can cause package conflicts
  which curl &> /dev/null
  if [[ $? -eq 0 ]]; then
    return
  fi
  # We install curl-minimal as we only need http(s)
  # For debian, curl-minimal is not available so install curl
  case "${OSName}" in
    ${REDHAT_NAME} | ${AMAZON_LINUX_NAME})
      adjust_command_to_user_options 'sudo dnf -y -q install curl-minimal'
    ;;
    ${SLE_NAME})
      adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive install curl-minimal"
      ;;
    ${UBUNTU_NAME} | ${DEBIAN_NAME})
      if [[ ${OSName} = ${UBUNTU_NAME} ]]; then
        export RUNLEVEL=1
        adjust_command_to_user_options 'sudo add-apt-repository -y universe'
        adjust_command_to_user_options "sudo apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq update"
      fi
      adjust_command_to_user_options "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq install curl"
    ;;
  esac
  which curl &> /dev/null
  if [[ $? -ne 0 ]]; then
    script_verbose_message 'Script cannot install required package curl' 'notify'
    exit_on_error_with_support_contact_message
  fi
}


# Installs dependencies needed by client and server.
install_common_dependencies() {
  install_curl
  case "${OSName}" in
    ${REDHAT_NAME} | ${AMAZON_LINUX_NAME})
      install_certificates_if_required
    ;;
    ${SLE_NAME})
      adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive install util-linux"
      ;;
    ${UBUNTU_NAME} | ${DEBIAN_NAME})
      if [[ ${OSName} = ${UBUNTU_NAME} ]]; then
        export RUNLEVEL=1
        adjust_command_to_user_options 'sudo add-apt-repository -y universe'
        adjust_command_to_user_options "sudo apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq update"
      fi
      adjust_command_to_user_options "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq install apt-transport-https uuid-runtime perl gnupg dialog debconf ssh"
      if [[ ${OSName} = ${UBUNTU_NAME} ]]; then
        if [[ ${OSVersion} = 22* ]]; then
          # the dialog is redirected to the log file, skip it first then call it without redirect
          echo 'debconf debconf/priority select critical' | debconf-set-selections
        fi
      fi
    ;;
  esac
}

install_jre() {
  for version in $(seq $MAX_SUPPORTED_JAVA_VERSION -1 $MIN_SUPPORTED_JAVA_VERSION); do
    local status=1
    case "${OSName}" in
      ${REDHAT_NAME})
        adjust_command_to_user_options "sudo dnf -y -q install java-${version}-openjdk-headless"
        status=$?
      ;;
      ${AMAZON_LINUX_NAME})
        adjust_command_to_user_options "sudo dnf -y -q install java-${version}-amazon-corretto-headless"
        status=$?
      ;;
      ${SLE_NAME})
        adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper -q --non-interactive install java-17-openjdk-headless java-${version}-openjdk-headless"
        status=$?
      ;;
      ${UBUNTU_NAME} | ${DEBIAN_NAME})
        adjust_command_to_user_options "sudo apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT update -yq"
        adjust_command_to_user_options "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq install openjdk-${version}-jre-headless"
        status=$?
      ;;
    esac
    if [[ "$status" -eq 0 ]]; then
      script_verbose_message "Installed OpenJre version ${version}" "info"
      break;
    fi
  done
}

install_dependencies(){
  install_common_dependencies
  install_jre
  install_time_service
  enable_time_service
}

check_quobyte_package_existence(){
  curl -IsL --connect-timeout "${HTTP_CONNECTIVITY_CHECK_TIMEOUT_SEC}" "${1}" 2>&1 \
   | grep -iq "http/.* 200"
  if [[ "$?" -ne 0 ]]; then
    script_verbose_message "Could not find Quobyte packages for your OS distribution at" 'notify'
    script_verbose_message "  ${1}. Please contact" 'notify'
    script_verbose_message "  Quobyte support with OS distribution details." 'notify'
    exit_on_error_with_support_contact_message
  fi
}

add_quobyte_repositories(){
  local quobyteImageURL="${REPO_URL}"
  script_verbose_message "Installing Quobyte packages from repository: ${quobyteImageURL}" "info"
  case "${OSName}" in
    ${REDHAT_NAME} | ${AMAZON_LINUX_NAME})
      if [[ ${OSName} = ${REDHAT_NAME} ]]; then
        quobyteImageURL="${quobyteImageURL}/rpm/RH_${OSVersion}"
      else
        quobyteImageURL="${quobyteImageURL}/rpm/${OSName}_${OSVersion}"
      fi
      check_quobyte_package_existence "${quobyteImageURL}"
      adjust_command_to_user_options "sudo curl -sL ${quobyteImageURL}/quobyte.repo -o /etc/yum.repos.d/quobyte.repo"
    ;;
    ${SLE_NAME})
      quobyteImageURL="${quobyteImageURL}/rpm/${OSName}_${OSVersion}"
      check_quobyte_package_existence "${quobyteImageURL}"
      adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive --gpg-auto-import-keys addrepo ${quobyteImageURL}/quobyte.repo"
      adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive --gpg-auto-import-keys refresh"
    ;;
    ${UBUNTU_NAME} | ${DEBIAN_NAME})
      if [[ ${OSName} = ${UBUNTU_NAME} ]]; then
        case ${OSVersion} in
          24.04) RELEASE='noble' ;;
          22.04) RELEASE='jammy' ;;
        esac
      elif [[ ${OSName} = ${DEBIAN_NAME} ]]; then
        case ${OSVersion} in
          11) RELEASE='bullseye' ;;
          12) RELEASE='bookworm' ;;
          13) RELEASE='trixie' ;;
        esac
      fi
      check_quobyte_package_existence "${quobyteImageURL}/apt/dists/${RELEASE}"
            adjust_command_to_user_options "sudo curl -sL ${quobyteImageURL}/apt/pubkey.gpg | \
        gpg --dearmor | sudo tee ${APT_GPG_KEY_LOCATION} > /dev/null"
      echo "deb [signed-by=${APT_GPG_KEY_LOCATION}] ${quobyteImageURL}/apt ${RELEASE} main" >> \
      /etc/apt/sources.list.d/quobyte.list
      adjust_command_to_user_options "sudo apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT update"
    ;;
  esac
 }

########################################################################################
# Installs quobyte-server and quobyte-client by default                                #
#  Optional argument: service names separated by comma (ex: nfs,openstack-juno)        #
#       - if optional argument given installs additional services                      #
########################################################################################
install_quobyte_software(){
  local install_packages=''
  if [[ ! -z "${1}" ]]; then
    local input_string="${1}"
    local additional_packages="quobyte-${input_string/,/ quobyte-}"
    install_packages="${install_packages} ${additional_packages}"
  fi
  case "${OSName}" in
   ${REDHAT_NAME} | ${AMAZON_LINUX_NAME})
      adjust_command_to_user_options "sudo dnf -yq install ${install_packages}"
    ;;
    ${SLE_NAME})
      adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive install ${install_packages}"
    ;;
    ${UBUNTU_NAME} | ${DEBIAN_NAME})
      adjust_command_to_user_options "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq install ${install_packages}"
    ;;
  esac
}

enable_time_service(){
  if [[ "${DAEMON_TYPE}" = 'systemd' ]]; then
    if [[ "${NTP_PACKAGE_NAME}" = 'ntp' ]]; then # On centos ntp service is ntpd and on Ubuntu it is ntp. Same is for chrony.
      adjust_command_to_user_options 'sudo systemctl start ntpd || sudo systemctl start ntp' # Try ntpd, if failed try ntp.
      adjust_command_to_user_options 'sudo systemctl enable ntpd || sudo systemctl enable ntp'
    else
      adjust_command_to_user_options 'sudo systemctl start chronyd || sudo systemctl start chrony'
      adjust_command_to_user_options 'sudo systemctl enable chronyd || sudo systemctl enable chrony'
    fi
  else
    if [[ "${NTP_PACKAGE_NAME}" = 'ntp' ]]; then # on init system service is named without d at the end of service name.
      adjust_command_to_user_options 'sudo /etc/init.d/ntp start'
    else
      adjust_command_to_user_options 'sudo /etc/init.d/chrony start'
    fi
  fi
}

add_registry_device(){
  registry_override='n'
  local registry_location="${1}"
  if [[ ! -f "${registry_location}" ]]; then
   adjust_command_to_user_options "sudo mkdir -p ${registry_location}"
  fi
  adjust_command_to_user_options "sudo chown quobyte:quobyte ${registry_location}"
  DEVICE_FILE="${registry_location}/QUOBYTE_DEV_SETUP"
  if [[ -f "${DEVICE_FILE}" ]];then
    script_verbose_message "Registry device already present at ${registry_location}. Override it(y/n)?" "user_action"
    read registry_override
  else
    registry_override='y'
  fi
  while [[ ! ( "${registry_override}" = 'y' || "${registry_override}" = 'n' ) ]]; do
    script_verbose_message "Valid options: y or n." 'notify'
    script_verbose_message "Enter your option(y/n)" "user_action"
    read registry_override
  done
  if [[ "${registry_override}" = 'y'  ]]; then
    sudo sh -c "echo \"# Quobyte device identifier file\" > ${DEVICE_FILE}"
    sudo sh -c "echo \"# Created from install_quobyte.sh on $(date)\" >> ${DEVICE_FILE}" >> /dev/null
    sudo sh -c "echo \"# Hostname: ${HOSTNAME}\" >> ${DEVICE_FILE}" >> /dev/null
    sudo sh -c "echo \"device.serial=$(uuidgen)\" >> ${DEVICE_FILE}" >> /dev/null
    sudo sh -c "echo \"device.model=unknown\" >> ${DEVICE_FILE}"
    sudo sh -c "echo \"device.type=DIR_DEVICE\" >> ${DEVICE_FILE}"
    if [[ -f "${DEVICE_FILE}" ]];then
      debug_message "Registry device created at ${DEVICE_FILE}"
    else
      script_verbose_message "Failed to create bootstrap device at  ${DEVICE_FILE} " 'notify'
    fi
    adjust_command_to_user_options "sudo chown quobyte:quobyte ${DEVICE_FILE}"
  fi
}

preprocess_registry_dev(){
  adjust_command_to_user_options "sudo mkfs.xfs -L quobyte-dev ${REGISTRY_DEV}"
  adjust_command_to_user_options "sudo mount ${REGISTRY_DEV} ${TEMP_REGISTRY_MOUNT}"
}

setup_registry_device(){
  if [[ "${REGISTRY_DEV}" != "" ]]; then
    sudo mkdir -p "${TEMP_REGISTRY_MOUNT}"
    preprocess_registry_dev
    add_registry_device "${TEMP_REGISTRY_MOUNT}"
  else
    if [[ "${COMMAND}" = "bootstrap" ]]; then
      add_registry_device "${DEFAULT_REGISTRY_LOCATION}-bootstrap"
    else
      add_registry_device "${DEFAULT_REGISTRY_LOCATION}"
    fi
  fi
 }

########################################################################################
# Utility to check if package installed                                                #
#   - echos 1 if NOT installed otherwise 0                                            #
# Used internally to verify dependencies installation.                                 #
# $(is_software_installed "ntp") NOT to call as normal function as it echos values     #
########################################################################################
is_software_installed(){
  case "${OSName}" in
     ${REDHAT_NAME} | ${AMAZON_LINUX_NAME} | ${SLE_NAME})
      sudo sh -c "rpm -q ${1}" &> /dev/null
      echo $?
      return
    ;;
    ${UBUNTU_NAME} | ${DEBIAN_NAME})
      sudo sh -c "dpkg -l ${1}" &> /dev/null
      echo $?
      return
    ;;
  esac
}

determine_system_daemon(){
  if [[  -z "${DAEMON_TYPE}" ]]; then
    local pid="$(ps -e | grep 'systemd$')"
    if [[ ! -z "${pid}" ]]; then
      DAEMON_TYPE=systemd
    else
      DAEMON_TYPE=non-systemd
    fi
  fi
}

########################################################################################
# Starts, stops and restarts quobyte_services                                          #
#      argument1: start/stop/restart/enable                                            #
#      argument2: list of commma separated services(ex:registry,metatdata,data)        #
########################################################################################
quobyte_services(){
  IFS=',' read -r -a services_list <<< "${2}"
  if [[ "${DAEMON_TYPE}" = 'systemd' ]]; then
    for service_name in "${services_list[@]}"
    do
      if [[ "${1}" = 'status' ]]; then
        printf "  ${service_name}:%s\n" "$(sh -c "sudo systemctl status quobyte-${service_name} 2>&1 |grep -w 'Active:' |  sed 's/.*Active: \?\([a-zA-Z0-9]* [(a-zA-Z0-9)-]*\).*/\1/'")"
      else
        adjust_command_to_user_options "sudo systemctl ${1} quobyte-${service_name}"
      fi
    done
    if [[ "${1}" = 'disable' ]]; then
      adjust_command_to_user_options 'sudo systemctl daemon-reload'
      adjust_command_to_user_options 'sudo systemctl reset-failed'
    fi
  else
    if [[  "${1}" != 'enable' && "${1}" != 'disable' ]]; then
      for service_name in "${services_list[@]}"
      do
        if [[ "${1}" = 'status' ]]; then
          echo ''
          echo "${service_name}:"
          printf "\n%s\n\n" "$(sh -c "sudo /etc/init.d/quobyte-${service_name} ${1}  2>&1 ")"
        else
          adjust_command_to_user_options "sudo /etc/init.d/quobyte-${service_name} ${1}"
        fi
      done
    fi
  fi
}

enable_performance_profile(){
  systemd-detect-virt &> /dev/null
  vm="$?"
  if [[ $vm = "0" ]]; then
    return 0;
  fi
  if [[ $vm = "1" || -z "$(grep '^flags.*hypervisor.*' /proc/cpuinfo )" ]]; then
    script_verbose_message "Enabling quobyte-$1-performance profile" 'info'
    if [[ ${OSName} = ${UBUNTU_NAME} || ${OSName} = ${DEBIAN_NAME} ]]; then
      sudo sh -c "echo \"# Added by Quobyte installer script\" >> /etc/sysctl.conf"
      sudo sh -c "awk '/\[sysctl\]/{flag=1;next}/\[bootloader\]/{flag=0}flag' /usr/lib/tuned/quobyte-$1-performance/tuned.conf >> /etc/sysctl.conf"
      script_verbose_message 'Performance profile is enabled and will take effect on the next reboot' 'info'
    else
      which tuned-adm &> /dev/null
      if [[ "$?" -ne 0 ]]; then
        script_verbose_message "Command tuned-adm is not available. Therefore, cannot configure quobyte-$1-performance profile." 'non-critical'
        script_verbose_message "Install tuned package and configure Quobyte performance profile" 'non-critical'
        script_verbose_message "manullay using \"sudo tuned-adm profile quobyte-$1-performance\"." 'non-critical'
        return 1
      fi
      adjust_command_to_user_options "sudo tuned-adm profile quobyte-$1-performance"
    fi
    tuned_service=$(systemctl list-units | grep "tuned.service" | awk '{$1=$1};1')
    if [[ -z "${tuned_service}" ]]; then
      script_verbose_message "Systemd unit tuned not found. Please install tuned for Quobyte" 'non-critical'
      script_verbose_message "performance profile to be effective." 'non-critical'
      return 1
    fi
    if [[ "$(echo ${tuned_service} | cut -d' ' -f3)" != 'active' || "$(echo ${tuned_service} | cut -d' ' -f4)" != 'running' ]]; then
      script_verbose_message "Systemd unit tuned is inactive and/or not running." 'non-critical'
      script_verbose_message "Please enable and start tuned.service for Quobyte performance profile to be effective." 'non-critical'
      return 1
    fi
  fi
}

execute_add(){
  if [[ "${REGISTRY_DEV}" != "" ]]; then
    validate_registry_device
  fi
  script_verbose_message 'Adding Quobyte repository' 'info'
  add_quobyte_repositories
  script_verbose_message 'Installing Quobyte software' 'info'
  if [[ ! -z "$(echo "${services_string}" | grep 'nfs')" ]]; then
    install_quobyte_software 'server,nfs'
    if [[ "$(is_software_installed 'quobyte-nfs')" -eq 1 ]]; then
      script_verbose_message 'Failed quobyte-nfs package installation' 'notify'
    fi
  else
    install_quobyte_software 'server'
  fi
  if [[ "$(is_software_installed 'quobyte-server')" -eq 1 ]];then
    script_verbose_message 'Failed quobyte-server package installation' 'notify'
    exit_on_error_with_support_contact_message
  else
    enable_performance_profile 'server'
  fi
}

check_dependencies(){
  if [[ ($(is_software_installed 'ntp') -eq 1 && $(is_software_installed 'chrony') -eq 1 ) ]]; then
    script_verbose_message 'Script cannot install required package ntp/chrony' 'notify'
    exit_on_error_with_support_contact_message
  fi
  verify_java
}

validate_registry_device(){
  if [[ "$(sudo wipefs -p ${REGISTRY_DEV} )" != "" ]]; then
    script_verbose_message "Quobyte requires unformatted device. The given device ${REGISTRY_DEV} is a formatted device." 'notify'
    exit_on_error_with_support_contact_message
  fi
}


execute_bootstrap(){
  execute_add
  script_verbose_message 'Setting up bootstrap node' 'info'
  setup_registry_device
  if [[ "${registry_override}" = 'y' ]]; then
    sudo sh -c "echo \"device.bootstrap=true\" >> ${DEVICE_FILE}"
  fi
}

remove_repositories(){
  case "${OSName}" in
    ${REDHAT_NAME} | ${AMAZON_LINUX_NAME})
      adjust_command_to_user_options 'sudo rm -f /etc/yum.repos.d/quobyte.repo*'
    ;;
    ${SLE_NAME})
      adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive removerepo quobyte*"
    ;;
    ${UBUNTU_NAME} | ${DEBIAN_NAME})
      if [[ -f /etc/apt/sources.list.d/quobyte.list ]]; then
        adjust_command_to_user_options 'sudo rm /etc/apt/sources.list.d/quobyte.list'
      fi
    ;;
  esac
}

is_quobyte_installed(){
  if [[ $(is_software_installed "quobyte-${1}") -eq 0 ]] ; then
    echo "installed"
  fi
}

unmount_client_mountpoint() {
  if [[ ! -f /etc/quobyte/client-service.cfg ]]; then
    return
  fi
  mount_path=$(sudo grep "^mount_point=" /etc/quobyte/client-service.cfg | cut -d'=' -f2)
  adjust_command_to_user_options "sudo umount ${mount_path}"
}

wait_for_client_service_termination() {
  max_wait=300 # seconds
  current_wait=0
  while [[ "${current_wait}" -lt "${max_wait}" ]]; do
    ((current_wait++))
    sudo systemctl is-active quobyte-client &> /dev/null
    if [[ "$?" -ne 0 || "${current_wait}" -eq "${max_wait}" ]]; then
      # inactive does not mean service exited cleanly - kill service forcefully and remove
      # the mount point. This is not normal state - observed if registry is not reachable
      # (for example, non-existing registry). This only kills service, if service is not normally
      # exited within the max_wait seconds.
      sudo systemctl kill --kill-who=main -s SIGKILL quobyte-client &> /dev/null
      unmount_client_mountpoint
      return
    fi
    echo -ne "Waiting for quobyte-client process to stop (elapsed ${current_wait}s/${max_wait}s)...\r"
    sleep 1 # sleep 1 second
  done
}

purge_packages() {
  case "${OSName}" in
    ${UBUNTU_NAME} | ${DEBIAN_NAME})
        # Sometimes packages are not completely removed
        adjust_command_to_user_options 'sudo DEBIAN_FRONTEND=noninteractive dpkg --purge quobyte-server quobyte-client quobyte-nfs quobyte-thirdparty-libraries &> /dev/null'
        adjust_command_to_user_options 'sudo systemctl daemon-reload'
    ;;
  esac
}

execute_uninstall(){
  debug_message 'Uninstallation started'
  remove_repositories
  if [[ -z "$(is_quobyte_installed 'server')" && -z "$(is_quobyte_installed 'client')" ]]; then
    remove_quobyte_directories
    purge_packages
    script_verbose_message "Quobyte packages are not found on ${HOSTNAME}"
    exit 0
  else
    quobyte_services 'stop --no-block' 'registry,data,metadata,api,s3,webconsole,nfs,client'
    wait_for_client_service_termination
    quobyte_services 'disable' 'registry,data,metadata,api,s3,webconsole,nfs,client'
    debug_message 'Services stopped'
    remove_quobyte_directories
    case "${OSName}" in
      ${REDHAT_NAME} | ${AMAZON_LINUX_NAME})
        adjust_command_to_user_options 'sudo dnf autoremove -yq quobyte-server quobyte-client quobyte-nfs quobyte-thirdparty-libraries'
      ;;
      ${SLE_NAME})
        adjust_command_to_user_options "sudo ZYPP_LOCK_TIMEOUT=$ZYPPER_LOCK_TIMEOUT zypper --non-interactive remove quobyte-client quobyte-server quobyte-nfs quobyte-thirdparty-libraries"
      ;;
      ${UBUNTU_NAME} | ${DEBIAN_NAME})
        adjust_command_to_user_options "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=$APT_LOCK_TIMEOUT -yq remove quobyte-client quobyte-server quobyte-nfs quobyte-thirdparty-libraries"
        purge_packages
      ;;
    esac
    if [[ -z $(is_quobyte_installed 'server') && -z "$(is_quobyte_installed 'client')" ]]; then
      script_verbose_message 'Removed quobyte software'
    else
      script_verbose_message 'Unable to remove Quobyte software' 'notify'
      exit_on_error_with_support_contact_message
    fi
    if [[ $? -eq 0 ]]; then
      script_verbose_message 'Uninstallation completed' 'info'
    fi
  fi
}

remove_quobyte_directories(){
  adjust_command_to_user_options 'sudo rm -rf /var/log/quobyte'
  if [[ "${FORCE_UNINSTALL}" = 'yes' ]]; then
    script_verbose_message 'Removing Quobyte /var/lib/quobyte and /etc/quobyte' 'info'
    adjust_command_to_user_options "sudo rm -rf /var/lib/quobyte"
    adjust_command_to_user_options "sudo rm -rf /etc/quobyte"
    if [[ -f "${APT_GPG_KEY_LOCATION}" ]]; then
      adjust_command_to_user_options "sudo rm ${APT_GPG_KEY_LOCATION}"
    fi
  else
    if [[ -d /var/lib/quobyte || -d /etc/quobyte ]]; then
      echo "Leaving configuration on ${HOSTNAME} for later use."
      echo '  If you do not want to keep the configuration, please use uninstall -f'
      echo '  or remove the following directories manually:'
    fi
    if [[ -d /var/lib/quobyte ]]; then
      echo '    Registry (/var/lib/quobyte)'
    fi
    if [[ -d /etc/quobyte ]]; then
      echo '    Configuration (/etc/quobyte)'
    fi
  fi
}

execute_configure(){
  script_verbose_message "Adding user ${config_user}"
  if [[ -z $(is_quobyte_installed 'server')  ]]; then
    script_verbose_message "'configure' requires Quobyte Server installation." 'notify'
    exit_on_error_with_support_contact_message
  fi
  local qmgmt_configure_message="$(qmgmt user config add ${config_user} ${email} SUPER_USER password ${config_passwd})"
  if [[ ! -z "$(echo "${qmgmt_configure_message}" | grep 'Success.' )" ]]; then
    script_verbose_message 'Configuration successful.' 'info'
  else
    script_verbose_message "${qmgmt_configure_message}" 'notify'
  fi
}

modify_registry_endpoints(){
  old_registry_string="$(cat /etc/quobyte/host.cfg | grep '^registry=')"
  if [[ ! -z ${old_registry_string} ]]; then
    sed -i -e "s/${old_registry_string}/registry=${1}/g" /etc/quobyte/host.cfg
  else
    echo "registry=${1}" >> /etc/quobyte/host.cfg
  fi
  if [[ ! -z "${qns_id}" ]]; then
     echo "qns.id=${qns_id}" >> /etc/quobyte/registry.cfg
  fi
  if [[ "${COMMAND}" = 'bootstrap' && ! -z "${qns_id}" ]]; then
    qns_secret="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 26 | head -n 1)"
    echo "qns.secret=${qns_secret}" >> /etc/quobyte/registry.cfg
  fi
}

check_qns_endpoint_reachability() {
  curl -IsL --connect-timeout "${HTTP_CONNECTIVITY_CHECK_TIMEOUT_SEC}" \
      "${QNS_ENDPOINT}" 2>&1 | grep -iq "HTTP/.* 403"
  if [[ "$?" -ne 0 ]]; then
    script_verbose_message "Your internet connection is not reliable or QNS endpoint '${QNS_ENDPOINT}' is not reachable." 'notify'
    script_verbose_message "This will affect system performance. Please ensure QNS endpoint is reachable from the node and restart the Quobyte service(s)." 'notify'
  fi
}

update_host_config(){
  if [[  ! -z "${registry_endpoints}" ]]; then
    modify_registry_endpoints "${registry_endpoints}"
  elif [[  ! -z "${qns_id}" ]]; then
    modify_registry_endpoints "${qns_id}${QNS_SUFFIX}"
    check_qns_endpoint_reachability
  elif [[ ( -z  "${registry_endpoints}") && ( -z "${qns_id}" ) ]]; then
    generated_qns_id="$(head -c 8 /dev/urandom | base64 | tr -d '/+=' | tr '[:upper:]' '[:lower:]')"
    qns_id=${generated_qns_id}
    debug_message "Generated QNS id: ${generated_qns_id}"
    modify_registry_endpoints "${generated_qns_id}${QNS_SUFFIX}"
    check_qns_endpoint_reachability
  fi
}

update_client_config(){
  local registry_config=''
  if [[ ! -z ${registry_endpoints} ]]; then
    registry_config=${registry_endpoints}
  elif [[ ! -z ${qns_id} ]]; then
    registry_config="${qns_id}${QNS_SUFFIX}"
    check_qns_endpoint_reachability
  else
    script_verbose_message 'One of --registry_endpoints or --qns-id is required' 'notify'
    exit_on_error_with_support_contact_message
  fi
  old_mount_string="$(cat /etc/quobyte/client-service.cfg | grep '^mount_point=')"
  if [[ ! -z ${old_mount_string} ]]; then
    sed -i -e "s/${old_mount_string}/mount_point=${CLIENT_MOUNT_POINT}/g" /etc/quobyte/client-service.cfg
  else
    echo "mount_point=${CLIENT_MOUNT_POINT}" >> /etc/quobyte/client-service.cfg
  fi

  old_registry_string="$(cat /etc/quobyte/client-service.cfg | grep '^registry=')"
  if [[ ! -z ${old_registry_string} ]]; then
    sed -i -e "s/${old_registry_string}/registry=${registry_config}/g" /etc/quobyte/client-service.cfg
  else
    echo "registry=${registry_config}" >> /etc/quobyte/client-service.cfg
  fi

  client_uuid=$(uuidgen)
  old_uuid_string="$(cat /etc/quobyte/client-service.cfg | grep '^uuid=')"
  if [[ ! -z ${old_uuid_string} ]]; then
    sed -i -e "s/${old_uuid_string}/uuid=${client_uuid}/g" /etc/quobyte/client-service.cfg
  else
    echo "uuid=${client_uuid}" >> /etc/quobyte/client-service.cfg
  fi
  grep -q "^user_allow_other" /etc/fuse.conf || echo "user_allow_other" >> /etc/fuse.conf
}

create_client_mount_device(){
  grep -wq "${CLIENT_MOUNT_POINT}" /proc/mounts
  if [[ "$?" -eq 0 ]]; then # mountpoint is already mounted - cannot be reused
    script_verbose_message "Mount point '${CLIENT_MOUNT_POINT}' is already mounted." 'notify'
    script_verbose_message "Please retry the command with a different mount point using --mount-point option" 'notify'
    script_verbose_message "or unmount the '${CLIENT_MOUNT_POINT}' and retry." 'notify'
    exit 1
  fi
  if [[ ! -d "${CLIENT_MOUNT_POINT}" ]]; then
    script_verbose_message "Creating client mount point at '${CLIENT_MOUNT_POINT}'" 'info'
    adjust_command_to_user_options "sudo mkdir -p '${CLIENT_MOUNT_POINT}'"
  fi
}

check_client_existence(){
 if [[ "$(is_software_installed 'quobyte-client')" -eq 0 ]]; then
   script_verbose_message 'Client already installed on the node' 'notify'
   exit_on_error_with_support_contact_message
 fi
}

start_and_enable_client_service(){
  if [[ "${DAEMON_TYPE}" = 'systemd' ]]; then
    adjust_command_to_user_options "sudo systemctl enable quobyte-client"
    adjust_command_to_user_options "sudo systemctl start --no-block quobyte-client"
  else
    adjust_command_to_user_options "sudo mount.quobyte -c /etc/quobyte/client-service.cfg"
    echo "Client as service not supported on non-systemd platforms."
    echo "Client needs to be re-mounted after restart"
  fi
}

execute_add_client(){
 install_common_dependencies
 add_quobyte_repositories
 install_quobyte_software 'client'
 create_client_mount_device
 update_client_config
 script_verbose_message 'Starting Quobyte client service' 'info'
 start_and_enable_client_service
 if [[ "$(is_software_installed 'quobyte-client')" -eq 0 ]];then
   enable_performance_profile 'client'
   script_verbose_message 'Added client' 'info'
 else
   script_verbose_message 'Failed to add client' 'notify'
 fi
}

check_and_init_script_host_path(){
  if [[ -z ${SCRIPT_HOST_PATH} ]]; then
    SCRIPT_HOST_PATH=${0}
  fi
}

print_time_service_status(){
  printf "  %s: " ${NTP_PACKAGE_NAME}
  echo "$(get_time_service_status ${NTP_PACKAGE_NAME})"
  echo
}

print_post_bootstrap_instructions(){
  if [[ -z ${HOST_NAME} ]]; then
    HOST_NAME=${HOSTNAME}
  fi
  check_and_init_script_host_path
  echo ''
  echo 'Congratulations! You successfully bootstrapped the first node of your Quobyte cluster.'
  echo ''
  if [[ ! -z "${generated_qns_id}" ]]; then
    echo "Your qns-id is \"${generated_qns_id}\". Please keep it for reference (you can also find it in /etc/quobyte/registry.cfg)."
    echo ''
  fi
  echo 'You can now add more nodes or configure the system.'
  echo ''
  echo 'You can add nodes with the following command. Please make sure to select the services that you need:'
  if [[ ! -z "${generated_qns_id}" ]]; then
    echo "  ${SCRIPT_HOST_PATH} add --services registry,data,metadata --qns-id ${generated_qns_id} [[user@]remote-host]"
  elif [[ ! -z "${registry_endpoints}" ]]; then
    echo "  ${SCRIPT_HOST_PATH} add --services registry,data,metadata --registry-endpoints ${registry_endpoints} [[user@]remote-host]"
  fi
  echo ''
  echo 'Configure the system with:'
  echo "  http://${HOST_NAME}:8080"
  echo ''
  echo '  or via the command line:'
  echo "    ${SCRIPT_HOST_PATH} configure --user <user> --password <pass> --email <email> [[user@]remote-host]"
  print_support_contact_message
}

print_quobyte_exists_instructions(){
  echo ''
  script_verbose_message "Found Quobyte packages (Server) on ${HOSTNAME}" 'notify'
  echo 'This script only operates on targets that do not have Quobyte installed yet.'
  echo ''
  echo 'Here are some commands that might help you achieve what you want.'
  echo 'Add additional Quobyte service:'
  echo '  On Systemd systems:'
  echo '    sudo systemctl enable quobyte-<service-name>'
  echo '    sudo systemctl start quobyte-<service-name>'
  echo ''
  echo '  On non-systemd systems:'
  echo '    sudo /etc/init.d/quobyte-<service-name> start'
  echo ''
  echo 'Reconfigure registry nodes:'
  echo '  1. Open /etc/quobyte/host.cfg on respective node'
  echo "  2. Update 'registry' field with new endpoints"
  echo '  3. Save and exit'
  echo ''
}

print_installer_log_file_instructions(){
  script_verbose_message "Installation log is created on host ${HOSTNAME}" 'info'
  script_verbose_message "  at ${INSTALLATION_LOG} . If you encounter any" 'info'
  script_verbose_message '  installation issues, please contact Quobyte support with the log file' 'info'
}

check_packages_url_reachability(){
  curl -IsL --connect-timeout "${HTTP_CONNECTIVITY_CHECK_TIMEOUT_SEC}" \
      "${QUOBYTE_PACKAGES_HOST}" 2>&1 | grep -iq "http/.* 200"
  if [[ "$?" -ne 0 ]]; then
    script_verbose_message "WARNING: Installer script cannot find internet
      connection, hence, given command may fail." 'notify'
    script_verbose_message "Quobyte installer requires internet connection
      to install Quobyte packages and its dependencies." 'notify'
    script_verbose_message "Your internet connection is not reliable or Quobyte
      packages URL ${QUOBYTE_PACKAGES_HOST} is not reachable." 'notify'
    script_verbose_message "If requests to internet are passing through a proxy
      server, this warning can be ignored." 'notify'
  fi
}

warn_if_firewall_iptables_active(){
  firewalld_service=$(systemctl list-units | grep "firewalld.service" | awk '{$1=$1};1')
  firewall_active=""
  if [[ "$(echo ${tuned_service} | cut -d' ' -f3)" = 'active' || "$(echo ${tuned_service} | cut -d' ' -f4)" = 'running' ]]; then
    firewall_active="active"
  fi
  which iptables &> /dev/null
  if [[ "$?" -ne 0 ]]; then
    ip_tables=""
  else
    ip_tables="$(iptables -L)"
  fi
  if [[ ! -z "${firewalld_service}" || ! -z "${ip_tables}" ]]; then
    script_verbose_message "Firewall/iptables rules are detected on host $HOSTNAME." 'non-critical'
    script_verbose_message "Please disable or configure (see Production Checklist section of documentation) to" 'non-critical'
    script_verbose_message "ensure that Quobyte services and clients can communicate properly." 'non-critical'
  fi
}

check_registry_startup() {
  if [[ "$1" != *"registry"* ]]; then
    return
  fi
  for i in $(seq 1 120); do
    registry_started="$(sudo journalctl -u quobyte-registry | grep -qi 'REGISTRY service is now ready')"
    if [[ "${registry_started}" -eq 0 ]]; then
      script_verbose_message "Quobyte Registry service is ready" 'info'
      return
    fi
    sleep 1
  done
  script_verbose_message "Quobyte Registry service is not ready in 2 Minutes.
    \nPlease verify Quobyte Registry logs and fix the reported issues (logs
    \ncan be obtained with journalctl -u quobyte-registry). Service restart
    \nmay be required after fixing the issues." 'notify'
  exit_on_error_with_support_contact_message
}

check_architecture_is_supported() {
  local arch="$(uname -m)"
  echo "$arch" | grep -iE "x86_64|aarch64" &> /dev/null
  if [[ "$?" -ne 0 ]]; then
    script_verbose_message "The host architecture ($arch) is not supported." 'notify'
    script_verbose_message "Only x86 or ARM architectures are supported." 'notify'
    exit 1
  fi
  script_verbose_message "Detected architecture: $arch" 'info'
}

validate_registry_endpoints() {
  if [[ ${DISABLE_REGISTRY_RESOLUTION} = 'yes' ]]; then
    return
  fi
  local registries=""
  if [[ ! -z "${registry_endpoints}" ]]; then
    registries="${registry_endpoints}"
  fi
  if [[ -z "${registries}" ]]; then  #bootstrap/uninstall command
    return
  fi
  IFS=',' read -r -a registry_array <<< "$registries"
  unresolvable_registries=()
  for registry in "${registry_array[@]}"; do
    registry_without_port="$(echo ${registry} | cut -d':' -f1)"
    host -t A "$registry_without_port" &> /dev/null
    if [[ "$?" -ne 0 ]]; then
      host -t SRV "$registry" &> /dev/null
      if [[ "$?" -ne 0 ]]; then
        unresolvable_registries+=("${registry}")
      fi
    fi
  done
  if [[ "${#unresolvable_registries[@]}" -gt 0 ]]; then
    script_verbose_message "Some of the registry endpoints '${unresolvable_registries[*]}' are unresolvable." 'notify'
    script_verbose_message "Please provide resolvable registry endpoints or fix resolution issues on the host '${HOST_NAME}' and retry the command." 'notify'
    exit 1
  fi
}

########################################################################################
# If --interactive given, turn on extra-verbose for user input                         #
#  all commands are adjusted to wait for user input by adjust_command_to_user_options  #
########################################################################################
  if [[ ${AUTO_CONFIRM} = 'no'  ]]; then
    EXTRA_VERBOSE='yes'
  fi

  if [[ "${COMMAND}" = 'add' && -z ${services_string} ]]; then
    script_verbose_message 'Add command requires --services option' 'notify'
    exit_on_error_with_support_contact_message
  fi

  check_architecture_is_supported
  validate_registry_endpoints
  determine_system_daemon
  determine_OS_details
  warn_if_firewall_iptables_active
########################################################################################
# Checks for Quobyte Server installation existence                                            #
########################################################################################
  if [[  ( ! -z $(is_quobyte_installed 'server') ) && ( "${COMMAND}" = 'add' || "${COMMAND}" = 'bootstrap' )  ]]; then
    print_quobyte_exists_instructions
    exit_on_error_with_support_contact_message
  fi

  # clean old repositories on the node
  remove_repositories
  # install commmon depdencies
  if [[ "${COMMAND}" != 'uninstall' && "${COMMAND}" != 'add-client' ]]; then
    script_verbose_message 'Installing dependencies' 'info'
    install_dependencies
    check_dependencies
  fi
  if [[ "${COMMAND}" != 'uninstall' ]]; then
    check_packages_url_reachability
  fi

########################################################################################
# @Execute Execute bootstrap, configure,uninstall or add_node etc                      #
########################################################################################
  if [[ "${COMMAND}" = 'bootstrap' ]]; then
    print_installer_log_file_instructions
    execute_bootstrap
    update_host_config
    script_verbose_message 'Starting Quobyte services' 'info'
    if [[ ! -z "$services_string" ]]; then
      quobyte_service_string="${BOOTSTRAP_NODE_SERVICES},$services_string"
    else
      quobyte_service_string="${BOOTSTRAP_NODE_SERVICES}"
    fi
    quobyte_services 'enable' "${quobyte_service_string}"
    quobyte_services 'start' "${quobyte_service_string}"
    script_verbose_message 'Services status:' 'info'
    quobyte_services 'status' "${quobyte_service_string}"
    print_time_service_status
    check_registry_startup "${quobyte_service_string}"
    script_verbose_message 'Quobyte bootstrap node installed successfully' 'info'
    if [ "$POST_INSTALL_MSG" == "yes" ]; then
      print_post_bootstrap_instructions
    fi
  elif [[ "${COMMAND}" = 'uninstall' ]]; then
    execute_uninstall
  elif [[ "${COMMAND}" = 'add' ]]; then
    print_installer_log_file_instructions
    execute_add
    if [[ ( ! -z "$(echo ${services_string}| grep 'registry')" ) && ( ${create_emtpy_registry} = 'yes' ) ]];then
      setup_registry_device
    fi
    update_host_config
    if [[ ! -z "$services_string" ]]; then
      script_verbose_message 'Starting Quobyte services' 'info'
      quobyte_services 'enable' "$services_string"
      quobyte_services 'start' "$services_string"
      script_verbose_message 'Services status:' 'info'
      quobyte_services 'status' "$services_string"
    else
      script_verbose_message 'Services status:' 'info'
    fi
    print_time_service_status
    check_registry_startup "${services_string}"
    script_verbose_message 'Added Quobyte node successfully' 'info'
  elif [[ "${COMMAND}" = 'configure' ]]; then
    execute_configure
  elif [[ "${COMMAND}" = 'add-client' ]];then
    print_installer_log_file_instructions
    check_client_existence
    script_verbose_message 'Adding client' 'info'
    execute_add_client
  else
    script_verbose_message "Invalid installer command ${COMMAND}" 'notify'
    exit_on_error_with_support_contact_message
  fi
fi
