#!/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-softwlc-helper-docker.log"
# ------------------ Установка переменных ------------------
# Public Eltex production docker registry
ELTEX_PUBLIC_REGISTRY="hub.eltex-co.ru/softwlc"
# Private (internal) repo
# Docker registry для feature: nexus.eltex.loc:9005
# Docker registry для release: nexus.eltex.loc:9014
ELTEX_PRIVATE_REGISTRY="nexus.eltex.loc:9014"
# Переменная, которая является рабочей. Внутри скрипта работа идёт с ней в зависимости от параметров вызова скрипта
ELTEX_DOCKER_REGISTRY=${ELTEX_PUBLIC_REGISTRY}
# Список env файлов для docker compose
COMPOSE_ENV_FILES=.env,.env.override

# swlc
SOFTWLC_VERSION="1.29"
LOCAL_SECRET_FILE="$current_dir/data/eltex-wifi-cab/etc/eltex-wifi-cab/local_secret"
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"
    echo -e "\nInstallation requires privileged mode"
    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}SOFTWLC containers have been removed\n${reset}"
    fi
}
# ------------------ DELETE CONTAINERS ------------------

# ------------------ CLEAN ------------------
function clean_if_needed() {
    if ${need_clean}; then
        echo -e "Cleaning SOFTWLC...\n"
        docker compose down -v --remove-orphans
        echo -e "${green}SOFTWLC 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 $current_dir/environment/eltex-mysql.env
    #before starts docker, because of mount
    if ${need_configure}; then
        configure_common_env
        configure_doors_env
        configure_wifi-cab_env
        configure_radius_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_swlc
    check_ports_wrap
    print_finished_message
}

function run_databases() {
    echo "Starting databases..."
    docker compose up -d --remove-orphans eltex-mysql eltex-mongo
    sleep ${WAIT_BETWEEN_STEPS}
    echo -e "${green}Databases has been started\n${reset}"
}

function run_swlc() {
    echo "Starting SOFTWLC services..."
    docker compose up -d --remove-orphans
    echo -e "${green}SOFTWLC has been started\n${reset}"
}

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

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

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

    Portal constructor: http://$SERVER_IP:8080/epadmin
        login: $ANSWER_AUTH_SERVICE_ADMIN_USER
        password: $ANSWER_AUTH_SERVICE_ADMIN_PASSWORD

    Wi-Fi customer cabinet (B2B): http://$SERVER_IP:8080/wifi-cab
        login: $ANSWER_AUTH_SERVICE_ADMIN_USER
        password: $ANSWER_AUTH_SERVICE_ADMIN_PASSWORD"

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

# ------------------ CONFIGURE ------------------
function configure_databases() {
    echo "Configure databases..."
    docker exec eltex-mysql /opt/softwlc/run-deploy-db.sh
    docker exec eltex-mysql sh -c "mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} < /opt/softwlc/create-syslog.sql"
    docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "GRANT ALL PRIVILEGES ON *.* TO '${MYSQL_USER}'@'%';FLUSH PRIVILEGES;"
    docker compose up -d eltex-portal-constructor eltex-wifi-cab
    sleep ${WAIT_BETWEEN_STEPS}
    configure_ems_database
    configure_mongo
    configure_portal_database
    echo -e "${green}Databases has been configured\n${reset}"
}

function configure_ems_database() {
    set_local_key_for_ems

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

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('pcrf', 'pcrf.url', 'http://eltex-pcrf:7070');"
    docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('softwlc.nbi', 'nbi.url', 'http://eltex-radius-nbi:8080/axis2/services/RadiusNbiService?wsdl');"
    docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('wirelessCommon', 'wificab.url', 'http://$external_ip:8080/wifi-cab/');"
    docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('wirelessCommon', 'wids.service.url', 'http://eltex-wids:9095');"
    docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

    mysql_command="REPLACE INTO eltex_ems.PARAMS (param1, param2, value) VALUES ('airtune', 'airtune.api.host', 'eltex-airtune');"
    docker exec eltex-mysql 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-mysql 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_mongo() {
    #link between eltex-wifi-cab and eltex-portal-constructor
    mongo_command="db.config.update({}, {\$set: {\"portal.url\": \"http://$external_ip:9001/epadmin/\"}})"
    docker exec eltex-mongo mongo wifi-customer-cab --eval "$mongo_command"

    # to 7070 pcrf
    mongo_command="db.config.update({}, {\$set: {\"pcrf.nbi.url\": \"http://eltex-pcrf:7070\"}})"
    docker exec eltex-mongo mongo wifi-customer-cab --eval "$mongo_command"

    # to 8040 ngw
    mongo_command="db.config.update({}, {\$set: {\"view.settings.integration.ngw.notification.caption\": \"http://eltex-ngw:8040\"}})"
    docker exec eltex-mongo mongo wifi-customer-cab --eval "$mongo_command"

    # restart container for apply
    recreate_service eltex-wifi-cab
}

function configure_portal_database() {
    CHECK_COUNT=20
    echo "Try to create customer cabinet link in portal constructor ($((${CHECK_COUNT} * ${WAIT_BETWEEN_STEPS})) seconds for retry).."
    PROP_EXISTS="1"
    for i in $(seq 1 ${CHECK_COUNT}); do

        properties_table_exists=$(docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "SHOW TABLES FROM ELTEX_PORTAL LIKE 'properties';")
        if [[ -z "$properties_table_exists" ]]; then
                    echo "'ELTEX_PORTAL.properties' table does not exist yet. Waiting for 5 sec and trying again. Attempt ${i}/${CHECK_COUNT}"
                    sleep ${WAIT_BETWEEN_STEPS}
                    continue
        fi

        lines_in_table=$(docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -se "SELECT COUNT(group_id) FROM ELTEX_PORTAL.properties;")
        if [[ "$lines_in_table" -gt 0 ]]; then
            #link between eltex-wifi-cab and eltex-portal-constructor
            mysql_command="UPDATE ELTEX_PORTAL.properties SET value = \"$external_ip\" WHERE group_id = 7 AND name = \"host\";"
            docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

            # web portal. client to 9000
            mysql_command="UPDATE ELTEX_PORTAL.properties SET value = \"$external_ip\" WHERE group_id = 1 AND name = \"host\";"
            docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

            # to nbi 8080
            mysql_command="UPDATE ELTEX_PORTAL.properties SET value = \"eltex-radius-nbi\" WHERE group_id = 3 AND name = \"host\";"
            docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

            # to 8040 ngw
            mysql_command="UPDATE ELTEX_PORTAL.properties SET value = \"eltex-ngw\" WHERE group_id = 4 AND name = \"host\";"
            docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

            # to 7070 pcrf
            mysql_command="UPDATE ELTEX_PORTAL.properties SET value = \"eltex-pcrf\" WHERE group_id = 6 AND name = \"host\";"
            docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"

            # to 6565 mercury
            mysql_command="UPDATE ELTEX_PORTAL.properties SET value = \"eltex-mercury\" WHERE group_id = 10 AND name = \"host\";"
            docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "$mysql_command"
            echo "${green}Database ELTEX_PORTAL has been updated${reset}"

            PROP_EXISTS="0"
            break
        else
            echo "'ELTEX_PORTAL.properties' table does not has data yet. Waiting for 5 sec and trying again. Attempt ${i}/${CHECK_COUNT}"
            sleep ${WAIT_BETWEEN_STEPS}
        fi
    done

    if [[ "$PROP_EXISTS" != "0" ]]; then
        echo "${red}Data in table 'ELTEX_PORTAL.properties' doesn't exist${reset}"
        exit 2
    fi
}

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

    for service in "eltex-wifi-cab" "eltex-pcrf"; do
        mkdir -p "$current_dir/volumes/logs/$service/java-heap-dump"
        chown -R 100001:100001 "$current_dir/volumes/logs/$service"
    done

    for service in "eltex-portal" "eltex-portal-constructor"; do
        mkdir -p "$current_dir/volumes/logs/$service"
        chown -R 100 "$current_dir/volumes/logs/$service"
    done
    echo "${green}Common environment has been created EMS${reset}"
}

function set_local_key_for_ems() {
    echo "Set local key to database for EMS"
    while read -r line; do
        LOCAL_SECRET_FILE="$line"
    done <"$LOCAL_SECRET_FILE"

    docker exec eltex-mysql mysql -u${MYSQL_ROOT_USER} -p${MYSQL_ROOT_PASSWORD} -e "INSERT INTO eltex_ems.PARAMS (id, param1, param2, value) \
    VALUES(1, 'wirelessCommon', 'wificab.secret', '$LOCAL_SECRET_FILE') \
    ON DUPLICATE KEY UPDATE param1='wirelessCommon', param2='wificab.secret', value='$LOCAL_SECRET_FILE';"
    echo "Set local key has been done"
}

function configure_doors_env() {
    # generate JWT keys for eltex-doors
    if [[ ! -f "$pub_key_path" && ! -f "$priv_key_path" ]]; then
        generate_jwt_doors
    fi
}

function generate_jwt_doors() {
    require_package openssl
    echo -e "\n✵ Create Eltex Doors JWT keys"
    openssl genrsa -out "$doors_keys_dir/key.pem" 2048 2>/dev/null
    openssl pkcs8 -topk8 -inform PEM -outform PEM -in "$doors_keys_dir/key.pem" -out "$priv_key_path" -nocrypt
    openssl rsa -in "$doors_keys_dir/key.pem" -pubout -outform PEM -out "$pub_key_path" 2>/dev/null
    rm -f "$doors_keys_dir/key.pem" || true
}

function configure_wifi-cab_env() {
    if [[ ! -f "${LOCAL_SECRET_FILE}" ]]; then
        generate_local_secret
    fi
}

function generate_local_secret() {
    case ${DISTR_ID} in
    "RED SOFT") require_package coreutils util-linux ;;
    "AstraLinux") require_package coreutils bsdmainutils ;;
    "Ubuntu") require_package coreutils bsdmainutils ;;
    esac

    echo -e "\n✵ Local secret file not found. Generating"
    echo $(head -c 16 /dev/random | hexdump -e '"%02x"') >"$LOCAL_SECRET_FILE"
    echo -e "${green}\n✵ Local secret has been created${reset}"
}

function configure_radius_env() {
    if [[ ! -f "$certs_src_dir/setup_er_eap.env" ]]; then
        docker_image="${ELTEX_HUB}/eltex-radius-nbi:${SWLC_VERSION}"
        docker pull "$docker_image"

        docker run --rm -it \
            -v "$certs_src_dir:/var/lib/eltex-radius-nbi" \
            -e ANSWER_NBI_MAKE_SERVER_CERTIFICATE=1 \
            -e ANSWER_NBI_SERVER_CERTIFICATE_PERIOD=3650 \
            -e ANSWER_NBI_SERVER_CERTIFICATE_KEY="${SERVER_CERTIFICATE_KEY}" \
            --entrypoint /bin/bash \
            "$docker_image" \
            /usr/lib/eltex-radius-nbi/eltex-radius-ca.sh

        sed -n '3,/SERVER_KEY_PASSWORD/p' "$certs_src_dir/setup_er_eap.sh" >"$certs_src_dir/setup_er_eap.env"
        source "$certs_src_dir/setup_er_eap.env"

        certs_radius_dir="$current_dir/data/eltex-radius/etc/eltex-radius/certs"
        echo "$CA_CERT" >"$certs_radius_dir/ca/default.pem"
        chmod 644 "$certs_radius_dir/ca/default.pem"

        echo "$SERVER_CERT" >"$certs_radius_dir/server.crt"
        chmod 644 "$certs_radius_dir/server.crt"

        echo "$SERVER_KEY" >"$certs_radius_dir/server.key"
        chmod 644 "$certs_radius_dir/server.key"

        echo "${green}CA certificate, server certificate and server private key were updated successfully.${reset}"
    fi
}
# ------------------ 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
}

# Делает валидацию аргумента и прерывает выполнение скрипта.
# Если адрес не валиден или '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
    recreate_service eltex-doors eltex-johnny eltex-disconnect-service eltex-portal eltex-portal-constructor
    generate_local_secret
    # нужно поднянуть login/pass для подключения к БД
    source $current_dir/environment/eltex-mysql.env
    set_local_key_for_ems
    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-softwlc-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'
        ['eltex-pcrf']='7070'
        ['eltex-apb']='8090'
        ['eltex-portal']='9000'
        ['eltex-portal-constructor']='9001'
        ['eltex-wifi-cab']='8083'
        ['eltex-wids-service']='9095'
    )

    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

    # == tomcat ==
    check_port_service_tcp "8085" "docker-proxy" "tomcat (NBI)" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # == wifi-customer-cab spring-boot ==
    check_port_service_tcp "8083" "docker-proxy" "WiFi Customer Cab (Jetty)" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # == nginx ==
    check_port_service_tcp "8080" "docker-proxy" "Nginx proxy server" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # Captive Portal
    check_port_service_tcp "9000" "docker-proxy" "Captive Portal, Portal Group" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # Portal Constructor
    check_port_service_tcp "9001" "docker-proxy" "Portal Constructor, Portal Group" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # apb
    check_port_service_tcp "8090" "docker-proxy" "eltex-apb, Portal Group" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # == PCRF ==
    check_port_service_tcp "7070" "docker-proxy" "PCRF monitoring API" var

    check_port_service_udp "1813" "docker-proxy" "PCRF, RADIUS accounting API" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # == RADIUS ==
    check_port_service_udp "1812" "docker-proxy" "RADIUS API" var
    if [[ "$var" != "0" ]]; then
        FATAL=$((FATAL + 1))
    fi

    # eltex-wids-service
    check_port_service_tcp "9095" "docker-proxy" "eltex-wids-service API" var

    # == Other ==
    check_port_service_udp "514" "docker-proxy" "Linux syslog server" var
    check_port_service_udp "69" "in.tftpd" "Linux TFTP server" var

    echo ""
    # Финальный контроль фатальных ошибок
    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
# Пример вызова функции: check_port_service_udp "8080" "java" "tomcat" result_var
# $1 - номер порта (строкой);
# $2 - название сервиса (java - для всех java приложений);
# $3 - описание сервиса, например, 'tomcat' или '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
# Пример вызова функции: check_port_service_tcp "8080" "java" "tomcat" result_var
# $1 - номер порта (строкой);
# $2 - название сервиса (java - для всех java приложений) или 'docker-proxy' для схемы с контейнерами;
# $3 - описание сервиса, например, 'tomcat' или '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-softwlc-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
