#!/usr/bin/env bash
# ==============================================================================
# Usage: install-linux.sh [OPTIONS]
#
# Options:
#   --no-home-policy
#       Disables configuration that allows opkssh to see policy files in user's
#       home directory (/home/<username>/auth_id). Greatly simplifies install.
#
#   --selinux-enable-squid
#       Enables the Squid proxy ports in opkssh SELinux module. Used when system
#       has HTTPS_PROXY set to a Squid proxy
#
#   --selinux-enable-proxy
#       Enables the HTTP Cache ports in opkssh SELinux module. Used when system
#       has set the HTTPS_PROXY to a general HTTP Proxy. Dynamicly configure ports
#       used with SELinux http_cache_port_t port type
#
#   --no-sshd-restart
#       Do not restart SSH after installation.
#
#   --overwrite-config
#       Overwrite the currently active sshd configuration for
#       AuthorizedKeysCommand and AuthorizedKeysCommandUser directives.
#
#   --install-from=FILEPATH
#       Install using a local file instead of downloading from GitHub.
#
#   --install-te-from=FILEPATH
#       Use local SELinux type enforcement file instead of downloading from GitHub
#
#   --install-version=VERSION
#       Install a specific version from GitHub instead of "latest".
#
#   --help
#       Display this help message.
# ==============================================================================
#

if [[ "$SHUNIT_RUNNING" != "1" ]]; then
    # Exit if any command fails, unless running tests
    set -e
fi

# Setting global variables

# OPKSSH_AUTH_CMD_USER
# Default: opksshuser
# Description: The system user responsible for executing the AuthorizedKeysCommand
AUTH_CMD_USER="${OPKSSH_INSTALL_AUTH_CMD_USER:-opksshuser}"

# OPKSSH_AUTH_CMD_GROUP
# Default: opksshuser
# Description: Group ownership for installed files and directories
AUTH_CMD_GROUP="${OPKSSH_INSTALL_AUTH_CMD_GROUP:-opksshuser}"

# OPKSSH_SUDOERS_PATH
# Default: /etc/sudoers.d/opkssh
# Description: Path to the sudoers file for opkssh
SUDOERS_PATH="${OPKSSH_INSTALL_SUDOERS_PATH:-/etc/sudoers.d/opkssh}"

# OPKSSH_HOME_POLICY
# Default: true
# Description: Whether to use the home directory policy feature
HOME_POLICY="${OPKSSH_INSTALL_HOME_POLICY:-true}"

# OPKSSH_INSTALL_SELINUX_ENABLE_PROXY
# Default: false
# Description: Whether to enable http_cache_port_t port type in SELinux module
SELINUX_ENABLE_PROXY="${OPKSSH_INSTALL_SELINUX_ENABLE_PROXY:-false}"

# OPKSSH_INSTALL_SELINUX_ENABLE_SQUID
# Default: false
# Description: Whether to enable squid_port_t port type in SELinux module
SELINUX_ENABLE_SQUID="${OPKSSH_INSTALL_SELINUX_ENABLE_SQUID:-false}"

# OPKSSH_RESTART_SSH
# Default: true
# Description: Whether to restart SSH after installation
RESTART_SSH="${OPKSSH_INSTALL_RESTART_SSH:-true}"

# OPKSSH_OVERWRITE_ACTIVE_CONFIG
# Default: false
# Description: Overwrite any existing active opkssh config
OVERWRITE_ACTIVE_CONFIG="${OPKSSH_INSTALL_OVERWRITE_ACTIVE_CONFIG:-false}"

# OPKSSH_LOCAL_INSTALL_FILE
# Default: (empty)
# Description: Path to local install file, used instead of downloading from GitHub
LOCAL_INSTALL_FILE="${OPKSSH_INSTALL_LOCAL_INSTALL_FILE:-}"

# OPKSSH_LOCAL_TE_FILE
# Default: (empty)
# Descriptiopn: path to local Type Enforcement file to install on SELinux enabled systems
LOCAL_TE_FILE="${OPKSSH_LOCAL_TE_FILE:-}"

# OPKSSH_INSTALL_VERSION
# Default: latest
# Description: Which version of opkssh to install from GitHub
INSTALL_VERSION="${OPKSSH_INSTALL_VERSION:-latest}"

# OPKSSH_INSTALL_DIR
# Default: /usr/local/bin
# Description: Where to install the opkssh binary
INSTALL_DIR="${OPKSSH_INSTALL_DIR:-/usr/local/bin}"

# OPKSSH_BINARY_NAME
# Default: opkssh
# Description: Name of the installed binary
BINARY_NAME="${OPKSSH_INSTALL_BINARY_NAME:-opkssh}"

# OPKSSH_GITHUB_REPO
# Default: openpubkey/opkssh
# Description: GitHub repository to download the opkssh binary from
GITHUB_REPO="${OPKSSH_INSTALL_GITHUB_REPO:-openpubkey/opkssh}"

# Global variables used by several functions
OS_TYPE=""
CPU_ARCH=""

# file_exists
# check is file exists, helpers that wrap real commands so it can be
# overridden in tests
#
# Arguments:
#   $1 - Path to file
#
# Returns:
#  0 if the file exists, otherwise
file_exists() { [[ -f "$1" ]]; }

# dir_exists
# check is directory exists, helpers that wrap real commands so it can be
# overridden in tests
#
# Arguments:
#   $1 - Path to directory
#
# Returns:
#  0 if the directory exists, otherwise
dir_exists() { [[ -d "$1" ]]; }

# check_bash_version
# Checks if a bash version is >= 3.2
#
# Arguments:
#   $1 - Major version
#   $2 - Minor version
#   $3 - Patch lever (optional, not used)
#   $4 - Build version (optional, not used)
#   $5 - Version string (optional, not used)
#   $6 - Vendor (optional, not used)
#   $7 - Operating system (optional, not used)
#
# Returns:
#   0 if version >= 3.2, 1 otherwise
#
# Example:
#   check_bash_version "${BASH_VERSINFO[@]}"
check_bash_version() {
    local major=$1
    local minor=$2

    if ((major > 3)); then
        echo "Bash version: $major.$minor"
        return 0
    elif ((major == 3 && minor >= 2)); then
        echo "Bash version: $major.$minor"
        return 0
    else
        echo "Error: Unsupported Bash version: $major.$minor" >&2
        return 1
    fi
}

# determine_linux_type
# Determine the linux type the script is executed in
#
# Outputs:
#   Writes the current Linux type detected
#
# Returns:
#   0 if successful, 1 if it's an unsupported OS
determine_linux_type() {
    local os_type
    if file_exists "/etc/redhat-release" ; then
        os_type="redhat"
    elif file_exists "/etc/debian_version" ; then
        os_type="debian"
    elif file_exists "/etc/arch-release"; then
        os_type="arch"
    elif file_exists "/etc/os-release" && \
        grep -q '^ID_LIKE=.*suse' /etc/os-release; then
        os_type="suse"
    else
        echo "Unsupported OS type."
        return 1
    fi
    echo "$os_type"
}

# check_cpu_architecture
# Checks the CPU architecture the script is running on
#
# Outputs:
#   Writes the CPU architechture the script is runnin on
#
# Returns:
#   0 if running on supported architectur, 1 otherwise
check_cpu_architecture() {
    local cpu_arch
    cpu_arch="$(uname -m)"
    case "$cpu_arch" in
        x86_64)
            cpu_arch="amd64"
            ;;
        aarch64)
            cpu_arch="arm64"
            ;;
        amd64 | arm64)
            # Supported architectures, no changes needed
            ;;
        *)
            echo "Error: Unsupported CPU architecture: $cpu_arch." >&2
            return 1
            ;;
    esac
    echo "$cpu_arch"
}

# running_as_root
# Checks if the script executes as root
#
# Arguments:
#   $1 - UID of user to check
#
# Returns:
#   0 if running as root, 1 otherwise
running_as_root() {
    userid="$1"
    if [[ "$userid" -ne 0 ]]; then
        echo "Error: This script must be run as root." >&2
        echo "sudo $0" >&2
        return 1
    fi
}

# display_help_message
# Prints script help message to stdout
#
# Returns:
#   0 on success
display_help_message() {
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  --no-home-policy            Disables configuration that allows opkssh see policy files in user's home directory"
    echo "                              (/home/<username>/auth_id). Greatly simplifies install, try this if you are having install failures."
    echo "  --no-sshd-restart           Do not restart SSH after installation"
    echo "  --overwrite-config          Overwrite the currently active sshd configuration for AuthorizedKeysCommand and AuthorizedKeysCommandUser"
    echo "                              directives. This may be necessary if the script cannot create a configuration with higher priority in /etc/ssh/sshd_config.d/."
    echo "  --install-from=FILEPATH     Install using a local file"
    echo "  --install-te-from=FILEPATH  Install SELinux Type Enforcement using a local file"
    echo "  --install-version=VER       Install a specific version from GitHub"
    echo "  --selinux-enable-squid      Enables the Squid proxy ports in opkssh SELinux module"
    echo "  --selinux-enable-proxy      Enables the HTTP Cache ports in opkssh SELinux module"
    echo "  --help                      Display this help message"
}

# ensure_command
# Checks whether a given command is available on the system.
#
# Arguments:
#   $1 - Name of the command to check (e.g. "curl", "git", "netstat").
#   $2 - Name of the package the command is delivered in (e.g. "curl", "git", "net-tools-deprecated" (optional, defauls to $1)
#   $3 - OS Type the script is running on, output from function determine_linux_type (optional, default so OS_TYPE)
#
# Outputs:
#   Writes an error message to stderr if the command is missing and how to install the command on supported OS types.
#
# Returns:
#   0 if the command is found, 1 otherwise.
#
# Example:
#   ensure_command "wget" || exit 1
#   ensure_command "netstat" "net-tools-deprecated" || exit
ensure_command() {
    local cmd="$1"
    local package="${2:-$cmd}"
    local os_type="${3:-$OS_TYPE}"
    if ! command -v "$cmd" >/dev/null 2>&1; then
        echo "Error: $cmd is not installed. Please install it first." >&2
        if [[ "$os_type" == "debian" ]]; then
            echo "sudo apt install $package" >&2
        elif [[ "$os_type" == "redhat" ]]; then
            # dnf might not be available on older versions
            if command -v dnf >/dev/null 2>&1; then
                echo "sudo dnf install $package" >&2
            else
                echo "sudo yum install $package" >&2
            fi
        elif [[ "$os_type" == "arch" ]]; then
            echo "sudo pacman -S $package" >&2
        elif [[ "$os_type" == "suse" ]]; then
            echo "sudo zypper install $package" >&2
        else
            echo "Unsupported OS type." >&2
        fi
        return 1
    fi
}

# ensure_openssh_server
# Ensures that openSSH-Server is installed and configuration targets exists
#
# Arguments:
#   $1 - OS Type the script is running on, output from function determine_linux_type
#
# Outputs:
#   Writes error if openSSH isn't installed with package manager
#   Writes error if it could verify target configuration files for opkssh
#
# Returns:
#   0 if openSSH is installed with package manager and configuration files exists, 1 otherwise.
ensure_openssh_server() {
    local os_type="$1"
    case "$os_type" in
        redhat)
            if ! rpm -q openssh-server &>/dev/null; then
                echo "OpenSSH server is NOT installed." >&2
                echo "To install it, run: sudo dnf install openssh-server" >&2
                return 1
            fi
            ;;
        debian)
            if ! dpkg -l | grep -q '^ii.*openssh-server'; then
                echo "OpenSSH server is NOT installed." >&2
                echo "To install it, run: sudo apt install openssh-server" >&2
                return 1
            fi
            ;;
        arch)
            if ! pacman -Q openssh &>/dev/null; then
                echo "OpenSSH server is NOT installed." >&2
                echo "To install it, run: sudo pacman -S openssh" >&2
                return 1
            fi
            ;;
        suse)
            if ! rpm -q openssh-server &>/dev/null; then
                echo "OpenSSH server is NOT installed." >&2
                echo "To install it, run: sudo zypper install openssh-server" >&2
                return 1
            fi
            ;;
    esac
    # Ensure OpenSSH server configuration targets exists
    if ! file_exists /etc/ssh/sshd_config && ! dir_exists /etc/ssh/sshd_config.d; then
        echo "Neither /etc/ssh/sshd_config nor /etc/ssh/sshd_config.d exists." >&2
        return 1
    fi
}

# ensure_opkssh_user_and_group
# Checks if the group and user used bu AuthorizedKeysCommand exists if not creates it
#
# Arguments:
#   $1 - AuthorizedKeysCommand User
#   $2 - AuthorizedKeysCommand Group
#
# Outputs:
#   Writes to stdout if group created and if user is created
#
# Returns:
#   0 on success
ensure_opkssh_user_and_group() {
    local auth_cmd_user="$1"
    local auth_cmd_group="$2"
    # Checks if the group used by the AuthorizedKeysCommand exists if not creates it
    if ! getent group "$auth_cmd_group" >/dev/null; then
        groupadd --system "$auth_cmd_group"
        echo "Created group: $auth_cmd_group"
    fi
    # If the AuthorizedKeysCommand user does not exist, create it and add it to the group
    if ! getent passwd "$auth_cmd_user" >/dev/null; then
        useradd -r -M -s /sbin/nologin -g "$auth_cmd_group" "$auth_cmd_user"
        echo "Created user: $auth_cmd_user with group: $auth_cmd_group"
    else
        # If the AuthorizedKeysCommand user exist, ensure it is added to the group
        usermod -aG "$auth_cmd_group" "$auth_cmd_user"
        echo "Added $auth_cmd_user to group: $auth_cmd_group"
    fi
}

# check_opkssh_version
# Checks if an earlier version that is not supported by this script is beeing installed
# If so, exit with error code and installation instructions
#
# Outputs:
#   Nothing is version is supported else outputs install instructions to stderr
#
# Returns:
#  0 on success
#  1 if INSTALL_VERSION isn't supported
check_opkssh_version() {
    local min_version="v0.10.0"

    [[ "$INSTALL_VERSION" == "latest" ]] && return 0

    if [[ ! "$(printf '%s\n' "$min_version" "$INSTALL_VERSION" | sed 's/^v//' | sort -V | head -n1)" = "${min_version#v}" ]]; then
        echo "Installing opkssh $INSTALL_VERSION with this script isn't supported" >&2
        echo "Use the following command to install $INSTALL_VERSION:" >&2
        echo "wget -qO- https://raw.githubusercontent.com/$GITHUB_REPO/refs/tags/$INSTALL_VERSION/scripts/install-linux.sh | sudo bash -s -- --install-version=$INSTALL_VERSION" >&2
        return 1
    fi
}

# parse_args
# Parses CLI arguments and sets configuration flags.
#
# Arguments:
#   $@ - Command-line arguments
#
# Outputs:
#   Sets global variables: HOME_POLICY, RESTART_SSH, OVERWRITE_ACTIVE_CONFIG,LOCAL_INSTALL_FILE, INSTALL_VERSION.
#
# Returns:
#   0 on success, 1 if help is in arguments
parse_args() {
    for arg in "$@"; do
        if [[ "$arg" == "--no-home-policy" ]]; then
            HOME_POLICY=false
        elif [[ "$arg" == "--selinux-enable-squid" ]]; then
            SELINUX_ENABLE_SQUID=true
        elif [[ "$arg" == "--selinux-enable-proxy" ]]; then
            SELINUX_ENABLE_PROXY=true
        elif [[ "$arg" == "--help" ]]; then
            display_help_message
            return 1
        elif [[ "$arg" == "--no-sshd-restart" ]]; then
            RESTART_SSH=false
        elif [[ "$arg" == "--overwrite-config" ]]; then
            OVERWRITE_ACTIVE_CONFIG=true
        elif [[ "$arg" == --install-from=* ]]; then
            LOCAL_INSTALL_FILE="${arg#*=}"
        elif [[ "$arg" == --install-te-from=* ]]; then
            LOCAL_TE_FILE="${arg#*=}"
        elif [[ "$arg" == --install-version=* ]]; then
            INSTALL_VERSION="${arg#*=}"
        fi
    done
}

# install_opkssh_binary
# Installs opkssh binary either from local file or downloads from repository
#
# Outputs:
#   Writes to stdout if installing from local file or repository or the URL from wher it's downloaded
#   Writes to stderr if install path doesn't exist
#
# Returns:
#   0 if installation is succeeded, 1 otherwise
install_opkssh_binary() {
    # Check if we should install from a local file
    if [[ -n "$LOCAL_INSTALL_FILE" ]]; then
        echo "LOCAL_INSTALL_FILE is set, installing from local file: $LOCAL_INSTALL_FILE"
        BINARY_PATH=$LOCAL_INSTALL_FILE
        if [[ ! -f "$BINARY_PATH" ]]; then
            echo "Error: Specified binary path does not exist." >&2
            return 1
        fi
        echo "Using binary from specified path: $BINARY_PATH"
    else
        if [[ "$INSTALL_VERSION" == "latest" ]]; then
            BINARY_URL="https://github.com/$GITHUB_REPO/releases/latest/download/opkssh-linux-$CPU_ARCH"
        else
            BINARY_URL="https://github.com/$GITHUB_REPO/releases/download/$INSTALL_VERSION/opkssh-linux-$CPU_ARCH"
        fi

        # Download the binary
        echo "Downloading version $INSTALL_VERSION of $BINARY_NAME from $BINARY_URL..."
        wget -q --show-progress -O "$BINARY_NAME" "$BINARY_URL"

        BINARY_PATH="$BINARY_NAME"
    fi

    # Move to installation directory
    mv "$BINARY_PATH" "$INSTALL_DIR/$BINARY_NAME"

    # Make the binary executable, correct permissions/ownership
    chmod +x "$INSTALL_DIR/$BINARY_NAME"
    chown root:"${AUTH_CMD_GROUP}" "$INSTALL_DIR/$BINARY_NAME"
    chmod 755 "$INSTALL_DIR/$BINARY_NAME"

    if command -v "$INSTALL_DIR"/"$BINARY_NAME" &>/dev/null; then
        echo "Installed $BINARY_NAME to $INSTALL_DIR/$BINARY_NAME"
    else
        echo "Installation failed." >&2
        return 1
    fi
}

# check_selinux
#   Checks if SELinux is enabled and if so, ensures the context is set correctly
#
# Outputs:
#   Progress of SELinux context installation/configuration or message that SELinux is disabled
#
# Returns:
#   0 if SELinux is disabled or if context is correctly
check_selinux() {
    local te_tmp mod_tmp pp_tmp version
    te_tmp="/tmp/opkssh.te"
    mod_tmp="/tmp/opkssh.mod" # SELinux requires that modules have the same file name as the module name
    pp_tmp="/tmp/opkssh.pp"

    if command -v getenforce >/dev/null 2>&1; then
        if [[ "$(getenforce)" != "Disabled" ]]; then
            echo "SELinux detected. Configuring SELinux for opkssh"
            echo "  Restoring context for $INSTALL_DIR/$BINARY_NAME..."
            restorecon "$INSTALL_DIR/$BINARY_NAME"

            if [[ -n "$LOCAL_TE_FILE" ]]; then
                echo "  Using local TE-file"
                cp "$LOCAL_TE_FILE" "$te_tmp"
            else
                if [[ "$INSTALL_VERSION" == "latest" ]]; then
                    version=$(wget --server-response --max-redirect=0 -O /dev/null "https://github.com/${GITHUB_REPO}/releases/latest" 2>&1 | sed -n -E 's/^  Location: .*\/tag\/(v[0-9.]+).*/\1/p')
                else
                    version="$INSTALL_VERSION"
                fi
                echo "  Downloading TE-file"
                wget -q -O "$te_tmp" "https://raw.githubusercontent.com/${GITHUB_REPO}/refs/tags/${version}/opkssh.te"
            fi

            echo "  Compiling SELinux module..."
            checkmodule -M -m -o "$mod_tmp" "$te_tmp"

            echo "  Packaging module..."
            semodule_package -o "$pp_tmp" -m "$mod_tmp"

            echo "  Installing module..."
            semodule -i "$pp_tmp"

            rm -f "$te_tmp" "$mod_tmp" "$pp_tmp"
            if [[ "$HOME_POLICY" == true ]]; then
                echo "  Configure SELinux module to permits home policy"
                setsebool -P opkssh_enable_home on
            fi
            if [[ "$SELINUX_ENABLE_SQUID" == true ]]; then
                echo "  Configure SELinux module to permits Squid"
                setsebool -P opkssh_enable_squid on
            fi
            if [[ "$SELINUX_ENABLE_PROXY" == true ]]; then
                echo "  Configure SELinux module to permits Proxy"
                setsebool -P opkssh_enable_proxy on
            fi
            echo "SELinux module installed successfully!"
        fi
    else
        echo "SELinux is disabled"
    fi
}

# configure_opkssh
# Creates/checks the opskssh configuration
#
# Arguments:
#   $1 - Path to etc directory (Optional, default /etc)
#
# Outputs:
#   Writes to stdout the configration progress
#
# Returns:
#   0
# shellcheck disable=SC2120
configure_opkssh() {
    local etc_path="${1:-/etc}"
    # Define the default OpenID Providers
    local provider_google="https://accounts.google.com 206584157355-7cbe4s640tvm7naoludob4ut1emii7sf.apps.googleusercontent.com 24h"
    local provider_microsoft="https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0 096ce0a3-5e72-4da8-9c86-12924b294a01 24h"
    local provider_gitlab="https://gitlab.com 8d8b7024572c7fd501f64374dec6bba37096783dfcd792b3988104be08cb6923 24h"
    local provider_hello="https://issuer.hello.coop app_xejobTKEsDNSRd5vofKB2iay_2rN 24h"

    echo "Configuring opkssh:"

    if [[ ! -e "$etc_path/opk" ]]; then
        mkdir -p "$etc_path/opk"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk"
        chmod 750 "$etc_path/opk"
    fi

    if [[ ! -e "$etc_path/opk/policy.d" ]]; then
        mkdir -p "$etc_path/opk/policy.d"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk/policy.d"
        chmod 750 "$etc_path/opk/policy.d"
    fi

    if [[ ! -e "$etc_path/opk/auth_id" ]]; then
        touch "$etc_path/opk/auth_id"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk/auth_id"
        chmod 640 "$etc_path/opk/auth_id"
    fi

    if [[ ! -e "$etc_path/opk/config.yml" ]]; then
        touch "$etc_path/opk/config.yml"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk/config.yml"
        chmod 640 "$etc_path/opk/config.yml"
    fi

    if [[ ! -e "$etc_path/opk/providers" ]]; then
        touch "$etc_path/opk/providers"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk/providers"
        chmod 640 "$etc_path/opk/providers"
    fi

    if [[ -s "$etc_path/opk/providers" ]]; then
        echo "  The providers policy file (/etc/opk/providers) is not empty. Keeping existing values"
    else
        {
            echo "$provider_google"
            echo "$provider_microsoft"
            echo "$provider_gitlab"
            echo "$provider_hello"
        } >> "$etc_path/opk/providers"
    fi
}

# configure_openssh_server
# Configure openSSH-server to use opkssh using AuthorizedKeysCommand
#
# Arguments:
#   $1 - Path to ssh root configuration directory (Optional, default /etc/ssh)
#
# Output:
#   Writes to stdout the progress of configuration
#
# Returns:
#   0 if succeeded, otherwise 1
# shellcheck disable=SC2120
configure_openssh_server() {
    local ssh_root="${1:-/etc/ssh}"
    local sshd_config="$ssh_root/sshd_config"
    local sshd_config_d="$ssh_root/sshd_config.d"
    local auth_key_cmd="AuthorizedKeysCommand ${INSTALL_DIR}/${BINARY_NAME} verify %u %k %t"
    local auth_key_user="AuthorizedKeysCommandUser ${AUTH_CMD_USER}"
    local opk_config_suffix="opk-ssh.conf"
    local new_prefix=""
    local active_config=""

    if [[ ! -f "$sshd_config" ]] || \
        (grep -Fxq 'Include /etc/ssh/sshd_config.d/*.conf' "$sshd_config" &&
            { ! grep -Eq '^AuthorizedKeysCommand|^AuthorizedKeysCommandUser' "$sshd_config" ||
                ! grep -Eq '^AuthorizedKeysCommand|^AuthorizedKeysCommandUser' "$sshd_config_d"/*.conf 2>/dev/null; }); then
        # Configuration should be put in /etc/ssh/sshd_config.d director
        # Find active configuration file with the directives we're interested in (sorted numerically)
        active_config=$(find "$sshd_config_d"/*.conf -exec grep -l '^AuthorizedKeysCommand\|^AuthorizedKeysCommandUser' {} \; 2>/dev/null | sort -V | head -n 1)

        if [[ "$active_config" == *"$opk_config_suffix" ]] || [[ "$OVERWRITE_ACTIVE_CONFIG" == true ]]; then
            # Overwrite the configuration, either from a previous run of this script or because user request it for the currently active config
            sed -i '/^AuthorizedKeysCommand /s/^/#/' "$active_config"
            sed -i '/^AuthorizedKeysCommandUser /s/^/#/' "$active_config"
            echo "$auth_key_cmd" >> "$active_config"
            echo "$auth_key_user" >> "$active_config"
        elif [[ "$(basename "$active_config")" =~ ^0+[^0-9]+ ]]; then
            # The active config starts with all zeros and is therefore the one with the
            # highest priority. We cannot add a new file with even higher priority.
            echo "  Cannot create configuration with higher priority. Remove $active_config or rerun the script with the --overwrite-config flag to overwrite"
            return 1
        else
            if [[ -z "$active_config" ]]; then
                # No active configuration found, let's set a default prefix
                new_prefix=60
            else
                # Create a new config file with higher priority
                prefix=$(basename "$active_config" | grep -o '^[0-9]*')
                new_prefix=$((prefix - 1))
            fi
            new_config="${sshd_config_d}/${new_prefix}-$opk_config_suffix"
            echo "$auth_key_cmd" > "$new_config"
            echo "$auth_key_user" >> "$new_config"
        fi
    else
        # The directives in 'sshd_config' are active
        sed -i '/^AuthorizedKeysCommand /s/^/#/' "$sshd_config"
        sed -i '/^AuthorizedKeysCommandUser /s/^/#/' "$sshd_config"
        echo "$auth_key_cmd" >> "$sshd_config"
        echo "$auth_key_user" >> "$sshd_config"
    fi
}

# restart_openssh_server
# Checks if RESTART_SSH is true and restarts the openSSH server daemon if that is the case
#
# Outputs:
#   Writes to stdout the status if the daemon is restarted
#   Writes to stdout is set to false and skipping daemon restart
#
# Returns:
#   0 if successful, 1 if it's an unsupported OS_TYPE
restart_openssh_server() {
    if [[ "$RESTART_SSH" == true ]]; then
        if [[ "$OS_TYPE" == "debian" ]]; then
            systemctl restart ssh
        elif [[ "$OS_TYPE" == "redhat" ]] || [[ "$OS_TYPE" == "arch" ]] || [[ "$OS_TYPE" == "suse" ]]; then
            systemctl restart sshd
        else
            echo "  Unsupported OS type."
            return 1
        fi
    else
        echo "  RESTART_SSH is not true, skipping SSH restart."
    fi
}

# configure_sudo
# Configures sudo for opkssh if HOME_POLICY is set to true
#
# Outputs:
#   Writes to stdout the progress of sudo configuration if HOME_POLICY=true
#   Writes to stdout that sudo is not configured if HOME_POLICY=false
#
# Returns:
#   0
configure_sudo() {
    if [[ "$HOME_POLICY" == true ]]; then
        if [[ ! -f "$SUDOERS_PATH" ]]; then
            echo "  Creating sudoers file at $SUDOERS_PATH..."
            touch "$SUDOERS_PATH"
            chmod 440 "$SUDOERS_PATH"
        fi
        SUDOERS_RULE_READ_HOME="$AUTH_CMD_USER ALL=(ALL) NOPASSWD: ${INSTALL_DIR}/${BINARY_NAME} readhome *"
        if ! grep -qxF "$SUDOERS_RULE_READ_HOME" "$SUDOERS_PATH"; then
            echo "  Adding sudoers rule for $AUTH_CMD_USER..."
            echo "# This allows opkssh to call opkssh readhome <username> to read the user's policy file in /home/<username>/auth_id" >> "$SUDOERS_PATH"
            echo "$SUDOERS_RULE_READ_HOME" >> "$SUDOERS_PATH"
        fi
    else
        echo "  Skipping sudoers configuration as it is only needed for home policy (HOME_POLICY is set to false)"
    fi
}



# log_opkssh_installation
# Log the installation details to /var/log/opkssh.log to help with debugging
#
# Arguments:
#   $1 - Path to opkssh log file (Optional, default /var/log/opkssh.log)
#
# Output:
#   Writes to stdout that installation is successful
#   Writes installation debug information to /var/log/opkssh.log
#
# Returns:
#   0
# shellcheck disable=SC2120
log_opkssh_installation() {
    local log_file="${1:-/var/log/opkssh.log}"
    touch "$log_file"
    chown root:"${AUTH_CMD_GROUP}" "$log_file"
    chmod 660 "$log_file"

    VERSION_INSTALLED=$("$INSTALL_DIR"/"$BINARY_NAME" --version)
    INSTALLED_ON=$(date)
    # Log the installation details to /var/log/opkssh.log to help with debugging
    echo "Successfully installed opkssh (INSTALLED_ON: $INSTALLED_ON, VERSION_INSTALLED: $VERSION_INSTALLED, INSTALL_VERSION: $INSTALL_VERSION, LOCAL_INSTALL_FILE: $LOCAL_INSTALL_FILE, HOME_POLICY: $HOME_POLICY, RESTART_SSH: $RESTART_SSH)" >> "$log_file"

    echo "Installation successful! Run '$BINARY_NAME' to use it."
}

# main
# Running main function only if executed, not sourced
#
# Arguments:
#   "$@"
#
# Returns:
#   0 if opkssh installs successfully, 1 if installation failed
main() {
    parse_args "$@" || return 0
    check_bash_version "${BASH_VERSINFO[@]}" || return 1
    check_opkssh_version || return 1
    running_as_root "$EUID" || return 1
    OS_TYPE=$(determine_linux_type) || return 1
    CPU_ARCH=$(check_cpu_architecture) || return 1
    ensure_command "wget" || return 1
    if [[ "$HOME_POLICY" == true ]]; then
        ensure_command "sudo" || return 1
    fi
    ensure_opkssh_user_and_group "$AUTH_CMD_USER" "$AUTH_CMD_GROUP" || return 1
    ensure_openssh_server "$OS_TYPE" || return 1
    install_opkssh_binary || return 1
    check_selinux
    configure_opkssh
    configure_openssh_server || return 1
    restart_openssh_server || return 1
    if [[ "$HOME_POLICY" == true ]]; then
        configure_sudo
    fi
    log_opkssh_installation
}

# Don't run main during testing (SH unit tests source this script)
if [[ -z "$SHUNIT_RUNNING" ]]; then
    main "$@"
    exit $?
fi
