#!/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.18.2325"
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 RULES_SCRIPT="${INSTALLER_DIR}"/installer_components/rules.sh
readonly MIN_SYSTEMD_VERSION_WITH_NAMESPACE_JOURNALS=245
readonly DEFAULT_FD_LIMIT=65535
readonly DEFAULT_SERVICE_PORT=51234
readonly DEFAULT_DEPLOYMENT_MODEL="vm"
readonly VIRTUAL_APPLIANCE_DEPLOYMENT_MODEL="virtual_appliance"

# 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(s)?://([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[s]://HOST:PORT\"."
        exit 1
    fi
}

check_if_binary_missing() {
    binary=$1

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

check_for_kerberos_env_vars() {
    if [ -n "${AEMBIT_KERBEROS_ATTESTATION_ENABLED}" ] && [ -z "${AEMBIT_PRIVILEGED_KEYTAB}" ] && [ -z "${AEMBIT_AGENT_PROXY_KERBEROS_PRINCIPAL}" ]; then
        log_err "Kerberos attestation is enabled, but the client principal name is unknown. Please set AEMBIT_AGENT_PROXY_KERBEROS_PRINCIPAL or AEMBIT_PRIVILEGED_KEYTAB=true."
        exit 1
    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_binary_missing pgrep
    check_if_binary_missing grep
    check_if_binary_missing id
    check_if_binary_missing getent
    check_if_binary_missing useradd
    check_if_binary_missing groupadd
    check_if_binary_missing dmidecode

    if [ -n "${AEMBIT_KERBEROS_ATTESTATION_ENABLED}" ]; then
        # Only required for Kerberos
        check_if_binary_missing curl
        check_if_binary_missing visudo
        check_if_binary_missing klist
        check_if_binary_missing kinit
        check_if_binary_missing sg
    fi

    if firewalld_is_active_and_running; then
        check_if_binary_missing firewall-cmd
    else
        check_if_binary_missing iptables
    fi

    if [ -n "${missing_binaries}" ]; then
        log_err "One or more necessary binaries were missing. Please install the following from the appropriate packages and then rerun the installer: ${missing_binaries}"
        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 --system --shell /bin/bash  --gid "${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
}

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
}

# get systemd version using systemctl
get_systemd_version() {
    systemd_version_output=$(systemctl --version)
    echo "$systemd_version_output" | grep -oP 'systemd \K\d+' | tr -dc '0-9'
}

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 [ "$(get_systemd_version)" -ge "${MIN_SYSTEMD_VERSION_WITH_NAMESPACE_JOURNALS}" ]; then
        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
    fi
}

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

    if [ "${AEMBIT_AGENT_PROXY_DEPLOYMENT_MODEL}" != "${VIRTUAL_APPLIANCE_DEPLOYMENT_MODEL}" ]; then
        # Copy iptables/firewalld specific rules script to the install dir.
        if ! cp "${RULES_SCRIPT}" "${AEMBIT_AGENT_PROXY_INSTALL_DIR_SCRIPTS}"/rules.sh; then
            log_err "Unable to copy rules startup script ""${RULES_SCRIPT}"" to the installation directory."
            exit 1
        fi
    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}"
}

copy_sudoers_file_to_host() {

    # DANGER! Edit the sudoers file in a temporary location, so its syntax can be checked. A malformed
    # sudoers file may render the machine unusable.
    if ! cp "${INSTALLER_DIR}"/installer_components/sudoers /tmp/aembit_agent_proxy_sudoers; then
        log_err "Unable to copy the Agent Proxy sudoers file to the ${SUDOERSD_DIR} directory."
        exit 1
    fi

    if [ -n "${AEMBIT_KERBEROS_ATTESTATION_ENABLED}" ] && [ -n "${AEMBIT_PRIVILEGED_KEYTAB}" ]; then
        if ! sed --in-place "s/# AEMBIT_PRIVILEGED_KEYTAB //" /tmp/aembit_agent_proxy_sudoers; then
            log_err "Unable to enable privileged keytab lines in sudoers file."
            exit 1
        fi

        # The ":" character needs to be escaped for sed AND escaped in the sudoers file,
        # hence the double escape.
        escaped_agent_controller_location="${AEMBIT_AGENT_CONTROLLER//:/\\\\:}"

        if ! sed --in-place "s#{{ AGENT_CONTROLLER_LOCATION }}#${escaped_agent_controller_location}#" /tmp/aembit_agent_proxy_sudoers; then
            log_err "Unable to replace the Agent Controller location in the sudoers file."
            exit 1
        fi

        # Our sudoers file needs to have 4 backslashes before the $ character,
        # for the `kinit`` command to work as expected. It needs all of these
        # backslashes because we need to escape both the shell and two instances
        # of `sed`.
        samaccount_principal=$(klist -k /etc/krb5.keytab | grep --max-count 1 '$@' | rev | cut -d " " -f 1 | rev | sed 's/\$@/\\\\\\\\\\\\\\\\\$@/')

        if ! sed --in-place "s#{{ SAMACCOUNT_PRINCIPAL }}#${samaccount_principal}#" /tmp/aembit_agent_proxy_sudoers; then
            log_err "Unable to replace the sAMAccount name in the sudoers file."
            exit 1
        fi
    else
        if ! sed --in-place "/# AEMBIT_PRIVILEGED_KEYTAB /d" /tmp/aembit_agent_proxy_sudoers; then
            log_warn "Unable to removed comment lines from the sudoers file."
        fi
    fi

    if [ -n "${AEMBIT_STEERING_ALLOWED_HOSTS}" ]; then
        if ! sed --in-place "s/# AEMBIT_STEERING_ALLOWED_HOSTS //" /tmp/aembit_agent_proxy_sudoers; then
            log_err "Unable to enable dynamic steering in sudoers file."
            exit 1
        fi

        if ! sed --in-place "s#{{ AEMBIT_AGENT_PROXY_INSTALL_DIR_SCRIPTS }}#${AEMBIT_AGENT_PROXY_INSTALL_DIR_SCRIPTS}#" /tmp/aembit_agent_proxy_sudoers; then
            log_err "Unable to replace the scripts path in the sudoers file."
            exit 1
        fi

        # If the user specified a container CIDR, we need to allow the aembit_agent_proxy user to
        # communicate this to the rules.sh subprocess while still using sudo (since inheriting env. variables
        # as sudo is disabled by default) .
        if [ -n "${AEMBIT_DOCKER_CONTAINER_CIDR}" ]; then
            if ! sed --in-place "s/# AEMBIT_STEERING_WITH_DOCKER_CIDR //" /tmp/aembit_agent_proxy_sudoers; then
                log_err "Unable to uncomment the env. variable inheritance line in the sudoers file."
                exit 1
            fi
        else
            if ! sed --in-place "/# AEMBIT_STEERING_WITH_DOCKER_CIDR /d" /tmp/aembit_agent_proxy_sudoers; then
                log_warn "Unable to removed comment lines from the sudoers file."
            fi
        fi 

    else
        if ! sed --in-place "/# AEMBIT_STEERING_ALLOWED_HOSTS /d" /tmp/aembit_agent_proxy_sudoers; then
            log_warn "Unable to removed comment lines from the sudoers file."
        fi

        if ! sed --in-place "/# AEMBIT_STEERING_WITH_DOCKER_CIDR /d" /tmp/aembit_agent_proxy_sudoers; then
            log_warn "Unable to removed comment lines from the sudoers file."
        fi
    fi

    # DANGER! If the sudoers file has any issues with it, it can render the machine unusable.
    if ! visudo --check --file /tmp/aembit_agent_proxy_sudoers > /dev/null; then
        log_err "There was a syntax error when generating the sudoers file."
        exit 1
    fi

    # DANGER! Always check that the sudoers file is well-formed before placing in the
    # sudoers directory. Otherwise it may render the machine unusable.
    if ! cp /tmp/aembit_agent_proxy_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

    rm /tmp/aembit_agent_proxy_sudoers

    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

    # Sets the port on which Agent Proxy's health/service server runs.
    service_port=$DEFAULT_SERVICE_PORT
    if [ -n "${AEMBIT_SERVICE_PORT}" ]; then
        service_port=$AEMBIT_SERVICE_PORT
    fi

    if ! sed --in-place "s#{{ SERVICE_PORT }}#${service_port}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update the health/service server port in the systemd service file."
        exit 1
    fi

    # Sets the file descriptor limit for the Agent Proxy service.
    fd_limit=$DEFAULT_FD_LIMIT
    if [ -n "${AEMBIT_FD_LIMIT}" ]; then
        fd_limit=$AEMBIT_FD_LIMIT
    fi

    if ! sed --in-place "s#{{ FD_LIMIT }}#${fd_limit}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update file descriptor limit in the systemd service file."
        exit 1
    fi
    
    # Set the deployment model.
    deployment_model="${DEFAULT_DEPLOYMENT_MODEL}"
    if [ -n "${AEMBIT_AGENT_PROXY_DEPLOYMENT_MODEL}" ]; then
        deployment_model="${AEMBIT_AGENT_PROXY_DEPLOYMENT_MODEL}"
    fi

    if ! sed --in-place "s#{{ DEPLOYMENT_MODEL }}#${deployment_model}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update the deployment model in the systemd service file."
        exit 1
    fi

    # For the Virtual Appliance, remove iptables rules from the systemd service.
    if [ "${deployment_model}" == "${VIRTUAL_APPLIANCE_DEPLOYMENT_MODEL}" ]; then
        if ! sed --in-place "/ExecStartPost/d" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
            log_warn "Unable to remove the iptables script from the systemd service file."
        fi
        
        if ! sed --in-place "/ExecStopPost/d" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
            log_warn "Unable to remove the iptables script from the systemd service file."
        fi
    fi

    # Use firewall-based steering only for select hosts, if provided.
    optional_hosts=''
    if [ -n "${AEMBIT_STEERING_ALLOWED_HOSTS}" ]; then
        optional_hosts="Environment=AEMBIT_STEERING_ALLOWED_HOSTS=${AEMBIT_STEERING_ALLOWED_HOSTS}"
    fi

    # If AEMBIT_STEERING_ALLOWED_HOSTS wasn't set, a blank line will be present in the unit file.
    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_STEERING_ALLOWED_HOSTS }}#${optional_hosts}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional steering hosts in the systemd service file."
        exit 1
    fi

    optional_listener_ip_value=''
    if [ -n "${AEMBIT_AGENT_PROXY_LISTENER_IP}" ]; then
        optional_listener_ip_value="Environment=AEMBIT_AGENT_PROXY_LISTENER_IP=${AEMBIT_AGENT_PROXY_LISTENER_IP}"
    fi

    # In order to proxy traffic from the Docker network, we need to listen on 0.0.0.0 instead of loopback.
    # In the event of both an explicit listener IP and a Docker CIDR specified, the 0.0.0.0 should override the explicit listener IP.
    if [ -n "${AEMBIT_DOCKER_CONTAINER_CIDR}" ]; then
        optional_listener_ip_value="Environment=AEMBIT_AGENT_PROXY_LISTENER_IP=0.0.0.0"
    fi

    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_AGENT_PROXY_LISTENER_IP }}#${optional_listener_ip_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

    # Sets Agent Proxy's log level if the user has provided one.
    aembit_log_level_replacement_value=''
    if [ -n "${AEMBIT_LOG_LEVEL}" ]; then
        aembit_log_level_replacement_value="Environment=AEMBIT_LOG_LEVEL=$AEMBIT_LOG_LEVEL"
    fi

    # If AEMBIT_LOG_LEVEL wasn't set, a blank line will be present in the unit file.
    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_LOG_LEVEL }}#${aembit_log_level_replacement_value}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional log level in the systemd service file."
        exit 1
    fi
    
    # Sets the deprecated log level, if the user has provided one.
    aembit_log_replacement_value=''
    if [ -n "${AEMBIT_LOG}" ]; then
        aembit_log_replacement_value="Environment=AEMBIT_LOG=$AEMBIT_LOG"
    fi

    # If AEMBIT_LOG wasn't set, a blank line will be present in the unit file.
    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_LOG }}#${aembit_log_replacement_value}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional log level 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

    # Enable debug packet capture if the user has provided a value.
    optional_debug_max_captured_packets=''
    if [ -n "${AEMBIT_DEBUG_MAX_CAPTURED_PACKETS_PER_DEVICE}" ]; then
        optional_debug_max_captured_packets="Environment=AEMBIT_DEBUG_MAX_CAPTURED_PACKETS_PER_DEVICE=${AEMBIT_DEBUG_MAX_CAPTURED_PACKETS_PER_DEVICE}"
    fi

    # If AEMBIT_DEBUG_MAX_CAPTURED_PACKETS_PER_DEVICE wasn't set, a blank line will be present in the unit file.
    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_DEBUG_MAX_CAPTURED_PACKETS_PER_DEVICE }}#${optional_debug_max_captured_packets}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional max captured packets in the systemd service file."
        exit 1
    fi
    
    # Pass in a resource set ID, if one is provided.
    optional_aembit_resource_set_id_value=''
    if [ -n "${AEMBIT_RESOURCE_SET_ID}" ]; then
        optional_aembit_resource_set_id_value="Environment=AEMBIT_RESOURCE_SET_ID=${AEMBIT_RESOURCE_SET_ID}"
    fi
    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_RESOURCE_SET_ID }}#${optional_aembit_resource_set_id_value}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update the optional Aembit resource set ID 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#{{ OPTIONAL_STEERING_ALLOWED_HOSTS }}#${AEMBIT_STEERING_ALLOWED_HOSTS}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update steering hosts in the systemd service file."
        exit 1
    fi

    # Add LogNamespace only if systemd version is 245 or greater (when LogNamespace was introduced).
    # Added in version 245.
    # See LogNamespace section in https://www.man7.org/linux/man-pages/man5/systemd.exec.5.html
    aembit_log_namespace_value=''
    if [ "$(get_systemd_version)" -ge "${MIN_SYSTEMD_VERSION_WITH_NAMESPACE_JOURNALS}" ]; then
        aembit_log_namespace_value="LogNamespace=$AEMBIT_AGENT_PROXY_USER_NAME"
    fi

    # If systemd version is prior to 245, 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

    # Kerberos environment variables

    optional_aembit_kerberos_attestation_enabled=''
    if [ -n "${AEMBIT_KERBEROS_ATTESTATION_ENABLED}" ]; then
        optional_aembit_kerberos_attestation_enabled="Environment=AEMBIT_KERBEROS_ATTESTATION_ENABLED=true"
    fi

    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_KERBEROS_ATTESTATION_ENABLED }}#${optional_aembit_kerberos_attestation_enabled}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional Kerberos attestation enabled flag in the systemd service file."
        exit 1
    fi

    optional_krb5_client_ktname=''
    if [ -n "${KRB5_CLIENT_KTNAME}" ]; then
        optional_krb5_client_ktname="Environment=KRB5_CLIENT_KTNAME=${KRB5_CLIENT_KTNAME}"
    fi

    if ! sed --in-place "s#{{ OPTIONAL_KRB5_CLIENT_KTNAME }}#${optional_krb5_client_ktname}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional Kerberos keytab filename in the systemd service file."
        exit 1
    fi

    optional_agent_proxy_kerberos_principal=''
    if [ -n "${AEMBIT_PRIVILEGED_KEYTAB}" ]; then
        samaccount_principal=$(klist -k /etc/krb5.keytab | grep --max-count 1 '$@' | rev | cut -d " " -f 1 | rev)
        optional_agent_proxy_kerberos_principal="Environment=AEMBIT_AGENT_PROXY_KERBEROS_PRINCIPAL=${samaccount_principal}"
    fi

    # If this env var is present, it should override the sAMAccount principal.
    if [ -n "${AEMBIT_AGENT_PROXY_KERBEROS_PRINCIPAL}" ]; then
        optional_agent_proxy_kerberos_principal="Environment=AEMBIT_AGENT_PROXY_KERBEROS_PRINCIPAL=${AEMBIT_AGENT_PROXY_KERBEROS_PRINCIPAL}"
    fi

    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_AGENT_PROXY_KERBEROS_PRINCIPAL }}#${optional_agent_proxy_kerberos_principal}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update the Kerberos principal name in the systemd service file."
        exit 1
    fi

    optional_privileged_keytab=''
    if [ -n "${AEMBIT_PRIVILEGED_KEYTAB}" ]; then
        optional_privileged_keytab="Environment=AEMBIT_PRIVILEGED_KEYTAB=${AEMBIT_PRIVILEGED_KEYTAB}"
    fi

    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_PRIVILEGED_KEYTAB }}#${optional_privileged_keytab}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update privileged keytab setting in the systemd service file."
        exit 1
    fi

    # Sets the metrics port, if the user has provided one.
    aembit_metrics_port_value=''
    if [ -n "${AEMBIT_METRICS_PORT}" ]; then
        aembit_metrics_port_value="Environment=AEMBIT_METRICS_PORT=${AEMBIT_METRICS_PORT}"
    fi

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

    optional_aembit_metrics_enabled=''
    if [ -n "${AEMBIT_METRICS_ENABLED}" ]; then
        optional_aembit_metrics_enabled="Environment=AEMBIT_METRICS_ENABLED=${AEMBIT_METRICS_ENABLED}"
    fi

    if ! sed --in-place "s#{{ OPTIONAL_AEMBIT_METRICS_ENABLED }}#${optional_aembit_metrics_enabled}#" "${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT}"; then
        log_err "Unable to update optional metrics enabled 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

    install_systemd_service || exit 1

    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
}

# Read and set environment variables based upon the environment variables of the
# installed agent proxy service.
# These environment variables can be overwritten by the ones passed as part of the
# installation script.
read_env_vars_from_ap_service() {
    env_vars=$(systemctl show -p Environment ${AEMBIT_AGENT_PROXY_SYSTEMD_UNIT} | cut -d '=' -f 2-)

    # Split the string into an array
    IFS=' ' read -r -a env_array <<< "$env_vars"

    # Iterate over the array and set all the environment values which are not
    # set.
    for var in "${env_array[@]}"; do
        # Split each element into variable name and value
        IFS='=' read -r key value <<< "$var"
        if [[ ! -v "${key}" ]] && [ -n "$value" ]; then
            export "$key"="$value"
        fi
    done
}

run_installer() {
    check_running_as_root || exit 1
    parse_options "${@}"

    if is_upgrade; then
        read_env_vars_from_ap_service
    fi

    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 AEMBIT_LOG_LEVEL AEMBIT_LOG AEMBIT_FD_LIMIT AEMBIT_STEERING_ALLOWED_HOSTS AEMBIT_DEBUG_MAX_CAPTURED_PACKETS_PER_DEVICE || exit 1
    check_for_kerberos_env_vars || 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
    copy_sudoers_file_to_host || exit 1

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

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

main "${@}"|| exit 1
