#!/bin/bash

set -e

current_dir=$(dirname "$(readlink -f "$0")")

red=$(tput setaf 1)
green=$(tput setaf 2)
reset=$(tput sgr 0)
# ------------------ Сохранение аргументов в лог -----------
args="$*"

date_local=$(date '+%Y-%m-%d %H:%M:%S')

args_log_path="$current_dir/args-eltex-ems-helper-docker.log"
# ------------------ Установка переменных ------------------
# Public Eltex production docker registry
ELTEX_PUBLIC_REGISTRY="hub.eltex-co.ru/softwlc"
ELTEX_PRIVATE_REGISTRY="secret"
# Переменная, которая является рабочей. Внутри скрипта работа идёт с ней в зависимости от параметров вызова скрипта
ELTEX_DOCKER_REGISTRY=${ELTEX_PUBLIC_REGISTRY}
# Список env файлов для docker compose
COMPOSE_ENV_FILES=.env,.env.override

# swlc
SOFTWLC_VERSION="1.36"
certs_src_dir="$current_dir/volumes/eltex-radius-nbi/var/lib/eltex-radius-nbi"
WAIT_BETWEEN_STEPS=5

ems_volume_path="$current_dir/volumes/eltex-ems/"
ems_logs_path="$current_dir/volumes/logs/eltex-ems/"
backup_dir="./backup"

doors_keys_dir="$current_dir/data/eltex-doors/etc/eltex-doors/keys"
pub_key_path="$doors_keys_dir/public.pem"
priv_key_path="$doors_keys_dir/private.pem"

need_pull=false
need_clean=false
need_download=false
need_run=false
need_stop=false
need_configure=false
need_delete_containers=false

help="
Options:
  --backup                              backup all files in current directory to archive. Default directory $current_dir/backup
  --clean, -c                           clean containers with its data
  --configure                           use for first start; configure databases and environment for application
  --delete-containers                   delete containers
  --download, -d                        download and export application archive with project
  --emsip <ADDRESS>                     set IPv4 server address in management net. Necessary to work with APs and other devices,
                                          by protocols TFTP, FTP, HTTP, AirTune etc
                                          This address will be written by script to database in properties of service 'eltex_ems'
  --generate-keys                       generate new keys for services and restart them
  --help, -h                            help
  --install                             install docker, docker-compose to host
  --pull, -p                            pull images before launching compose
  --private                             used only with --run and --download; for changing repo to local
  --recreate-service <SERVICE>          recreates specified service
  --serverip <ADDRESS>                  set IPv4 server address, for link between eltex-wifi-cab and eltex-portal-constructor
                                          127.0.0.1 is restricted
  --run, -r                             run compose application
  --show-containers                     lists all containers
  --show-images                         lists all images
  --stop                                stop containers
  --test-ports                          check if the application ports are open

"
# ----------------- Проверка диситрибутива -----------------
DISTR_ID=$(lsb_release -i --short)

function install_docker_ubuntu() {
    echo "Install docker for Ubuntu"
    require_package curl conntrack
    echo "Remove unofficial docker versions..."
    apt remove docker docker-engine docker.io containerd runc || true
    DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
    rm -f $DOCKER_CONFIG/cli-plugins/docker-compose
    echo -e "\nInstall official docker and docker compose..."
    curl -fsSL https://get.docker.com -o /tmp/get-docker.sh
    sh /tmp/get-docker.sh
    systemctl enable docker
    systemctl restart docker
    echo -e "${green}\nInstall is complete${reset}"
}

function install_docker_redos() {
    echo "Install docker for REDOS"
    echo "Remove unofficial docker-compose versions..."
    dnf remove docker-compose-* -y
    dnf install -y docker-ce git wget curl conntrack
    echo -e "\nInstall official docker compose..."
    export DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
    mkdir -p $DOCKER_CONFIG/cli-plugins
    curl -SL https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
    chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
    chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
    systemctl enable docker
    systemctl restart docker
    echo -e "${green}\nInstall is complete${reset}"
}

function install_docker_astralinux() {
    echo "Remove unofficial docker-compose versions..."
    apt remove docker-compose-* -y
    echo "Install docker for Astralinux"
    echo "Check configured Astrlinux deb repositories"
    apt update
    cached_docker_compose_version=$(apt-cache madison docker-compose | cut -d"|" -f2)
    expected_compose_version="1.29.2"

    # нужно проверить, какая версия docker-compose в кэше apt
    if [[ ${cached_docker_compose_version} < ${expected_compose_version} ]]; then
        echo "В репозитории старая версия docker-compose: $(cached_docker_compose_version)."
        echo "Для запуска проекта нужна версия $(expected_compose_version) или новее."

        echo "Добавить официальные публичные репозитории Astralinux и получить нужные версии оттуда?"
        read -r -p "[y/N] " response
        case "$response" in
        [yY][eE][sS] | [yY])
            repos=(
                "deb https://download.astralinux.ru/astra/stable/1.7_x86-64/repository-main/ 1.7_x86-64 main contrib non-free"
                "deb https://download.astralinux.ru/astra/stable/1.7_x86-64/repository-update/ 1.7_x86-64 main contrib non-free"
                "deb https://download.astralinux.ru/astra/stable/1.7_x86-64/repository-base/ 1.7_x86-64 main contrib non-free"
                "deb https://download.astralinux.ru/astra/stable/1.7_x86-64/repository-extended/ 1.7_x86-64 main contrib non-free"
            )
            ;;
        *)
            echo "Пожалуйста, подключите репозитории с версией docker. И повторите установку."
            echo "Или установите docker самостоятельно."
            exit 1
            ;;
        esac
    elif [ -z "$(apt-cache madison conntrack)" ]; then
        echo "В репозитории нет пакета conntrack."
        echo "Для запуска проекта нужен conntrack."

        echo "Добавить официальный публичный репозиторий Astralinux и получить пакет оттуда?"
        read -r -p "[y/N] " response
        case "$response" in
        [yY][eE][sS] | [yY])
            repos=(
                "deb https://download.astralinux.ru/astra/stable/1.7_x86-64/repository-extended/ 1.7_x86-64 main contrib non-free"
            )
            ;;
        *)
            echo "Пожалуйста, подключите репозитории с версией docker. И повторите установку."
            echo "Или установите docker самостоятельно."
            exit 1
            ;;
        esac
    fi

    readarray -t target </etc/apt/sources.list

    for repo_index in "${!repos[@]}"; do
        for value in "${target[@]}"; do
            if [[ "${repos[$repo_index]}" == "$value" ]]; then
                unset repos["$repo_index"]
            fi
        done
    done

    if [ ${#repos[@]} -ne 0 ]; then
        # делаем бэкап
        SOURCE_BACKUP_FILE=/etc/apt/sources.list.backup_$(date +"%Y%m%dT%H%M%S")
        cp -p /etc/apt/sources.list ${SOURCE_BACKUP_FILE}
        echo "SourceList was copied to ${SOURCE_BACKUP_FILE}"
        for repo in "${repos[@]}"; do
            echo "$repo" >>/etc/apt/sources.list
        done
        echo "SourceList was updated"
        apt update
        cached_docker_compose_version_2=$(apt-cache madison docker-compose | cut -d"|" -f2)
        if [[ ${cached_docker_compose_version_2} < ${expected_compose_version} ]]; then
            echo "${red}Не удалось получить обновления из репозиториев. Возможно нет доступа в интернет.${reset}"
            exit 1
        fi
    fi

    apt install -y docker.io git wget curl conntrack
    echo -e "\nInstall official docker compose..."
    export DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
    mkdir -p $DOCKER_CONFIG/cli-plugins
    curl -SL https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
    chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
    chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
    systemctl enable docker
    systemctl restart docker
    echo -e "${green}\nInstall is complete${reset}"

}

# ------------------ Установка переменных ------------------

# ------------------ STOP ------------------
function stop_if_needed() {
    if ${need_stop}; then
        echo -e "Stopping services...\n"
        docker compose stop
        echo -e "${green}Services stopped\n${reset}"
    fi
}
# ------------------ STOP ------------------

# ------------------ DELETE CONTAINERS ------------------
function delete_containers_if_need() {
    if ${need_delete_containers}; then
        echo -e "Deleting containers\n"
        docker compose down --remove-orphans
        echo -e "${green}EMS containers have been removed\n${reset}"
    fi
}
# ------------------ DELETE CONTAINERS ------------------

# ------------------ CLEAN ------------------
function clean_if_needed() {
    if ${need_clean}; then
        echo -e "Cleaning EMS...\n"
        docker compose down -v --remove-orphans
        echo -e "${green}EMS stopped and removed\n${reset}"
    fi
}
# ------------------ CLEAN ------------------

# ------------------ PULL ------------------
function pull_if_needed() {
    if ${need_pull}; then
        echo -e "Pulling images...\n"
        docker compose pull
        echo -e "${green}Images have been pulled\n${reset}"
    fi
}
# ------------------ PULL ------------------

# ------------------ DOWNLOAD ------------------
function download_if_needed() {
    if ! ${need_download}; then
        return
    fi
    echo -n "If you already have configurations it will be replaced, are you sure you want to continue ? [y/N] "
    read ans
    if [ "$ans" = "y" ] || [ "$ans" = "Y" ] || [ "$ans" = "Д" ] || [ "$ans" = "д" ]; then
        change_repo
        download_archives
    else
        echo "${red}Downloading aborted!${reset}"
    fi
}

function download_archives() {
    # pull the newest docker image from registry
    img="$ELTEX_DOCKER_REGISTRY/eltex-softwlc-docker:$SOFTWLC_VERSION"
    docker pull "$img"
    # run docker image
    docker run --rm -it -v "$current_dir:/opt/softwlc-docker" "$img"
    echo "${green}Downloading has been completed!${reset}"
}
# ------------------ DOWNLOAD ------------------

# ------------------ RUN ------------------
function run_if_needed() {
    if ! ${need_run}; then
        return
    fi
    change_repo
    source .env
    source .env.override
    source $current_dir/environment/eltex-mariadb.env
    source $current_dir/environment-overrides/eltex-mariadb.env
    #before starts docker, because of mount
    if ${need_configure}; then
        configure_common_env
    else
        #simple --run, we have to check ip settings
        check_ip_env
    fi
    run_databases
    if ${need_configure}; then
        setup_swlc_addresses
        configure_databases
    fi
    run_ems
    check_ports_wrap
    print_finished_message
}

function run_databases() {
    # Останавливаем контейнеры eltex-mysql в связи с переходом на eltex-mariadb
    if [ "$(docker ps -a -q -f name="eltex-mysql")" ]; then
        echo "Stop mysql container"
        docker stop eltex-mysql
    fi

    echo "Starting database..."
    docker compose up -d --remove-orphans eltex-mariadb
    # Нужно подождать, чтобы бд точно встала
    echo "Waiting 15 seconds.."
    sleep 15
    echo -e "${green}Database has been started\n${reset}"

    echo "MariaDB upgrading..."
    docker exec eltex-mariadb mariadb-upgrade --upgrade-system-tables "-u${MYSQL_ROOT_USER}" "-p${MYSQL_ROOT_PASSWORD}"

    # запуск миграции при старте контейнеров
    docker exec eltex-mariadb /opt/softwlc/run-deploy-db.sh
}

function run_ems() {
    echo "Starting EMS..."
    docker compose up -d --remove-orphans eltex-ems-core eltex-ems-vsftpd eltex-ems-tftpd eltex-syslog-ng eltex-cron
    echo -e "${green}EMS has been started\n${reset}"
}

function print_finished_message() {
    # Всё
    echo ""
    echo "Installation of Eltex EMS finished successfully."
    echo ""
    echo ""
    echo "URLs of EMS components:

    Eltex.EMS Server management (internal) IP: $EMS_IP
    Eltex.EMS Server external IP: $SERVER_IP

    Eltex.EMS GUI: http://$SERVER_IP:8087/ems/jws
        login: admin
        password: <empty>"

    exit 0
}
# ------------------ RUN ------------------

# ------------------ CONFIGURE ------------------
function configure_databases() {
    echo "Configure databases..."
    docker exec eltex-mariadb sh -c "mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} < /opt/softwlc/create-syslog.sql"
    sleep ${WAIT_BETWEEN_STEPS}
    configure_ems_database
    echo -e "${green}Databases has been configured\n${reset}"
}

function configure_ems_database() {

    # Обновление блока конфигурации EMS сервера в БД internal IP
    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('ftpserver', 'ftp.addr', '$ems_ip');"
    docker exec eltex-mariadb mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('gPon', 'gpon.ont.tftp.host', '$ems_ip');"
    docker exec eltex-mariadb mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('gePon', 'tftp_host_no_vlan', '$ems_ip');"
    docker exec eltex-mariadb mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('system', 'ip.adress', '$ems_ip');"
    docker exec eltex-mariadb mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('tftpserver', 'tftp.addr', '$ems_ip');"
    docker exec eltex-mariadb mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('system', 'tomcat.urlembeded', 'http://$ems_ip:8080');"
    docker exec eltex-mariadb mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    echo "Database eltex_ems for internal EMS IP '$ems_ip' updated"

    # Задать в конфигурации сервера EMS внешний Tomcat URL
    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('system', 'tomcat.url', 'http://$external_ip:8080');"
    docker exec eltex-mariadb mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    echo "${green}Database eltex_ems for external EMS IP '$external_ip' updated${reset}"
}

function configure_common_env() {
    echo "Create EMS user: UID=100001,GID=100001"
    getent group 100001 >/dev/null || groupadd --system --gid 100001 eltex-ems
    getent passwd eltex-ems >/dev/null || useradd --uid 100001 --gid 100001 --system --shell /sbin/nologin -c "EMS user" eltex-ems

    if ! [ -d "$ems_logs_path" ]; then
        mkdir -p "$ems_logs_path"
    fi

    chown -R 100001:100001 "$ems_volume_path" "$ems_logs_path"

    tftpboot_dir="$current_dir/volumes/eltex-ems/tftpboot"
    mkdir -p $tftpboot_dir
    chown -R 100001:100001 $tftpboot_dir

    echo "${green}Common environment has been created EMS${reset}"
}

# ------------------ CONFIGURE ------------------

# ------------------ SETUP ------------------
function setup_swlc_addresses() {
    # Получаем список адресов, что есть на данном сервере
    find_external_ips
    # Показываем пользователю какие ip мы нашли
    for index in "${!EXTERNAL_IPS[@]}"; do
        echo "$((index + 1)). ${EXTERNAL_IPS[index]}"
    done

    # Валидация с реальным списком адресов на интерфейсах. Если ошибка, то выход из скрипта.
    validate_server_ip_from_args $external_ip
    validate_server_ip_from_args $ems_ip

    sed -i "s|EMS_IP=.*|EMS_IP=${ems_ip}|" "$current_dir/.env"
    sed -i "s|SERVER_IP=.*|SERVER_IP=${external_ip}|" "$current_dir/.env"
    # подтягиваем заново переменные в окружение системы с их значениями
    source .env
    source .env.override
}

# Делает валидацию аргумента и прерывает выполнение скрипта.
# Если адрес не валиден или '127.0.0.1', или его нет среди адресов интерфейсов сервера,
# то скрипт прерывается с ошибкой
function validate_server_ip_from_args() {
    # Формальная проверка на правильность адреса
    if ! valid_ipv4 $1; then
        echo "${red}Server IP addr argument is invalid: '$1', script aborted${reset}"
        exit 1
    fi

    # Проверка, что это не localhost (так как этот адрес приведёт пользователя браузера на собственный ПК)
    if [[ $1 == "127.0.0.1" || $1 == "::1" ]]; then
        echo "${red}Cannot assign localhost IP '$1' to server IP address; script aborted${reset}"
        exit 1
    fi

    local found_var=0

    # Проводим контроль, что юзер передал один из реальных адресов сервера
    for index in "${!EXTERNAL_IPS[@]}"; do
        if [[ $1 == ${EXTERNAL_IPS[index]} ]]; then
            found_var=1
            break
        fi
    done

    if ((found_var == 0)); then
        echo "${red}Address not found on interfaces : '$1', script aborted${reset}"
        exit 1
    fi
}

# Валидация IP адреса через regex
function valid_ipv4() {
    local ip=$1
    local stat=1

    if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        OIFS=$IFS
        IFS='.'
        ip=($ip)
        IFS=$OIFS
        [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]

        stat=$?
    fi
    return $stat
}

# Заполнить массив EXTERNAL_IPS внешними IP сервера
# Для IPv4 — весь ifconfig, кроме 127.0.0.1
function find_external_ipv4_ips() {
    # Debian & U20 не содержат утилиту ifconfig (net-tools) в базовой поставке
    # local ifconfig="$(ifconfig | grep -Po '(?<=inet\s)[^\s]*')"
    local ipv4_addresses=$(ip -4 addr | grep inet | awk -F '[ \t]+|/' '{print $3}' | grep -v ^127.0.0.1)
    for address in ${ipv4_addresses}; do
        # Контроль, что адрес получился валидным. Если нет, то пробуем дополнительно очистить
        if ! valid_ipv4 $address; then
            address="$(echo $address | grep -oE '((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])')"
        fi
        # Если после очистки всё ОК, то включаем строку в выходной массив, за исключением localhost (127.0.0.1)
        if (valid_ipv4 $address) && [[ ! (${address} == "127.0.0.1") ]]; then
            EXTERNAL_IPS+=($address)
        fi
    done
}

# Заполнить массив EXTERNAL_IPS внешними IP сервера
function find_external_ips() {
    EXTERNAL_IPS=()
    find_external_ipv4_ips
}
# ------------------ SETUP ------------------

# ------------------ SETUP OPTIONS ------------------
function show_containers() {
    docker container ls --format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}\t{{.Ports}}"
}

function show_images() {
    local imageIds

    imageIds=($(docker compose images -q))
    imageIds+=($(docker compose images -q))

    if [[ ${#imageIds[@]} == 0 ]]; then
        return
    fi

    # оставить от id первые 12 символов
    for ((i = 0; i < ${#imageIds[@]}; i++)); do
        imageIds[i]=${imageIds[i]::12}
    done

    imageIds=$(perl -le 'print join "|",@ARGV' "${imageIds[@]}")
    # всегда выводить первую строку с именами столбцов
    docker images -a | (
        read line
        echo "$line"
        grep -P $imageIds
    )
}

function recreate_service() {
    local service=${1:?service name is missing}
    local config
    local recreated=false
    local running

    echo "Recreating service..."

    # пересоздать сервис
    config=$(docker compose config)
    if echo "$config" | grep -qP "^\s+$service:$"; then
        recreated=true
        running=$(docker compose ps --services --filter status=running)

        # если сервис запущен
        if echo "$running" | grep -Fxq $service; then
            docker compose up --no-deps --force-recreate -d $service
        else
            docker compose up --no-deps --force-recreate --no-start $service
        fi
    fi

    if [[ $recreated == false ]]; then
        echo "${red}Did not find any service with specified name${reset}"
        exit 1
    fi

    echo "${green}Service recreated${reset}"
}

function generate_keys() {
    generate_jwt_doors
    # нужно поднянуть login/pass для подключения к БД
    source $current_dir/environment/eltex-mariadb.env
    recreate_service eltex-ems-core
}

function backup() {
    mkdir -p "$backup_dir"
    tar --exclude="$backup_dir" -C $current_dir -czf $backup_dir/backup_$(date +"%Y%m%dT%H%M%S").tar.gz ./
    echo "${green}Backup has been completed!${reset}"
}
# ------------------ SETUP OPTIONS ------------------

function print_help() {
    echo "${help}"
}

function check_env() {
    if ! command -v docker &>/dev/null || ! command -v docker compose &>/dev/null; then
        if [[ ! "${@}" =~ "--install" ]]; then
            echo "${red}docker/docker-compose is not installed. Run \`./eltex-ems-helper-docker_<VERSION>.sh --install\`${reset}"
            exit 1
        fi
    fi
}

check_memory() {
    ram=$(grep MemTotal /proc/meminfo | awk '{printf "%d", $2 / 1000}')

    if [[ $ram -lt 8000 ]]; then
        echo "${red}You have $ram MB of RAM. You need 8 GB of RAM and 10 GB of free hard disk space, installation aborted!${reset}"
        exit 1
    fi

    free_space=$(df / | grep / | awk '{printf "%d", $4 / 1000}')

    if [[ $free_space -lt 10000 ]]; then
        echo "${red}You have $free_space MB of free hard disk space. 10 GB required for installation. Installation aborted!${reset}"
        exit 1
    fi
}

function check_args() {
    if [[ $# -lt 1 ]]; then
        print_help
        exit 1
    fi
}

function check_ip_env() {
    if [[ -z ${EMS_IP} || -z ${SERVER_IP} ]]; then
        echo "${red}Variable EMS_IP or SERVER_IP is empty.${reset}" \
            "${red}Use script with flags --configure --emsip <IP> --serverip <IP>${reset}"
        exit 1
    fi
}

function check_ports_wrap() {
    #install check utils
    case ${DISTR_ID} in
    "RED SOFT") require_package iproute net-tools ;;
    "AstraLinux") require_package iproute2 net-tools ;;
    "Ubuntu") require_package iproute2 net-tools ;;
    esac

    # проверить открытые порты
    echo "Waiting 120 seconds.."
    echo -ne '##                        (10%)\r'
    sleep 12
    echo -ne '####                      (20%)\r'
    sleep 12
    echo -ne '######                    (30%)\r'
    sleep 12
    echo -ne '########                  (40%)\r'
    sleep 12
    echo -ne '##########                (50%)\r'
    sleep 12
    echo -ne '############              (60%)\r'
    sleep 12
    echo -ne '################          (70%)\r'
    sleep 12
    echo -ne '##################        (80%)\r'
    sleep 12
    echo -ne '####################      (90%)\r'
    sleep 12
    echo -ne '######################    (100%)\r'
    echo -ne '\n'

    # Проверка на то, что все сервисы успели подняться
    CHECK_COUNT=25
    echo "Checking services ports ($CHECK_COUNT attempts for check)"
    check_all_ports "$CHECK_COUNT"

    # Ждём ещё 10 секунд, т.к. между инициализацией порта 9310 и SNMP-engine проходит долгое время внутри EMS
    echo "Waiting 10 seconds.."
    sleep 10
    check_tcp_and_udp_ports
}

# Метод проверки открытых портов. Количество попыток проверки передается как входной аргумент.
# Задержка между попытками - 5 секунд.
# Если по завершении проверки какой-либо из портов до сих пор закрыт - функция завершит выполнение с кодом 2.
# Вызов функции: check_all_ports "25", где первый аргумент - максимальное количество попыток проверки.
function check_all_ports() {
    local __CHECK_COUNT=$1

    declare -A service_ports_to_check
    declare -A service_ports_failed_results
    service_ports_to_check=(
        ['eltex-ems']='9310'
        ['eltex-ems-snmp-api']='162'
    )

    CHECK_PORTS_PASSED="1"
    for i in $(seq 1 ${__CHECK_COUNT}); do
        echo "Attempt ${i}/${__CHECK_COUNT}"
        for service in "${!service_ports_to_check[@]}"; do
            check_port "${service_ports_to_check[$service]}" check_port_result
            if [[ "$check_port_result" == "1" ]]; then
                service_ports_failed_results[$service]=$check_port_result
            else
                unset service_ports_to_check[$service]
                unset service_ports_failed_results[$service]
            fi
        done
        echo
        if [[ "${#service_ports_failed_results[@]}" != "0" ]]; then
            sleep 5
        else
            CHECK_PORTS_PASSED="0"
            break
        fi
    done
    if [[ "$CHECK_PORTS_PASSED" != "0" ]]; then
        for service in "${!service_ports_failed_results[@]}"; do
            echo "${red}Component ${service} out of service (${service_ports_to_check[${service}]} not opened)${reset}"
        done
        exit 2
    fi
}

# Контроль открытого порта (без контроля службы)
# Вызов функции: check_port "8080" result_var
# В результат будет помещён 0 - OK (порт открыт) или 1 - Ошибка (порта нет)
function check_port() {
    local __resultvar=$2
    local myresult='0'
    if [[ $(netstat -pna | grep -s ":$1") ]]; then
        echo "${green}Checking port '$1' - passed${reset}"
        myresult='0'
    else
        echo "${red}Checking port '$1' - error${reset}"
        myresult='1'
    fi
    eval ${__resultvar}="'$myresult'"
}

# Функция для финальной проверки всех портов однохостовой инсталляции
# Считает количество фатальных ошибок (не все порты критичные)
# Если количество фатальных ошибок больше нуля, то после отчёта скрипт прервётся и выйдет с "-1"
function check_tcp_and_udp_ports() {
    local FATAL=0

    echo ""
    echo "Start TCP/UDP port checking for all services.."

    # == EMS ==
    check_port_service_tcp "9310" "docker-proxy" "EMS-server Applet API " var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    check_port_service_udp "162" "docker-proxy" "EMS server SNMP API" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # Финальный контроль фатальных ошибок
    if [ $FATAL != 0 ]; then
        echo "${red}Found $FATAL port errors, script aborted!${reset}"
        exit 2
    else
        echo "${green}> Main core ports are tested successfully!${reset}"
    fi
    echo ""
}

# Контроль открытого порта для определённой службы в режиме UDP (нет LISTEN)
# Пример вызова функции: check_port_service_udp "8080" "nginx" "nginx" result_var
# $1 - номер порта (строкой);
# $2 - название сервиса (java - для всех java приложений);
# $3 - описание сервиса, например 'Captive Portal' для Java приложения;
# $4 - В результат будет помещена строка: "0" - это OK (порт открыт) или "1" - это ошибка (порта нет)
function check_port_service_udp() {
    local __resultvar="$4"
    local myresult=0
    local port=":$1"
    local service="$2"
    local service_descr="$3"
    local variable=$(ss -unlp | awk '{if (($1 == "UNCONN" || $1 == "ESTAB") && $4 ~ "'"$port"'" && ($6 ~ /'"$service"'/ || $6 ~ /docker-proxy/)) print "passed"}')
    if [[ $variable =~ "passed" ]]; then
        echo "${green}Checking udp port '$1' for application '$2' ($service_descr) - passed${reset}"
        myresult="0"
    else
        # альтернативная команда для проверки портов
        local variable=$(ss -unlp | awk '{if (($1 ~ "UNCONN" || $1 ~ "ESTAB") && $3 ~ "'"$port"'" && ($5 ~ /'"$service"'/ || $5 ~ /docker-proxy/)) print "passed"}')
        if [[ $variable =~ "passed" ]]; then
            echo "${green}Checking tcp port '$1' for application '$2' ($service_descr) - passed${reset}"
            myresult='0'
        else
            echo "${red}Checking tcp port '$1' for application '$2' ($service_descr) - error${reset}"
            myresult='1'
        fi
    fi
    eval ${__resultvar}="'$myresult'"
}

# Контроль открытого порта для определённой службы в режиме TCP (+ контроль LISTEN)
# Пример вызова функции: check_port_service_tcp "8080" "nginx" "nginx" result_var
# $1 - номер порта (строкой);
# $2 - название сервиса (java - для всех java приложений) или 'docker-proxy' для схемы с контейнерами;
# $3 - описание сервиса, например 'Captive Portal' для Java приложения;
# $4 - В результат будет помещена строка: "0" - это OK (порт открыт) или "1" - это ошибка (порта нет)
function check_port_service_tcp() {
    local __resultvar="$4"
    local myresult='0'
    local port=":$1"
    local service="$2"
    local service_descr="$3"
    local variable=$(ss -tnlp | awk '{if ($1 == "LISTEN" && $4 ~ "'"$port"'" && ($6 ~ /'"$service"'/ || $6 ~ /docker-proxy/)) print "passed"}')
    if [[ $variable =~ "passed" ]]; then
        echo "${green}Checking tcp port '$1' for application '$2' ($service_descr) - passed${reset}"
        myresult='0'
    else
        echo "${red}Checking tcp port '$1' for application '$2' ($service_descr) - error${reset}"
        myresult='1'
    fi
    eval ${__resultvar}="'$myresult'"
}

# проверяет все перечисленные пакеты и устанавливает те, что не установлены.
function require_package() {
    pkgs=("$@")
    for pkg in "${pkgs[@]}"; do
        pkg_status=$(dpkg-query -W --showformat='${Status}' "$pkg" 2>/dev/null | true)
        if [ "$pkg_status" == "install ok installed" ]; then
            echo "$pkg is already installed"
        else
            echo "Installing $pkg..."
            if [[ ${DISTR_ID} == "RED SOFT" ]]; then
                dnf install -y "$pkg"
            else
                apt install -y "$pkg"
            fi
        fi
    done
}

# перезаписывает значение переменной окружения ELTEX_HUB в файле .env в соответствии с входными параметрами
function change_repo() {
    if [ -f "$current_dir/.env" ]; then
        sed -i "s|ELTEX_HUB=.*|ELTEX_HUB=${ELTEX_DOCKER_REGISTRY}|" "$current_dir/.env"
    fi
}

# Самое первое - это запрет работы не от root
if [[ $(id -u) -ne 0 ]]; then
    echo "${red}This script can only be run as root${reset}"
    exit 1
fi

echo "[$date_local] eltex-ems-helper-docker run with: $args" >>"$args_log_path"

check_args "$@"

export COMPOSE_ENV_FILES

while [[ $# -gt 0 ]]; do
    key=$1
    case ${key} in
    --install)
        DISTR_ID=$(lsb_release -i --short)

        case ${DISTR_ID} in
        "RED SOFT") install_docker_redos ;;
        "AstraLinux") install_docker_astralinux ;;
        "Ubuntu") install_docker_ubuntu ;;
        esac
        ;;
    -r | --run)
        need_run=true
        ;;
    -d | --download)
        need_download=true
        ;;
    --serverip)
        shift
        external_ip=$1
        ;;
    --emsip)
        shift
        ems_ip=$1
        ;;
    --configure)
        check_memory
        need_configure=true
        ;;
    --generate-keys)
        generate_keys
        ;;
    --delete-containers)
        need_delete_containers=true
        ;;
    -c | --clean)
        need_clean=true
        ;;
    -p | --pull)
        need_pull=true
        ;;
    -h | --help)
        print_help
        exit 0
        ;;
    --stop)
        need_stop=true
        ;;
    --show-containers)
        show_containers
        exit 0
        ;;
    --show-images)
        show_images
        exit 0
        ;;
    --recreate-service)
        shift
        recreate_service $1
        exit 0
        ;;
    --backup)
        backup
        exit 0
        ;;
    --test-ports)
        check_tcp_and_udp_ports
        ;;
    --private)
        ELTEX_DOCKER_REGISTRY="$ELTEX_PRIVATE_REGISTRY"
        ;;
    *)
        print_help
        exit 1
        ;;
    esac
    shift
done

check_env "$@"

stop_if_needed
delete_containers_if_need
clean_if_needed
pull_if_needed
download_if_needed
run_if_needed
