#!/bin/bash

export PATH="${PATH}:/bin:/sbin:/usr/bin:/usr/sbin"

readonly AEMBIT_GROUP_NAME="aembit"
readonly AEMBIT_AGENT_PROXY_USER_NAME="aembit_agent_proxy"
readonly AEMBIT_AGENT_PROXY_SYSTEMD_UNIT="aembit_agent_proxy.service"
readonly AEMBIT_AGENT_PROXY_VERSION="1.12.1600"
readonly AEMBIT_AGENT_PROXY_INSTALL_DIR="/opt/aembit/edge/agent_proxy"
readonly AEMBIT_AGENT_PROXY_INSTALL_DIR_BIN="${AEMBIT_AGENT_PROXY_INSTALL_DIR}"/"${AEMBIT_AGENT_PROXY_VERSION}"/bin
readonly AEMBIT_AGENT_PROXY_INSTALL_DIR_SCRIPTS="${AEMBIT_AGENT_PROXY_INSTALL_DIR}"/"${AEMBIT_AGENT_PROXY_VERSION}"/scripts
readonly AEMBIT_AGENT_PROXY_JOURNALD_CONFIG_FILE="/etc/systemd/journald@aembit_agent_proxy.conf"
readonly AEMBIT_AGENT_PROXY_VERSION_PATTERN="[[:digit:]]*.[[:digit:]]*.[[:digit:]]*"

INSTALLER_DIR=$(dirname "${0}")
readonly LOG_FILE="${INSTALLER_DIR}"/installer.log

readonly SYSTEMD_UNIT_FILES_FOLDER="/etc/systemd/system"
readonly SUDOERSD_DIR="/etc/sudoers.d"
readonly FIREWALLD_RULES_UP_SCRIPT="${INSTALLER_DIR}"/installer_components/firewallcmd_up.sh
readonly FIREWALLD_RULES_DOWN_SCRIPT="${INSTALLER_DIR}"/installer_components/firewallcmd_down.sh
readonly IPTABLES_RULES_UP_SCRIPT="${INSTALLER_DIR}"/installer_components/iptables_up.sh
readonly IPTABLES_RULES_DOWN_SCRIPT="${INSTALLER_DIR}"/installer_components/iptables_down.sh

# Set by set_resolv_dependency_service
RESOLV_FILE_MANAGED_BY=null

# OPTIONS
TO_UPGRADE=false

parse_options() {
    for _ in $(getopt --options "" --longoptions "upgrade" -- "${@}"); do
        case $1 in
            --upgrade)
                TO_UPGRADE=true ;;
        esac
        shift
    done

}

log() {
    local level="${1}"
    shift

    local log_fmt="%s %s\n"
    if [ "${level}" ]; then
        log_fmt="%s ${level} %s\n"
    fi

    for line in "$@"; do
        # shellcheck disable=SC2059
        printf "${log_fmt}" "$(date +"%H:%M:%S")" "${line}" | tee -a "${LOG_FILE}"
    done
}

log_info() {
    log "Info:" "$@"
}

log_warn() {
    log "Warning:" "$@"
}

log_err() {
    log "Error:" "$@"
}

check_running_as_root() {
    if [ "$(id --user)" -ne 0 ]; then
        log_err "Installer must be run as root."
        exit 1
    fi
}

check_for_required_env_var() {
    env_var_name=$1
    env_var_value="${!env_var_name}"

    if [ -z "${env_var_value}" ]; then
        log_err "Required environment variable \"${env_var_name}\" is not defined."
        exit 1
    fi
}

check_for_optional_env_vars() {
    until [ -z "$1" ]; do
        env_var_name=$1
        # `env_var_value` contents will be `unset` if the var is unset and `null` if it's set but null/empty string
        env_var_value="${!env_var_name-unset}"
        shift

        if [ -z "${env_var_value}" ]; then
            log_err "Optional environment variable \"${env_var_name}\" is set to null but a value is required."
            exit 1
        fi
    done
}

check_valid_aembit_agent_controller_env_var() {
    http_regex='^http://([a-zA-Z0-9.-]+)(:[0-9]+)?$'

    if [[ ! $AEMBIT_AGENT_CONTROLLER =~ $http_regex ]]; then
        log_err "The value of AEMBIT_AGENT_CONTROLLER should be in the format: \"http://HOST:PORT\"."
        exit 1
    fi
}

check_if_package_missing() {
    package=$1

    if ! which "${package}" > /dev/null 2>&1; then
        if [ -z "${missing_packages}" ]; then
            missing_packages="${package}"
        else
            missing_packages="${missing_packages}, ${package}"
        fi
    fi
}

# check if firewalld is active and running
firewalld_is_active_and_running() {
    if [ -n "${firewalld_status+x}" ]; then
	    return "${firewalld_status}"
    fi

    # Check if firewalld is installed
    if ! command -v firewalld &> /dev/null; then
	    log_info "firewalld not installed."
	    firewalld_status=1
	    return "${firewalld_status}"
    fi

    # Check if firewalld is active and running
    if systemctl is-active --quiet firewalld && systemctl is-enabled --quiet firewalld; then
        log_info "firewalld is active and running."
	    firewalld_status=0
	    return "${firewalld_status}"
    else
        log_info "firewalld is either not active or not running."
	    firewalld_status=1
	    return "${firewalld_status}"
    fi
}

check_required_dependencies() {
    log_info "Checking for required package dependencies."

    check_if_package_missing pgrep
    check_if_package_missing grep
    check_if_package_missing id
    check_if_package_missing getent
    check_if_package_missing useradd
    check_if_package_missing groupadd
    check_if_package_missing dmidecode
    check_if_package_missing iptables

    if firewalld_is_active_and_running; then
        check_if_package_missing firewall-cmd
    fi

    if [ -n "${missing_packages}" ]; then
        log_err "One or more necessary packages were missing. Please install the following package(s) and then rerun the installer: ${missing_packages}"
        exit 1
    fi
}


agent_proxy_is_already_installed() {
    if ! [ -d "${AEMBIT_AGENT_PROXY_INSTALL_DIR}" ]; then
        return 1
    fi

    for file in ${AEMBIT_AGENT_PROXY_INSTALL_DIR}/${AEMBIT_AGENT_PROXY_VERSION_PATTERN}; do
        # If there is no match, $file just refers to the pattern, instead of a
        # file name. No file exists with the name of the pattern literal.
        if [ -e "${file}" ]; then
            return 0
        else
            return 1
        fi
    done
}

agent_proxy_is_already_running() {
    if pgrep --euid "${AEMBIT_AGENT_PROXY_USER_NAME}" &>/dev/null; then
        return 0
    else
        return 1
    fi
}

# Whether the installer is being run in upgrade mode.
is_upgrade() {
    if [ "${TO_UPGRADE}" == "true" ]; then
        return 0
    else
        return 1
    fi
}

# Exits 0 if the current platform is $1, else exits with 1.
#
# Examples:
# * is_platform rhel
# * is_platform ubuntu
# * is_platform "Red Hat Enterprise Linux"
# * is_platform Jammy
is_platform() {
    platform=$1
    val=$(cat /etc/*-release | grep -c "$platform")
    if [[ $val -gt 0 ]]; then
        return 0
    else
        return 1
    fi
}

handle_existing_agent_proxy() {
    if (agent_proxy_is_already_running || agent_proxy_is_already_installed) && ! is_upgrade; then
        log_err "Aembit Agent Proxy is already installed. Please uninstall it first or use the \"--upgrade\" option."
        exit 1
    fi
}

create_user_and_group() {
    if getent group "${AEMBIT_GROUP_NAME}" > /dev/null 2>&1; then
        log_info "Group \"${AEMBIT_GROUP_NAME}\" exists"
    else
        log_info "Group \"${AEMBIT_GROUP_NAME}\" does not exist. Creating."
        if groupadd "${AEMBIT_GROUP_NAME}"; then
            log_info "Group created"
        else
            log_err "Error creating the \"${AEMBIT_GROUP_NAME}\" group."
            exit 1
        fi
    fi

    if getent passwd "${AEMBIT_AGENT_PROXY_USER_NAME}" > /dev/null 2>&1; then
        log_info "User \"${AEMBIT_AGENT_PROXY_USER_NAME}\" exists"
    else
        log_info "User \"${AEMBIT_AGENT_PROXY_USER_NAME}\" does not exist. Creating."
        if useradd --no-create-home --shell /bin/bash  -g "${AEMBIT_GROUP_NAME}" "${AEMBIT_AGENT_PROXY_USER_NAME}"; then
            log_info "User created"
        else
            log_err "There was an error creating the \"${AEMBIT_AGENT_PROXY_USER_NAME}\" user."
            exit 1
        fi
    fi
}

# Determines which service manages /etc/resolv.conf.
#
# RHEL uses NetworkManager, while Ubuntu uses systemd-resolved. If neither is detected,
# the function exits with an error code.
set_resolv_dependency_service() {

    if is_platform rhel; then
        RESOLV_FILE_MANAGED_BY=NetworkManager.service
    elif is_platform ubuntu; then
        RESOLV_FILE_MANAGED_BY=systemd-resolved.service
    else
        log_err "Unrecognized distribution detected."
        exit 1
    fi
}

set_file_permissions() {
    file_path=$1
    permissions=$2

    if ! chmod "${permissions}" "${file_path}"; then
        log_err "Unable to set the permissions of ${file_path}."
        exit 1
    fi
}

set_file_user_and_group_owner() {
    file_path=$1
    user=$2
    group=$3

    if ! chown "${user}:${group}" "${file_path}"; then
        log_err "Unable to set the ownership of ${file_path}."
        exit 1
    fi
}

set_recursive_file_permissions() {
    file_path=$1
    permissions=$2

    if ! chmod --recursive "${permissions}" "${file_path}"; then
        log_err "Unable to set the permissions of ${file_path}."
        exit 1
    fi
}

set_recursive_file_user_and_group_owner() {
    file_path=$1
    user=$2
    group=$3

    if ! chown --recursive "${user}:${group}" "${file_path}"; then
        log_err "Unable to set the ownership of ${file_path}."
        exit 1
    fi
}

set_up_logs() {

    # From "man systemd-journald.service"
    # "On systems where /var/log/journal/ does not exist yet but where persistent logging is
    #  desired (and the default journald.conf is used), it is sufficient to create the
    #  directory, and ensure it has the correct access modes and ownership:"
    if [ ! -d "/var/log/journal" ]; then
        mkdir -p /var/log/journal
        chgrp systemd-journal /var/log/journal
        if ! systemd-tmpfiles --create --prefix /var/log/journal; then
            exit 1
        fi
    fi

    if ! cp "${INSTALLER_DIR}"/installer_components/journald@aembit_agent_proxy.conf "${AEMBIT_AGENT_PROXY_JOURNALD_CONFIG_FILE}"; then
        log_err "Unable to copy the journald configuration file to host."
        exit 1
    fi

    set_file_permissions "${AEMBIT_AGENT_PROXY_JOURNALD_CONFIG_FILE}" 400 || exit 1
    set_file_user_and_group_owner "${AEMBIT_AGENT_PROXY_JOURNALD_CONFIG_FILE}" root root || exit 1
}

copy_static_components_to_host() {
    if ! mkdir --parents "${AEMBIT_AGENT_PROXY_INSTALL_DIR_BIN}"; then
        log_err "Unable to make installation directory for Agent Proxy binary."
        exit 1
    fi

    if ! cp "${INSTALLER_DIR}"/aembit_agent_proxy "${AEMBIT_AGENT_PROXY_INSTALL_DIR_BIN}"/"${AEMBIT_AGENT_PROXY_BIN}"; then
        log_err "Unable to copy the Agent Proxy binary to the installation directory."
        exit 1
    fi

    if ! mkdir --parents "${AEMBIT_AGENT_PROXY_INSTALL_DIR_SCRIPTS}"; then
        log_err "Unable to make installation directory for Agent Proxy scripts."
        exit 1
    fi

    # Copy iptables/firewalld specific rules script to the install dir.
    if firewalld_is_active_and_running; then
        rules_up_file="${FIREWALLD_RULES_UP_SCRIPT}"
        rules_down_file="${FIREWALLD_RULES_DOWN_SCRIPT}"
    else
        rules_up_file="${IPTABLES_RULES_UP_SCRIPT}"
        rules_down_file="${IPTABLES_RULES_DOWN_SCRIPT}"
    fi
    if ! cp "${rules_up_file}" "${AEMBIT_AGENT_PROXY_INSTALL_DIR_SCRIPTS}"/rules_up.sh; then
        log_err "Unable to copy rules startup script ""${rules_up_file}"" to the installation directory."
        exit 1
    fi

    if ! cp "${rules_down_file}" "${AEMBIT_AGENT_PROXY_INSTALL_DIR_SCRIPTS}"/rules_down.sh; then
        log_err "Unable to copy rules shutdown script ""${rules_down_file}"" to the installation directory."
        exit 1
    fi

    if ! cp "${INSTALLER_DIR}"/uninstall "${AEMBIT_AGENT_PROXY_INSTALL_DIR_SCRIPTS}"/uninstall; then
        log_err "Unable to copy the Agent Proxy uninstall script to the installation directory."
        exit 1
    fi

    set_recursive_file_permissions "${AEMBIT_AGENT_PROXY_INSTALL_DIR}" 500
    set_recursive_file_user_and_group_owner "${AEMBIT_AGENT_PROXY_INSTALL_DIR}" "${AEMBIT_AGENT_PROXY_USER_NAME}" "${AEMBIT_GROUP_NAME}"

    if ! cp "${INSTALLER_DIR}"/installer_components/sudoers "${SUDOERSD_DIR}/aembit_agent_proxy"; then
        log_err "Unable to copy the Agent Proxy sudoers file to the ${SUDOERSD_DIR} directory."
        exit 1
    fi

    set_file_permissions "${SUDOERSD_DIR}/aembit_agent_proxy" 400 || exit 1
    set_file_user_and_group_owner "${SUDOERSD_DIR}/aembit_agent_proxy" root root || exit 1
}

install_systemd_service() {
    if ! cp "${INSTALLER_DIR}"/installer_components/aembit_agent_proxy.service "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to copy the systemd service file to the destination."
        exit 1
    fi

    set_file_permissions "${SYSTEMD_UNIT_FILES_FOLDER}/${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}" 400 || exit 1
    set_file_user_and_group_owner "${SYSTEMD_UNIT_FILES_FOLDER}/${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}" root root || exit 1

    if ! sed --in-place "s#{{ AGENT_CONTROLLER_LOCATION }}#${AEMBIT_AGENT_CONTROLLER}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update the Agent Controller location in the systemd service file."
        exit 1
    fi

    if ! sed --in-place "s#{{ DOCKER_CONTAINER_CIDR }}#${AEMBIT_DOCKER_CONTAINER_CIDR}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update the containers' CIDR block location in the systemd service file."
        exit 1
    fi

    # Listen on 0.0.0.0 instead of loopback if we're expected to proxy containerized client workloads
    env_line_replacement_value=''
    if [ -n "${AEMBIT_DOCKER_CONTAINER_CIDR}" ]; then
        env_line_replacement_value="Environment=AEMBIT_AGENT_PROXY_LISTENER_IP=0.0.0.0"
    fi

    # If AEMBIT_DOCKER_CONTAINER_CIDR wasn't set, a blank line will be present in the unit file.
    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_AGENT_PROXY_LISTENER_IP }}#${env_line_replacement_value}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional developer mode IP in the systemd service file."
        exit 1
    fi

    # Listen on a user-provided port for the HTTP server if one is provided
    optional_http_port_replacement_value=''
    if [ -n "${AEMBIT_HTTP_SERVER_PORT}" ]; then
        optional_http_port_replacement_value="Environment=AEMBIT_HTTP_SERVER_PORT=${AEMBIT_HTTP_SERVER_PORT}"
    fi

    # If AEMBIT_HTTP_SERVER_PORT wasn't set, a blank line will be present in the unit file.
    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_HTTP_SERVER_PORT }}#${optional_http_port_replacement_value}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional HTTP server port in the systemd service file."
        exit 1
    fi

    if ! sed --in-place "s#{{ CLIENT_WORKLOAD_ID }}#${CLIENT_WORKLOAD_ID}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update the Client Workload Id in the systemd service file."
        exit 1
    fi

    if ! sed --in-place "s#{{ RESOLV_FILE_MANAGED_BY }}#${RESOLV_FILE_MANAGED_BY}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update the Client Workload Id in the systemd service file."
        exit 1
    fi

    # Add LogNamespace only if systemd version is 240 or greater (when LogNamespace was introduced).
    systemd_version_output=$(systemctl --version)
    systemd_version_number=$(echo "$systemd_version_output" | grep -oP 'systemd \K\d+' | tr -dc '0-9')

    aembit_log_namespace_value=''
    if [ "$systemd_version_number" -ge 240 ]; then
        aembit_log_namespace_value="LogNamespace=$AEMBIT_AGENT_PROXY_USER_NAME"
    fi

    # If systemd is older than 240, a blank line will be present in the unit file.
    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_LOG_NAMESPACE }}#${aembit_log_namespace_value}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional log namespace in the systemd service file."
        exit 1
    fi

    if ! systemctl enable "${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to enable the Agent Proxy systemd service."
        exit 1
    fi
}

start_systemd_service() {
    if ! systemctl start "${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to start the Agent Proxy systemd service."
        exit 1
    fi
}

upgrade_systemd_service() {
    # The Agent Proxy needs to be stopped and started, instead of just
    # restarted, because it needs to remove its existing iptables rules
    # prior to referencing the updated ones.

    # If the Agent Proxy service is not running or not loaded, continue
    # with the upgrade process.
    systemctl stop "${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}" 2> /dev/null

    if ! systemctl daemon-reload; then
        log_err "Unable to reload the Agent Proxy systemd service."
        exit 1
    fi

    if ! systemctl start "${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to start the updated Agent Proxy systemd service."
        exit 1
    fi

    if ! systemctl restart systemd-journald; then
        log_err "Unable to restart journald with the updated log configuration."
        exit 1
    fi
}

remove_all_previous_installations() {
    log_info "Deleting old Agent Proxy versions."
    for dir in ${AEMBIT_AGENT_PROXY_INSTALL_DIR}/${AEMBIT_AGENT_PROXY_VERSION_PATTERN}; do
        case "${dir}" in
            # Don't delete the upgraded Agent Proxy version.
            *"${AEMBIT_AGENT_PROXY_VERSION}"*)
                # Do nothing
                ;;
            *)
                rm -rf "${dir}"
                ;;
        esac
    done
}

run_installer() {
    check_running_as_root || exit 1
    parse_options "${@}"
    check_for_required_env_var AEMBIT_AGENT_CONTROLLER || exit 1
    check_valid_aembit_agent_controller_env_var || exit 1
    check_for_optional_env_vars AEMBIT_DOCKER_CONTAINER_CIDR AEMBIT_HTTP_SERVER_PORT || exit 1
    set_resolv_dependency_service || exit 1
    check_required_dependencies || exit 1
    handle_existing_agent_proxy || exit 1
    create_user_and_group || exit 1
    set_up_logs || exit 1
    copy_static_components_to_host || exit 1
    install_systemd_service || exit 1

    if is_upgrade; then
        upgrade_systemd_service || exit 1
        remove_all_previous_installations || exit 1
    else
        start_systemd_service || exit 1
    fi
}

main() {
    set -o pipefail
    log_info "Installing Aembit Agent Proxy"
    run_installer "${@}" 2>&1
}

main "${@}"|| exit 1
