#!/usr/bin/env bash

# This script could work in two different modes:
# - installer - default mode, used when script is called without any arguments,
#   or with "--url" and/or with "--version" arguments.
# - repo-config-updater - that mode is used for gradual rollouts of WP Agent,
#   the script is switched to it when the "--generate-configs" and optional "--build-path"
#   arguments is found.
CURRENT_MODE="installer"

DEFAULT_REPO_BASE_URL="https://get.wpguardian.io/wp-agent"
DEFAULT_PLATFORM_API_URL="https://app.wpguardian.io"
PATTERN_HOST_WITH_DEV_BUILDS="wpt-builds"
HELLO_RESPONSE_FILE_PATH='/tmp/wpg-response.tmp'
USER_ID=$(id -u)

REPO_BASE_URL=""
PACKAGE_VERSION=""
WP_AGENT_VERSION="latest"
WP_AGENT_BUILD=""
AUTH_TOKEN=""
PLATFORM_API_URL=""
USE_WGET=0

if [[ "${USER_ID}" != "0" ]]; then
  echo "Running under root user is required"
  exit 1
fi

parse_args() {
  while [ "$#" -gt 0 ]; do
    # shellcheck disable=SC2015
    case "$1" in
    --url)
      REPO_BASE_URL="${2}"
      [ "$#" -ge 2 ] && shift 2 || break
      ;;
    --version)
      PACKAGE_VERSION="${2}"
      IFS=- read -r WP_AGENT_VERSION WP_AGENT_BUILD <<< "$PACKAGE_VERSION"; WP_AGENT_VERSION="${WP_AGENT_VERSION:-latest}"
      [ "$#" -ge 2 ] && shift 2 || break
      ;;
    --platform-url)
      PLATFORM_API_URL="${2}"
      [ "$#" -ge 2 ] && shift 2 || break
      ;;
    --token)
      AUTH_TOKEN="${2}"
      [ "$#" -ge 2 ] && shift 2 || break
      ;;
    --generate-configs)
      CURRENT_MODE="repo-config-updater"
      [ "$#" -ge 1 ] && shift 1 || break
      ;;
    *)
      shift
      ;;
    esac
  done
}

# Sets the value for REPO_BASE_URL and PLATFORM_API_URL variables to default when it's empty.
fill_repo_base_url_variable() {
  if [[ -z "${REPO_BASE_URL}" ]]; then
    REPO_BASE_URL=${DEFAULT_REPO_BASE_URL}
  fi

  if [[ -z "${PLATFORM_API_URL}" ]]; then
    PLATFORM_API_URL=${DEFAULT_PLATFORM_API_URL}
  fi
}

# $1 <string> - step ID
# $2 <bool> - status
# $3 <string> - message
function sendStepStatus() {
  if [[ -z "${AUTH_TOKEN}" ]]; then
      printf "Auth token is not specified. Connection to WP Guardian will not be established.\n" >> ~/wp-agent-install.log 2>&1
      return
  fi

  if [[ "${SERVER_GUID}" == "" ]]; then
    if [[ -e "$HELLO_RESPONSE_FILE_PATH" ]]; then
      SERVER_GUID=$(sed 's/{"uid":"//' "$HELLO_RESPONSE_FILE_PATH" | sed 's/".*//')
    else
      return
    fi
  fi

  URL="${PLATFORM_API_URL}/api/v1/security-dashboard/servers/hello/${SERVER_GUID}"
  B64_MESSAGE=$(echo -n "$3" | base64 -)

  if [[ $3 == "" ]]; then
    PAYLOAD="{\"id\":\"$1\",\"status\":$2}"
  else
    PAYLOAD="{\"id\":\"$1\",\"status\":$2,\"message\":\"$B64_MESSAGE\"}"
  fi

  # Timeout in seconds
  TIMEOUT=5

  printf "Sending installation step status to WP Guardian.\n" >> ~/wp-agent-install.log 2>&1
  if [[ "${USE_WGET}" == 0 ]]; then
    curl \
      -H 'Content-type: application/json' \
      -H "Authorization: Bearer $AUTH_TOKEN" \
      -d "$PAYLOAD" \
      -m "$TIMEOUT" \
      "$URL" >> ~/wp-agent-install.log 2>&1 &
    else
      wget -O- \
        --header 'Content-Type: application/json' \
        --header "Authorization: Bearer $AUTH_TOKEN" \
        --post-data "$PAYLOAD" \
        --connect-timeout "$TIMEOUT" \
        "$URL" >> ~/wp-agent-install.log 2>&1 &
    fi
}

function displayHelpMessage() {
  printf "
To address the issues with the installation process of WP Guardian agent, we'd
like you to share the installation log located at '/root/wp-agent-install.log'
with WP Guardian team.  You can do this through LiveChat widget in WP Guardian
UI or through the following contact form:
https://pt-research.typeform.com/to/inhgEltN

Thank you for helping us make our product better!
"
}

# $1 <string> - step ID
function reportStepDone() {
  sendStepStatus "$1" true
}

# $1 <string> - step ID
# $2 <string> - sub step ID
function reportStepError() {
  sendStepStatus "$1" false "$2 ${DIST_ID}-${OS_VERSION}"
}

function exitWithError() {
  echo "ERROR: $*" >&2
  displayHelpMessage
  exit 1
}

# $1 <integer> - Expected exit code
# $2 <string> - Step ID
# $3 <string> - Sub step ID
# $4 <string> - Exit message
function checkExitCode() {
  if [ $? -ne "$1" ]; then
    reportStepError "$2" "$3"
    exitWithError "$4"
  fi
}

function import_gpg_keys() {
  if [[ "${OS_NAME}" == "Ubuntu" || "${OS_NAME}" == "Debian" ]]; then
    apt-get install -y gnupg >> ~/wp-agent-install.log 2>&1
    checkExitCode 0 'importRepoKeys' 'installGnupg' "Failed to install gnupg package, see installation log above for details"
    (curl -L "${REPO_BASE_URL}/wp-toolkit-cpanel.gpg" || wget -qO - "${REPO_BASE_URL}/wp-toolkit-cpanel.gpg") | apt-key add - >> ~/wp-agent-install.log 2>&1
    checkExitCode 0 'importRepoKeys' 'importWpgKey' "Unable to import WP Guardian GPG key."
  else
    echo "Installing GPG ..." >> ~/wp-agent-install.log
    yum -y install gnupg >> ~/wp-agent-install.log 2>&1
    checkExitCode 0 'importRepoKeys' 'installGnupg' "Failed to install gnupg package, see installation log above for details"

    # Thirdparties are signed with Plesk GPG
    rpm --import ${REPO_BASE_URL}/plesk.gpg >> ~/wp-agent-install.log 2>&1
    checkExitCode 0 'importRepoKeys' 'import3rdPartyKey' "Unable to import required third party GPG key."

    # WP Agent are signed with own GPG
    rpm --import ${REPO_BASE_URL}/wp-toolkit-cpanel.gpg >> ~/wp-agent-install.log 2>&1
    checkExitCode 0 'importRepoKeys' 'importWpgKey' "Unable to import WP Guardian GPG key."
  fi

  reportStepDone 'importRepoKeys'
}

function create_apt_sources_list() {
  cat <<EOF >/etc/apt/sources.list.d/wp-agent.list
# WP Guardian agent
deb ${REPO_BASE_URL}/${OS_NAME}-${OS_VERSION}-x86_64/${WP_AGENT_VERSION}/wp-agent/ ./

# WP Guardian agent - third parties
deb ${REPO_BASE_URL}/${OS_NAME}-${OS_VERSION}-x86_64/${WP_AGENT_VERSION}/thirdparty/ ./
EOF
}

function create_yum_repo_config() {
  cat <<EOF >/etc/yum.repos.d/wp-agent.repo
[wp-agent]
name=WP Guardian agent
baseurl=${REPO_BASE_URL}/${OS_NAME}-${OS_VERSION}-x86_64/${WP_AGENT_VERSION}/wp-agent/
enabled=1
gpgcheck=1

[wp-agent-thirdparties]
name=WP Guardian agent
baseurl=${REPO_BASE_URL}/${OS_NAME}-${OS_VERSION}-x86_64/${WP_AGENT_VERSION}/thirdparty/
enabled=1
gpgcheck=1
EOF
}

function create_repo_config() {
  if [[ "${OS_NAME}" == "Ubuntu" || "${OS_NAME}" == "Debian" ]]; then
    create_apt_sources_list
  else
    create_yum_repo_config
  fi
}

function clean_repo_cache() {
  echo "Cleaning repo cache..." >> ~/wp-agent-install.log
  if [[ "${OS_NAME}" == "Ubuntu" || "${OS_NAME}" == "Debian" ]]; then
    DEBIAN_FRONTEND=noninteractive LANG=C apt-get update >> ~/wp-agent-install.log 2>&1
  else
    yum clean all --disablerepo="*" --enablerepo=wp-agent --enablerepo=wp-agent-thirdparties >> ~/wp-agent-install.log 2>&1
  fi
}

function detect_os() {
  if [ -e /etc/os-release ]; then
    source /etc/os-release

    DIST_ID="${ID}"
    OS_VERSION="${VERSION_ID}"
  elif [ "$(which lsb_release 2>/dev/null)" ]; then
    DIST_ID="$(lsb_release -s -i)"
    OS_VERSION="$(lsb_release -s -r)"
  elif [ -e /etc/redhat-release ]; then
    DIST_ID="$(sed -n -e 's/\([[:alnum:]]*\)[[:space:]].*/\1/p' /etc/redhat-release)"
    OS_VERSION="$(sed -n -e 's/[^0-9]*\([0-9\.]\+\).*/\1/p' /etc/redhat-release)"
  elif [ -e /etc/issue ]; then
    DIST_ID="$(sed -n -e 's/\([[:alnum:]]*\)[[:space:]].*/\1/p' /etc/issue)"
    OS_VERSION="$(sed -n -e 's/[^0-9]*\([0-9\.]\+\).*/\1/p' /etc/issue)"
  fi

  if [ -n "${DIST_ID}" ]; then
    DIST_ID="$(echo ${DIST_ID} | tr '[:upper:]' '[:lower:]')"
  fi

  case "$DIST_ID" in
  redhat*)
    # We use the same RPMs for RedHat as for CentOS
    OS_NAME="CentOS"
    ;;
  centos)
    OS_NAME="CentOS"
    ;;
  rhel | almalinux | rocky | cloudlinux | cloudlinuxserver)
    # We use the same RPMs for RHEL, AlmaLinux and RockyLinux as for CentOS
    OS_NAME="CentOS"
    ;;
  ubuntu)
    OS_NAME="Ubuntu"
    ;;
  debian)
    OS_NAME="Debian"
    ;;
  *)
    OS_NAME="${DIST_ID}"
    ;;
  esac
}

function check_supported_os_and_version() {
  if [ -z "${OS_NAME}" ]; then
    reportStepError 'checkCompatibility' 'osNotDetected'
    exitWithError "Cannot determine OS name"
  fi

  if [ "${OS_NAME}" != "CentOS" ] && [ "${OS_NAME}" != "Ubuntu" ] && [ "${OS_NAME}" != "Debian" ]; then
    reportStepError 'checkCompatibility' 'unsupportedOs'
    exitWithError "Only CentOS-based distributions, Ubuntu, and Debian are supported at the moment"
  fi

  if [ "${OS_NAME}" == "Ubuntu" ]; then
    # In Ubuntu take two parts of OS_VERSION, e.g. for "Ubuntu 20.04.2 LTS" we consider OS_VERSION=20.04
    OS_VERSION=$(echo "$OS_VERSION" | sed -n -e 's/\([0-9]\+\.[0-9]\+\).*/\1/p')

    if [ "${OS_VERSION}" != "18.04" ] && [ "${OS_VERSION}" != "20.04" ] && [ "${OS_VERSION}" != "22.04" ]; then
      reportStepError 'checkCompatibility' 'unsupportedOs'
      exitWithError "Only Ubuntu 18.04, 20.04, and 22.04 are supported at the moment"
    fi
  elif [ "${OS_NAME}" == "Debian" ]; then
    OS_VERSION=$(echo "$OS_VERSION" | sed -n -e 's/\([0-9]\+\).*/\1/p')

    if [ "${OS_VERSION}" != "10" ] && [ "${OS_VERSION}" != "11" ] && [ "${OS_VERSION}" != "12" ]; then
      reportStepError 'checkCompatibility' 'unsupportedOs'
      exitWithError "Only Debian 10, 11, and 12 are supported at the moment"
    fi
  else
    # In CentOS based OS take only first part of OS_VERSION, e.g. for CentOS 7.7 we consider OS_VERSION=7
    OS_VERSION=$(echo "$OS_VERSION" | sed -n -e 's/\([0-9]\+\).*/\1/p')

    if [ "${OS_VERSION}" -lt 7 ] || [ "${OS_VERSION}" -gt 9 ]; then
      reportStepError 'checkCompatibility' 'unsupportedOs'
      exitWithError "Only CentOS 7, CloudLinux 7, 8 or 9, AlmaLinux 8 or 9, and RockyLinux 8 or 9 are supported at the moment"
    fi
  fi

  reportStepDone checkCompatibility
}

function check_conflicts() {
  # Do not use this variable ever and do not try to install wp-agent on server with Plesk or cPanel
  # wp-agent do not support such servers and MAY BROKE YOUR PANEL installation
  if [ "${DO_NOT_USE_THIS_EVER_____SKIP_CHECKING_FOR_CONFLICTS}" == "skip-conflicts-check-and-potentially-broke-plesk-or-cpanel-installation" ]; then
    return
  fi

  # Check if cPanel installed on the server
  for dir in "/usr/local/cpanel" "/var/cpanel" "/etc/cpanel"
  do
    if [ -d "$dir" ]; then
      reportStepError 'checkConflicts' 'cpanel'
      exitWithError "You are trying to connect a server with cPanel (installed in $dir) to WP Guardian. WordPress sites hosted on cPanel servers are already protected by WP Toolkit, so WP Guardian does not support such servers at the moment."
    fi
  done
  # Check if Plesk installed on the server
  for dir in "/usr/local/psa" "/opt/psa" "/etc/psa"
  do
    if [ -d "$dir" ]; then
      reportStepError 'checkConflicts' 'plesk'
      exitWithError "You are trying to connect a server with Plesk (installed in $dir) to WP Guardian. WordPress sites hosted on Plesk servers are already protected by WP Toolkit, so WP Guardian does not support such servers at the moment."
    fi
  done

  reportStepDone 'checkConflicts'
}

function install_wp_agent() {
  # The option for Dpkg is required in case when some configs are left on the server
  # from previous versions of package.
  # Installation of WP Guardian agent package should always overwrite configs,
  # this allows to deliver fixes for these files without manual actions from server admin.

  if [ -z "${WP_AGENT_BUILD}" ]; then
    echo "(4/4) Installing WP Guardian agent ..." | tee -a ~/wp-agent-install.log

    if [[ "${OS_NAME}" == "Ubuntu" || "${OS_NAME}" == "Debian" ]]; then
      apt-get install -o Dpkg::Options::="--force-confnew" -y wp-agent >> ~/wp-agent-install.log 2>&1
    else
      yum install -y wp-agent >> ~/wp-agent-install.log 2>&1
    fi
  else
    echo "(4/4) Installing WP Guardian agent with version ${PACKAGE_VERSION}..." | tee -a ~/wp-agent-install.log

    if [[ "${OS_NAME}" == "Ubuntu" || "${OS_NAME}" == "Debian" ]]; then
      apt-get install -o Dpkg::Options::="--force-confnew" -y "wp-agent=${PACKAGE_VERSION}*" >> ~/wp-agent-install.log 2>&1
    else
      yum install -y "wp-agent-${PACKAGE_VERSION}" >> ~/wp-agent-install.log 2>&1
    fi
  fi

  checkExitCode 0 'installAgent' '' "Failed to install WP Guardian agent, see installation log in ~/wp-agent-install.log"
  reportStepDone 'installAgent'
}

function sendHello() {
  if [ -e $HELLO_RESPONSE_FILE_PATH ]; then
    printf "Response file already exists. Connection to WP Guardian will not be established.\n" >> ~/wp-agent-install.log 2>&1
    return
  fi

  if [[ -z "${AUTH_TOKEN}" ]]; then
      printf "Auth token is not specified. Connection to WP Guardian will not be established.\n" >> ~/wp-agent-install.log 2>&1
      return
  fi

  URL="${PLATFORM_API_URL}/api/v1/security-dashboard/servers/hello"
  BASE64_HOSTNAME=$(echo -n "$HOSTNAME" | base64 -)
  HELLO_PAYLOAD="{\"product\":\"wp\",\"hostnameBase64\":\"$BASE64_HOSTNAME\"}"
  # Timeout in seconds
  TIMEOUT=10

  printf "Connecting to WP Guardian.\n" >> ~/wp-agent-install.log 2>&1
  curl -f -o "$HELLO_RESPONSE_FILE_PATH" \
    -H 'Content-type: application/json' \
    -H "Authorization: Bearer $AUTH_TOKEN" \
    -d "$HELLO_PAYLOAD" \
    -m "$TIMEOUT" \
    "$URL" >> ~/wp-agent-install.log 2>&1
  curlExitCode=$?

  if [ $curlExitCode -ne "0" ]; then
    {
      echo "Failed to connect to WP Guardian using 'curl': exit code: $curlExitCode."
      printf "Trying with 'wget'\n"
      wget -O "$HELLO_RESPONSE_FILE_PATH" \
        --header 'Content-Type: application/json' \
        --header "Authorization: Bearer $AUTH_TOKEN" \
        --post-data "$HELLO_PAYLOAD" \
        --connect-timeout "$TIMEOUT" \
        "$URL"
    } >> ~/wp-agent-install.log 2>&1
    wgetExitCode=$?

    if [ $wgetExitCode -ne "0" ]; then
        {
          echo "Failed to connect to WP Guardian using 'wget': exit code: $wgetExitCode"
          rm -f "$HELLO_RESPONSE_FILE_PATH"
        } >> ~/wp-agent-install.log 2>&1

        {
          echo 'Installation stopped: Failed to connect to WP Guardian.'
          echo "Check that $PLATFORM_API_URL is accessible from this server, refresh and copy installation snippet, and try again."
          echo 'You can see detailed installation log in ~/wp-agent-install.log'
        }  | tee -a ~/wp-agent-install.log
        displayHelpMessage
        exit 1
    fi
    USE_WGET=1
  fi
}

parse_args "$@"

if [ "${CURRENT_MODE}" == "installer" ]; then
  fill_repo_base_url_variable

  # The installer script defines the default URL to the host with repositories. You can
  # override it in "--url" argument during installation process. For example, to install specific
  # version which isn't yet delivered to all servers you can use installer as following:
  # "./installer.sh --url https://get.wpguardian.io/wp-agent".
  #
  # The same version of installer script is used for development purposes and it's required
  # to override the default URL to the host with repositories in such case, both when installer works
  # in "installer" mode and in "repo-config-updater" mode.
  # When the passed value for "--url" argument matches specific pattern, then the
  # file "/root/.wp-agent-installer-config-for-development-purposes" is created and used
  # in "repo-config-updater" mode.
  # Mentioned file shouldn't be used on production servers, because if it contains
  # incorrect URL to the host with repositories, then receiving of updates can be broken.
  if [[ "${REPO_BASE_URL}" == *"${PATTERN_HOST_WITH_DEV_BUILDS}"* ]]; then
    cat <<EOF >/root/.wp-agent-installer-config-for-development-purposes
REPO_BASE_URL=${REPO_BASE_URL}
EOF
  else
    rm -f /root/.wp-agent-installer-config-for-development-purposes
  fi

  echo "(1/4) Starting ..." | tee -a ~/wp-agent-install.log
  sendHello

  echo "(2/4) Checking compatibility ..." | tee -a ~/wp-agent-install.log
  detect_os
  check_supported_os_and_version
  check_conflicts

  echo "(3/4) Configuring installer ..." | tee -a ~/wp-agent-install.log
  create_repo_config
  import_gpg_keys
  clean_repo_cache

  install_wp_agent

  echo "Installation complete!" | tee -a ~/wp-agent-install.log
else
  # When the installer script is working as "repo-config-updater", it's possible to override
  # the repo base URL to the host with repositories.
  # This is allowed only for development purposes and the file with override is removed
  # when the value doesn't match specific pattern. This is required to avoid possible
  # problems with updates on production servers.
  if [ -e /root/.wp-agent-installer-config-for-development-purposes ]; then
    source /root/.wp-agent-installer-config-for-development-purposes

    if [[ -n "${REPO_BASE_URL}" && "${REPO_BASE_URL}" != *"${PATTERN_HOST_WITH_DEV_BUILDS}"* ]]; then
      rm -f /root/.wp-agent-installer-config-for-development-purposes
      REPO_BASE_URL=""
    fi
  fi

  fill_repo_base_url_variable
  detect_os
  check_supported_os_and_version
  create_repo_config
  clean_repo_cache
fi
