#!/bin/bash

# Wazuh installer
# Copyright (C) 2015, Wazuh Inc.
#
# This program is a free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License (version 2) as published by the FSF - Free Software
# Foundation.

adminpem="/etc/wazuh-indexer/certs/admin.pem"
adminkey="/etc/wazuh-indexer/certs/admin-key.pem"
readonly wazuh_major="5"
readonly wazuh_version="5.1.0"
readonly wazuh_install_vesion="0.1"
bucket="packages.wazuh.com"
staging_url_stage="alpha0"
base_path="$(dirname "$(readlink -f "$0")")"
readonly base_path
config_file="${base_path}/config.yml"
readonly tar_file_name="wazuh-install-files.tar"
tar_file="${base_path}/${tar_file_name}"
artifact_urls_file_name="artifact_urls.yaml"
readonly artifact_urls_bucket_folder="artifact-urls"
readonly download_packages_directory="wazuh-install-packages"
readonly dashboard_cert_path="/etc/wazuh-dashboard/certs"
readonly manager_cert_path="/var/wazuh-manager/etc/certs"
readonly indexer_cert_path="/etc/wazuh-indexer/certs"
readonly logfile="/var/log/wazuh-install.log"
debug=">> ${logfile} 2>&1"
readonly yum_lockfile="/var/run/yum.pid"
readonly apt_lockfile="/var/lib/dpkg/lock"
readonly base_dest_folder="wazuh-offline"
http_port=443
wazuh_aio_ports=( 9200 9300 1514 1515 1516 55000 "${http_port}")
readonly wazuh_indexer_ports=( 9200 9300 )
readonly wazuh_manager_ports=( 1514 1515 1516 55000 )
wazuh_dashboard_port="${http_port}"
wia_yum_dependencies=( systemd grep tar coreutils sed procps-ng gawk curl )
readonly wia_apt_dependencies=( systemd grep tar coreutils sed procps gawk curl )
readonly wazuh_yum_dependencies=()
readonly wazuh_apt_dependencies=( apt-transport-https gnupg )
readonly indexer_yum_dependencies=( coreutils )
readonly indexer_apt_dependencies=( debconf adduser procps )
readonly dashboard_yum_dependencies=( libcap )
readonly dashboard_apt_dependencies=( debhelper tar curl libcap2-bin )
wia_offline_dependencies=( curl tar gnupg openssl )
wia_dependencies_installed=()
assistant_yum_dependencies=( "${wia_yum_dependencies[@]}" )
assistant_apt_dependencies=( "${wia_apt_dependencies[@]}" )

trap installCommon_cleanExit SIGINT
export JAVA_HOME="/usr/share/wazuh-indexer/jdk/"
# ------------ installMain.sh ------------ 
function getHelp() {

    echo -e ""
    echo -e "NAME"
    echo -e "        $(basename "$0") - Install and configure Wazuh central components: Wazuh manager, Wazuh indexer, and Wazuh dashboard."
    echo -e ""
    echo -e "SYNOPSIS"
    echo -e "        $(basename "$0") [OPTIONS] -a | -s | -wi <indexer-node-name> | -wd <dashboard-node-name> | -wm <manager-node-name>"
    echo -e ""
    echo -e "DESCRIPTION"
    echo -e "        -a,  --all-in-one"
    echo -e "                Install and configure Wazuh manager, Wazuh indexer, Wazuh dashboard."
    echo -e ""
    echo -e "        -d [pre-release|local],  --development"
    echo -e "                Use development repositories. By default it uses the pre-release package repository. If local is specified, it will use a local artifact_urls.yml file located in the same path as the wazuh-install-5.1.0.sh."
    echo -e ""
    echo -e "        -dw,  --download-wazuh <deb|rpm>"
    echo -e "                Download all the packages necessary for offline installation. Type of packages to download for offline installation (rpm, deb)"
    echo -e ""
    echo -e "        -da,  --download-arch <amd64|arm64|x86_64|aarch64>"
    echo -e "                Define the architecture of the packages to download for offline installation."
    echo -e ""
    echo -e "        -g,  --generate-config-files"
    echo -e "                Generate wazuh-install-files.tar file containing the files that will be needed for installation from config.yml. In distributed deployments you will need to copy this file to all hosts."
    echo -e ""
    echo -e "        -h,  --help"
    echo -e "                Display this help and exit."
    echo -e ""
    echo -e "        -id,  --install-dependencies"
    echo -e "                Installs automatically the necessary dependencies for the installation."
    echo -e ""
    echo -e "        -o,  --overwrite"
    echo -e "                Overwrites previously installed components. This will erase all the existing configuration and data."
    echo -e ""
    echo -e "        -of,  --offline-installation"
    echo -e "                Perform an offline installation. This option must be used with -a, -wm, -s, -wi, or -wd."
    echo -e ""
    echo -e "        -s,  --start-cluster"
    echo -e "                Initialize Wazuh indexer cluster security settings."
    echo -e ""
    echo -e "        -u,  --uninstall"
    echo -e "                Uninstalls all Wazuh components. This will erase all the existing configuration and data."
    echo -e ""
    echo -e "        -v,  --verbose"
    echo -e "                Shows the complete installation output."
    echo -e ""
    echo -e "        -V,  --version"
    echo -e "                Shows the version of the script and Wazuh packages."
    echo -e ""
    echo -e "        -wd,  --wazuh-dashboard <dashboard-node-name>"
    echo -e "                Install and configure Wazuh dashboard, used for distributed deployments."
    echo -e ""
    echo -e "        -wi,  --wazuh-indexer <indexer-node-name>"
    echo -e "                Install and configure Wazuh indexer, used for distributed deployments."
    echo -e ""
    echo -e "        -wm,  --wazuh-manager <manager-node-name>"
    echo -e "                Install and configure Wazuh manager, used for distributed deployments."
    exit 1

}
function main() {
    umask 177

    if [ -z "${1}" ]; then
        getHelp
    fi

    while [ -n "${1}" ]
    do
        case "${1}" in
            "-a"|"--all-in-one")
                AIO=1
                shift 1
                ;;
            "-d"|"--development")
                development=1
                if [ -z "${2}" ] || [[ "${2}" == -* ]]; then
                    devrepo="pre-release"
                    bucket="packages-staging.xdrsiem.wazuh.info"
                    shift 1
                elif [ "${2}" = "pre-release" ] || [ "${2}" = "local" ]; then
                    devrepo="${2}"
                    bucket="packages-staging.xdrsiem.wazuh.info"
                    shift 2
                else
                    common_logger -e "Error: Invalid value '${2}' after -d|--development. Accepted values are 'pre-release' or 'local'. If no value is specified, 'pre-release' is used by default."
                    getHelp
                    exit 1
                fi
                ;;
            "-g"|"--generate-config-files")
                configurations=1
                shift 1
                ;;
            "-h"|"--help")
                getHelp
                ;;
            "-id"|"--install-dependencies")
                install_dependencies=1
                shift 1
                ;;
            "-o"|"--overwrite")
                overwrite=1
                shift 1
                ;;
            "-of"|"--offline-installation")
                offline_install=1
                shift 1
                ;;
            "-s"|"--start-cluster")
                start_indexer_cluster=1
                shift 1
                ;;
            "-u"|"--uninstall")
                uninstall=1
                shift 1
                ;;
            "-v"|"--verbose")
                debugEnabled=1
                debug="2>&1 | tee -a ${logfile}"
                shift 1
                ;;
            "-V"|"--version")
                showVersion=1
                shift 1
                ;;
            "-wd"|"--wazuh-dashboard")
                if [ -z "${2}" ]; then
                    common_logger -e "Error on arguments. Probably missing <node-name> after -wd|---wazuh-dashboard"
                    getHelp
                    exit 1
                fi
                dashboard=1
                dashname="${2}"
                shift 2
                ;;
            "-wi"|"--wazuh-indexer")
                if [ -z "${2}" ]; then
                    common_logger -e "Arguments contain errors. Probably missing <node-name> after -wi|--wazuh-indexer."
                    getHelp
                    exit 1
                fi
                indexer=1
                indxname="${2}"
                shift 2
                ;;
            "-wm"|"--wazuh-manager")
                if [ -z "${2}" ]; then
                    common_logger -e "Error on arguments. Probably missing <node-name> after -wm|--wazuh-manager"
                    getHelp
                    exit 1
                fi
                wazuh=1
                winame="${2}"
                shift 2
                ;;
            "-dw"|"--download-wazuh")
                if [ "${2}" != "deb" ] && [ "${2}" != "rpm" ]; then
                    common_logger -e "Error on arguments. Probably missing <deb|rpm> after -dw|--download-wazuh"
                    getHelp
                    exit 1
                fi
                download=1
                package_type="${2}"
                shift 2
                ;;
            "-da"|"--download-arch")
                if [ "${2}" != "amd64" ] && [ "${2}" != "x86_64" ] && [ "${2}" != "arm64" ] && [ "${2}" != "aarch64" ]; then
                    common_logger -e "Error on arguments. Probably missing <amd64|x86_64|arm64|aarch64> after -da|--download-arch"
                    getHelp
                    exit 1
                fi
                download_arch=1
                arch="${2}"
                shift 2
                ;;
            *)
                echo "Unknow option: ${1}"
                getHelp
        esac
    done

    cat /dev/null > "${logfile}"

    if [ -z "${download}" ] && [ -z "${showVersion}" ]; then
        common_checkRoot
    fi

    if [ -n "${showVersion}" ]; then
        common_logger "Wazuh version: ${wazuh_version}"
        common_logger "Wazuh installation assistant version: ${wazuh_install_vesion}"
        exit 0
    fi

    common_logger "Starting Wazuh installation assistant. Wazuh version: ${wazuh_version}"
    common_logger "Verbose logging redirected to ${logfile}"

# -------------- Uninstall case  ------------------------------------

    common_checkSystem
    common_checkInstalled
    checks_arguments
    check_dist

    if [ -z "${uninstall}" ] && [ -z "${offline_install}" ]; then
        installCommon_scanDependencies
        installCommon_installCheckDependencies "assistant"
        installCommon_installCheckDependencies "wazuh"
        installCommon_determinePorts
    elif [ -n "${offline_install}" ]; then
        offline_checkPrerequisites "wia_offline_dependencies" "${wia_offline_dependencies[@]}"
    fi

    if [ -n "${uninstall}" ]; then
        installCommon_rollBack
        exit 0
    fi

    checks_arch

    common_logger "Verifying that your system meets the recommended minimum hardware requirements."
    checks_health


# -------------- Preliminary checks and Prerequisites --------------------------------

    if [ -z "${configurations}" ] && [ -z "${AIO}" ] && [ -z "${download}" ]; then
        checks_previousCertificate
    fi

    if [ -n "${AIO}" ]; then
        if [ -z "${offline_install}" ]; then
            rm -f "${tar_file}"
        fi
        checks_ports "${wazuh_aio_ports[@]}"
        if [ -n "${offline_install}" ]; then
            offline_checkPrerequisites "AIO"
        fi
    fi

    if [ -n "${indexer}" ]; then
        checks_ports "${wazuh_indexer_ports[@]}"
        if [ -n "${offline_install}" ]; then
            offline_checkPrerequisites "indexer"
        fi
    fi

    if [ -n "${wazuh}" ]; then
        checks_ports "${wazuh_manager_ports[@]}"
        if [ -n "${offline_install}" ]; then
            offline_checkPrerequisites "wazuh"
        fi
    fi

    if [ -n "${dashboard}" ]; then
        checks_ports "${wazuh_dashboard_port}"
        if [ -n "${offline_install}" ]; then
            offline_checkPrerequisites "dashboard"
        fi
    fi


# --------------  Wazuh repo  ----------------------

    # Offline installation case: extract the compressed files
    if [ -n "${offline_install}" ]; then
        offline_checkPreinstallation
        offline_extractFiles
    elif [ -n "${AIO}" ] || [ -n "${indexer}" ] || [ -n "${dashboard}" ] || [ -n "${wazuh}" ]; then
        check_curlVersion
        if [ -n "${development}" ] && [ "${devrepo}" = "local" ]; then
            checks_localArtifactURLs_exists
        else
            installCommon_downloadArtifactURLs
        fi
        checks_ArtifactURLs_format
        checks_ArtifactURLs_component_present
    fi

# -------------- Configuration creation case  -----------------------

    # Creation certificate case: Only AIO and -g option can create certificates.
    if [ -z "${offline_install}" ] && { [ -n "${configurations}" ] || [ -n "${AIO}" ]; }; then
        common_logger "--- Configuration files ---"
        installCommon_createInstallFiles
    fi

    if [ -z "${configurations}" ] && [ -z "${download}" ]; then
        installCommon_extractConfig
        config_file="/tmp/wazuh-install-files/config.yml"
        cert_readConfig
    fi

    # Distributed architecture: node names must be different
    if [[ -z "${AIO}" && -z "${download}" && ( -n "${indexer}"  || -n "${dashboard}" || -n "${wazuh}" ) ]]; then
        checks_names
    fi

    if [ -n "${configurations}" ]; then
        installCommon_removeWIADependencies
    fi

# -------------- Wazuh indexer case -------------------------------

    if [ -n "${indexer}" ]; then
        common_logger "--- Wazuh indexer ---"
        installCommon_downloadComponent "wazuh_indexer"
        indexer_install
        indexer_configure
        installCommon_startService "wazuh-indexer"
        installCommon_removeDownloadPackagesDirectory
        installCommon_removeWIADependencies
    fi

# -------------- Start Wazuh indexer cluster case  ------------------

    if [ -n "${start_indexer_cluster}" ]; then
        indexer_startCluster
        installCommon_removeWIADependencies
    fi

# -------------- Wazuh dashboard case  ------------------------------

    if [ -n "${dashboard}" ]; then
        common_logger "--- Wazuh dashboard ----"
        installCommon_downloadComponent "wazuh_dashboard"
        dashboard_install
        dashboard_configure
        installCommon_startService "wazuh-dashboard"
        dashboard_displaySummary
        installCommon_removeDownloadPackagesDirectory
        installCommon_removeWIADependencies

    fi

# -------------- Wazuh manager case  ---------------------------------------

    if [ -n "${wazuh}" ]; then
        common_logger "--- Wazuh manager ---"
        installCommon_downloadComponent "wazuh_manager"
        manager_install
        manager_configure
        if [ -n "${manager_node_types[*]}" ]; then
            manager_startCluster
        fi
        installCommon_startService "wazuh-manager"
        installCommon_removeWIADependencies
    fi

# -------------- AIO case  ------------------------------------------

    if [ -n "${AIO}" ]; then

        common_logger "--- Wazuh indexer ---"
        installCommon_downloadComponent "wazuh_indexer"
        indexer_install
        indexer_configure
        installCommon_startService "wazuh-indexer"
        indexer_startCluster
        common_logger "--- Wazuh manager ---"
        installCommon_downloadComponent "wazuh_manager"
        manager_install
        manager_configure
        installCommon_startService "wazuh-manager"
        common_logger "--- Wazuh dashboard ---"
        installCommon_downloadComponent "wazuh_dashboard"
        dashboard_install
        dashboard_configure
        installCommon_startService "wazuh-dashboard"
        dashboard_displaySummary
        installCommon_removeDownloadPackagesDirectory
        installCommon_removeWIADependencies

    fi

# -------------- Offline case  ------------------------------------------

    if [ -n "${download}" ]; then
        common_logger "--- Download Packages ---"
        if [ -n "${development}" ] && [ "${devrepo}" = "local" ]; then
            checks_localArtifactURLs_exists
        else
            installCommon_downloadArtifactURLs
        fi
        checks_ArtifactURLs_format
        offline_checkArtifactURLs_component_present
        offline_download
    fi


# -------------------------------------------------------------------

    if [ -n "${AIO}" ] || [ -n "${indexer}" ] || [ -n "${dashboard}" ] || [ -n "${wazuh}" ]; then
        eval "rm -rf /tmp/wazuh-install-files ${debug}"
        common_logger "Installation finished."
    elif [ -n "${start_indexer_cluster}" ]; then
        common_logger "Wazuh indexer cluster started."
    fi

}

# ------------ installCommon.sh ------------ 
function installCommon_cleanExit() {

    rollback_conf=""

    if [ -n "$spin_pid" ]; then
        eval "kill -9 $spin_pid ${debug}"
    fi

    until [[ "${rollback_conf}" =~ ^[N|Y|n|y]$ ]]; do
        echo -ne "\nDo you want to remove the ongoing installation?[Y/N]"
        read -r rollback_conf
    done
    if [[ "${rollback_conf}" =~ [N|n] ]]; then
        exit 1
    else
        common_checkInstalled
        installCommon_rollBack
        exit 1
    fi

}
function installCommon_aptInstall() {

    package="${1}"
    version="${2}"
    attempt=0

    # Determine the installer package
    if [[ "${package}" == *.deb ]]; then
        installer="${package}"
    elif [ -n "${version}" ]; then
        installer="${package}${sep}${version}"
    else
        installer="${package}"
    fi

    # Override with offline package if needed
    if [ -n "${offline_install}" ] && [[ "${package}" != *.deb ]]; then
        package_name=$(ls ${offline_packages_path} | grep ${package})
        installer="${offline_packages_path}/${package_name}"
    fi

    # Build the installation command
    command="DEBIAN_FRONTEND=noninteractive apt-get install ${installer} -y -q"

    common_checkAptLock

    if [ "${attempt}" -ne "${max_attempts}" ]; then
        apt_output=$(eval "${command} 2>&1")
        install_result="${PIPESTATUS[0]}"
        eval "echo \${apt_output} ${debug}"
    fi

}
function installCommon_createCertificates() {

    common_logger -d "Creating Wazuh certificates."
    if [ -n "${AIO}" ]; then
        #Create download directory if it doesn't exist
        download_dir="${base_path}/${download_packages_directory}"
        if [ ! -d "${download_dir}" ]; then
            eval "mkdir -p ${download_dir} ${debug}"
            if [ ! -d "${download_dir}" ]; then
                common_logger -e "Failed to create download directory: ${download_dir}"
                exit 1
            fi
        fi

        artifact_file="${base_path}/${artifact_urls_file_name}"
        artifact_key="wazuh_config_yml"
        config_filename=$(basename "${config_file}")
        component_url=$(grep "^${artifact_key}:" "$artifact_file" | cut -d' ' -f2- | tr -d '"' | xargs)
        component_filepath="${download_dir}/${config_filename}"

        common_logger -d "Downloading configuration file for the AIO installation."
        common_curl -sSLo '${component_filepath}' '${component_url}' --max-time 300 --retry 5 --retry-delay 5 --fail "${debug}"
        mv "${download_dir}/${config_filename}" "${config_file}"

        if [ ! -f "${config_file}" ]; then
            common_logger -e "Failed to download te configuration file from ${component_url}."
            installCommon_rollBack
            exit 1
        fi

        common_logger -d "Configuration file downloaded successfully"

        eval "sed -i 's|ip: \"<indexer-node-ip>\"|ip: \"127.0.0.1\"|' '${config_file}'" ${debug}
        eval "sed -i 's|ip: \"<wazuh-manager-ip>\"|ip: \"127.0.0.1\"|' '${config_file}'" ${debug}
        eval "sed -i 's|ip: \"<dashboard-node-ip>\"|ip: \"127.0.0.1\"|' '${config_file}'" ${debug}
    fi

    cert_readConfig

    if [ -d /tmp/wazuh-certificates/ ]; then
        eval "rm -rf /tmp/wazuh-certificates/ ${debug}"
    fi
    eval "mkdir /tmp/wazuh-certificates/ ${debug}"

    cert_tmp_path="/tmp/wazuh-certificates/"

    cert_generateRootCAcertificate
    cert_generateAdmincertificate
    cert_generateIndexercertificates
    cert_generateManagercertificates
    cert_generateDashboardcertificates
    cert_cleanFiles
    eval "chmod 400 /tmp/wazuh-certificates/* ${debug}"
    eval "mv /tmp/wazuh-certificates/* /tmp/wazuh-install-files ${debug}"
    eval "rm -rf /tmp/wazuh-certificates/ ${debug}"

}
function installCommon_createClusterKey() {

    openssl rand -hex 16 >> "/tmp/wazuh-install-files/clusterkey"

}
function installCommon_createInstallFiles() {

    if [ -d /tmp/wazuh-install-files ]; then
        eval "rm -rf /tmp/wazuh-install-files ${debug}"
    fi

    if eval "mkdir /tmp/wazuh-install-files ${debug}"; then
        common_logger "Generating configuration files."

        if [ -n "${configurations}" ]; then
            cert_checkOpenSSL
        fi
        installCommon_createCertificates
        if [ -n "${manager_node_types[*]}" ]; then
            installCommon_createClusterKey
        fi
        eval "cp '${config_file}' '/tmp/wazuh-install-files/config.yml' ${debug}"
        eval "chown root:root /tmp/wazuh-install-files/* ${debug}"
        eval "tar -zcf '${tar_file}' -C '/tmp/' wazuh-install-files/ ${debug}"
        eval "rm -rf '/tmp/wazuh-install-files' ${debug}"
	    eval "rm -rf ${config_file} ${debug}"
        common_logger "Created ${tar_file_name}. It contains the Wazuh cluster key and the certificates necessary for installation."
    else
        common_logger -e "Unable to create /tmp/wazuh-install-files"
        exit 1
    fi
}
function installCommon_determinePorts {

    used_ports=()

    if [ -n "${AIO}" ]; then
        used_ports+=( "${wazuh_aio_ports[@]}" )
    elif [ -n "${wazuh}" ]; then
        used_ports+=( "${wazuh_manager_ports[@]}" )
    elif [ -n "${indexer}" ]; then
        used_ports+=( "${wazuh_indexer_ports[@]}" )
    elif [ -n "${dashboard}" ]; then
        used_ports+=( "${wazuh_dashboard_port[@]}" )
    fi
}
function installCommon_downloadArtifactURLs() {

    common_logger -d "Downloading artifact URLs file."
    if [ -n "${devrepo}" ] && [ "${devrepo}" == "pre-release" ]; then
        artifact_urls_file_name="artifact_urls_${wazuh_version}-${staging_url_stage}.yaml"
        artifact_url="https://${bucket}/pre-release/${wazuh_major}.x/${artifact_urls_bucket_folder}/${artifact_urls_file_name}"
    else
        artifact_urls_file_name="artifact_urls_${wazuh_version}.yaml"
        artifact_url="https://${bucket}/production/${wazuh_major}.x/${artifact_urls_bucket_folder}/${artifact_urls_file_name}"
    fi
    eval "common_curl -sSo ${base_path}/${artifact_urls_file_name} ${artifact_url} --max-time 300 --retry 5 --retry-delay 5 --fail ${debug}"

    curl_exit_code="${PIPESTATUS[0]}"
    if [ "${curl_exit_code}" -ne 0 ]; then
        common_logger -e "Failed to download artifact URLs from ${artifact_url}. Exit code: ${curl_exit_code}"
        exit 1
    fi

    if [ ! -f "${artifact_urls_file_name}" ]; then
        common_logger -e "Failed to download artifact URLs from ${artifact_url}."
        exit 1
    fi

}
function installCommon_downloadComponent() {
    if [ -n "${offline_install}" ]; then
        common_logger -d "Skipping download in offline installation mode. Package already available."
        return 0
    fi

    if [ "$#" -ne 1 ]; then
        common_logger -e "installCommon_downloadComponent must be called with one argument (component name)."
        exit 1
    fi

    component="${1}"
    artifact_file="${base_path}/${artifact_urls_file_name}"
    download_dir="${base_path}/${download_packages_directory}"

    # Create download directory if it doesn't exist
    if [ ! -d "${download_dir}" ]; then
        eval "mkdir -p ${download_dir} ${debug}"
        if [ ! -d "${download_dir}" ]; then
            common_logger -e "Failed to create download directory: ${download_dir}"
            exit 1
        fi
    fi

    # Determine package type based on system
    if [ "${sys_type}" == "yum" ]; then
        pkg_type="rpm"
        # Determine architecture suffix for artifact keys
        if [ "${architecture}" == "x86_64" ]; then
            arch_suffix="x86_64"
        elif [ "${architecture}" == "aarch64" ]; then
            arch_suffix="aarch64"
        fi
    elif [ "${sys_type}" == "apt-get" ]; then
        pkg_type="deb"
        # Determine architecture suffix for artifact keys
        if [ "${architecture}" == "x86_64" ]; then
            arch_suffix="amd64"
        elif [ "${architecture}" == "aarch64" ]; then
            arch_suffix="arm64"
        fi
    fi

    # Build the artifact key
    artifact_key="${component}_${arch_suffix}_${pkg_type}"

    # Get the URL from the artifact file
    component_url=$(grep "^${artifact_key}:" "$artifact_file" | cut -d' ' -f2- | tr -d '"' | xargs)

    # Extract filename from URL (remove query parameters after ?)
    component_filename=$(basename "${component_url%%\?*}")
    component_filepath="${download_dir}/${component_filename}"

    common_logger "Downloading ${component} package: ${component_filename}"

    # Download the component to the download directory
    common_curl -sSLo '${component_filepath}' '${component_url}' --max-time 600 --retry 5 --retry-delay 5 --fail ${debug}
    curl_exit_code="${PIPESTATUS[0]}"

    # Check if download was successful
    if [ "${curl_exit_code}" -ne 0 ]; then
        common_logger -e "Failed to download ${component} from ${component_url}. Curl exit code: ${curl_exit_code}"
        # Remove incomplete file if it exists
        if [ -f "${component_filepath}" ]; then
            common_logger -d "Removing incomplete download: ${component_filepath}"
            eval "rm -f ${component_filepath} ${debug}"
        fi
        exit 1
    fi

    if [ ! -f "${component_filepath}" ]; then
        common_logger -e "Failed to download ${component} from ${component_url}."
        exit 1
    fi

    common_logger "${component} package downloaded successfully: ${component_filepath}"

}
function installCommon_extractConfig() {

    common_logger -d "Extracting Wazuh configuration."
    if ! tar -tf "${tar_file}" | grep -q wazuh-install-files/config.yml; then
        common_logger -e "There is no config.yml file in ${tar_file}."
        exit 1
    fi
    eval "tar -xf ${tar_file} -C /tmp wazuh-install-files/config.yml ${debug}"

}
function installCommon_getConfig() {

    if [ "$#" -ne 2 ]; then
        common_logger -e "installCommon_getConfig should be called with two arguments"
        exit 1
    fi

    config_name="config_file_$(eval "echo ${1} | sed 's|/|_|g;s|.yml||'")"
    if [ -z "$(eval "echo \${${config_name}}")" ]; then
        common_logger -e "Unable to find configuration file ${1}. Exiting."
        installCommon_rollBack
        exit 1
    fi
    eval "echo \"\${${config_name}}\"" > "${2}"
}
function installCommon_installCheckDependencies() {

    if [ "${1}" == "assistant" ]; then
        installing_assistant_deps=1
        wia_dependencies_installed=()
        installCommon_installList "${assistant_deps_to_install[@]}"
    else
        installing_assistant_deps=0
        installCommon_installList "${wazuh_deps_to_install[@]}"
    fi
}
function installCommon_installList(){

    dependencies=("$@")
    if [ "${#dependencies[@]}" -gt 0 ]; then

        if [ "${sys_type}" == "apt-get" ]; then
            eval "apt-get update -q ${debug}"
        fi

        common_logger "--- Dependencies ----"
        for dep in "${dependencies[@]}"; do
            common_logger "Installing $dep."
            if [ "${sys_type}" = "apt-get" ]; then
                installCommon_aptInstall "${dep}"
            else
                installCommon_yumInstall "${dep}"
            fi
            if [ "${install_result}" != 0 ]; then
                common_logger -e "Cannot install dependency: ${dep}."
                installCommon_rollBack
                exit 1
            fi
            if [ "${installing_assistant_deps}" == 1 ]; then
                wia_dependencies_installed+=("${dep}")
            fi
        done
    fi

}
function installCommon_removeCentOSrepositories() {

    eval "rm -f ${centos_repo} ${debug}"
    eval "rm -f ${centos_key} ${debug}"
    eval "yum clean all ${debug}"
    centos_repos_configured=0
    common_logger -d "CentOS repositories and key deleted."

}
function installCommon_rollBack() {

    common_logger "--- Removing existing Wazuh installation ---"

    if [[ -n "${wazuh_installed}" && ( -n "${wazuh}" || -n "${AIO}" || -n "${uninstall}" ) ]];then
        common_logger "Removing Wazuh manager."
        if [ "${sys_type}" == "yum" ]; then
            common_checkYumLock
            if [ "${attempt}" -ne "${max_attempts}" ]; then
                eval "yum remove wazuh-manager -y ${debug}"
                rpm -q wazuh-manager --quiet && wazuh_failed_uninstall=1
            fi
        elif [ "${sys_type}" == "apt-get" ]; then
            common_checkAptLock
            eval "apt-get remove --purge wazuh-manager -y ${debug}"
            wazuh_failed_uninstall=$(apt list --installed 2>/dev/null | grep wazuh-manager)
        fi

        if [ -n "${wazuh_failed_uninstall}" ]; then
            common_logger -w "The Wazuh manager package could not be removed."
        else
            common_logger "Wazuh manager removed."
        fi

    fi

    if [[ ( -n "${wazuh_remaining_files}"  || -n "${wazuh_installed}" ) && ( -n "${wazuh}" || -n "${AIO}" || -n "${uninstall}" ) ]]; then
        eval "rm -rf /var/wazuh-manager/ ${debug}"
    fi

    if [[ -n "${indexer_installed}" && ( -n "${indexer}" || -n "${AIO}" || -n "${uninstall}" ) ]]; then
        common_logger "Removing Wazuh indexer."
        if [ "${sys_type}" == "yum" ]; then
            common_checkYumLock
            if [ "${attempt}" -ne "${max_attempts}" ]; then
                eval "yum remove wazuh-indexer -y ${debug}"
                rpm -q wazuh-indexer --quiet && indexer_failed_uninstall=1
            fi
        elif [ "${sys_type}" == "apt-get" ]; then
            common_checkAptLock
            eval "apt-get remove --purge wazuh-indexer -y ${debug}"
            indexer_failed_uninstall=$(apt list --installed 2>/dev/null | grep wazuh-indexer)
        fi

        if [ -n "${indexer_failed_uninstall}" ]; then
            common_logger -w "The Wazuh indexer package could not be removed."
        else
            common_logger "Wazuh indexer removed."
        fi
    fi

    if [[ ( -n "${indexer_remaining_files}" || -n "${indexer_installed}" ) && ( -n "${indexer}" || -n "${AIO}" || -n "${uninstall}" ) ]]; then
        eval "rm -rf /var/lib/wazuh-indexer/ ${debug}"
        eval "rm -rf /usr/share/wazuh-indexer/ ${debug}"
        eval "rm -rf /etc/wazuh-indexer/ ${debug}"
    fi

    if [[ -n "${dashboard_installed}" && ( -n "${dashboard}" || -n "${AIO}" || -n "${uninstall}" ) ]]; then
        common_logger "Removing Wazuh dashboard."
        if [ "${sys_type}" == "yum" ]; then
            common_checkYumLock
            if [ "${attempt}" -ne "${max_attempts}" ]; then
                eval "yum remove wazuh-dashboard -y ${debug}"
                rpm -q wazuh-dashboard --quiet && dashboard_failed_uninstall=1
            fi
        elif [ "${sys_type}" == "apt-get" ]; then
            common_checkAptLock
            eval "apt-get remove --purge wazuh-dashboard -y ${debug}"
            dashboard_failed_uninstall=$(apt list --installed 2>/dev/null | grep wazuh-dashboard)
        fi

        if [ -n "${dashboard_failed_uninstall}" ]; then
            common_logger -w "The Wazuh dashboard package could not be removed."
        else
            common_logger "Wazuh dashboard removed."
        fi
    fi

    if [[ ( -n "${dashboard_remaining_files}" || -n "${dashboard_installed}" ) && ( -n "${dashboard}" || -n "${AIO}" || -n "${uninstall}" ) ]]; then
        eval "rm -rf /var/lib/wazuh-dashboard/ ${debug}"
        eval "rm -rf /usr/share/wazuh-dashboard/ ${debug}"
        eval "rm -rf /etc/wazuh-dashboard/ ${debug}"
        eval "rm -rf /run/wazuh-dashboard/ ${debug}"
    fi

    elements_to_remove=(    "/var/log/wazuh-indexer/"
                            "/etc/systemd/system/opensearch.service.wants/"
                            "/securityadmin_demo.sh"
                            "/etc/systemd/system/multi-user.target.wants/wazuh-manager.service"
                            "/etc/systemd/system/multi-user.target.wants/opensearch.service"
                            "/etc/systemd/system/multi-user.target.wants/wazuh-dashboard.service"
                            "/etc/systemd/system/wazuh-dashboard.service"
                            "/lib/firewalld/services/dashboard.xml"
                            "/lib/firewalld/services/opensearch.xml" )

    eval "rm -rf ${elements_to_remove[*]} ${debug}"

    installCommon_removeWIADependencies

    eval "systemctl daemon-reload ${debug}"

    if [ -z "${uninstall}" ]; then
        if [ -n "${rollback_conf}" ] || [ -n "${overwrite}" ]; then
            common_logger "Installation cleaned."
        else
            common_logger "Installation cleaned. Check the ${logfile} file to learn more about the issue."
        fi
    fi

}
function installCommon_scanDependencies() {

    wazuh_deps=()
    if [ -n "${AIO}" ]; then
        if [ "${sys_type}" == "yum" ]; then
            wazuh_deps+=( "${indexer_yum_dependencies[@]}" "${wazuh_yum_dependencies[@]}" "${dashboard_yum_dependencies[@]}" )
        else
            wazuh_deps+=( "${indexer_apt_dependencies[@]}" "${wazuh_apt_dependencies[@]}" "${dashboard_apt_dependencies[@]}" )
        fi
    elif [ -n "${indexer}" ]; then
        if [ "${sys_type}" == "yum" ]; then
            wazuh_deps+=( "${indexer_yum_dependencies[@]}" )
        else
            wazuh_deps+=( "${indexer_apt_dependencies[@]}" )
        fi
    elif [ -n "${wazuh}" ]; then
        if [ "${sys_type}" == "yum" ]; then
            wazuh_deps+=( "${wazuh_yum_dependencies[@]}" )
        else
            wazuh_deps+=( "${wazuh_apt_dependencies[@]}" )
        fi
    elif [ -n "${dashboard}" ]; then
        if [ "${sys_type}" == "yum" ]; then
            wazuh_deps+=( "${dashboard_yum_dependencies[@]}" )
        else
            wazuh_deps+=( "${dashboard_apt_dependencies[@]}" )
        fi
    fi

    all_deps=( "${wazuh_deps[@]}" )
    if [ "${sys_type}" == "apt-get" ]; then
        assistant_deps+=( "${assistant_apt_dependencies[@]}" )
        command='! apt list --installed 2>/dev/null | grep -q -E ^"${dep}"\/'
    else
        assistant_deps+=( "${assistant_yum_dependencies[@]}" )
        command='! rpm -q ${dep} --quiet'
    fi

    # Remove openssl dependency if not necessary
    if [ -z "${configurations}" ] && [ -z "${AIO}" ]; then
        assistant_deps=( "${assistant_deps[@]/openssl}" )
    fi

    # Remove lsof dependency if not necessary
    if [ -z "${AIO}" ] && [ -z "${wazuh}" ] && [ -z "${indexer}" ] && [ -z "${dashboard}" ]; then
        assistant_deps=( "${assistant_deps[@]/lsof}" )
    fi

    # Delete duplicates and sort
    all_deps+=( "${assistant_deps[@]}" )
    all_deps=( $(echo "${all_deps[@]}" | tr ' ' '\n' | sort -u) )
    deps_to_install=()

    # Get not installed dependencies of Assistant and Wazuh
    for dep in "${all_deps[@]}"; do
        if eval "${command}"; then
            deps_to_install+=("${dep}")
            if [[ "${assistant_deps[*]}" =~ "${dep}" ]]; then
                assistant_deps_to_install+=("${dep}")
            else
                wazuh_deps_to_install+=("${dep}")
            fi
        fi
    done

    # Format and print the message if the option is not specified
    if [ -z "${install_dependencies}" ] && [ "${#deps_to_install[@]}" -gt 0 ]; then
        printf -v joined_deps_not_installed '%s, ' "${deps_to_install[@]}"
        printf -v joined_assistant_not_installed '%s, ' "${assistant_deps_to_install[@]}"
        joined_deps_not_installed="${joined_deps_not_installed%, }"
        joined_assistant_not_installed="${joined_assistant_not_installed%, }"

        message="To perform the installation, the following package/s must be installed: ${joined_deps_not_installed}."
        if [ "${#assistant_deps_to_install[@]}" -gt 0 ]; then
            message+=" The following package/s will be removed after the installation: ${joined_assistant_not_installed}."
        fi
        message+=" Add the -id|--install-dependencies parameter to install them automatically or install them manually."
        common_logger -w "${message}"
        exit 1
    fi

}
function installCommon_startService() {

    if [ "$#" -ne 1 ]; then
        common_logger -e "installCommon_startService must be called with 1 argument."
        exit 1
    fi

    common_logger "Starting service ${1}."

    if [[ -d /run/systemd/system ]]; then
        eval "systemctl daemon-reload ${debug}"
        eval "systemctl enable ${1}.service ${debug}"
        eval "systemctl start ${1}.service ${debug}"
        if [  "${PIPESTATUS[0]}" != 0  ]; then
            common_logger -e "${1} could not be started."
            if [ -n "$(command -v journalctl)" ]; then
                eval "journalctl -u ${1} >> ${logfile}"
            fi
            installCommon_rollBack
            exit 1
        else
            common_logger "${1} service started."
        fi
    elif ps -p 1 -o comm= | grep "init"; then
        eval "chkconfig ${1} on ${debug}"
        eval "service ${1} start ${debug}"
        eval "/etc/init.d/${1} start ${debug}"
        if [  "${PIPESTATUS[0]}" != 0  ]; then
            common_logger -e "${1} could not be started."
            if [ -n "$(command -v journalctl)" ]; then
                eval "journalctl -u ${1} >> ${logfile}"
            fi
            installCommon_rollBack
            exit 1
        else
            common_logger "${1} service started."
        fi
    elif [ -x "/etc/rc.d/init.d/${1}" ] ; then
        eval "/etc/rc.d/init.d/${1} start ${debug}"
        if [  "${PIPESTATUS[0]}" != 0  ]; then
            common_logger -e "${1} could not be started."
            if [ -n "$(command -v journalctl)" ]; then
                eval "journalctl -u ${1} >> ${logfile}"
            fi
            installCommon_rollBack
            exit 1
        else
            common_logger "${1} service started."
        fi
    else
        common_logger -e "${1} could not start. No service manager found on the system."
        exit 1
    fi

}
function installCommon_restartService() {

    if [ "$#" -ne 1 ]; then
        common_logger -e "installCommon_restartService must be called with 1 argument."
        exit 1
    fi

    common_logger "Restarting service ${1}."

    if [[ -d /run/systemd/system ]]; then
        eval "systemctl restart ${1}.service ${debug}"
        if [  "${PIPESTATUS[0]}" != 0  ]; then
            common_logger -e "${1} could not be restarted."
            if [ -n "$(command -v journalctl)" ]; then
                eval "journalctl -u ${1} >> ${logfile}"
            fi
            installCommon_rollBack
            exit 1
        else
            common_logger "${1} service restarted."
        fi
    elif ps -p 1 -o comm= | grep "init"; then
        eval "chkconfig ${1} on ${debug}"
        eval "service ${1} restart ${debug}"
        eval "/etc/init.d/${1} restart ${debug}"
        if [  "${PIPESTATUS[0]}" != 0  ]; then
            common_logger -e "${1} could not be restarted."
            if [ -n "$(command -v journalctl)" ]; then
                eval "journalctl -u ${1} >> ${logfile}"
            fi
            installCommon_rollBack
            exit 1
        else
            common_logger "${1} service restarted."
        fi
    elif [ -x "/etc/rc.d/init.d/${1}" ] ; then
        eval "/etc/rc.d/init.d/${1} restart ${debug}"
        if [  "${PIPESTATUS[0]}" != 0  ]; then
            common_logger -e "${1} could not be restarted."
            if [ -n "$(command -v journalctl)" ]; then
                eval "journalctl -u ${1} >> ${logfile}"
            fi
            installCommon_rollBack
            exit 1
        else
            common_logger "${1} service restarted."
        fi
    else
        common_logger -e "${1} could not restart. No service manager found on the system."
        exit 1
    fi

}
function installCommon_removeWIADependencies() {

    if [ "${sys_type}" == "yum" ]; then
        installCommon_yumRemoveWIADependencies
    elif [ "${sys_type}" == "apt-get" ]; then
        installCommon_aptRemoveWIADependencies
    fi

}
function installCommon_yumRemoveWIADependencies(){

    if [ "${#wia_dependencies_installed[@]}" -gt 0 ]; then
        common_logger "--- Dependencies ---"
        local wazuh_deps=()

        if [ -n "${AIO}" ]; then
            wazuh_deps=( $(echo "${wazuh_yum_dependencies[@]}" "${indexer_yum_dependencies[@]}" "${dashboard_yum_dependencies[@]}" | tr ' ' '\n' | sort -u) )
        else
            if [ -n "${wazuh}" ]; then
                wazuh_deps+=( "${wazuh_yum_dependencies[@]}" )
            fi
            if [ -n "${indexer}" ]; then
                wazuh_deps+=( "${indexer_yum_dependencies[@]}" )
            fi
            if [ -n "${dashboard}" ]; then
                wazuh_deps+=( "${dashboard_yum_dependencies[@]}" )
            fi

            if [ "${#wazuh_deps[@]}" -gt 0 ]; then
                mapfile -t wazuh_deps < <(printf '%s\n' "${wazuh_deps[@]}" | sort -u)
            fi
        fi

        for dep in "${wia_dependencies_installed[@]}"; do
            if [ "${dep}" != "systemd" ]; then
                if [[ " ${wazuh_deps[*]} " == *" ${dep} "* ]]; then
                    common_logger -d "Skipping removal of ${dep}: it is also a Wazuh component dependency."
                    continue
                fi
                common_logger "Removing $dep."
                yum_output=$(yum remove ${dep} -y 2>&1)
                yum_code="${PIPESTATUS[0]}"

                eval "echo \${yum_output} ${debug}"
                if [  "${yum_code}" != 0  ]; then
                    common_logger -e "Cannot remove dependency: ${dep}."
                    exit 1
                fi
            fi
        done
    fi

}
function installCommon_aptRemoveWIADependencies(){

    if [ "${#wia_dependencies_installed[@]}" -gt 0 ]; then
        common_logger "--- Dependencies ----"
        local wazuh_deps=()

        if [ -n "${AIO}" ]; then
            wazuh_deps=( $(echo "${wazuh_apt_dependencies[@]}" "${indexer_apt_dependencies[@]}" "${dashboard_apt_dependencies[@]}" | tr ' ' '\n' | sort -u) )
        else
            if [ -n "${wazuh}" ]; then
                wazuh_deps+=( "${wazuh_apt_dependencies[@]}" )
            fi
            if [ -n "${indexer}" ]; then
                wazuh_deps+=( "${indexer_apt_dependencies[@]}" )
            fi
            if [ -n "${dashboard}" ]; then
                wazuh_deps+=( "${dashboard_apt_dependencies[@]}" )
            fi

            if [ "${#wazuh_deps[@]}" -gt 0 ]; then
                mapfile -t wazuh_deps < <(printf '%s\n' "${wazuh_deps[@]}" | sort -u)
            fi
        fi

        for dep in "${wia_dependencies_installed[@]}"; do
            if [ "${dep}" != "systemd" ]; then
                if [[ " ${wazuh_deps[*]} " == *" ${dep} "* ]]; then
                    common_logger -d "Skipping removal of ${dep}: it is also a Wazuh component dependency."
                    continue
                fi
                common_logger "Removing $dep."
                apt_output=$(apt-get remove --purge ${dep} -y 2>&1)
                apt_code="${PIPESTATUS[0]}"

                eval "echo \${apt_output} ${debug}"
                if [  "${apt_code}" != 0  ]; then
                    common_logger -e "Cannot remove dependency: ${dep}."
                    exit 1
                fi
            fi
        done
    fi

}
function installCommon_removeDownloadPackagesDirectory() {

    if [ -n "${offline_install}" ]; then
        common_logger -d "Skipping removal of download packages directory in offline installation mode."
        return 0
    fi

    download_dir="${base_path}/${download_packages_directory}"
    if [ -d "${download_dir}" ]; then
        eval "rm -rf ${download_dir} ${debug}"
        common_logger -d "Removed download packages directory: ${download_dir}"
    else
        common_logger -w "Download packages directory does not exist: ${download_dir}"
    fi

}
function installCommon_yumInstall() {

    package="${1}"
    version="${2}"
    install_result=1

    # If package is a file path (contains .rpm), install directly
    if [[ "${package}" == *.rpm ]]; then
        installer="${package}"
        command="rpm -ivh ${installer}"
        common_logger -d "Installing local package: ${installer}"
    elif [ -n "${version}" ]; then
        installer="${package}-${version}"
        # Offline installation case: get package name and install it
        if [ -n "${offline_install}" ]; then
            package_name=$(ls ${offline_packages_path} | grep ${package})
            installer="${offline_packages_path}/${package_name}"
            command="rpm -ivh ${installer}"
            common_logger -d "Installing local package: ${installer}"
        else
            command="yum install ${installer} -y"
        fi
    else
        installer="${package}"
        # Offline installation case: get package name and install it
        if [ -n "${offline_install}" ]; then
            package_name=$(ls ${offline_packages_path} | grep ${package})
            installer="${offline_packages_path}/${package_name}"
            command="rpm -ivh ${installer}"
            common_logger -d "Installing local package: ${installer}"
        else
            command="yum install ${installer} -y"
        fi
    fi
    common_checkYumLock

    if [ "${attempt}" -ne "${max_attempts}" ]; then
        yum_output=$(eval "${command} 2>&1")
        install_result="${PIPESTATUS[0]}"
        eval "echo \${yum_output} ${debug}"
    fi

}
function installCommon_checkAptLock() {

    attempt=0
    seconds=30
    max_attempts=10

    while fuser "${apt_lockfile}" >/dev/null 2>&1 && [ "${attempt}" -lt "${max_attempts}" ]; do
        attempt=$((attempt+1))
        common_logger "Another process is using APT. Waiting for it to release the lock. Next retry in ${seconds} seconds (${attempt}/${max_attempts})"
        sleep "${seconds}"
    done

}

# ------------ wazuh-offline-download.sh ------------ 
function offline_checkArtifactURLs_component_present() {
    common_logger -d "Checking required components are present in ${artifact_urls_file_name} file for download."
    common_logger -d "Architecture: ${arch}"
    common_logger -d "Package type: ${package_type}"
    artifact_file="${base_path}/${artifact_urls_file_name}"

    # Validate supported architectures
    case "${arch}" in
      x86_64|amd64|aarch64|arm64)
        ;;
      *)
        common_logger -e "Unsupported architecture: ${arch}"
        exit 1
        ;;
    esac

    indexer_key="wazuh_indexer_${arch}_${package_type}"
    dashboard_key="wazuh_dashboard_${arch}_${package_type}"
    manager_key="wazuh_manager_${arch}_${package_type}"

    # Check that all required artifacts exist for offline download
    missing_keys=()

    if ! grep -q "^${indexer_key}:" "$artifact_file"; then
        missing_keys+=("${indexer_key}")
    fi

    if ! grep -q "^${dashboard_key}:" "$artifact_file"; then
        missing_keys+=("${dashboard_key}")
    fi

    if ! grep -q "^${manager_key}:" "$artifact_file"; then
        missing_keys+=("${manager_key}")
    fi

    if [ "${#missing_keys[@]}" -gt 0 ]; then
        common_logger -e "Missing required artifact keys in ${artifact_urls_file_name}:"
        for key in "${missing_keys[@]}"; do
            common_logger -e "  - ${key}"
        done
        exit 1
    fi

    common_logger -d "All required artifact keys found for offline download."
}
function offline_download() {

  common_logger "Starting Wazuh packages download."
  common_logger "Downloading Wazuh ${package_type} packages for ${arch}."
  dest_path="${base_dest_folder}/wazuh-packages"

  if [ -d "${dest_path}" ]; then
    eval "rm -f ${dest_path}/* ${debug}" # Clean folder before downloading specific versions
    eval "chmod 700 ${dest_path} ${debug}"
  else
    eval "mkdir -m700 -p ${dest_path} ${debug}" # Create folder if it does not exist
  fi

    # Validate supported architectures
    case "${arch}" in
      x86_64|amd64|aarch64|arm64)
        ;;
      *)
        common_logger -e "Unsupported architecture: ${arch}"
        exit 1
        ;;
    esac

  artifact_file="${base_path}/${artifact_urls_file_name}"

  # Define components to download
  components=("wazuh_indexer" "wazuh_dashboard" "wazuh_manager")

  for component in "${components[@]}"; do
    # Build the artifact key
    artifact_key="${component}_${arch}_${package_type}"

    # Get the URL from the artifact file
    component_url=$(grep "^${artifact_key}:" "$artifact_file" | cut -d' ' -f2- | tr -d '"' | xargs)

    if [ -z "${component_url}" ]; then
      common_logger -e "Could not find URL for ${artifact_key} in ${artifact_urls_file_name}"
      exit 1
    fi

    # Extract filename from URL (remove query parameters after ?)
    component_filename=$(basename "${component_url%%\?*}")
    component_filepath="${dest_path}/${component_filename}"

    common_logger "Downloading ${component} package: ${component_filename}"

    # Download the component to the destination directory
    common_curl -sSLo '${component_filepath}' '${component_url}' --max-time 300 --retry 5 --retry-delay 5 --fail ${debug}

    if [ ! -f "${component_filepath}" ]; then
      common_logger -e "Failed to download ${component} from ${component_url}."
      exit 1
    fi

    common_logger "The ${component} package was downloaded."
  done

  common_logger "The packages are in ${dest_path}"

  eval "chmod 500 ${base_dest_folder} ${debug}"

  common_logger "Creating wazuh-offline.tar.gz with all packages."

  eval "tar -czf ${base_dest_folder}.tar.gz ${base_dest_folder} ${debug}"
  eval "chmod -R 700 ${base_dest_folder} && rm -rf ${base_dest_folder} ${debug}"

  common_logger "You can follow the installation guide here https://documentation.wazuh.com/current/deployment-options/offline-installation.html"

}
# ------------ wazuh-offline-installation.sh ------------ 
function offline_checkPrerequisites(){

    if [ "$1" == "wia_offline_dependencies" ]; then
        dependencies=( "${@:2}" )
        common_logger "Checking dependencies for Wazuh installation assistant."
    else
        common_logger "Checking prerequisites for Offline installation."
        if [ "${sys_type}" == "yum" ]; then
            if [ "$1" == "AIO" ]; then
                dependencies=( $(echo "${indexer_yum_dependencies[@]}" "${dashboard_yum_dependencies[@]}" | tr ' ' '\n' | sort -u) )
            elif [ "$1" == "indexer" ]; then
                dependencies=( "${indexer_yum_dependencies[@]}" )
            elif [ "$1" == "dashboard" ]; then
                dependencies=( "${dashboard_yum_dependencies[@]}" )
            elif [ "$1" == "wazuh" ]; then
                dependencies=( "${wazuh_yum_dependencies[@]}" )
            fi
        elif [ "${sys_type}" == "apt-get" ]; then
            if [ "$1" == "AIO" ]; then
                dependencies=( $(echo "${wazuh_apt_dependencies[@]}" "${indexer_apt_dependencies[@]}" "${dashboard_apt_dependencies[@]}" | tr ' ' '\n' | sort -u) )
            elif [ "$1" == "indexer" ]; then
                dependencies=( "${indexer_apt_dependencies[@]}" )
            elif [ "$1" == "dashboard" ]; then
                dependencies=( "${dashboard_apt_dependencies[@]}" )
            elif [ "$1" == "wazuh" ]; then
                dependencies=( "${wazuh_apt_dependencies[@]}" )
            fi
        fi
    fi

    for dep in "${dependencies[@]}"; do
        if [ "${sys_type}" == "yum" ]; then
            eval "yum list installed 2>/dev/null | grep -q -E ^"${dep}"\\."
        elif [ "${sys_type}" == "apt-get" ]; then
            eval "apt list --installed 2>/dev/null | grep -q -E ^"${dep}"\/"
        fi

        if [ "${PIPESTATUS[0]}" != 0 ]; then
            common_logger -e "${dep} is necessary for the offline installation."
            exit 1
        fi
    done
    if [ "$1" == "wia_offline_dependencies" ]; then
        common_logger -d "Dependencies for Wazuh installation assistant are installed."
    else
        common_logger -d "Prerequisites for Offline installation are installed."
    fi
}
function offline_checkPreinstallation() {

    offline_tarfile="${base_dest_folder}.tar.gz"
    common_logger "Checking ${offline_tarfile} file."
    if [ ! -f "${base_path}/${offline_tarfile}" ]; then
        common_logger -e "The ${offline_tarfile} file was not found in ${base_path}."
        exit 1
    fi
    common_logger -d "${offline_tarfile} was found correctly."

}
function offline_extractFiles() {

    common_logger -d "Extracting files from ${offline_tarfile}"
    if [ ! -d "${base_path}/wazuh-offline/" ]; then
        eval "tar -xzf ${offline_tarfile} ${debug}"

        if [ ! -d "${base_path}/wazuh-offline/" ]; then
            common_logger -e "The ${offline_tarfile} file could not be decompressed."
            exit 1
        fi
    fi

    offline_packages_path="${base_path}/wazuh-offline/wazuh-packages"

    if [ "${sys_type}" == "apt-get" ]; then
        required_files+=("${offline_packages_path}/wazuh-dashboard*.deb" "${offline_packages_path}/wazuh-indexer*.deb" "${offline_packages_path}/wazuh-manager*.deb")
    elif [ "${sys_type}" == "rpm" ]; then
        required_files+=("${offline_packages_path}/wazuh-dashboard*.rpm" "${offline_packages_path}/wazuh-indexer*.rpm" "${offline_packages_path}/wazuh-manager*.rpm")
    fi

    for file in "${required_files[@]}"; do
        if ! compgen -G "${file}" > /dev/null; then
            common_logger -e "Missing necessary offline file: ${file}"
            exit 1
        fi
    done

    common_logger -d "Offline files extracted successfully."
}

# ------------ manager.sh ------------ 
function manager_startCluster() {

    common_logger -d "Starting Wazuh manager cluster."
    for i in "${!manager_node_names[@]}"; do
        if [[ "${manager_node_names[i]}" == "${winame}" ]]; then
            pos="${i}";
        fi
    done

    for i in "${!manager_node_types[@]}"; do
        if [[ "${manager_node_types[i],,}" == "master" ]]; then
            master_address=${manager_node_ips[i]}
        fi
    done

    key=$(tar -axf "${tar_file}" wazuh-install-files/clusterkey -O)
    bind_address="0.0.0.0"
    port="1516"
    hidden="no"
    disabled="no"
    lstart=$(grep -n "<cluster>" /var/wazuh-manager/etc/wazuh-manager.conf | cut -d : -f 1)
    lend=$(grep -n "</cluster>" /var/wazuh-manager/etc/wazuh-manager.conf | cut -d : -f 1)

    sed -i -e "${lstart},${lend}s/<name>.*<\/name>/<name>wazuh_cluster<\/name>/" \
        -e "${lstart},${lend}s/<node_name>.*<\/node_name>/<node_name>${winame}<\/node_name>/" \
        -e "${lstart},${lend}s/<node_type>.*<\/node_type>/<node_type>${manager_node_types[pos],,}<\/node_type>/" \
        -e "${lstart},${lend}s/<key>.*<\/key>/<key>${key}<\/key>/" \
        -e "${lstart},${lend}s/<port>.*<\/port>/<port>${port}<\/port>/" \
        -e "${lstart},${lend}s/<bind_addr>.*<\/bind_addr>/<bind_addr>${bind_address}<\/bind_addr>/" \
        -e "${lstart},${lend}s/<node>.*<\/node>/<node>${master_address}<\/node>/" \
        -e "${lstart},${lend}s/<hidden>.*<\/hidden>/<hidden>${hidden}<\/hidden>/" \
        -e "${lstart},${lend}s/<disabled>.*<\/disabled>/<disabled>${disabled}<\/disabled>/" \
        /var/wazuh-manager/etc/wazuh-manager.conf

}
function manager_configure(){

    common_logger -d "Configuring Wazuh manager."

    for i in "${!indexer_node_ips[@]}"; do
        if [ $i -eq 0 ]; then
            eval "sed -i 's/<host>.*<\/host>/<host>https:\/\/${indexer_node_ips[0]}:9200<\/host>/g' /var/wazuh-manager/etc/wazuh-manager.conf ${debug}"
        else
            sed -i "/<hosts>/a\      <host>https://${indexer_node_ips[$i]}:9200</host>" /var/wazuh-manager/etc/wazuh-manager.conf
        fi
    done

    if [ "${AIO}" ]; then
        winame="${manager_node_names[0]}"
    fi
    eval "sed -i s/manager.pem/${winame}.pem/ /var/wazuh-manager/etc/wazuh-manager.conf ${debug}"
    eval "sed -i s/manager-key.pem/${winame}-key.pem/ /var/wazuh-manager/etc/wazuh-manager.conf ${debug}"
    manager_copyCertificates "${debug}"
    common_logger -d "Setting provisional Wazuh indexer password."
    /var/wazuh-manager/bin/wazuh-manager-keystore -f indexer -k username -v admin
    /var/wazuh-manager/bin/wazuh-manager-keystore -f indexer -k password -v admin
}
function manager_install() {

    common_logger "Starting the Wazuh manager installation."

    if [ -n "${offline_install}" ]; then
        download_dir="${offline_packages_path}"
    else
        download_dir="${base_path}/${download_packages_directory}"
    fi

    # Find the downloaded package file
    if [ "${sys_type}" == "yum" ]; then
        package_file=$(ls "${download_dir}"/wazuh-manager*.rpm 2>/dev/null | head -n 1)
        if [ -z "${package_file}" ]; then
            common_logger -e "Wazuh manager package file not found in ${download_dir}."
            exit 1
        fi
        installCommon_yumInstall "${package_file}"
    elif [ "${sys_type}" == "apt-get" ]; then
        package_file=$(ls "${download_dir}"/wazuh-manager*.deb 2>/dev/null | head -n 1)
        if [ -z "${package_file}" ]; then
            common_logger -e "Wazuh manager package file not found in ${download_dir}."
            exit 1
        fi
        installCommon_aptInstall "${package_file}"
    fi

    common_checkInstalled
    if [  "$install_result" != 0  ] || [ -z "${wazuh_installed}" ]; then
        common_logger -e "Wazuh installation failed."
        installCommon_rollBack
        exit 1
    else
        common_logger "Wazuh manager installation finished."
    fi
}
function manager_copyCertificates() {

    common_logger -d "Copying Manager certificates."

    if [ "${AIO}" ]; then
        winame="${manager_node_names[0]}"
    fi

    if [ -f "${tar_file}" ]; then
        if ! tar -tvf "${tar_file}" | grep -q "${winame}" ; then
            common_logger -e "Tar file does not contain certificate for the node ${winame}."
            installCommon_rollBack
            exit 1
        fi
        eval "mkdir -p ${manager_cert_path} ${debug}"
        eval "tar -xf ${tar_file} -C ${manager_cert_path} wazuh-install-files/${winame}.pem --strip-components 1 ${debug}"
        eval "tar -xf ${tar_file} -C ${manager_cert_path} wazuh-install-files/${winame}-key.pem --strip-components 1 ${debug}"
        eval "tar -xf ${tar_file} -C ${manager_cert_path} wazuh-install-files/root-ca.pem --strip-components 1 ${debug}"
        eval "rm -rf ${manager_cert_path}/wazuh-install-files/ ${debug}"
        eval "chown -R wazuh-manager:wazuh-manager ${manager_cert_path} ${debug}"
        eval "chmod 500 ${manager_cert_path} ${debug}"
        eval "chmod 400 ${manager_cert_path}/* ${debug}"
    else
        common_logger -e "No certificates found. Could not initialize Wazuh manager"
        installCommon_rollBack
        exit 1
    fi

}

# ------------ checks.sh ------------ 
function checks_arch() {

    common_logger -d "Checking system architecture."
    architecture=$(uname -m)

    if [ "${architecture}" == "x86_64" ] || [ "${architecture}" == "aarch64" ]; then
        common_logger -d "System architecture: ${architecture}"
    else
        common_logger -e "Uncompatible system architecture: ${architecture}. Supported any 64-bit system"
        exit 1
    fi
}
function checks_arguments() {

    # -------------- Repository selection ---------------------

    if [ -n "${development}" ]; then
        if [ -z "${AIO}" ] && [ -z "${dashboard}" ] && [ -z "${indexer}" ] && [ -z "${wazuh}" ] && [ -z "${start_indexer_cluster}" ] && [ -z "${download}" ]; then
            common_logger -e "The -d|--development option must be used with -a, -wm, -s, -wi, -wd or -dw."
            exit 1
        fi
    fi

    # -------------- Offline installation ---------------------

    if [ -n "${offline_install}" ]; then
        if [ -z "${AIO}" ] && [ -z "${dashboard}" ] && [ -z "${indexer}" ] && [ -z "${wazuh}" ] && [ -z "${start_indexer_cluster}" ]; then
            common_logger -e "The -of|--offline-installation option must be used with -a, -wm, -s, -wi, or -wd."
            exit 1
        fi
    fi

    if [ -n "${download}" ] && [ -z "${download_arch}" ]; then
        common_logger -e "To download the packages it is necessary to set the architecture in -da|--download-arch <amd64|x86_64|arm64|aarch64>"
        exit 1
    fi

    if [ -n "${download}" ] && [ -n "${download_arch}" ]; then
        if [ "${package_type}" = "deb" ]; then
            if [ "${arch}" != "amd64" ] && [ "${arch}" != "arm64" ]; then
                if [ "${arch}" = "x86_64" ]; then
                    common_logger -e "Architecture ${arch} not valid for package type ${package_type}. Use amd64 instead."
                elif [ "${arch}" = "aarch64" ]; then
                    common_logger -e "Architecture ${arch} not valid for package type ${package_type}. Use arm64 instead."
                else
                    common_logger -e "Architecture ${arch} not valid for package type ${package_type}"
                fi
                exit 1
            fi
        elif [ "${package_type}" = "rpm" ]; then
            if [ "${arch}" != "x86_64" ] && [ "${arch}" != "aarch64" ]; then
                if [ "${arch}" = "amd64" ]; then
                    common_logger -e "Architecture ${arch} not valid for package type ${package_type}. Use x86_64 instead."
                elif [ "${arch}" = "arm64" ]; then
                    common_logger -e "Architecture ${arch} not valid for package type ${package_type}. Use aarch64 instead."
                else
                    common_logger -e "Architecture ${arch} not valid for package type ${package_type}"
                fi
                exit 1
            fi
        fi
    fi

    # -------------- Configurations ---------------------------------

    if [ -f "${tar_file}" ]; then
        if [ -n "${AIO}" ] && [ -z "${offline_install}" ]; then
            rm -f "${tar_file}"
        fi
        if [ -n "${configurations}" ]; then
            common_logger -e "File ${tar_file} already exists. Please remove it if you want to use a new configuration."
            exit 1
        fi
    fi

    if [[ -n "${configurations}" && ( -n "${AIO}" || -n "${indexer}" || -n "${dashboard}" || -n "${wazuh}" || -n "${overwrite}" || -n "${start_indexer_cluster}" || -n "${uninstall}" ) ]]; then
        common_logger -e "The argument -g|--generate-config-files can't be used with -a|--all-in-one, -o|--overwrite, -s|--start-cluster, -u|--uninstall, -wd|--wazuh-dashboard, -wi|--wazuh-indexer, or -wm|--wazuh-manager."
        exit 1
    fi

    # -------------- Overwrite --------------------------------------

    if [ -n "${overwrite}" ] && [ -z "${AIO}" ] && [ -z "${indexer}" ] && [ -z "${dashboard}" ] && [ -z "${wazuh}" ]; then
        common_logger -e "The argument -o|--overwrite must be used in conjunction with -a|--all-in-one, -wd|--wazuh-dashboard, -wi|--wazuh-indexer, or -wm|--wazuh-manager."
        exit 1
    fi

    # -------------- Uninstall --------------------------------------

    if [ -n "${uninstall}" ]; then

        if [ -n "$AIO" ] || [ -n "$indexer" ] || [ -n "$dashboard" ] || [ -n "$wazuh" ]; then
            common_logger -e "It is not possible to uninstall and install in the same operation. If you want to overwrite the components use -o|--overwrite."
            exit 1
        fi

        if [ -z "${wazuh_installed}" ] && [ -z "${wazuh_remaining_files}" ]; then
            common_logger "Wazuh manager not found in the system so it was not uninstalled."
        fi

        if [ -z "${indexer_installed}" ] && [ -z "${indexer_remaining_files}" ]; then
            common_logger "Wazuh indexer not found in the system so it was not uninstalled."
        fi

        if [ -z "${dashboard_installed}" ] && [ -z "${dashboard_remaining_files}" ]; then
            common_logger "Wazuh dashboard not found in the system so it was not uninstalled."
        fi

    fi

    # -------------- All-In-One -------------------------------------

    if [ -n "${AIO}" ]; then

        if [ -n "$indexer" ] || [ -n "$dashboard" ] || [ -n "$wazuh" ]; then
            common_logger -e "Argument -a|--all-in-one is not compatible with -wi|--wazuh-indexer, -wd|--wazuh-dashboard or -wm|--wazuh-manager."
            exit 1
        fi

        if [ -n "${overwrite}" ]; then
            installCommon_rollBack
        fi

        if  [ -z "${overwrite}" ] && { [ -n "${wazuh_installed}" ] || [ -n "${wazuh_remaining_files}" ]; }; then
            common_logger -e "Wazuh manager already installed."
            installedComponent=1
        fi
        if [ -z "${overwrite}" ] && { [ -n "${indexer_installed}" ] || [ -n "${indexer_remaining_files}" ]; };then
            common_logger -e "Wazuh indexer already installed."
            installedComponent=1
        fi
        if [ -z "${overwrite}" ] && { [ -n "${dashboard_installed}" ] || [ -n "${dashboard_remaining_files}" ]; }; then
            common_logger -e "Wazuh dashboard already installed."
            installedComponent=1
        fi
        if [ -n "${installedComponent}" ]; then
            common_logger "If you want to overwrite the current installation, run this script adding the option -o/--overwrite. This will erase all the existing configuration and data."
            exit 1
        fi

    fi

    # -------------- Indexer ----------------------------------

    if [ -n "${indexer}" ]; then

        if [ -n "${indexer_installed}" ] || [ -n "${indexer_remaining_files}" ]; then
            if [ -n "${overwrite}" ]; then
                installCommon_rollBack
            else
                common_logger -e "Wazuh indexer is already installed in this node or some of its files have not been removed. Use option -o|--overwrite to overwrite all components."
                exit 1
            fi
        fi
    fi

    # -------------- Wazuh dashboard --------------------------------

    if [ -n "${dashboard}" ]; then
        if [ -n "${dashboard_installed}" ] || [ -n "${dashboard_remaining_files}" ]; then
            if [ -n "${overwrite}" ]; then
                installCommon_rollBack
            else
                common_logger -e "Wazuh dashboard is already installed in this node or some of its files have not been removed. Use option -o|--overwrite to overwrite all components."
                exit 1
            fi
        fi
    fi

    # -------------- Wazuh ------------------------------------------

    if [ -n "${wazuh}" ]; then
        if [ -n "${wazuh_installed}" ] || [ -n "${wazuh_remaining_files}" ]; then
            if [ -n "${overwrite}" ]; then
                installCommon_rollBack
            else
                common_logger -e "Wazuh manager components are already installed in this node or some of their files have not been removed. Use option -o|--overwrite to overwrite all components."
                exit 1
            fi
        fi
    fi

    # -------------- Cluster start ----------------------------------

    if [[ -n "${start_indexer_cluster}" && ( -n "${AIO}" || -n "${indexer}" || -n "${dashboard}" || -n "${wazuh}" || -n "${overwrite}" || -n "${configurations}" || -n "${uninstall}") ]]; then
        common_logger -e "The argument -s|--start-cluster can't be used with -a|--all-in-one, -g|--generate-config-files,-o|--overwrite , -u|--uninstall, -wi|--wazuh-indexer, -wd|--wazuh-dashboard, -s|--start-cluster, -wm|--wazuh-manager."
        exit 1
    fi

    # -------------- Global -----------------------------------------

    if [ -z "${AIO}" ] && [ -z "${indexer}" ] && [ -z "${dashboard}" ] && [ -z "${wazuh}" ] && [ -z "${start_indexer_cluster}" ] && [ -z "${configurations}" ] && [ -z "${uninstall}" ] && [ -z "${download}" ]; then
        common_logger -e "At least one of these arguments is necessary -a|--all-in-one, -g|--generate-config-files, -wi|--wazuh-indexer, -wd|--wazuh-dashboard, -s|--start-cluster, -wm|--wazuh-manager, -u|--uninstall, -dw|--download-wazuh."
        exit 1
    fi

}
function check_curlVersion() {

    common_logger -d "Checking curl tool version."
    # --retry-connrefused was added in 7.52.0
    curl_version=$(curl -V | head -n 1 | awk '{ print $2 }')
    if [ $(check_versions ${curl_version} 7.52.0) == "0" ]; then
        curl_has_connrefused=0
    fi

}
function check_dist() {
    common_logger -d "Checking system distribution."
    dist_detect
    if  [ "${DIST_NAME}" != "rhel" ] &&
        [ "${DIST_NAME}" != "amzn" ] && [ "${DIST_NAME}" != "ubuntu" ]; then
        notsupported=1
    fi

    if [ "${DIST_NAME}" == "rhel" ] && { [ "${DIST_VER}" -ne "9" ] && [ "${DIST_VER}" -ne "10" ]; }; then
        notsupported=1
    fi

    if [ "${DIST_NAME}" == "amzn" ]; then
        if [ "${DIST_VER}" != "2023" ]; then
            notsupported=1
        else
            checks_specialDepsAL2023
        fi
    fi

    if [ "${DIST_NAME}" == "ubuntu" ]; then
        if [ "${DIST_VER}" == "24" ] || [ "${DIST_VER}" == "26" ]; then
            if [ "${DIST_SUBVER}" != "04" ]; then
                notsupported=1
            fi
        else
            notsupported=1
        fi
    fi

    if [ -n "${notsupported}" ]; then
        common_logger "The supported systems are: Red Hat Enterprise Linux 9, 10; Amazon Linux 2023; Ubuntu 24.04, 26.04."
        common_logger -w "The current system does not match with the list of supported systems. The installation may not work properly."
    fi
    common_logger -d "Detected distribution name: ${DIST_NAME}"
    common_logger -d "Detected distribution version: ${DIST_VER}"

}
function checks_health() {

    checks_specifications

    common_logger -d "CPU cores detected: ${cores}"
    common_logger -d "Free RAM memory detected: ${ram_gb}"

    if [ -n "${indexer}" ]; then
        if [ "${cores}" -lt 4 ] || [ "${ram_gb}" -lt 7300 ]; then
            common_logger -w "Your system does not meet the recommended minimum hardware requirements of 8Gb of RAM and 4 CPU cores. The installation will continue, but it may not work properly."
        fi
    fi

    if [ -n "${dashboard}" ]; then
        if [ "${cores}" -lt 2 ] || [ "${ram_gb}" -lt 3300 ]; then
            common_logger -w "Your system does not meet the recommended minimum hardware requirements of 4Gb of RAM and 2 CPU cores. The installation will continue, but it may not work properly."
        fi
    fi

    if [ -n "${wazuh}" ]; then
        if [ "${cores}" -lt 4 ] || [ "${ram_gb}" -lt 7300 ]; then
            common_logger -w "Your system does not meet the recommended minimum hardware requirements of 8Gb of RAM and 4 CPU cores. The installation will continue, but it may not work properly."
        fi
    fi

    if [ -n "${AIO}" ]; then
        if [ "${cores}" -lt 8 ] || [ "${ram_gb}" -lt 15300 ]; then
            common_logger -w "Your system does not meet the recommended minimum hardware requirements of 16Gb of RAM and 8 CPU cores. The installation will continue, but it may not work properly."
        fi
    fi

}
function checks_names() {

    common_logger -d "Checking node names in the configuration file."
    if [ -n "${indxname}" ] && [ -n "${dashname}" ] && [ "${indxname}" == "${dashname}" ]; then
        common_logger -e "The node names for Wazuh indexer and Wazuh dashboard must be different."
        exit 1
    fi

    if [ -n "${indxname}" ] && [ -n "${winame}" ] && [ "${indxname}" == "${winame}" ]; then
        common_logger -e "The node names for Elastisearch and Wazuh must be different."
        exit 1
    fi

    if [ -n "${winame}" ] && [ -n "${dashname}" ] && [ "${winame}" == "${dashname}" ]; then
        common_logger -e "The node names for Wazuh manager and Wazuh indexer must be different."
        exit 1
    fi

    if [ -n "${winame}" ] && ! echo "${manager_node_names[@]}" | grep -w -q "${winame}"; then
        common_logger -e "The Wazuh manager node name ${winame} does not appear on the configuration file."
        exit 1
    fi

    if [ -n "${indxname}" ] && ! echo "${indexer_node_names[@]}" | grep -w -q "${indxname}"; then
        common_logger -e "The Wazuh indexer node name ${indxname} does not appear on the configuration file."
        exit 1
    fi

    if [ -n "${dashname}" ] && ! echo "${dashboard_node_names[@]}" | grep -w -q "${dashname}"; then
        common_logger -e "The Wazuh dashboard node name ${dashname} does not appear on the configuration file."
        exit 1
    fi

    if [[ "${dashname}" == -* ]] || [[ "${indxname}" == -* ]] || [[ "${winame}" == -* ]]; then
        common_logger -e "Node name cannot start with \"-\""
        exit 1
    fi

}
function checks_previousCertificate() {
    common_logger -d "Checking previous certificate existence."
    if [ ! -f "${tar_file}" ]; then
        common_logger -e "Cannot find ${tar_file}. Run the script with the option -g|--generate-config-files to create it or copy it from another node."
        exit 1
    fi

    if [ -n "${indxname}" ]; then
        if ! tar -tf "${tar_file}" | grep -q -E ^wazuh-install-files/"${indxname}".pem  || ! tar -tf "${tar_file}" | grep -q -E ^wazuh-install-files/"${indxname}"-key.pem; then
            common_logger -e "There is no certificate for the indexer node ${indxname} in ${tar_file}."
            exit 1
        fi
    fi

    if [ -n "${dashname}" ]; then
        if ! tar -tf "${tar_file}" | grep -q -E ^wazuh-install-files/"${dashname}".pem || ! tar -tf "${tar_file}" | grep -q -E ^wazuh-install-files/"${dashname}"-key.pem; then
            common_logger -e "There is no certificate for the Wazuh dashboard node ${dashname} in ${tar_file}."
            exit 1
        fi
    fi

    if [ -n "${winame}" ]; then
        if ! tar -tf "${tar_file}" | grep -q -E ^wazuh-install-files/"${winame}".pem || ! tar -tf "${tar_file}" | grep -q -E ^wazuh-install-files/"${winame}"-key.pem; then
            common_logger -e "There is no certificate for the wazuh manager node ${winame} in ${tar_file}."
            exit 1
        fi
    fi
}
function checks_specialDepsAL2023() {

    # Change curl for curl-minimal
    wia_yum_dependencies=( "${wia_yum_dependencies[@]/curl/curl-minimal}" )
    assistant_yum_dependencies=( "${assistant_yum_dependencies[@]/curl/curl-minimal}" )
    wia_offline_dependencies=( "${wia_offline_dependencies[@]/curl/curl-minimal}" )

    # In containers, coreutils is replaced for coreutils-single
    if [ -f "/.dockerenv" ]; then
        wia_yum_dependencies=( "${wia_yum_dependencies[@]/coreutils/coreutils-single}" )
        assistant_yum_dependencies=( "${assistant_yum_dependencies[@]/coreutils/coreutils-single}" )
    fi
}
function checks_specifications() {

    cores=$(grep -c processor /proc/cpuinfo)
    ram_gb=$(free -m | awk 'FNR == 2 {print $2}')

}
function checks_ports() {

    common_logger -d "Checking ports availability."
    used_port=0
    ports=("$@")

    checks_firewall "${ports[@]}"

    if command -v lsof > /dev/null; then
        port_command="lsof -sTCP:LISTEN  -i:"
    else
        common_logger -w "Cannot find lsof. Port checking will be skipped."
        return 1
    fi

    for i in "${!ports[@]}"; do
        if ${port_command}"${ports[i]}" > /dev/null; then
            used_port=1
            common_logger -e "Port ${ports[i]} is being used by another process. Please, check it before installing Wazuh."
        fi
    done

    if [ "${used_port}" -eq 1 ]; then
        common_logger "The installation can not continue due to port usage by other processes."
        installCommon_rollBack
        exit 1
    fi

}
function check_versions() {

    if test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; then
        echo 0
    else
        echo 1
    fi
}
function checks_localArtifactURLs_exists() {
    common_logger -d "Checking if ${artifact_urls_file_name} exists locally."
    if [ ! -f "${base_path}/${artifact_urls_file_name}" ]; then
        common_logger -e "Cannot find ${artifact_urls_file_name} in ${base_path}."
        exit 1
    fi
}
function checks_ArtifactURLs_format() {
    common_logger -d "Checking ${artifact_urls_file_name} file format."
    artifact_file="${base_path}/${artifact_urls_file_name}"
    # Check that all values are valid URLs
    while IFS=': ' read -r key value; do
        # Skip empty lines and comments
        [[ -z "$key" || "$key" =~ ^#.*$ ]] && continue

        # Remove quotes and whitespace from value
        value=$(echo "$value" | tr -d '"' | xargs)

        # Validate URL format (must start with http:// or https://)
        if [[ ! "$value" =~ ^https?:// ]]; then
            common_logger -e "Invalid URL format for key '${key}': ${value}"
            common_logger -e "All values in ${artifact_urls_file_name} must be valid URLs starting with http:// or https://"
            exit 1
        fi
    done < "$artifact_file"
}
function checks_ArtifactURLs_component_present() {
    common_logger -d "Checking required component is present in ${artifact_urls_file_name} file."
    artifact_file="${base_path}/${artifact_urls_file_name}"
    # Determine package type based on system
    if [ "${sys_type}" == "yum" ]; then
        pkg_type="rpm"
        # Determine architecture suffix for artifact keys
        if [ "${architecture}" == "x86_64" ]; then
            arch_suffix="x86_64"
        elif [ "${architecture}" == "aarch64" ]; then
            arch_suffix="aarch64"
        fi
    elif [ "${sys_type}" == "apt-get" ]; then
        pkg_type="deb"
        # Determine architecture suffix for artifact keys
        if [ "${architecture}" == "x86_64" ]; then
            arch_suffix="amd64"
        elif [ "${architecture}" == "aarch64" ]; then
            arch_suffix="arm64"
        fi
    fi

    indexer_key="wazuh_indexer_${arch_suffix}_${pkg_type}"
    dashboard_key="wazuh_dashboard_${arch_suffix}_${pkg_type}"
    manager_key="wazuh_manager_${arch_suffix}_${pkg_type}"

    # Check component-specific artifacts exist
    if [ -n "${AIO}" ] || [ -n "${indexer}" ]; then
        if ! grep -q "^${indexer_key}:" "$artifact_file"; then
            common_logger -e "Missing required artifact key: ${indexer_key}"
            exit 1
        fi
    fi
    if [ -n "${AIO}" ] || [ -n "${dashboard}" ]; then
        if ! grep -q "^${dashboard_key}:" "$artifact_file"; then
            common_logger -e "Missing required artifact key: ${dashboard_key}"
            exit 1
        fi
    fi
    if [ -n "${AIO}" ] || [ -n "${wazuh}" ]; then
        if ! grep -q "^${manager_key}:" "$artifact_file"; then
            common_logger -e "Missing required artifact key: ${manager_key}"
            exit 1
        fi
    fi
}
function checks_firewall(){
    ports_list=("$@")
    f_ports=""
    f_message="The system has firewall enabled. Please ensure that traffic is allowed on "
    firewalld_installed=0
    ufw_installed=0


    # Record of the ports that must be exposed according to the installation
    if [ -n "${AIO}" ]; then
        f_message+="these ports: 1515, 1514, ${http_port}"
    elif [ -n "${dashboard}" ]; then
        f_message+="this port: ${http_port}"
    else
        f_message+="these ports:"
        for port in "${ports_list[@]}"; do
            f_message+=" ${port},"
        done

        # Deletes last comma
        f_message="${f_message%,}"
    fi

    # Check if the firewall is installed
    if [ "${sys_type}" == "yum" ]; then
        rpm -q firewalld --quiet && firewalld_installed=1
        rpm -q ufw --quiet && ufw_installed=1
    elif [ "${sys_type}" == "apt-get" ]; then
        if apt list --installed 2>/dev/null | grep -q -E ^"firewalld"\/; then
            firewalld_installed=1
        fi
        if apt list --installed 2>/dev/null | grep -q -E ^"ufw"\/; then
            ufw_installed=1
        fi
    fi

    # Check if the firewall is running
    if [ "${firewalld_installed}" == "1" ]; then
        if firewall-cmd --state 2>/dev/null | grep -q -w "running"; then
            common_logger -w "${f_message/firewall/Firewalld}."
        fi
    fi
    if [ "${ufw_installed}" == "1" ]; then
        if ufw status 2>/dev/null | grep -q -w "active"; then
            common_logger -w "${f_message/firewall/UFW}."
        fi
    fi

}

# ------------ indexer.sh ------------ 
function indexer_configure() {

    common_logger -d "Configuring Wazuh indexer."

    # Configure JVM options for Wazuh indexer
    ram_mb=$(free -m | awk 'FNR == 2 {print $2}')
    ram="$(( ram_mb / 2 ))"

    if [ "${ram}" -eq "0" ]; then
        ram=1024;
    fi
    eval "sed -i "s/-Xms1g/-Xms${ram}m/" /etc/wazuh-indexer/jvm.options ${debug}"
    eval "sed -i "s/-Xmx1g/-Xmx${ram}m/" /etc/wazuh-indexer/jvm.options ${debug}"

    if [ "${AIO}" ]; then
        indexer_ip="${indexer_node_ips[0]}"
        indxname="${indexer_node_names[0]}"
        # This variables are used to not overwrite the indexer_node* arrays
        indexer_configuration_ips=("${indexer_node_ips[0]}") # I'll take only the first ip
        indexer_configuration_names=("${indexer_node_names[0]}") # I'll take only the first name
    else
        for i in "${!indexer_node_names[@]}"; do
            if [[ "${indexer_node_names[i]}" == "${indxname}" ]]; then
                indexer_ip=${indexer_node_ips[i]};
                break
            fi
        done
        indexer_configuration_ips=("${indexer_node_ips[@]}") # I'll take all the ips
        indexer_configuration_names=("${indexer_node_names[@]}") # I'll take all the names
    fi

    eval "sed -i 's|node.name:.*|node.name: ${indxname}|' /etc/wazuh-indexer/opensearch.yml ${debug}"
    eval "sed -i 's|network.host:.*|network.host: ${indexer_ip}|' /etc/wazuh-indexer/opensearch.yml ${debug}"
    eval "sed -i '/.*- \"node-.*/d' /etc/wazuh-indexer/opensearch.yml ${debug}"

    # cluster.initial_cluster_manager_nodes configuration
    indexer_master_nodes="cluster.initial_cluster_manager_nodes:\n"
    for node_name in "${indexer_configuration_names[@]}"; do
        indexer_master_nodes+="- \"${node_name}\"\n"
    done
    eval "sed -i 's|cluster.initial_cluster_manager_nodes:.*|${indexer_master_nodes}|' /etc/wazuh-indexer/opensearch.yml ${debug}"

    # seed_hosts configuration
    indexer_seed_hosts="discovery.seed_hosts:\n"
    for ip in "${indexer_configuration_ips[@]}"; do
        indexer_seed_hosts+="  - \"${ip}\"\n"
    done
    eval "sed -i 's|#discovery.seed_hosts:.*|${indexer_seed_hosts}|' /etc/wazuh-indexer/opensearch.yml ${debug}"

    # CN configuration
    eval "sed -i '/.*- \"CN=node-.*/d' /etc/wazuh-indexer/opensearch.yml ${debug}"
    indexer_cn_nodes="plugins.security.nodes_dn:\n"
    for node_name in "${indexer_configuration_names[@]}"; do
        indexer_cn_nodes+="- \"CN=${node_name},OU=Wazuh,O=Wazuh,L=California,C=US\"\n"
    done
    eval "sed -i 's|plugins.security.nodes_dn:.*|${indexer_cn_nodes}|' /etc/wazuh-indexer/opensearch.yml ${debug}"

    indexer_copyCertificates

    jv=$(java -version 2>&1 | grep -o -m1 '1.8.0' )
    if [ "$jv" == "1.8.0" ]; then
        {
        echo "wazuh-indexer hard nproc 4096"
        echo "wazuh-indexer soft nproc 4096"
        echo "wazuh-indexer hard nproc 4096"
        echo "wazuh-indexer soft nproc 4096"
        } >> /etc/security/limits.conf
        echo -ne "\nbootstrap.system_call_filter: false" >> /etc/wazuh-indexer/opensearch.yml
    fi

    common_logger "Wazuh indexer post-install configuration finished."
}
function indexer_copyCertificates() {

    common_logger -d "Copying Wazuh indexer certificates."
    eval "rm -f ${indexer_cert_path}/* ${debug}"

    if [ "${AIO}" ]; then
        indxname="${indexer_node_names[0]}"
    fi

    if [ -f "${tar_file}" ]; then
        if ! tar -tvf "${tar_file}" | grep -q "${indxname}" ; then
            common_logger -e "Tar file does not contain certificate for the node ${indxname}."
            installCommon_rollBack
            exit 1;
        fi
        eval "mkdir ${indexer_cert_path} ${debug}"
        eval "sed -i s/indexer.pem/${indxname}.pem/ /etc/wazuh-indexer/opensearch.yml ${debug}"
        eval "sed -i s/indexer-key.pem/${indxname}-key.pem/ /etc/wazuh-indexer/opensearch.yml ${debug}"
        eval "tar -xf ${tar_file} -C ${indexer_cert_path} wazuh-install-files/${indxname}.pem --strip-components 1 ${debug}"
        eval "tar -xf ${tar_file} -C ${indexer_cert_path} wazuh-install-files/${indxname}-key.pem --strip-components 1 ${debug}"
        eval "tar -xf ${tar_file} -C ${indexer_cert_path} wazuh-install-files/root-ca.pem --strip-components 1 ${debug}"
        eval "tar -xf ${tar_file} -C ${indexer_cert_path} wazuh-install-files/admin.pem --strip-components 1 ${debug}"
        eval "tar -xf ${tar_file} -C ${indexer_cert_path} wazuh-install-files/admin-key.pem --strip-components 1 ${debug}"
        eval "rm -rf ${indexer_cert_path}/wazuh-install-files/ ${debug}"
        eval "chown -R wazuh-indexer:wazuh-indexer ${indexer_cert_path} ${debug}"
        eval "chmod 500 ${indexer_cert_path} ${debug}"
        eval "chmod 400 ${indexer_cert_path}/* ${debug}"
    else
        common_logger -e "No certificates found. Could not initialize Wazuh indexer"
        installCommon_rollBack
        exit 1;
    fi

}
function indexer_install() {

    common_logger "Starting Wazuh indexer installation."

    if [ -n "${offline_install}" ]; then
        download_dir="${offline_packages_path}"
    else
        download_dir="${base_path}/${download_packages_directory}"
    fi

    # Find the downloaded package file
    if [ "${sys_type}" == "yum" ]; then
        package_file=$(ls "${download_dir}"/wazuh-indexer*.rpm 2>/dev/null | head -n 1)
        if [ -z "${package_file}" ]; then
            common_logger -e "Wazuh indexer package file not found in ${download_dir}."
            exit 1
        fi
        installCommon_yumInstall "${package_file}"
    elif [ "${sys_type}" == "apt-get" ]; then
        package_file=$(ls "${download_dir}"/wazuh-indexer*.deb 2>/dev/null | head -n 1)
        if [ -z "${package_file}" ]; then
            common_logger -e "Wazuh indexer package file not found in ${download_dir}."
            exit 1
        fi
        installCommon_aptInstall "${package_file}"
    fi

    common_checkInstalled
    if [  "$install_result" != 0  ] || [ -z "${indexer_installed}" ]; then
        common_logger -e "Wazuh indexer installation failed."
        installCommon_rollBack
        exit 1
    else
        common_logger "Wazuh indexer installation finished."
    fi

    eval "sysctl -q -w vm.max_map_count=262144 ${debug}"

}
function indexer_startCluster() {

    common_logger -d "Starting Wazuh indexer cluster."

    wazuh_indexer_ip=$(grep "network.host" /etc/wazuh-indexer/opensearch.yml | sed 's/network.host:\s//')
    eval "JAVA_HOME=/usr/share/wazuh-indexer/jdk/ OPENSEARCH_CONF_DIR=/etc/wazuh-indexer /usr/share/wazuh-indexer/plugins/opensearch-security/tools/securityadmin.sh -cd /etc/wazuh-indexer/opensearch-security -icl -p 9200 -nhnv -cacert ${indexer_cert_path}/root-ca.pem -cert ${indexer_cert_path}/admin.pem -key ${indexer_cert_path}/admin-key.pem -h ${wazuh_indexer_ip} ${debug}"
    if [  "${PIPESTATUS[0]}" != 0  ]; then
        common_logger -e "The Wazuh indexer cluster security configuration could not be initialized."
        installCommon_rollBack
        exit 1
    else
        common_logger "Wazuh indexer cluster security configuration initialized."
    fi

}

# ------------ dashboard.sh ------------ 
function dashboard_configure() {

    common_logger -d "Configuring Wazuh dashboard."

    # dashboard configuration itself
    dashboard_copyCertificates "${debug}"

    # dashboard configuration to connect to the indexer cluster
    if [ "${#indexer_node_names[@]}" -eq 1 ]; then
        eval "sed -i 's|opensearch.hosts:.*|opensearch.hosts: https://${indexer_node_ips[0]}:9200|' /etc/wazuh-dashboard/opensearch_dashboards.yml ${debug}"
    else
        ips_list="["
        for i in "${indexer_node_ips[@]}"; do
            ips_list+="\"https://${i}:9200\", "
        done
        ips_list=${ips_list%, }"]" # if there are more than one indexer, there will be a list of urls ["url1", "url2", ...]
        eval "sed -i 's|opensearch.hosts:.*|opensearch.hosts: ${ips_list}|' /etc/wazuh-dashboard/opensearch_dashboards.yml ${debug}"
    fi

    # dashboard configuration to connect to the wazuh api
    if [ -n "${AIO}" ]; then
        wazuh_api_address=${manager_node_ips[0]}
    else
        if [ -n "${manager_node_types[*]}" ]; then
            for i in "${!manager_node_types[@]}"; do
                if [[ "${manager_node_types[i]}" == "master" ]]; then
                    wazuh_api_address=${manager_node_ips[i]}
                fi
            done
        else
            wazuh_api_address=${manager_node_ips[0]}
        fi
    fi
    eval "sed -i 's|url:.*|url: https://${wazuh_api_address}|' /etc/wazuh-dashboard/opensearch_dashboards.yml ${debug}"

    common_logger "Wazuh dashboard post-install configuration finished."

}
function dashboard_copyCertificates() {

    common_logger -d "Copying Wazuh dashboard certificates."
    eval "rm -f ${dashboard_cert_path}/* ${debug}"
    if [ "${AIO}" ]; then
        dashname="${dashboard_node_names[0]}"
    fi
    # else we assume that dashname is already set

    if [ -f "${tar_file}" ]; then
        if ! tar -tvf "${tar_file}" | grep -q "${dashname}" ; then
            common_logger -e "Tar file does not contain certificate for the node ${dashname}."
            installCommon_rollBack
            exit 1;
        fi
        eval "mkdir ${dashboard_cert_path} ${debug}"
        eval "sed -i s/dashboard.pem/${dashname}.pem/ /etc/wazuh-dashboard/opensearch_dashboards.yml ${debug}"
        eval "sed -i s/dashboard-key.pem/${dashname}-key.pem/ /etc/wazuh-dashboard/opensearch_dashboards.yml ${debug}"
        eval "tar -xf ${tar_file} -C ${dashboard_cert_path} wazuh-install-files/${dashname}.pem --strip-components 1 ${debug}"
        eval "tar -xf ${tar_file} -C ${dashboard_cert_path} wazuh-install-files/${dashname}-key.pem --strip-components 1 ${debug}"
        eval "tar -xf ${tar_file} -C ${dashboard_cert_path} wazuh-install-files/root-ca.pem --strip-components 1 ${debug}"
        eval "chown -R wazuh-dashboard:wazuh-dashboard ${dashboard_cert_path} ${debug}"
        eval "chmod 500 ${dashboard_cert_path} ${debug}"
        eval "chmod 400 ${dashboard_cert_path}/* ${debug}"
        common_logger -d "Wazuh dashboard certificate setup finished."
    else
        common_logger -e "No certificates found. Wazuh dashboard  could not be initialized."
        installCommon_rollBack
        exit 1
    fi

}
function dashboard_displaySummary() {

    common_logger -nl "--- Summary ---"
    common_logger -nl "You can access the web interface https://<wazuh_dashboard_ip>:${http_port}\n    User: admin\n    Password: admin"

}
function dashboard_install() {

    common_logger "Starting Wazuh dashboard installation."

    if [ -n "${offline_install}" ]; then
        download_dir="${offline_packages_path}"
    else
        download_dir="${base_path}/${download_packages_directory}"
    fi

    # Find the downloaded package file
    if [ "${sys_type}" == "yum" ]; then
        package_file=$(ls "${download_dir}"/wazuh-dashboard*.rpm 2>/dev/null | head -n 1)
        if [ -z "${package_file}" ]; then
            common_logger -e "Wazuh dashboard package file not found in ${download_dir}."
            exit 1
        fi
        installCommon_yumInstall "${package_file}"
    elif [ "${sys_type}" == "apt-get" ]; then
        package_file=$(ls "${download_dir}"/wazuh-dashboard*.deb 2>/dev/null | head -n 1)
        if [ -z "${package_file}" ]; then
            common_logger -e "Wazuh dashboard package file not found in ${download_dir}."
            exit 1
        fi
        installCommon_aptInstall "${package_file}"
    fi

    common_checkInstalled
    if [  "$install_result" != 0  ] || [ -z "${dashboard_installed}" ]; then
        common_logger -e "Wazuh dashboard installation failed."
        installCommon_rollBack
        exit 1
    else
        common_logger "Wazuh dashboard installation finished."
    fi

}

function dist_detect() {


DIST_NAME="Linux"
DIST_VER="0"
DIST_SUBVER="0"

if [ -r "/etc/os-release" ]; then
    . /etc/os-release
    DIST_NAME=$ID
    DIST_VER=$(echo $VERSION_ID | sed -rn 's/[^0-9]*([0-9]+).*/\1/p')
    if [ "X$DIST_VER" = "X" ]; then
        DIST_VER="0"
    fi
    if [ "$DIST_NAME" = "amzn" ] && [ "$DIST_VER" = "2018" ]; then
        DIST_VER="1"
    fi
    DIST_SUBVER=$(echo $VERSION_ID | sed -rn 's/[^0-9]*[0-9]+\.([0-9]+).*/\1/p')
    if [ "X$DIST_SUBVER" = "X" ]; then
        DIST_SUBVER="0"
    fi
fi

if [ ! -r "/etc/os-release" ] || [ "$DIST_NAME" = "centos" ]; then
    # CentOS
    if [ -r "/etc/centos-release" ]; then
        DIST_NAME="centos"
        DIST_VER=`sed -rn 's/.* ([0-9]{1,2})\.*[0-9]{0,2}.*/\1/p' /etc/centos-release`
        DIST_SUBVER=`sed -rn 's/.* [0-9]{1,2}\.*([0-9]{0,2}).*/\1/p' /etc/centos-release`

    # Fedora
    elif [ -r "/etc/fedora-release" ]; then
        DIST_NAME="fedora"
        DIST_VER=`sed -rn 's/.* ([0-9]{1,2}) .*/\1/p' /etc/fedora-release`

    # RedHat
    elif [ -r "/etc/redhat-release" ]; then
        if grep -q "CentOS" /etc/redhat-release; then
            DIST_NAME="centos"
        else
            DIST_NAME="rhel"
        fi
        DIST_VER=`sed -rn 's/.* ([0-9]{1,2})\.*[0-9]{0,2}.*/\1/p' /etc/redhat-release`
        DIST_SUBVER=`sed -rn 's/.* [0-9]{1,2}\.*([0-9]{0,2}).*/\1/p' /etc/redhat-release`

    # Ubuntu
    elif [ -r "/etc/lsb-release" ]; then
        . /etc/lsb-release
        DIST_NAME="ubuntu"
        DIST_VER=$(echo $DISTRIB_RELEASE | sed -rn 's/.*([0-9][0-9])\.[0-9][0-9].*/\1/p')
        DIST_SUBVER=$(echo $DISTRIB_RELEASE | sed -rn 's/.*[0-9][0-9]\.([0-9][0-9]).*/\1/p')

    # Gentoo
    elif [ -r "/etc/gentoo-release" ]; then
        DIST_NAME="gentoo"
        DIST_VER=`sed -rn 's/.* ([0-9]{1,2})\.[0-9]{1,2}.*/\1/p' /etc/gentoo-release`
        DIST_SUBVER=`sed -rn 's/.* [0-9]{1,2}\.([0-9]{1,2}).*/\1/p' /etc/gentoo-release`

    # SuSE
    elif [ -r "/etc/SuSE-release" ]; then
        DIST_NAME="suse"
        DIST_VER=`sed -rn 's/.*VERSION = ([0-9]{1,2}).*/\1/p' /etc/SuSE-release`
        DIST_SUBVER=`sed -rn 's/.*PATCHLEVEL = ([0-9]{1,2}).*/\1/p' /etc/SuSE-release`
        if [ "$DIST_SUBVER" = "" ]; then #openSuse
          DIST_SUBVER=`sed -rn 's/.*VERSION = ([0-9]{1,2})\.([0-9]{1,2}).*/\1/p' /etc/SuSE-release`
        fi

    # Arch
    elif [ -r "/etc/arch-release" ]; then
        DIST_NAME="arch"
        DIST_VER=$(uname -r | sed -rn 's/[^0-9]*([0-9]+).*/\1/p')
        DIST_SUBVER=$(uname -r | sed -rn 's/[^0-9]*[0-9]+\.([0-9]+).*/\1/p')

    # Debian
    elif [ -r "/etc/debian_version" ]; then
        DIST_NAME="debian"
        DIST_VER=`sed -rn 's/[^0-9]*([0-9]+).*/\1/p' /etc/debian_version`
        DIST_SUBVER=`sed -rn 's/[^0-9]*[0-9]+\.([0-9]+).*/\1/p' /etc/debian_version`

    # Slackware
    elif [ -r "/etc/slackware-version" ]; then
        DIST_NAME="slackware"
        DIST_VER=`sed -rn 's/.* ([0-9]{1,2})\.[0-9].*/\1/p' /etc/slackware-version`
        DIST_SUBVER=`sed -rn 's/.* [0-9]{1,2}\.([0-9]).*/\1/p' /etc/slackware-version`

    # Darwin
    elif [ "$(uname)" = "Darwin" ]; then
        DIST_NAME="darwin"
        DIST_VER=$(uname -r | sed -En 's/[^0-9]*([0-9]+).*/\1/p')
        DIST_SUBVER=$(uname -r | sed -En 's/[^0-9]*[0-9]+\.([0-9]+).*/\1/p')

    # Solaris / SunOS
    elif [ "$(uname)" = "SunOS" ]; then
        DIST_NAME="sunos"
        DIST_VER=$(uname -r | cut -d\. -f1)
        DIST_SUBVER=$(uname -r | cut -d\. -f2)

    # HP-UX
    elif [ "$(uname)" = "HP-UX" ]; then
        DIST_NAME="HP-UX"
        DIST_VER=$(uname -r | cut -d\. -f2)
        DIST_SUBVER=$(uname -r | cut -d\. -f3)

    # AIX
    elif [ "$(uname)" = "AIX" ]; then
        DIST_NAME="AIX"
        DIST_VER=$(oslevel | cut -d\. -f1)
        DIST_SUBVER=$(oslevel | cut -d\. -f2)

    # BSD
    elif [ "X$(uname)" = "XOpenBSD" -o "X$(uname)" = "XNetBSD" -o "X$(uname)" = "XFreeBSD" -o "X$(uname)" = "XDragonFly" ]; then
        DIST_NAME="bsd"
        DIST_VER=$(uname -r | sed -rn 's/[^0-9]*([0-9]+).*/\1/p')
        DIST_SUBVER=$(uname -r | sed -rn 's/[^0-9]*[0-9]+\.([0-9]+).*/\1/p')

    elif [ "X$(uname)" = "XLinux" ]; then
        DIST_NAME="Linux"

    fi
    if [ "X$DIST_SUBVER" = "X" ]; then
        DIST_SUBVER="0"
    fi
fi
}
function common_checkAptLock() {

    attempt=0
    seconds=30
    max_attempts=10

    while fuser "${apt_lockfile}" >/dev/null 2>&1 && [ "${attempt}" -lt "${max_attempts}" ]; do
        attempt=$((attempt+1))
        common_logger "Another process is using APT. Waiting for it to release the lock. Next retry in ${seconds} seconds (${attempt}/${max_attempts})"
        sleep "${seconds}"
    done

}
function common_logger() {

    now=$(date +'%d/%m/%Y %H:%M:%S')
    mtype="INFO:"
    debugLogger=
    nolog=
    if [ -n "${1}" ]; then
        while [ -n "${1}" ]; do
            case ${1} in
                "-e")
                    mtype="ERROR:"
                    shift 1
                    ;;
                "-w")
                    mtype="WARNING:"
                    shift 1
                    ;;
                "-d")
                    debugLogger=1
                    mtype="DEBUG:"
                    shift 1
                    ;;
                "-nl")
                    nolog=1
                    shift 1
                    ;;
                *)
                    message="${1}"
                    shift 1
                    ;;
            esac
        done
    fi

    if [ -z "${debugLogger}" ] || { [ -n "${debugLogger}" ] && [ -n "${debugEnabled}" ]; }; then
        if [ -z "${nolog}" ] && { [ "$EUID" -eq 0 ] || [[ "$(basename "$0")" =~ $cert_tool_script_name ]]; }; then
            printf "%s\n" "${now} ${mtype} ${message}" | tee -a ${logfile}
        else
            printf "%b\n" "${now} ${mtype} ${message}"
        fi
    fi

}
function common_checkRoot() {

    common_logger -d "Checking root permissions."
    if [ "$EUID" -ne 0 ]; then
        echo "This script must be run as root."
        exit 1;
    fi

}
function common_checkInstalled() {

    common_logger -d "Checking Wazuh installation."
    wazuh_installed=""
    indexer_installed=""
    dashboard_installed=""

    if [ "${sys_type}" == "yum" ]; then
        rpm -q wazuh-manager --quiet && wazuh_installed=1
    elif [ "${sys_type}" == "apt-get" ]; then
        wazuh_installed=$(apt list --installed  2>/dev/null | grep wazuh-manager)
    fi

    if [ -d "/var/wazuh-manager" ]; then
        common_logger -d "There are Wazuh remaining files."
        wazuh_remaining_files=1
    fi

    if [ "${sys_type}" == "yum" ]; then
        rpm -q wazuh-indexer --quiet && indexer_installed=1

    elif [ "${sys_type}" == "apt-get" ]; then
        indexer_installed=$(apt list --installed 2>/dev/null | grep wazuh-indexer)
    fi

    if [ -d "/var/lib/wazuh-indexer/" ] || [ -d "/usr/share/wazuh-indexer" ] || [ -d "/etc/wazuh-indexer" ] || [ -f "${base_path}/search-guard-tlstool*" ]; then
        common_logger -d "There are Wazuh indexer remaining files."
        indexer_remaining_files=1
    fi

    if [ "${sys_type}" == "yum" ]; then
        eval "rpm -q wazuh-dashboard --quiet && dashboard_installed=1"
    elif [ "${sys_type}" == "apt-get" ]; then
        dashboard_installed=$(apt list --installed  2>/dev/null | grep wazuh-dashboard)
    fi

    if [ -d "/var/lib/wazuh-dashboard/" ] || [ -d "/usr/share/wazuh-dashboard" ] || [ -d "/etc/wazuh-dashboard" ] || [ -d "/run/wazuh-dashboard/" ]; then
        common_logger -d "There are Wazuh dashboard remaining files."
        dashboard_remaining_files=1
    fi

}
function common_checkSystem() {

    if [ -n "$(command -v yum)" ]; then
        sys_type="yum"
        sep="-"
        common_logger -d "YUM package manager will be used."
    elif [ -n "$(command -v apt-get)" ]; then
        sys_type="apt-get"
        sep="="
        common_logger -d "APT package manager will be used."
    else
        common_logger -e "Couldn't find YUM or APT package manager. Try installing the one corresponding to your operating system and then, launch the installation assistant again."
        exit 1
    fi

}
function common_checkWazuhConfigYaml() {

    common_logger -d "Checking Wazuh YAML configuration file."
    filecorrect=$(cert_parseYaml "${config_file}" | grep -Ev '^#|^\s*$' | grep -Pzc "\A(\s*(nodes_indexer__name|nodes_indexer__ip|nodes_manager__name|nodes_manager__ip|nodes_manager__node_type|nodes_dashboard__name|nodes_dashboard__ip)=.*?)+\Z")
    if [[ "${filecorrect}" -ne 1 ]]; then
        common_logger -e "The configuration file ${config_file} does not have a correct format."
        exit 1
    fi

}
function common_curl() {

    if [ -n "${curl_has_connrefused}" ]; then
        eval "curl --retry-connrefused $@"
        e_code="${PIPESTATUS[0]}"
    else
        retries=0
        eval "curl $@"
        e_code="${PIPESTATUS[0]}"
        while [ "${e_code}" -eq 7 ] && [ "${retries}" -ne 12 ]; do
            retries=$((retries+1))
            sleep 5
            eval "curl $@"
            e_code="${PIPESTATUS[0]}"
        done
    fi
    return "${e_code}"

}
function common_checkYumLock() {

    attempt=0
    seconds=30
    max_attempts=10

    while [ -f "${yum_lockfile}" ] && [ "${attempt}" -lt "${max_attempts}" ]; do
        attempt=$((attempt+1))
        common_logger "Another process is using YUM. Waiting for it to release the lock. Next retry in ${seconds} seconds (${attempt}/${max_attempts})"
        sleep "${seconds}"
    done

}
function cert_validatePath() {
    local path="$1"
    local path_type="${2:-file}"

    # Check if path is empty
    if [[ -z "${path}" ]]; then
        common_logger -e "Path cannot be empty."
        return 1
    fi

    # Prevent path traversal attacks - reject paths with suspicious patterns
    if [[ "${path}" =~ \.\./|\.\.\\ ]]; then
        common_logger -e "Path traversal detected in: ${path}"
        return 1
    fi

    # Reject paths with newlines, carriage returns, or tabs (specific problematic characters)
    if [[ "${path}" =~ $'\n'|$'\r'|$'\t' ]]; then
        common_logger -e "Invalid characters detected in path: ${path}"
        return 1
    fi

    # For absolute paths validation
    if [[ "${path}" == /* ]]; then
        # Resolve to canonical path to prevent symlink attacks
        if command -v realpath >/dev/null 2>&1; then
            local canonical_path
            canonical_path=$(realpath -m "${path}" 2>/dev/null) || return 1

            # Ensure the canonical path doesn't escape expected boundaries
            if [[ ! "${canonical_path}" =~ ^/[a-zA-Z0-9/_.\-]+$ ]]; then
                common_logger -e "Invalid canonical path: ${canonical_path}"
                return 1
            fi
        fi
    fi

    return 0
}
function cert_sanitizeFilename() {
    local filename="$1"

    # Remove any path components
    filename="${filename##*/}"

    # Only allow alphanumeric, dash, underscore, and dot
    filename=$(echo "${filename}" | sed 's/[^a-zA-Z0-9._-]/_/g')

    # Prevent hidden files
    filename="${filename#.}"

    # Limit length to 255 characters
    if [[ ${#filename} -gt 255 ]]; then
        filename="${filename:0:255}"
    fi

    echo "${filename}"
}
function cert_sanitizeNodeName() {
    local component_name="$1"
    local node_names_var="$2"

    # Use nameref for safe dynamic array access
    declare -n component_node_names="${node_names_var}"

    for i in "${!component_node_names[@]}"; do

        # Only allow alphanumeric, dash, underscore, and dot (typical for hostnames)
        if [[ ! "${component_node_names[$i]}" =~ ^[a-zA-Z0-9._-]+$ ]]; then
            common_logger -e "Invalid ${component_name} node name: ${component_node_names[$i]}. Only alphanumeric characters, dots, dashes, and underscores are allowed."
            exit 1
        fi

        # Prevent names starting with dash or dot
        if [[ "${component_node_names[$i]}" =~ ^[-\.] ]]; then
            common_logger -e "${component_name} node name cannot start with dash or dot: ${component_node_names[$i]}"
            exit 1
        fi

        # Limit length
        if [[ ${#component_node_names[$i]} -gt 253 ]]; then
            common_logger -e "${component_name} node name too long: ${component_node_names[$i]}"
            exit 1
        fi
    done

    return 0
}
function cert_validatePath() {
    local path="$1"
    local path_type="${2:-file}"

    # Check if path is empty
    if [[ -z "${path}" ]]; then
        common_logger -e "Path cannot be empty."
        return 1
    fi

    # Prevent path traversal attacks - reject paths with suspicious patterns
    if [[ "${path}" =~ \.\./|\.\.\\ ]]; then
        common_logger -e "Path traversal detected in: ${path}"
        return 1
    fi

    # Reject paths with newlines, carriage returns, or tabs (specific problematic characters)
    if [[ "${path}" =~ $'\n'|$'\r'|$'\t' ]]; then
        common_logger -e "Invalid characters detected in path: ${path}"
        return 1
    fi

    # For absolute paths validation
    if [[ "${path}" == /* ]]; then
        # Resolve to canonical path to prevent symlink attacks
        if command -v realpath >/dev/null 2>&1; then
            local canonical_path
            canonical_path=$(realpath -m "${path}" 2>/dev/null) || return 1

            # Ensure the canonical path doesn't escape expected boundaries
            if [[ ! "${canonical_path}" =~ ^/[a-zA-Z0-9/_.\-]+$ ]]; then
                common_logger -e "Invalid canonical path: ${canonical_path}"
                return 1
            fi
        fi
    fi

    return 0
}
function cert_sanitizeFilename() {
    local filename="$1"

    # Remove any path components
    filename="${filename##*/}"

    # Only allow alphanumeric, dash, underscore, and dot
    filename=$(echo "${filename}" | sed 's/[^a-zA-Z0-9._-]/_/g')

    # Prevent hidden files
    filename="${filename#.}"

    # Limit length to 255 characters
    if [[ ${#filename} -gt 255 ]]; then
        filename="${filename:0:255}"
    fi

    echo "${filename}"
}
function cert_sanitizeNodeName() {
    local nodename="$1"

    # Only allow alphanumeric, dash, underscore, and dot (typical for hostnames)
    if [[ ! "${nodename}" =~ ^[a-zA-Z0-9._-]+$ ]]; then
        common_logger -e "Invalid node name: ${nodename}. Only alphanumeric characters, dots, dashes, and underscores are allowed."
        return 1
    fi

    # Prevent names starting with dash or dot
    if [[ "${nodename}" =~ ^[-\.] ]]; then
        common_logger -e "Node name cannot start with dash or dot: ${nodename}"
        return 1
    fi

    # Limit length
    if [[ ${#nodename} -gt 253 ]]; then
        common_logger -e "Node name too long: ${nodename}"
        return 1
    fi

    return 0
}
function cert_cleanFiles() {

    # Validate cert_tmp_path before use
    if ! cert_validatePath "${cert_tmp_path}" "directory"; then
        common_logger -e "Invalid certificate temporary path."
        exit 1
    fi

    # Remove files
    rm -f "${cert_tmp_path}"/*.csr
    rm -f "${cert_tmp_path}"/*.srl
    rm -f "${cert_tmp_path}"/*.conf
    rm -f "${cert_tmp_path}"/admin-key-temp.pem

}
function cert_checkOpenSSL() {

    common_logger -d "Checking if OpenSSL is installed."

    if [ -z "$(command -v openssl)" ]; then
        common_logger -e "OpenSSL not installed."
        exit 1
    fi

}
function cert_checkRootCA() {

    common_logger -d "Checking if the root CA exists."

    if  [[ -n ${rootca} || -n ${rootcakey} ]]; then
        # Verify variables match keys
        if [[ ${rootca} == *".key" ]]; then
            ca_temp=${rootca}
            rootca=${rootcakey}
            rootcakey=${ca_temp}
        fi

        # Validate paths
        if ! cert_validatePath "${rootca}" "file"; then
            common_logger -e "Invalid root CA certificate path: ${rootca}"
            cert_cleanFiles
            exit 1
        fi

        if ! cert_validatePath "${rootcakey}" "file"; then
            common_logger -e "Invalid root CA key path: ${rootcakey}"
            cert_cleanFiles
            exit 1
        fi

        if ! cert_validatePath "${cert_tmp_path}" "directory"; then
            common_logger -e "Invalid certificate temporary path."
            cert_cleanFiles
            exit 1
        fi

        # Validate that files exist
        if [[ -e ${rootca} ]]; then
            cp "${rootca}" "${cert_tmp_path}/root-ca.pem"
        else
            common_logger -e "The file ${rootca} does not exists"
            cert_cleanFiles
            exit 1
        fi
        if [[ -e ${rootcakey} ]]; then
            cp "${rootcakey}" "${cert_tmp_path}/root-ca.key"
        else
            common_logger -e "The file ${rootcakey} does not exists"
            cert_cleanFiles
            exit 1
        fi
    else
        cert_generateRootCAcertificate
    fi

}
function cert_executeAndValidate() {

    command_output=$("$@" 2>&1)
    e_code="${PIPESTATUS[0]}"

    if [ "${e_code}" -ne 0 ]; then
        common_logger -e "Error generating the certificates."
        common_logger -d "Error executing command: $@"
        common_logger -d "Error output: ${command_output}"
        cert_cleanFiles
        exit 1
    fi

}
function cert_generateAdmincertificate() {

    common_logger "Generating Admin certificates."

    # Validate cert_tmp_path
    if ! cert_validatePath "${cert_tmp_path}" "directory"; then
        common_logger -e "Invalid certificate temporary path."
        exit 1
    fi

    common_logger -d "Generating Admin private key."
    cert_executeAndValidate openssl genrsa -out "${cert_tmp_path}/admin-key-temp.pem" 2048
    common_logger -d "Converting Admin private key to PKCS8 format."
    cert_executeAndValidate openssl pkcs8 -inform PEM -outform PEM -in "${cert_tmp_path}/admin-key-temp.pem" -topk8 -nocrypt -v1 PBE-SHA1-3DES -out "${cert_tmp_path}/admin-key.pem"
    common_logger -d "Generating Admin CSR."
    cert_executeAndValidate openssl req -new -key "${cert_tmp_path}/admin-key.pem" -out "${cert_tmp_path}/admin.csr" -batch -subj '/C=US/L=California/O=Wazuh/OU=Wazuh/CN=admin'
    common_logger -d "Creating Admin certificate."
    cert_executeAndValidate openssl x509 -days 3650 -req -in "${cert_tmp_path}/admin.csr" -CA "${cert_tmp_path}/root-ca.pem" -CAkey "${cert_tmp_path}/root-ca.key" -CAcreateserial -sha256 -out "${cert_tmp_path}/admin.pem"

}
function cert_generateCertificateconfiguration() {

    common_logger -d "Generating certificate configuration."

    local node_name="$1"

    # Validate cert_tmp_path
    if ! cert_validatePath "${cert_tmp_path}" "directory"; then
        common_logger -e "Invalid certificate temporary path."
        exit 1
    fi

    cat > "${cert_tmp_path}/${node_name}.conf" <<- EOF
        [ req ]
        prompt = no
        default_bits = 2048
        default_md = sha256
        distinguished_name = req_distinguished_name
        x509_extensions = v3_req

        [req_distinguished_name]
        C = US
        L = California
        O = Wazuh
        OU = Wazuh
        CN = cname

        [ v3_req ]
        authorityKeyIdentifier=keyid,issuer
        basicConstraints = CA:FALSE
        keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
        subjectAltName = @alt_names

        [alt_names]
        IP.1 = cip
	EOF


    conf="$(awk '{sub("CN = cname", "CN = '"${node_name}"'")}1' "${cert_tmp_path}/${node_name}.conf")"
    echo "${conf}" > "${cert_tmp_path}/${node_name}.conf"

    if [ "${#@}" -gt 1 ]; then
        sed -i '/IP.1/d' "${cert_tmp_path}/${node_name}.conf"
        local ip_counter=0
        local dns_counter=0
        for (( i=2; i<=${#@}; i++ )); do
            if cert_isIP "${!i}"; then
                ip_counter=$((ip_counter+1))
                printf '%s\n' "        IP.${ip_counter} = ${!i}" >> "${cert_tmp_path}/${node_name}.conf"
            elif cert_isDNS "${!i}"; then
                dns_counter=$((dns_counter+1))
                printf '%s\n' "        DNS.${dns_counter} = ${!i}" >> "${cert_tmp_path}/${node_name}.conf"
            else
                common_logger -e "Invalid IP or DNS ${!i}"
                exit 1
            fi
        done
    else
        common_logger -e "No IP or DNS specified"
        exit 1
    fi

}
function cert_generateIndexercertificates() {

    if [ ${#indexer_node_names[@]} -gt 0 ]; then
        common_logger "Generating Wazuh indexer certificates."

        for i in "${!indexer_node_names[@]}"; do
            indexer_node_name=${indexer_node_names[$i]}

            common_logger -d "Creating the certificates for ${indexer_node_name} indexer node."
            j=$((i+1))
            # Use nameref for safe dynamic array access
            declare -n idx_ip="indexer_node_ip_${j}"
            declare -n idx_dns="indexer_node_dns_${j}"
            declare -a idx_san=()
            if [ "${#idx_ip[@]}" -gt 0 ]; then
                idx_san+=("${idx_ip[@]}")
            fi
            if [ "${#idx_dns[@]}" -gt 0 ]; then
                idx_san+=("${idx_dns[@]}")
            fi
            cert_generateCertificateconfiguration "${indexer_node_name}" "${idx_san[@]}"
            common_logger -d "Creating the Wazuh indexer tmp key pair."
            cert_executeAndValidate openssl req -new -nodes -newkey rsa:2048 -keyout "${cert_tmp_path}/${indexer_node_name}-key.pem" -out "${cert_tmp_path}/${indexer_node_name}.csr" -config "${cert_tmp_path}/${indexer_node_name}.conf"
            common_logger -d "Creating the Wazuh indexer certificates."
            cert_executeAndValidate openssl x509 -req -in "${cert_tmp_path}/${indexer_node_name}.csr" -CA "${cert_tmp_path}/root-ca.pem" -CAkey "${cert_tmp_path}/root-ca.key" -CAcreateserial -out "${cert_tmp_path}/${indexer_node_name}.pem" -extfile "${cert_tmp_path}/${indexer_node_name}.conf" -extensions v3_req -days 3650
        done
    else
        return 1
    fi

}
function cert_generateManagercertificates() {

    if [ ${#manager_node_names[@]} -gt 0 ]; then
        common_logger "Generating Wazuh manager certificates."

        for i in "${!manager_node_names[@]}"; do
            manager_name="${manager_node_names[i]}"

            common_logger -d "Generating the certificates for ${manager_name} manager node."
            j=$((i+1))
            # Use nameref for safe dynamic array access
            declare -n manager_ip="manager_node_ip_${j}"
            declare -n mgr_dns="manager_node_dns_${j}"
            declare -a manager_san=()
            if [ "${#manager_ip[@]}" -gt 0 ]; then
                manager_san+=("${manager_ip[@]}")
            fi
            if [ "${#mgr_dns[@]}" -gt 0 ]; then
                manager_san+=("${mgr_dns[@]}")
            fi
            cert_generateCertificateconfiguration "${manager_name}" "${manager_san[@]}"
            common_logger -d "Creating the Wazuh manager tmp key pair."
            cert_executeAndValidate openssl req -new -nodes -newkey rsa:2048 -keyout "${cert_tmp_path}/${manager_name}-key.pem" -out "${cert_tmp_path}/${manager_name}.csr" -config "${cert_tmp_path}/${manager_name}.conf"
            common_logger -d "Creating the Wazuh manager certificates."
            cert_executeAndValidate openssl x509 -req -in "${cert_tmp_path}/${manager_name}.csr" -CA "${cert_tmp_path}/root-ca.pem" -CAkey "${cert_tmp_path}/root-ca.key" -CAcreateserial -out "${cert_tmp_path}/${manager_name}.pem" -extfile "${cert_tmp_path}/${manager_name}.conf" -extensions v3_req -days 3650
        done
    else
        return 1
    fi

}
function cert_generateDashboardcertificates() {
    if [ ${#dashboard_node_names[@]} -gt 0 ]; then
        common_logger "Generating Wazuh dashboard certificates."

        for i in "${!dashboard_node_names[@]}"; do
            dashboard_node_name="${dashboard_node_names[i]}"

            j=$((i+1))
            # Use nameref for safe dynamic array access
            declare -n dash_ip="dashboard_node_ip_${j}"
            declare -n dash_dns="dashboard_node_dns_${j}"
            declare -a dash_san=()
            if [ "${#dash_ip[@]}" -gt 0 ]; then
                dash_san+=("${dash_ip[@]}")
            fi
            if [ "${#dash_dns[@]}" -gt 0 ]; then
                dash_san+=("${dash_dns[@]}")
            fi
            cert_generateCertificateconfiguration "${dashboard_node_name}" "${dash_san[@]}"
            common_logger -d "Creating the Wazuh dashboard tmp key pair."
            cert_executeAndValidate openssl req -new -nodes -newkey rsa:2048 -keyout "${cert_tmp_path}/${dashboard_node_name}-key.pem" -out "${cert_tmp_path}/${dashboard_node_name}.csr" -config "${cert_tmp_path}/${dashboard_node_name}.conf"
            common_logger -d "Creating the Wazuh dashboard certificates."
            cert_executeAndValidate openssl x509 -req -in "${cert_tmp_path}/${dashboard_node_name}.csr" -CA "${cert_tmp_path}/root-ca.pem" -CAkey "${cert_tmp_path}/root-ca.key" -CAcreateserial -out "${cert_tmp_path}/${dashboard_node_name}.pem" -extfile "${cert_tmp_path}/${dashboard_node_name}.conf" -extensions v3_req -days 3650
        done
    else
        return 1
    fi

}
function cert_generateRootCAcertificate() {

    common_logger "Generating the root certificate."

    # Validate cert_tmp_path
    if ! cert_validatePath "${cert_tmp_path}" "directory"; then
        common_logger -e "Invalid certificate temporary path."
        exit 1
    fi

    cert_executeAndValidate openssl req -x509 -new -nodes -newkey rsa:2048 -keyout "${cert_tmp_path}/root-ca.key" -out "${cert_tmp_path}/root-ca.pem" -batch -subj '/OU=Wazuh/O=Wazuh/L=California/' -days 3650

}
function cert_normalizeYamlFormat() {

    # Normalize the certs-tool YAML schema regardless of incoming indentation.
    # It supports optional node fields (ip, dns, node_type), with dns defined as
    # either a scalar value or as a list.
    awk '
    function ltrim(str) {
        sub(/^[ \t]+/, "", str)
        return str
    }
    function rtrim(str) {
        sub(/[ \t]+$/, "", str)
        return str
    }
    function trim(str) {
        return rtrim(ltrim(str))
    }
    BEGIN {
        in_nodes = 0
        nodes_indent = 0
        current_section = ""
        in_node = 0
        in_dns_list = 0
    }
    {
        line = $0
        match(line, /^[ \t]*/)
        indent = RLENGTH

        if (match(line, /^[ \t]*$/)) {
            print ""
            in_dns_list = 0
            next
        }

        if (match(line, /^[ \t]*#/)) {
            print line
            next
        }

        stripped = trim(line)

        if (stripped == "nodes:") {
            print "nodes:"
            in_nodes = 1
            nodes_indent = indent
            current_section = ""
            in_node = 0
            in_dns_list = 0
            next
        }

        if (in_nodes == 1 && current_section != "" && match(stripped, /^-[ \t]/)) {
            list_payload = trim(substr(stripped, 2))

            if (match(list_payload, /^name:[ \t]*/)) {
                name_value = trim(substr(list_payload, 6))
                print "    - name: " name_value
                in_node = 1
                in_dns_list = 0
                next
            }

            if (in_node == 1 && in_dns_list == 1) {
                print "        - " list_payload
                next
            }

            print "    - " list_payload
            in_node = 1
            in_dns_list = 0
            next
        }

        if (in_nodes == 1 && in_node == 1 && in_dns_list == 1 && match(stripped, /^-[ \t]/)) {
            dns_value = trim(substr(stripped, 2))
            print "        - " dns_value
            next
        }

        if (in_nodes == 1 && match(stripped, /^[a-zA-Z0-9_ ]+:[ \t]*/)) {
            key_name = trim(substr(stripped, 1, index(stripped, ":") - 1))
            key_value = trim(substr(stripped, index(stripped, ":") + 1))

            if (key_name == "node type") {
                key_name = "node_type"
            }

            if (in_node == 1 && key_name == "name") {
                print "    - name: " key_value
                in_dns_list = 0
                next
            }

            if (in_node == 1 && (key_name == "ip" || key_name == "node_type")) {
                print "      " key_name ": " key_value
                in_dns_list = 0
                next
            }

            if (in_node == 1 && key_name == "dns") {
                if (key_value == "") {
                    print "      dns:"
                    in_dns_list = 1
                } else {
                    print "      dns: " key_value
                    in_dns_list = 0
                }
                next
            }

            if (key_value == "" && (in_node == 0 || indent <= nodes_indent + 2)) {
                print "  " key_name ":"
                current_section = key_name
                in_node = 0
                in_dns_list = 0
                next
            }

            if (in_node == 1) {
                if (key_value == "") {
                    print "      " key_name ":"
                } else {
                    print "      " key_name ": " key_value
                }
                in_dns_list = 0
                next
            }
        }

        if (!match(stripped, /^-[ \t]/)) {
            in_dns_list = 0
        }

        print line
    }
    '
}
function cert_parseYaml() {

    local config_file_path="$1"
    local prefix="$2"
    local separator="${3:-_}"
    local indexfix

    # Detect awk flavor
    if awk --version 2>&1 | grep -q "GNU Awk" ; then
    # GNU Awk detected
    indexfix=-1
    elif awk -Wv 2>&1 | grep -q "mawk" ; then
    # mawk detected
    indexfix=0
    fi

    local s='[[:space:]]*' sm='[ \t]*' w='[a-zA-Z0-9_]*' fs=${fs:-$(echo @|tr @ '\034')} i=${i:-  }

    # Normalize YAML format first to handle both valid YAML indentation styles
    cat $config_file_path 2>/dev/null | cert_normalizeYamlFormat | \
    awk -F$fs "{multi=0;
        if(match(\$0,/$sm\|$sm$/)){multi=1; sub(/$sm\|$sm$/,\"\");}
        if(match(\$0,/$sm>$sm$/)){multi=2; sub(/$sm>$sm$/,\"\");}
        while(multi>0){
            str=\$0; gsub(/^$sm/,\"\", str);
            indent=index(\$0,str);
            indentstr=substr(\$0, 0, indent+$indexfix) \"$i\";
            obuf=\$0;
            getline;
            while(index(\$0,indentstr)){
                obuf=obuf substr(\$0, length(indentstr)+1);
                if (multi==1){obuf=obuf \"\\\\n\";}
                if (multi==2){
                    if(match(\$0,/^$sm$/))
                        obuf=obuf \"\\\\n\";
                        else obuf=obuf \" \";
                }
                getline;
            }
            sub(/$sm$/,\"\",obuf);
            print obuf;
            multi=0;
            if(match(\$0,/$sm\|$sm$/)){multi=1; sub(/$sm\|$sm$/,\"\");}
            if(match(\$0,/$sm>$sm$/)){multi=2; sub(/$sm>$sm$/,\"\");}
        }
    print}" | \
    sed  -e "s|^\($s\)?|\1-|" \
        -ne "s|^$s#.*||;s|$s#[^\"']*$||;s|^\([^\"'#]*\)#.*|\1|;t1;t;:1;s|^$s\$||;t2;p;:2;d" | \
    sed -ne "s|,$s\]$s\$|]|" \
        -e ":1;s|^\($s\)\($w\)$s:$s\(&$w\)\?$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1\2: \3[\4]\n\1$i- \5|;t1" \
        -e "s|^\($s\)\($w\)$s:$s\(&$w\)\?$s\[$s\(.*\)$s\]|\1\2: \3\n\1$i- \4|;" \
        -e ":2;s|^\($s\)-$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1- [\2]\n\1$i- \3|;t2" \
        -e "s|^\($s\)-$s\[$s\(.*\)$s\]|\1-\n\1$i- \2|;p" | \
    sed -ne "s|,$s}$s\$|}|" \
        -e ":1;s|^\($s\)-$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1- {\2}\n\1$i\3: \4|;t1" \
        -e "s|^\($s\)-$s{$s\(.*\)$s}|\1-\n\1$i\2|;" \
        -e ":2;s|^\($s\)\($w\)$s:$s\(&$w\)\?$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1\2: \3 {\4}\n\1$i\5: \6|;t2" \
        -e "s|^\($s\)\($w\)$s:$s\(&$w\)\?$s{$s\(.*\)$s}|\1\2: \3\n\1$i\4|;p" | \
    sed  -e "s|^\($s\)\($w\)$s:$s\(&$w\)\(.*\)|\1\2:\4\n\3|" \
        -e "s|^\($s\)-$s\(&$w\)\(.*\)|\1- \3\n\2|" | \
    sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)\(---\)\($s\)||" \
        -e "s|^\($s\)\(\.\.\.\)\($s\)||" \
        -e "s|^\($s\)-$s[\"']\(.*\)[\"']$s\$|\1$fs$fs\2|p;t" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p;t" \
        -e "s|^\($s\)-$s\(.*\)$s\$|\1$fs$fs\2|" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\?\(.*\)$s\$|\1$fs\2$fs\3|" \
        -e "s|^\($s\)[\"']\?\([^&][^$fs]\+\)[\"']$s\$|\1$fs$fs$fs\2|" \
        -e "s|^\($s\)[\"']\?\([^&][^$fs]\+\)$s\$|\1$fs$fs$fs\2|" \
        -e "s|$s\$||p" | \
    awk -F$fs "{
        gsub(/\t/,\"        \",\$1);
        gsub(\"name: \", \"\");
        if(NF>3){if(value!=\"\"){value = value \" \";}value = value  \$4;}
        else {
        if(match(\$1,/^&/)){anchor[substr(\$1,2)]=full_vn;getline};
        indent = length(\$1)/length(\"$i\");
        vname[indent] = \$2;
        value= \$3;
        for (i in vname) {if (i > indent) {delete vname[i]; idx[i]=0}}
        if(length(\$2)== 0){  vname[indent]= ++idx[indent] };
        vn=\"\"; for (i=0; i<indent; i++) { vn=(vn)(vname[i])(\"$separator\")}
        vn=\"$prefix\" vn;
        full_vn=vn vname[indent];
        if(vn==\"$prefix\")vn=\"$prefix$separator\";
        if(vn==\"_\")vn=\"__\";
        }
        assignment[full_vn]=value;
        if(!match(assignment[vn], full_vn))assignment[vn]=assignment[vn] \" \" full_vn;
        if(match(value,/^\*/)){
            ref=anchor[substr(value,2)];
            if(length(ref)==0){
            printf(\"%s=\\\"%s\\\"\n\", full_vn, value);
            } else {
            for(val in assignment){
                if((length(ref)>0)&&index(val, ref)==1){
                    tmpval=assignment[val];
                    sub(ref,full_vn,val);
                if(match(val,\"$separator\$\")){
                    gsub(ref,full_vn,tmpval);
                } else if (length(tmpval) > 0) {
                    printf(\"%s=\\\"%s\\\"\n\", val, tmpval);
                }
                assignment[val]=tmpval;
                }
            }
        }
    } else if (length(value) > 0) {
        printf(\"%s=\\\"%s\\\"\n\", full_vn, value);
    }
    }END{
        for(val in assignment){
            if(match(val,\"$separator\$\"))
                printf(\"%s=\\\"%s\\\"\n\", val, assignment[val]);
        }
    }"

}
function cert_checkPrivateIp() {

    local ip="$1"
    common_logger -d "Checking if ${ip} is private."

    # Check private IPv4 ranges
    if [[ $ip =~ ^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^(127\.) ]]; then
        return 0
    fi

    # Check private IPv6 ranges (fc00::/7 prefix), link-local (fe80::/10), and loopback (::1)
    if [[ $ip =~ ^(fc|fd) ]] || [[ $ip =~ ^fe[89abAB] ]] || [[ $ip == "::1" ]]; then
        return 0
    fi

    return 1

}
function cert_isIPv4() {

    local ip="$1"
    [[ ${ip} =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]

}
function cert_isIPv6() {

    local ip="$1"
    [[ ${ip} =~ ^(([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|([0-9A-Fa-f]{1,4}:){1,7}:|:([0-9A-Fa-f]{1,4}:){1,7}|([0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|([0-9A-Fa-f]{1,4}:){1,5}(:[0-9A-Fa-f]{1,4}){1,2}|([0-9A-Fa-f]{1,4}:){1,4}(:[0-9A-Fa-f]{1,4}){1,3}|([0-9A-Fa-f]{1,4}:){1,3}(:[0-9A-Fa-f]{1,4}){1,4}|([0-9A-Fa-f]{1,4}:){1,2}(:[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:((:[0-9A-Fa-f]{1,4}){1,6})|::)$ ]]

}
function cert_isIP() {

    local ip="$1"
    cert_isIPv4 "${ip}" || cert_isIPv6 "${ip}"

}
function cert_isDNS() {

    local dns="$1"
    if ! cert_isIP "${dns}" && [[ ${dns} =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$ ]]; then
        return 0
    fi
    return 1

}
function cert_validateComponentSanValues() {

    local component_name="$1"
    local node_names_var="$2"
    local node_ip_prefix="$3"
    local node_dns_prefix="$4"
    local i
    local j

    # Use nameref for safe dynamic array access
    declare -n component_node_names="${node_names_var}"

    for i in "${!component_node_names[@]}"; do
        j=$((i+1))
        # Use namerefs for dynamic array names
        declare -n component_ip="${node_ip_prefix}_${j}"
        declare -n component_dns="${node_dns_prefix}_${j}"

        if [ "${#component_ip[@]}" -eq 0 ] && [ "${#component_dns[@]}" -eq 0 ]; then
            common_logger -e "${component_name} node ${component_node_names[$i]} requires at least one field: ip or dns."
            exit 1
        fi

        for ip in "${component_ip[@]}"; do
            if ! cert_isIP "${ip}"; then
                common_logger -e "Invalid IP in field ip for ${component_name,,} node ${component_node_names[$i]}: ${ip}."
                exit 1
            fi
            if ! cert_checkPrivateIp "$ip"; then
                common_logger -e "The IP ${ip} is public."
                exit 1
            fi
        done

        for dns in "${component_dns[@]}"; do
            if ! cert_isDNS "${dns}"; then
                common_logger -e "Invalid DNS in field dns for ${component_name,,} node ${component_node_names[$i]}: ${dns}."
                exit 1
            fi
        done
    done

}
function cert_validateComponentDuplicatedValues() {

    local component_name="$1"
    local node_names_var="$2"
    local node_ips_var="$3"
    local node_dns_prefix="$4"
    local i
    local j

    # Use namerefs for safe dynamic array access
    declare -n component_node_names="${node_names_var}"
    declare -n component_node_ips="${node_ips_var}"
    declare -a component_node_dns=()

    for i in "${!component_node_names[@]}"; do
        j=$((i+1))
        # Use nameref for dynamic DNS array
        declare -n node_dns="${node_dns_prefix}_${j}"
        if [ "${#node_dns[@]}" -gt 0 ]; then
            component_node_dns+=("${node_dns[@]}")
        fi
    done

    unique_names=($(echo "${component_node_names[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
    if [ "${#unique_names[@]}" -ne "${#component_node_names[@]}" ]; then
        common_logger -e "Duplicated ${component_name,,} node names."
        exit 1
    fi

    unique_ips=($(echo "${component_node_ips[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
    if [ "${#unique_ips[@]}" -ne "${#component_node_ips[@]}" ]; then
        common_logger -e "Duplicated ${component_name,,} node ips."
        exit 1
    fi

    unique_dns=($(echo "${component_node_dns[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
    if [ "${#unique_dns[@]}" -ne "${#component_node_dns[@]}" ]; then
        common_logger -e "Duplicated ${component_name,,} node dns."
        exit 1
    fi

}
function cert_validateManagerNodeTypes() {

    for i in "${manager_node_types[@]}"; do
        if ! echo "$i" | grep -ioq master && ! echo "$i" | grep -ioq worker; then
            common_logger -e "Incorrect node_type $i must be master or worker"
            exit 1
        fi
    done

    if [ "${#manager_node_names[@]}" -le 1 ]; then
        if [ "${#manager_node_types[@]}" -ne 0 ]; then
            common_logger -e "The tag node_type can only be used with more than one Wazuh manager."
            exit 1
        fi
    elif [ "${#manager_node_names[@]}" -gt "${#manager_node_types[@]}" ]; then
        common_logger -e "The tag node_type needs to be specified for all Wazuh manager nodes."
        exit 1
    elif [ "${#manager_node_names[@]}" -lt "${#manager_node_types[@]}" ]; then
        common_logger -e "Found extra node_type tags."
        exit 1
    elif [ "$(grep -io master <<< "${manager_node_types[*]}" | wc -l)" -ne 1 ]; then
        common_logger -e "Wazuh cluster needs a single master node."
        exit 1
    elif [ "$(grep -io worker <<< "${manager_node_types[*]}" | wc -l)" -ne $(( ${#manager_node_types[@]} - 1 )) ]; then
        common_logger -e "Incorrect number of workers."
        exit 1
    fi

}
function cert_readConfig() {

    common_logger -d "Reading configuration file."

    if [ -f "${config_file}" ]; then
        if [ ! -s "${config_file}" ]; then
            common_logger -e "File ${config_file} is empty"
            exit 1
        fi
        # Convert CRLF to LF without eval
        cert_convertCRLFtoLF "${config_file}"

        # Use mapfile for safe array assignment (prevents command injection)
        mapfile -t indexer_node_names < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+indexer[_]+[0-9]+=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        mapfile -t manager_node_names < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+manager[_]+[0-9]+=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        mapfile -t dashboard_node_names < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+dashboard[_]+[0-9]+=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        mapfile -t indexer_node_ips < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+indexer[_]+[0-9]+[_]+ip=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        mapfile -t manager_node_ips < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+manager[_]+[0-9]+[_]+ip=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        mapfile -t dashboard_node_ips < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+dashboard[_]+[0-9]+[_]+ip=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        mapfile -t manager_node_types < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+manager[_]+[0-9]+[_]+node_type=" | cut -d = -f 2 | sed 's/^"//;s/"$//')

        # Parse DNS entries for each indexer node
        for i in "${!indexer_node_names[@]}"; do
            j=$((i+1))
            # Create dynamic arrays using declare and mapfile
            mapfile -t "indexer_node_ip_${j}" < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+indexer[_]+${j}[_]+ip=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
            mapfile -t "indexer_node_dns_${j}" < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+indexer[_]+${j}[_]+dns([_]+[0-9]+)?=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        done

        # Parse DNS entries for each dashboard node
        for i in "${!dashboard_node_names[@]}"; do
            j=$((i+1))
            mapfile -t "dashboard_node_ip_${j}" < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+dashboard[_]+${j}[_]+ip=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
            mapfile -t "dashboard_node_dns_${j}" < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+dashboard[_]+${j}[_]+dns([_]+[0-9]+)?=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        done

        for i in $(seq 1 "${#manager_node_names[@]}"); do
            mapfile -t "manager_node_ip_$i" < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+manager[_]+${i}[_]+ip=" | cut -d = -f 2 | sed 's/^"//;s/"$//' | sed -r 's/\s+//g')
            mapfile -t "manager_node_dns_$i" < <(cert_parseYaml "${config_file}" | grep -E "nodes[_]+manager[_]+${i}[_]+dns([_]+[0-9]+)?=" | cut -d = -f 2 | sed 's/^"//;s/"$//')
        done

        cert_validateComponentSanValues "Indexer" "indexer_node_names" "indexer_node_ip" "indexer_node_dns"
        cert_validateComponentSanValues "Manager" "manager_node_names" "manager_node_ip" "manager_node_dns"
        cert_validateComponentSanValues "Dashboard" "dashboard_node_names" "dashboard_node_ip" "dashboard_node_dns"

        cert_sanitizeNodeName "Indexer" "indexer_node_names"
        cert_sanitizeNodeName "Manager" "manager_node_names"
        cert_sanitizeNodeName "Dashboard" "dashboard_node_names"

        cert_validateComponentDuplicatedValues "Indexer" "indexer_node_names" "indexer_node_ips" "indexer_node_dns"
        cert_validateComponentDuplicatedValues "Wazuh manager" "manager_node_names" "manager_node_ips" "manager_node_dns"
        cert_validateComponentDuplicatedValues "Dashboard" "dashboard_node_names" "dashboard_node_ips" "dashboard_node_dns"

        cert_validateManagerNodeTypes

    else
        common_logger -e "No configuration file found."
        exit 1
    fi

}
function cert_setpermisions() {
    # Validate cert_tmp_path before setting permissions
    if ! cert_validatePath "${cert_tmp_path}" "directory"; then
        common_logger -e "Invalid certificate temporary path."
        return 1
    fi

    if [ -n "${debugEnabled}" ]; then
        chmod -R 744 "${cert_tmp_path}"
    else
        chmod -R 744 "${cert_tmp_path}" > /dev/null 2>&1
    fi
}
function cert_convertCRLFtoLF() {
    local config_file_path="$1"
    local temp_dir="/tmp/wazuh-install-files"

    # Validate input file path
    if ! cert_validatePath "${config_file_path}" "file"; then
        common_logger -e "Invalid config file path."
        return 1
    fi

    # Create temp directory if it doesn't exist
    if [[ ! -d "${temp_dir}" ]]; then
        if [ -n "${debugEnabled}" ]; then
            mkdir "${temp_dir}"
        else
            mkdir "${temp_dir}" > /dev/null 2>&1
        fi
    fi

    # Set permissions on temp directory
    if [ -n "${debugEnabled}" ]; then
        chmod -R 755 "${temp_dir}"
    else
        chmod -R 755 "${temp_dir}" > /dev/null 2>&1
    fi

    # Convert CRLF to LF
    tr -d '\015' < "${config_file_path}" > "${temp_dir}/new_config.yml"

    # Move converted file back
    if [ -n "${debugEnabled}" ]; then
        mv "${temp_dir}/new_config.yml" "${config_file_path}"
    else
        mv "${temp_dir}/new_config.yml" "${config_file_path}" > /dev/null 2>&1
    fi
}

main "$@"
