#!/bin/bash


# Short Description :Deploy imunify360/ImunifyAV
# Description       :Installs imunify360/ImunifyAV repository
# Copyright         :Cloud Linux Zug GmbH
# License           :Cloud Linux Commercial License

# Do not edit/move/reformat this line except for actual version bump
# it is used by old versions of deploy scripts to check for update
version="2.91"

readonly package="imunify-antivirus"
readonly imunify360="imunify360-firewall"
readonly imunify_av="imunify-antivirus"


if [[ "$package" != "$imunify360" ]] && [[ "$package" != "$imunify_av" ]]; then
  exit 1
fi



if [[ "$package" = "$imunify_av" ]]; then
    PRODUCT="ImunifyAV"
    UI_PACKAGE="imunify-ui-antivirus"
    COMMAND="imunify-antivirus"
    STAND_ALONE_URL="https://docs.imunifyav.com/stand_alone_mode"
    LOG_FILE="/var/log/imav-deploy.log"
    LOCK="/var/lock/imav-deploy.lck"
    # packages mentioned in the update command in the daily cron job
    readonly additional_packages_to_remove="ai-bolit\
    alt-php-hyperscan\
    imunify-common\
    imunify-notifier\
    imunify-core\
    imunify-ui\
    imunify360-venv\
    alt-php-internal\
    app-version-detector"
    readonly additional_packages_to_remove_cl="ai-bolit\
    alt-php-hyperscan\
    imunify-common\
    imunify-notifier\
    imunify-core\
    imunify-ui\
    imunify360-venv"
    readonly additional_packages_to_remove_centos="minidaemon"
    readonly additional_packages_to_remove_debian=""
fi

readonly YUM_DISABLED_PHP_REPOS_OPTION="--disablerepo=imunify360-alt-php,imunify360-ea-php-hardened"
OS_RELEASE_INFO=/etc/os-release
set -o pipefail
# fail if any error: will not
#set -e
# error for unbound variable: not for now
#set -eu

# $1 = Message prompt
# Returns ans=0 for yes, ans=1 for no
yesno() {
    local YES=0
    local NO=1
    local PENDING=2

    if [ $dry_run -eq 1 ]; then
        echo "Would be asked here if you wanted to"
        echo "$1 (y/n - y is assumed)"
        local ans=$YES
    elif [ "$assumeyes" = "true" ]; then
        local ans=$YES
    else
        local ans=$PENDING
    fi

    while [ $ans -eq $PENDING ]; do
        echo "Do you want to $1 (y/n) ?" ; read -r reply
        case "$reply" in
            Y*|y*) ans=$YES ;;
            N*|n*) ans=$NO ;;
            *) echo "Please answer y or n" ;;
        esac
    done

    return "$ans"
}

prepend_timestamp() {
    # Prepend current time to each line
    #
    # Usage: source-program | prepend_timestamp
    #
    # Note: it may fail if the input contains \0 bytes
    while IFS= read -r line
    do
        printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$line"
    done
}

log()
{
    # Run given command and append its duplicated stdout/stderr to
    # $LOG_FILE.
    #
    # Usage: log <command> [<args>...]
    #
    "$@" |& prepend_timestamp | tee -a "$LOG_FILE"
    return "${PIPESTATUS[0]}"
}

exit_with_error()
{
    log echo -e "$@"
    rm -rf "$lock"
    exit 1
}

print_debug_info()
{
    if [ "$DEBUG" == "true" ]; then
        echo "$@"
    fi
}

set_panel_detection_path()
{
    readonly CPANEL_BIN="/usr/local/cpanel/cpanel"
    readonly DA_BIN="/usr/local/directadmin/directadmin"
    readonly PLESK_BIN="/usr/sbin/plesk"
    readonly INTEGRATION_CONF_PATH="/etc/sysconfig/imunify360/integration.conf"
}

detect_python ()
{
    # python executable is not present in CentOS 8
    if hash python2 2>/dev/null; then
        PYTHON=python2
        CONFIG_PARSER="ConfigParser"
    elif hash python3 2>/dev/null; then
        PYTHON=python3
        CONFIG_PARSER="configparser"
    else
        exit_with_error "Neither python2 nor python3 executables found"
    fi
}

detect_panel ()
{
    # note: keep the panel test order in sync with agent's get_hosting_panel(),
    #   to avoid detecting conflicting panels in agent vs. the deploy script

    PANEL=""
    if [ -f "$INTEGRATION_CONF_PATH" ] ; then
        PANEL="generic"
    elif [ -f "$PLESK_BIN" ]; then
        PANEL="plesk"
    elif [ -f "$CPANEL_BIN" ]; then
        PANEL="cpanel"
    elif [ -f "$DA_BIN" ]; then
        PANEL="directadmin"
    else
        exit_with_error "$PRODUCT has not detected any compatible hosting panel as well as integration.conf file to run the installation without a panel. \
Please, follow the instructions on $STAND_ALONE_URL"
    fi
    print_debug_info "$PANEL panel was detected."
}

# Only for imunify360-firewall
set_low_resource_usage_mode_if_necessary()
{
  imunify360_low_mem_limit=2147483648
  # total usable memory in bytes
  mem_total=$(</proc/meminfo awk '$1 == "MemTotal:" { printf "%.0f", $2 * 1024 }')
  if (( mem_total < imunify360_low_mem_limit )); then
    # enable "Low Resource Usage" mode
    imunify360-agent config update '{"MOD_SEC": {"ruleset": "MINIMAL"}, "WEBSHIELD": {"enable": false}, "OSSEC": {"active_response": true}, "MALWARE_SCANNING": {"hyperscan": false}}'
  fi
}

populate_os_release_vars()
{
    # shellcheck source=/etc/os-release
    [ -f "$OS_RELEASE_INFO" ] && source "$OS_RELEASE_INFO"
}

detect_ostype()
{
    echo -n "Detecting ostype... "
    if [ ! -f "$OS_RELEASE_INFO" ]; then
        ostype=centos
    else
        populate_os_release_vars
        if echo "$ID" "$ID_LIKE" | grep debian >/dev/null
        then
            ostype=debian
        else
            ostype=centos
        fi
    fi
    echo $ostype
}

is_ubuntu()
{
    populate_os_release_vars
    [ "$ID" == "ubuntu" ]
}

is_debian()
{
    populate_os_release_vars
    [ "$ID" == "debian" ]
}

UNSUPPORTED_OS_MSG="You are running an unsupported OS. $PRODUCT supports only x86_64 processors."

check_debian_release()
{
    populate_os_release_vars

    if is_debian && ([ "$VERSION_ID" -lt 10 ] || [ "$VERSION_ID" -gt 12 ])
    then
        exit_with_error "You are running unsupported version of debian based OS. $PRODUCT supports only Debian [10, 11, 12]"
    fi
    if is_ubuntu && [ "$VERSION_ID" != 16.04 ] && [ "$VERSION_ID" != 18.04 ] && [ "$VERSION_ID" != 20.04 ] && [ "$VERSION_ID" != 22.04 ]
    then
        exit_with_error "You are running unsupported version of debian based OS. $PRODUCT supports only Ubuntu 16.04, 18.04, 20.04 and 22.04"
    fi

    if [ "$(uname -m)" != x86_64 ]
    then
        exit_with_error "$UNSUPPORTED_OS_MSG"
    fi
}

check_centos_release()
{
    rpm -q --whatprovides redhat-release > /dev/null 2>&1
    check_exit_code 0 "There is no package providing /etc/redhat-release, please install redhat-release or centos-release first"

    ARCH=$(uname -i)

    # handle 32bit xen with x86_64 host kernel
    if (! rpm -q glibc.x86_64 > /dev/null 2>&1) || [ "$ARCH" != "x86_64" ] ; then
        exit_with_error "$UNSUPPORTED_OS_MSG"
    fi

    check_centos_compatible
    check_virtuozzo_compatible
}

check_virtuozzo_compatible()
{
    if [ -f /proc/vz/vestat ]; then
        if version "$(uname -r)" -lt "$VZ_VERSION_BRIEF"; then
            echo "You are inside VZ."
            echo "Virtuozzo 7 with kernel $VZ_VERSION_LONG or later has support for ipset in Containers."
            exit_with_error "Please upgrade your OpenVZ hypervisor kernel version to $VZ_VERSION_LONG or later."
        fi
    fi
}

check_centos_compatible()
{
    local os_version="${1:-$(rpm --eval '%{rhel}')}"
    # shellcheck disable=SC2015
    [ "${os_version}" -lt 10 ] 2>/dev/null && [ "${os_version}" -gt 5 ] || \
        exit_with_error "Only CentOS/CloudLinux 6, 7, 8 and 9 are supported at the moment, but got os_version='${os_version}'"
}

check_exit_code() { if [ $? -ne "$1" ]; then exit_with_error "$2"; fi; }

disable_3rd_party_ids()
{
    if [ -d "$venv_path" ]; then
        echo "imunify360-venv detected"
        imunify360_python38_datadir=$venv_path/share/imunify360
    fi
    if [ -d "$imunify360_python38_datadir" ]; then
        datadir="$imunify360_python38_datadir"
    else
        datadir="$imunify360_python35_datadir"
    fi

    $datadir/scripts/disable_3rd_party_ids
}

install_first_install_default_overrides()
{
    local first_install_package=$1
    local package_version=$2
    if [ -d "$venv_path" ]; then
        echo "imunify360-venv detected"
        imunify360_python38_datadir=$venv_path/share/imunify360
    fi
    local first_install_config_deprecated="$imunify360_python38_datadir/10_on_first_install.config"
    local first_install_config_core="$imunify360_python38_datadir/10_on_first_install_core.config"
    local first_install_config_av="$imunify360_python38_datadir/10_on_first_install_av.config"
    local first_install_config_ids="$imunify360_python38_datadir/10_on_first_install_ids.config"
    if [ -f "$first_install_config_deprecated" ]; then
        echo "Installing $first_install_config_deprecated from $first_install_package $package_version"
        cp -v "$first_install_config_deprecated" /etc/sysconfig/imunify360/imunify360.config.d/ && echo "Done."
    else
        echo "Installing $first_install_config_core from $first_install_package $package_version"
        cp -v "$first_install_config_core" /etc/sysconfig/imunify360/imunify360.config.d/ && echo "Done."

        echo "Installing $first_install_config_av from $first_install_package $package_version"
        cp -v "$first_install_config_av" /etc/sysconfig/imunify360/imunify360.config.d/ && echo "Done."

        if [[ "$package" = "$imunify360" ]]; then
            echo "Installing $first_install_config_ids from $first_install_package $package_version"
            cp -v "$first_install_config_ids" /etc/sysconfig/imunify360/imunify360.config.d/ && echo "Done."
        fi
    fi
}

remove_first_install_default_overrides()
{
    unlink /etc/sysconfig/imunify360/imunify360.config.d/10_on_first_install.config 2>/dev/null || true
    unlink /etc/sysconfig/imunify360/imunify360.config.d/10_on_first_install_core.config 2>/dev/null || true
    unlink /etc/sysconfig/imunify360/imunify360.config.d/10_on_first_install_av.config 2>/dev/null || true
    unlink /etc/sysconfig/imunify360/imunify360.config.d/10_on_first_install_ids.config 2>/dev/null || true
}

init_vars()
{
    wget="/usr/bin/wget"
    wget_options="-q"
    uninstall=false
    conversion=false
    beta=false
    install_vendors=false

    # get full path to the current script
    script="$1"
    case "$script" in
        ./*) script="$(pwd)/${script#*/}" ;;
        /*) script="$script" ;;
        *) script="$(pwd)/$script" ;;
    esac
    scriptname=$(basename "$script")
    script_run_args="$2"

    # Update checker URL
    checksite="https://repo.imunify360.cloudlinux.com/defense360/"
    checksite_forcurl='https://defense360:nraW!F%40%24x4Xd6HHQ@repo.imunify360.cloudlinux.com/defense360/'
    upgradeurl="$checksite$scriptname"
    dry_run="0"

    assumeyes=false
    modifying_call=false
    yum_beta_option=""
    yum_beta_repo_enable=0
    apt_force=""

    apt_allow_unauthenticated=""
    if [[ "$package" = "$imunify360" ]]; then
        # Virtuozzo 7 with kernel 3.10.0 or later has support for ipset in Container
        VZ_VERSION_LONG=3.10.0-327.10.1.vz7.12.8
        # Inside VZ version is provided without release
        VZ_VERSION_BRIEF=3.10.0
        readonly imunify360_python35_datadir=/opt/alt/python35/share/imunify360
    fi
    imunify360_python38_datadir=/opt/alt/python38/share/imunify360
    venv_path=/opt/imunify360/venv
}

version()
{
    local lhs=$1
    local op=$2
    local rhs=$3

    case $op in
        -lt) test "$(echo -e "$lhs\\n$rhs" | sort --version-sort | head -1)" = "$lhs" && \
            test "$lhs" != "$rhs"
            return $?
        ;;
        *) echo "function version(): operator $op is not supported."
            return 2
        ;;
    esac
}

check_package_version()
{
    version="$1"
    if [ -z "$version" ]; then
        echo "No available package detected"
        return 1
    else
        echo "$version"
    fi
}

get_available_debian_package_version()
{
    version=$(apt-cache policy "$1" 2>/dev/null | sed -n '3p' | awk '{split($0, candidate); print candidate[2]}')
    check_package_version "$version"
    return $?
}

get_available_centos_package_version()
{
    version=$(yum $yum_beta_option $YUM_DISABLED_PHP_REPOS_OPTION list available "$1" 2>/dev/null | grep -E "$1.x86_64|$1.noarch" | awk '{split($0, candidate); print candidate[2]}' | sort --version-sort | tail -n 1)
    check_package_version "$version"
    return $?
}

check_integration_conf()
{
#
# Check whether $INTEGRATION_CONF_PATH is sufficiently valid to continue the installation, exit with an error message otherwise
#
# Globals:
#  Uses (reads) $INTEGRATION_CONF_PATH;
#  Populates (writes) $PYTHON, $CONFIG_PARSER and uses them to parse $INTEGRATION_CONF_PATH ini-file
#
    detect_python
    # sanity check: the integration.conf is a valid ini-like file
    $PYTHON -c "from $CONFIG_PARSER import ConfigParser; conf = ConfigParser(); conf.read('$INTEGRATION_CONF_PATH')" 2>/dev/null
    check_exit_code 0 "syntax error in $INTEGRATION_CONF_PATH \
Read the manual $STAND_ALONE_URL on how to create a valid config file.
"
    # sanity check: ui_path should be present in the config
    local ui_path="$($PYTHON -c "from $CONFIG_PARSER import ConfigParser; conf = ConfigParser(); conf.read('$INTEGRATION_CONF_PATH'); print(conf.get('paths', 'ui_path'))" 2>/dev/null)"
    check_exit_code 0 "$PRODUCT has detected $INTEGRATION_CONF_PATH file from the stand-alone version of $PRODUCT. \
Stand-alone version requires \"ui_path\" parameter specified in the $INTEGRATION_CONF_PATH. \
Read the manual $STAND_ALONE_URL on how to create a valid config file.
"
    # ui_path must not be empty
    if [ -z "$ui_path" ]; then
        exit_with_error "Could not get ui_path from $INTEGRATION_CONF_PATH. UI will not be installed. \
Please ensure that you have provided ui_path in $INTEGRATION_CONF_PATH \
More details is at $STAND_ALONE_URL."
    # even if ui_path doesn't refer to a directory (first install),
    # its parent directory must exist
    elif [ ! -d "$ui_path" ]; then
        # first install
        log mkdir -m 0700 "$ui_path"
        check_exit_code 0 "An error occurred during creating $ui_path directory. \
Possibly parent directory doesn't exist or path point to file"
    # symlinks are not supported (current behavior)
    elif [[ -L "$ui_path" ]]; then
        exit_with_error "ui_path specified in $INTEGRATION_CONF_PATH file $ui_path points to an existing symlink. \
Installation is aborted, to avoid overwriting it. \
ui_path must point to an empty web directory. More details is at $STAND_ALONE_URL."
    # ui_path dir must be empty or point to an existing imunify installation;
    # use bin/execute.py as a heuristic for detecting the agent install
    elif [[ -n "$(ls -A "$ui_path" 2>/dev/null)" ]] && [[ ! -e "$ui_path/bin/execute.py" ]]; then
        exit_with_error "ui_path specified in $INTEGRATION_CONF_PATH file $ui_path points to a non-empty directory. \
Installation is aborted, to avoid overwriting data in it. \
ui_path must point to an empty web directory. More details is at $STAND_ALONE_URL."
    fi

    # sanity check: panel_info should be present in the config
    $PYTHON -c "from $CONFIG_PARSER import ConfigParser; conf = ConfigParser(); conf.read('$INTEGRATION_CONF_PATH'); print(conf.get('integration_scripts', 'panel_info'))" 2>/dev/null
    if [ $? -ne "0" ]; then
        yesno "WARNING: integration_scripts.panel_info field will be mandatory soon, please refer to [documentation](https://docs.imunify360.com/control_panel_integration/#specifying-panel-information) and fill it! Do you want to continue? [y/N]"
    else
        # sanity check: panel_info must be a valid json with name and version fields in data object
        local panel_info=$($PYTHON -c "from $CONFIG_PARSER import ConfigParser; conf = ConfigParser(); conf.read('$INTEGRATION_CONF_PATH'); print(conf.get('integration_scripts', 'panel_info'))" 2>/dev/null)
        $panel_info | $PYTHON -c 'import sys,json; d=json.load(sys.stdin); print("Panel info:", d["data"]["name"], d["data"]["version"]);'
        check_exit_code 0 "panel_info script should be executable and return valid json with name and version fields in data object. Please refer to the [documentation](https://docs.imunify360.com/control_panel_integration/#specifying-panel-information)."
    fi
}

install_plugin() {
    local plugin_package=$1
    local panel=$2

    echo "Installing $PRODUCT $panel plugin..."
    install_${ostype}_pkgs "$plugin_package-$panel"
    check_exit_code 0 "Failed to install $PRODUCT $panel plugin."
}

install_ui_part() {
    local ui_package=$1
    local panel=$2

    echo "Installing UI part of $PRODUCT $panel plugin..."
    install_${ostype}_pkgs "$ui_package-$panel"
    check_exit_code 0 "Failed to install UI part of $PRODUCT $panel plugin."
}

check_and_install() {
    local package_to_install=$1
    local package_version=$2
    local ui_package=$3
    local panel=$4

    if version "$package_version" -lt "7.3"; then
        install_plugin "$package_to_install" "$panel"
    else
        if [[ "$package_to_install" = "$imunify360" ]]; then
            install_plugin $package_to_install $panel
        fi
        install_ui_part "$ui_package" "$panel"
    fi
}

check_hardened_php_status()
{
    local agent_bin='/usr/bin/imunify360-agent'
    local python_bin='/opt/imunify360/venv/bin/python'
    local status=$(\
        ${agent_bin} features status hardened-php --json |\
        ${python_bin} -c 'import json; print(json.loads(input()).get("items", {}).get("status"))'\
    )
    echo ${status}
}

remove_hardened_php_feature()
{
    local agent_bin='/usr/bin/imunify360-agent'
    local python_bin='/opt/imunify360/venv/bin/python'
    local timeout=600

    if [ ! -f ${agent_bin} ] || [ ! -f ${python_bin} ]; then
        return
    fi

    if [ "$(check_hardened_php_status)" != "installed" ]; then
        return
    fi

    echo "Warning: Hardened PHP packages will be removed"\
         "and replaced with the default ones if possible."\
         "Some sites may stop working"

    echo -n "Starting removal, this may take a couple of minutes"
    local logfile="$(${agent_bin} features remove hardened-php)"
    local start_ts=$(date +%s)

    while true; do
        removal_status="$(check_hardened_php_status)"
        [ "${removal_status}" = "not_installed" ] && echo OK && break

        local current_ts=$(date +%s)
        if [ $((current_ts - start_ts)) -gt ${timeout} ]; then
            echo TIMEOUT
            exit_with_error "Error occured while trying to remove Hardened PHP packages."\
                            "See log: ${logfile}"
        fi
        echo -n "."
        sleep 5
    done
}


run_with_retries()
{
    cmd=$1
    expected_error_text=$2
    ignore_res=$3

    timeout=15
    nattempts=10

    for ((i=1;i<=nattempts;i++)); do
        output=$( { $cmd ; } 2>&1 )
        res=$?
        if [ $res -eq 0 ] && [[ "$ignore_res" != "true" ]]; then
            echo "$output"
            break
        else
            if echo "$output" | grep -q "$expected_error_text"; then
                echo "$output"
                echo "Attempt #$i/$nattempts: to run $cmd."
                [ $i -ne $nattempts ] && echo "Retrying in $timeout seconds.."
                sleep $timeout
            else
                echo "$output"
                break
            fi
        fi
    done
    return $res
}

reopen_log()
{
    echo "-- $(date -R): $script $script_run_args --" >> "$LOG_FILE"
    chmod 0600 "$LOG_FILE"
}

check_debian_pkg_presence()
{
    test "$(dpkg-query --show --showformat='${db:Status-Status}\n' "$1" 2>/dev/null)" = "installed"
}

check_centos_pkg_presence()
{
    rpm --query "$1" >/dev/null
}

remove_debian_imunify()
{
    local pkgs_to_remove="$package $additional_packages_to_remove $additional_packages_to_remove_debian"
    # shellcheck disable=SC2086
    apt-get remove --autoremove --ignore-missing --dry-run $pkgs_to_remove
    yesno "apt-get --AUTOREMOVE to remove $pkgs_to_remove plus \
    aforementioned packages [y] or just $pkgs_to_remove [n]"
    local res=$?
    if [ $res = 0 ]; then
        local autoremove="--autoremove"
    else
        local autoremove=""
    fi
    # --ignore-missing doesn't work if apt doesn't know about package
    # shellcheck disable=SC2086
    apt-get remove $autoremove -y --ignore-missing $(dpkg-query -W -f='${binary:Package}\n' $pkgs_to_remove 2>/dev/null)
}

remove_centos_imunify()
{
    if [ -f /etc/cloudlinux-release ]; then
        local pkgs_to_remove="$package $additional_packages_to_remove_cl $additional_packages_to_remove_centos"
    else
        local pkgs_to_remove="$package $additional_packages_to_remove $additional_packages_to_remove_centos"
    fi
    yum remove -y $yum_beta_option $YUM_DISABLED_PHP_REPOS_OPTION                       \
        $pkgs_to_remove --setopt=clean_requirements_on_remove=1
}

get_debian_pkgs_manager() {
    pkgs_manager="apt-get"
}

get_centos_pkgs_manager() {
    pkgs_manager="yum"
}

remove_debian_pkgs()
{
    run_with_retries "apt-get remove -y $*" "Could not get lock"
}

remove_centos_pkgs()
{
    yum remove -y "$@"
}

install_debian_pkgs()
{
    local pkgs=$*
    run_with_retries "apt-get $apt_opts install -y $apt_allow_unauthenticated $apt_force $pkgs" "Could not get lock"
}

install_centos_pkgs()
{
    local pkgs=$*
    yum install -y $yum_beta_option $YUM_DISABLED_PHP_REPOS_OPTION $pkgs
}

install_debian_ipset()
{
    install_debian_pkgs ipset
}

install_centos_ipset()
{
    yum install -y ipset
}

detect_first_install()
{
    if check_${ostype}_pkg_presence "$package" >/dev/null
    then
        first_install=false
    else
        first_install=true
    fi
}

is_systemctl_avail()
{
    command -v systemctl >/dev/null 2>&1
}


# $1 = Full URL to download
# $2 = Optional basename to save to (if omitted, then = basename $1)
#      Also allow download to fail without exit if $2 is set
download_file() {
    if [ "$2" = "" ]; then
        dlbase="$(basename "$1")"
    else
        dlbase="$2"
    fi

    if [ $dry_run -eq 1 ]; then
        echo "Would download this URL to $dlbase :"
        echo "$1" ; echo
        return
    fi

    old_dlbase="$dlbase.old"
    if [ -f "$dlbase" ]; then
        rm -f "$old_dlbase"
        mv -f "$dlbase" "$old_dlbase"
    fi

    echo "Downloading $dlbase (please wait)"
    $wget $wget_options -O "$dlbase" "$1"

    if [ ! -s "$dlbase" ]; then
        if [ -f "$old_dlbase" ]; then
            mv -f "$old_dlbase" "$dlbase"
        fi
        if [ "$2" = "" ]; then
            echo "Failed to download $dlbase"
            exit 1
        fi
    fi
}

# Make sure that we are running the latest version
# $* = Params passed to script
check_version() {
    echo "Checking for an update to $scriptname"
    script_from_repo="$scriptname.repo_version"
    download_file "$upgradeurl" "$script_from_repo"
    newversion=$(grep  "^version=" "$script_from_repo" | sed 's/[^0-9.]*//g')
    if [ -z "$newversion" ]; then
        newversion=$version
    fi

    if [ $dry_run -eq 1 ]; then
        echo "Would check if this running script (version $version) is out of date."
        echo "If it's been superseded, the new version would be downloaded and you'd be asked"
        echo "if you want to upgrade to it and run the new version."
        echo
        return
    fi

    local latest_version
    latest_version=$(echo -e "$version\\n$newversion" | sort --reverse --version-sort | head -1)
    if [ "$latest_version" = "$version" ]; then
        echo "$scriptname is already the latest version ($version) - continuing"
        rm -f "$script_from_repo"
    else
        echo "New version ($newversion) of $scriptname detected"
        if yesno "run $scriptname $newversion now"
        then
            echo "OK, executing $script_from_repo $*"
            # replace the current script with a new one
            mv -f "$script_from_repo" "$script"
            chmod u+x "$script"
            echo "Download of $scriptname $newversion successful"
            rm "$LOCK"
            echo "Run $script $script_run_args"
            # use $script_run_args without quotes to avoid error
            # `getopt: unrecognized option` if there are more than 1 arguments
            # shellcheck disable=SC2086
            exec "$script" --skip-version-check $script_run_args
            error "Failed to run $script $script_run_args"
        else
            echo "New version of script is available: $upgradeurl"
            echo "It was downloaded to $script_from_repo"
            echo "If you prefer to use current version, run it with \"--skip-version-check\" key."
            exit 1
        fi
    fi
}

save_debian_repo()
{
    log install_debian_pkgs gnupg

    $wget $wget_options -O- https://repo.imunify360.cloudlinux.com/defense360/RPM-GPG-KEY-CloudLinux \
        | gpg --dearmor > /etc/apt/trusted.gpg.d/RPM-GPG-KEY-CloudLinux.gpg

    echo "deb [arch=amd64] https://repo.imunify360.cloudlinux.com/imunify360/$ID/$VERSION_ID/ $VERSION_CODENAME main" \
        > /etc/apt/sources.list.d/imunify360.list

    if [ "$beta" = "true" ]; then
        echo "deb [arch=amd64] https://repo.imunify360.cloudlinux.com/imunify360/$ID-testing/$VERSION_ID/ $VERSION_CODENAME main" \
            > /etc/apt/sources.list.d/imunify360-testing.list
    fi

    if ! log apt-get update; then
        test "$dev_install" = true
        check_exit_code 0 "apt-get update error."
    fi
}

save_centos_repo()
{
    local RPM_KEY=$checksite/RPM-GPG-KEY-CloudLinux
    local RPM_KEY_forcurl=$checksite_forcurl/RPM-GPG-KEY-CloudLinux

    cat >/etc/yum.repos.d/imunify360.repo <<-EOF
[imunify360]
name=EL-\$releasever - Imunify360
baseurl=$checksite/el/\$releasever/updates/x86_64/
enabled=1
gpgcheck=1
gpgkey=$RPM_KEY
EOF

    # add testing repo as disabled by default
    cat >/etc/yum.repos.d/imunify360-testing.repo <<-EOF
[imunify360-testing]
name=EL-\$releasever - Imunify360
baseurl=$checksite/el/\$releasever/updates-testing/x86_64/
enabled=$yum_beta_repo_enable
gpgcheck=1
gpgkey=$RPM_KEY
EOF

    log rpm --import "$RPM_KEY_forcurl"
    check_exit_code 0 "RPM import error."
    modifying_call=true
}

remove_debian_repo()
{
    rm /etc/apt/sources.list.d/imunify360.list \
        /etc/apt/sources.list.d/imunify360-testing.list \
        /etc/apt/sources.list.d/imunify360-alt-php.list 2>/dev/null
}

remove_centos_repo()
{
    rm /etc/yum.repos.d/imunify360.repo \
    /etc/yum.repos.d/imunify360-testing.repo \
    /etc/yum.repos.d/imunify360-ea-php-hardened \
    /etc/yum.repos.d/imunify360-alt-php 2>/dev/null
}

remove_acronis_agent()
{
    [ ! -e /usr/bin/restore_infected ] && return

    if /usr/bin/restore_infected acronis extra is_installed 2> /dev/null; then
        # If Acronis installation logs are present in restore_infected folder,
        #   then remove Acronis and the logs
        if ls /var/restore_infected/acronis_installation*.log; then
            /usr/bin/restore_infected acronis extra uninstall > /dev/null || :
            rm -f /var/restore_infected/acronis_installation*.log
        fi
    fi
}

terminate_detached_scans ()
{
    for file in /var/imunify360/aibolit/run/*/pid; do
        test -e "$file" && kill -9 "$(cat "$file")"
    done
    rm -rf /var/imunify360/aibolit/run/
    rm -rf /var/imunify360/aibolit/scans.pickle
}

# Only for imunify360-firewall
check_users() {
    CHECK_GROUPS="ossec"
    CHECK_USERS="ossec ossecr ossecm ossece"

    SYS_GID_MAX=$(awk '/^SYS_GID_MAX/ {print $2}' /etc/login.defs)
    SYS_UID_MAX=$(awk '/^SYS_UID_MAX/ {print $2}' /etc/login.defs)

    # detect SYS_GID_MAX, SYS_UID_MAX indirectly (Ubuntu 16.04)
    GID_MIN=$(awk '/^GID_MIN/ {print $2}' /etc/login.defs)
    UID_MIN=$(awk '/^UID_MIN/ {print $2}' /etc/login.defs)
    if [ "$SYS_GID_MAX" = "" -a "$GID_MIN" != "" ]; then
        SYS_GID_MAX=$((GID_MIN - 1))
    fi
    if [ "$SYS_UID_MAX" = "" -a "$UID_MIN" != "" ]; then
        SYS_UID_MAX=$((UID_MIN - 1))
    fi

    for grp in $CHECK_GROUPS; do
        gid=$(getent group $grp 2> /dev/null | cut -d ':' -f 3)
        if [ -z "$gid" ]; then
            gid='-1'
        fi
        if [ "$SYS_GID_MAX" != "" ]; then
            if [ "$gid" -gt "$SYS_GID_MAX" ]; then
                exit_with_error "Non-system group $grp already exists"
            fi
            elif [ "$first_install" = "true" -a "$gid" != "-1" ]; then
            exit_with_error "Group $grp already exists"
        fi
    done
    for usr in $CHECK_USERS; do
        uid=$(id -u "$usr" 2>/dev/null || echo -1)
        if [ "$SYS_UID_MAX" != "" ]; then
            if [ "$uid" -gt "$SYS_UID_MAX" ]; then
                exit_with_error "Non-system user $usr already exists"
            fi
            elif [ "$first_install" = "true" -a "$uid" != "-1" ]; then
            exit_with_error "User $usr already exists"
        fi
    done
}


# Only for imunify360-firewall
remove_hardened_php_repos()
{
    if [[ $ostype = centos ]]; then
        ALT_PHP=imunify360-alt-php.repo
        EA_PHP=imunify360-ea-php-hardened.repo
        REPOS_DIR=/etc/yum.repos.d

        # fix permissions
        for REPO in $ALT_PHP $EA_PHP; do
            test -f $REPOS_DIR/$REPO || continue
            chattr -i $REPOS_DIR/$REPO
            chmod 644 $REPOS_DIR/$REPO
        done

        # remove unconditionally
        rm -f $REPOS_DIR/$ALT_PHP
        rm -f $REPOS_DIR/$EA_PHP
    fi
}

print_help ()
{
    cat << EOF >&2
Usage:

  -h, --help            Print this message
  --version             Print script's version and exit
  -k, --key <key>       Deploy $PRODUCT with activation key
  -c, --uninstall       Uninstall $PRODUCT
  --skip-version-check  Do not check for script updates
  --skip-registration   Do not register, just install (the default)
  --dev-install         Turn off software defect reporting
  --beta                Install packages from 'testing' repo
  --check               Check if imunify360 Agent can be installed and exit
  -y, --yes             Assume "yes" as answer to all prompts and run non-interactively
EOF
}

print_version()
{
    echo "$scriptname $version"
}

check_centos_iptables_compatibility() {
    # dummy function that does nothing
    :
}

check_debian_iptables_compatibility() {
    if is_debian && [ "$VERSION_ID" == "10" ]
    then
        local apt_opts="-t buster-backports"
        log install_debian_pkgs iptables
        check_exit_code 0 "iptables >= 1.8.5 required on Debian 10. \n\
    Please, turn on buster-backports repository and run the script/installation again. \n\
    Buster-backports repository may be turned on by following command: \n\
        echo "deb http://ftp.debian.org/debian buster-backports main" > /etc/apt/sources.list.d/buster-backports.list \n\
    Then run: \n\
        apt-get update \n"
    fi
}

cleanup()
{
    rm -f "$LOCK"
}

# Lets start

# if environment has umask=0000 (if called from plesk extension), all created files have -rw-rw-rw- permission
umask 0022

init_vars "$0" "$*"
reopen_log

if [ -f "$LOCK" ] ; then
    if [ -d "/proc/$(cat "$LOCK")" ] ; then
        exit_with_error "$scriptname is already running"
    fi
fi

echo $$ > "$LOCK"
check_exit_code 0 "Please run $scriptname as root"

trap cleanup SIGTERM SIGINT SIGHUP EXIT

options=$(getopt -o ychk: -l yes,uninstall,help,version,check,skip-version-check,skip-registration,beta,dev-install,force,apt-force,key: -- "$@")
res=$?

if [ "$res" != 0 ]; then
    print_help
    exit 1
fi

eval set -- "$options"

while true; do
    case "$1" in
        -h|--help)
            print_help
            exit 0
        ;;
        --version)
            print_version
            exit 0
        ;;
        -y|--yes)
            assumeyes=true
            shift
        ;;
        -c|--uninstall)
            uninstall=true
            shift
        ;;
        -k|--key)
            conversion=true
            activationkey="$2"
            shift 2
        ;;
        --skip-version-check)
            skipversioncheck=true
            shift
        ;;
        --skip-registration)
            registration=false
            shift
        ;;
        --beta)
            beta=true
            yum_beta_option="--enablerepo=imunify360-testing"
            yum_beta_repo_enable=1
            shift
        ;;
        --dev-install)
            dev_install=true
            apt_allow_unauthenticated=--allow-unauthenticated
            shift
        ;;
        --force|--apt-force)  # used for Plesk extension installation
            export DEBIAN_FRONTEND=noninteractive
            apt_force='-o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew'
            shift
        ;;
        --check)
            detect_ostype
            check_${ostype}_release
            EXIT_CODE=$?
            exit $EXIT_CODE
        ;;
        --)
            shift
            break
        ;;
        -*)
            echo "$0: error - unrecognized option $1" 1>&2
            print_help
            exit 1
        ;;
        *) exit_with_error "Internal error!" ;;
    esac
done

if [ "$skipversioncheck" = "true" ]; then
    echo "Skipping check version"
else
    log check_version "$*"
fi

detect_ostype
check_${ostype}_release
detect_first_install


if [ "$conversion" = "true" ] && [ "$uninstall" = "true" ] ; then
    exit_with_error "invalid combination";
fi

if [ "$conversion" = "false" ] && [ "$registration" != "false" ] ; then
    # Register by IP is the default now
    conversion=true
    activationkey=false
fi

if [ "$uninstall" = "true" ]; then
    remove_acronis_agent
    remove_hardened_php_feature
    log remove_${ostype}_imunify
    terminate_detached_scans

    remove_${ostype}_repo
    log remove_first_install_default_overrides
    log echo "Uninstall complete."
    exit 0
fi

set_panel_detection_path
detect_panel


if [[ "$package" = "$imunify_av" ]]; then
    if check_${ostype}_pkg_presence imunify360-firewall; then
        echo "You are trying to install ImunifyAV over Imunify360 that already includes it. You can open Malware Scanner via UI Imunify360 -> Malware Scanner."
        exit 1
    fi
fi



if [ "$first_install" = "true" ]; then
    echo "In a few moments the script will install latest $package" \
    "package (w/dependencies)... (Ctrl-C to cancel)"
    sleep 4
    save_${ostype}_repo

    package_version=$(get_available_${ostype}_package_version "$package")
    check_exit_code 0 "Failed to get package version"

    log install_${ostype}_pkgs $package
    rc=$?
    # try installing the config overrides regardless of the installation success
    log install_first_install_default_overrides "$package" "$package_version"
    # fail
    if [ $rc -ne 0 ]; then
        exit_with_error "Package $package $package_version was not installed."
    fi
    modifying_call=true
fi

log "Installing ui packages..."
case "$PANEL" in
    cpanel)
        log check_and_install "$package" "$package_version" "$UI_PACKAGE" "$PANEL"
        check_exit_code 0 "Failed to install $PRODUCT $PANEL plugin."
        modifying_call=true
        ;;
    directadmin)
        log check_and_install "$package" "$package_version" "$UI_PACKAGE" "$PANEL"
        check_exit_code 0 "Failed to install $PRODUCT $PANEL plugin."
        modifying_call=true
        ;;
    generic)
        log check_and_install "$package" "$package_version" "imunify-ui" "$PANEL"
        check_exit_code 0 "Failed to install $PRODUCT $PANEL plugin."
        modifying_call=true
        ;;
    plesk)


        if [[ "$package" = "$imunify_av" ]]; then
            log echo "ImunifyAV for Plesk panel is available in Plesk Extension Catalog."
        fi

        ;;
    *)
        log echo "UI plugin is not installed."
        log echo "No supported hosted panel detected and $INTEGRATION_CONF_PATH file is missing."
        install_vendors=false
        ;;
esac

if [ "$conversion" = "true" ] ; then



    if [[ "$package" = "$imunify_av" ]]; then
        if imunify-antivirus rstatus >/dev/null 2>&1; then
            if [ "$activationkey" == false ]; then
            log    echo "Already registered"
                exit 0
            fi

            imunify-antivirus unregister >/dev/null 2>&1
        fi

        if [ "$activationkey" != false ] && imunify-antivirus register "$activationkey" >/dev/null 2>&1; then
            log echo "Registered by key"
        else
            log echo "Not registered"
        fi

        imunify-antivirus rstatus >/dev/null 2>&1

        if is_systemctl_avail; then
            log systemctl start "$package"
        else
            log /sbin/service minidaemon start
        fi
    fi

    check_exit_code 0 "Failed to start $package service."

    agent_start_success=false

    log echo "Waiting for $PRODUCT to start..."

    for i in {1..10}; do
        if log $COMMAND version
        then
            log echo "$PRODUCT is started"
            agent_start_success=true
            break
        else
            sleep 60
        fi
    done

    if ! $agent_start_success; then
        log echo "Something went wrong during $PRODUCT start up"
        exit_with_error "See /var/log/imunify360/error.log for details"
    fi

    if [ "$install_vendors" = "true" ]; then
        /usr/bin/imunify360-agent --console-log-level=WARNING install-vendors
    fi

elif ! $COMMAND rstatus >/dev/null 2>&1
then
    log echo "You have to register this software with registration key:"
    log echo "  $script --key <key>"
fi

if $modifying_call; then
    log echo "Success"
    log echo "You can find complete log in $LOG_FILE"
fi
exit 0
