#!/bin/bash

#Скрипт рекомендуется запускать от пользователя root.
#На фронтах, которые будут настраиваться и входить в федерацию,
#от пользователя root должны быть проброшены ssh ключи,
#в том числе и на localhost (узел с которого запускается скрипт).

tmp_folder=/tmp/brest-configure
tmp_passfile=$tmp_folder/p
BOLD=$(tput bold)
NORMAL=$(tput sgr0)
ONECONF=/etc/one/one.d/raft.conf
ONEFLOW=/etc/one/oneflow-server.conf
BCKCONF=/var/lib/one/backups/config/raft.conf.before_raft
brest_version=$(cat /etc/brest_version)
user_db="$(awk '/^ *USER *=/ {gsub(/(^ *USER *= *)|(")|(, *$)|(, *#.*$)/,""); print}' /etc/one/one.d/db.conf)"
HOSTNAME=`hostname -s`
DOMAIN=`hostname -d`
FQDN=$HOSTNAME.$DOMAIN
KDC="$(awk '{if ($1 == "server") print $3}' '/etc/ipa/default.conf')"
zone_id=99

SUFFIXS=$(echo $DOMAIN | tr "." "\n")
SUFFIX=""
for N in $SUFFIXS
do
    PART="dc=$N"
    if [ "$SUFFIX" != "" ]; then
       SUFFIX="$SUFFIX,$PART"
    else
       SUFFIX="$PART"
    fi
done

path_to_db="/tmp"
onelock=/var/lock/one/one

server01="$FQDN"


function digit {
    re='^[0-9]+$'

    # Запрашиваем количество фронтов (серверов) для мастер зоны
    while true; do
        read -p "Укажите количество фронтов (серверов) для мастер зоны: " n_master
        if [[ $n_master =~ $re ]]; then
            break
        else
            echo "ОШИБКА: Это должно быть целое число"
        fi
    done

    # Запрашиваем количество отдельных зон для слейв зон
    while true; do
        read -p "Укажите количество отдельных зон для слейв зоны: " n_slave_zones
        if [[ $n_slave_zones =~ $re ]]; then
            break
        else
            echo "ОШИБКА: Это должно быть целое число"
        fi
    done

    # Запрашиваем количество фронтов (серверов) для каждой слейв зоны
    for ((i = 1; i <= n_slave_zones; i++)); do
        while true; do
            read -p "Укажите количество фронтов (серверов) для слейв зоны $i: " n_slave[$i]
            if [[ ${n_slave[$i]} =~ $re ]]; then
                break
            else
                echo "ОШИБКА: Это должно быть целое число"
            fi
        done
    done
}

digit

declare -a master_servers=("$server01")
declare -A zone_servers
declare -A zone_ips
declare -A zone_uses_raft_slave  # Добавляем массив для хранения флага использования RAFT
declare -a servers=()

function input_master_servers {
    echo "Построчно введите имена (hostname) серверов, пример ниже, которые будут Мастерами федерации, этот сервер уже введен:"
    echo "Server01 (Master) :" $HOSTNAME

    for ((a=2; a <= "$n_master" ; a++))
    do
        i=$((i+1))
        read -p "Server0""$a: (Master) : " server
        master_servers+=("$server.$DOMAIN")
    done

    verify_raft_master
    echo "В работу поступили следующие сервера: "
    for server in "${master_servers[@]}"
    do
        echo $server
    done
}

# Функция ввода серверов для каждой зоны
function input_slave_servers {
    echo "Построчно введите имена (hostname) серверов для slave зон:"
    for ((i=1; i<=n_slave_zones; i++)); do
        zone_id=$((zone_id + 1))
        echo "Слейв зона $zone_id:"
        declare -a slave_servers_zone=()

        for ((a=1; a <= ${n_slave[i]}; a++)); do
            read -p "Server0""$a (Slave): " server
            slave_servers_zone+=("$server.$DOMAIN")
        done

        zone_servers["$zone_id"]="${slave_servers_zone[@]}"
    line=$(ssh "${slave_servers_zone[0]}" "grep 'RAFT_LEADER_HOOK' -A 2 /etc/one/one.d/raft.conf" | grep 'ARGUMENTS')
        verify_raft_slave "$zone_id"

        echo "В работу поступили следующие слейв сервера для зоны $zone_id: "
        for server in "${slave_servers_zone[@]}"; do
            echo $server
            slave_servers+=("$server")
        done
        echo
    done
}

function verify_raft_master {
    line_master=$(ssh "${master_servers[0]}" "grep 'RAFT_LEADER_HOOK' -A 2 /etc/one/one.d/raft.conf" | grep 'ARGUMENTS')
    while true; do
        read -p "Вы используете RAFT? (y/n): " -n 1 -r
        echo
        if [[ $REPLY =~ ^[yYnN]$ ]]; then
            if [[ $REPLY =~ ^[Yy]$ ]]; then
                while true; do
                    float_ip_master=($(echo "${line_master}" | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'))
	            if [[ -z "$float_ip_master" ]]; then
                        echo "Ошибка: IP-адрес пустой на ${master_servers[0]}. Попробуйте снова."
	                exit 1
                    else
                        break
                    fi
                done
            else
                float_ip_master="${master_servers[0]}"
            fi
        else
            echo "Пожалуйста, введите только 'y' или 'n'."
        fi
        echo "Вы выбрали IP: $float_ip_master"
    break
    done
}


function verify_raft_slave {

    local zone_index="$1"
    line_slave=$(ssh "${slave_servers_zone[0]}" "grep 'RAFT_LEADER_HOOK' -A 2 /etc/one/one.d/raft.conf" | grep 'ARGUMENTS')
    while true; do
        read -p "Вы используете RAFT для слейв зоны $zone_index? (y/n): " -n 1 -r
        echo
        zone_uses_raft_slave["$zone_index"]="$REPLY"
        if [[ $REPLY =~ ^[yYnN]$ ]]; then
            if [[ $REPLY =~ ^[Yy]$ ]]; then
	while true; do
	    float_ip_slave=($(echo "${line_slave}" | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'))
	    if [[ -z "$float_ip_slave" ]]; then
                        echo "Ошибка: IP-адрес пустой на ${slave_servers_zone[0]}. Попробуйте снова."
	    exit 1
                    else
                        break
                    fi
	done
            else
                float_ip_slave="${slave_servers_zone[0]}"
            fi
        else
            echo "Пожалуйста, введите только 'y' или 'n'."
        fi
        zone_ips["$zone_index"]="$float_ip_slave"
        echo "Вы выбрали IP: $float_ip_slave"
    break
    done
}

function verify_servers {
    while true; do
        read -p "Все верно? y/n: " -n 1 -r
        echo
        if [[ $REPLY =~ ^[yYnN]$ ]]; then
            if [[  $REPLY =~ ^[Yy]$ ]]; then
                for server in "${master_servers[@]}" "${slave_servers[@]}"
                do
                    if ! ssh -o BatchMode=yes -o ConnectTimeout=5 ${server} "exit 0"; then
                        echo "Ошибка: Нет доступа по ssh к \"${server}\""
                        error_ssh=1
                    fi
                    remote_brest_version=$(ssh ${server} "cat /etc/brest_version" 2>/dev/null)
                    if [[ $? -ne 0 || "$brest_version" != "$remote_brest_version" ]]; then
                        echo "Ошибка: Версия не сходится на узле \"${server}\""
                        error_ssh=1
                    fi
                    remote_db_user=$(ssh "${server}" "awk '/^ *USER *=/ {gsub(/(^ *USER *= *)|(\"|, *$|, *#.*$)/,\"\"); print}' /etc/one/one.d/db.conf" 2>/dev/null)
                    if  [[ $? -ne 0 || "$user_db" != "$remote_db_user" ]]; then
                        echo "Ошибка: пользователи БД не сходятся на \"${server}\""
                        error_db=1
                    fi
                done
            else
                unset server
                master_servers=()
                slave_servers_zone=()
                input_master_servers
                input_slave_servers

                if [[ -n "$error_ssh" ]]; then
                    echo "Произошла ошибка SSH. Перезапуск ввода данных."
                    error_ssh=
                    unset server
                    master_servers=()
                    slave_servers_zone=()
                    input_master_servers
                    input_slave_servers
                fi


                if [[ -n "$error_db" ]]; then
                    echo "Произошла ошибка БД. Перезапуск ввода данных."
                    error_db=
                    unset server
                    master_servers=()
                    slave_servers_zone=()
                    input_master_servers
                    input_slave_servers
                fi
            fi
            break
        else
            echo "Пожалуйста, введите только 'y' или 'n'."
        fi
    done
}

echo "Начинается настройка Федерации."

function master_zone {
#Запускаем сервис OpenNebula и добавляем точку в зону.
    for server in "${master_servers[@]}"
    do
        ssh $server /bin/bash << EOF
            cp "$ONECONF" "$BCKCONF"
            chown :oneadmin "$ONECONF"


EOF
    done
    if [[ ! -f "$onelock" ]]; then
        echo "Запускаем сервис OpenNebula и добавляем сервер в новую зону."
        systemctl start opennebula
        while [[ ! -f "$onelock"  ]] ;
        do
            echo "Ждем старта сервиса opennebula"
            sleep 2
        done
    fi
    CONF="ENDPOINT = http://$float_ip_master:2633/RPC2"
    ENDPOINT=/tmp/endpoint_zone
    echo "NAME = $name_master" >> "$ENDPOINT"
    echo "MASTER_ZONE = yes" >> "$ENDPOINT"
    echo "$CONF" > "$ENDPOINT"
    onezone update 0 "$ENDPOINT"
    rm -rf /tmp/endpoint_zone
    for server in "${master_servers[@]}"
    do
        ssh $server /bin/bash << EOF
            echo "Останавливаем сервис opennebula на $server"
            systemctl stop opennebula
EOF
    done
}



function server_id_master {
#В raft.conf изменяем параметр "MODE" и ZONE_ID с -1 на X, где X - это ID сервера

    for server in "${master_servers[@]}"
    do
        ssh $server /bin/bash << EOF
            sed -i 's/MODE          \=\ \"STANDALONE\"\,/MODE          \=\ \"MASTER\"\,/' $ONECONF
            systemctl start opennebula
            echo "Запускаем сервис opennebula"
EOF
    done
}

function server_id_slave {
    local zone="$1"
    local float_ip="${zone_ips[$zone]}"
    local use_raft="${zone_uses_raft_slave[$zone]}"
    local servers=(${zone_servers[$zone]})
    for server in "${servers[@]}"
    do
        echo "Добавляем зону на ведомом $server "
        ssh "$server" /bin/bash << EOF
            systemctl stop opennebula
            cp "$ONECONF" "$BCKCONF"
            chown :oneadmin "$ONECONF"
            sed -i 's|MODE          = "STANDALONE"|MODE          = "SLAVE"|' $ONECONF
            sed -i 's|ZONE_ID       = 0|ZONE_ID       = '"$zone"'|' $ONECONF
            sed -i 's|MASTER_ONED   = ""|MASTER_ONED   = "http://'"$float_ip_master"':2633/RPC2"|' $ONECONF
EOF
    done
    echo "NAME = SLAVE_$zone" >>  /tmp/zone_$zone.tmpl
    echo "ENDPOINT = http://$float_ip:2633/RPC2 " >>  /tmp/zone_$zone.tmpl
    onezone create /tmp/zone_$zone.tmpl
}

function backup_base {
#На Master создаем полный бэкап актуальной базы Postgres.
    for server in "${master_servers[@]}"
    do
        echo "Создаем backup БД Брест"
        catpassdb=$(ssh "$server" "cat /etc/one/one.d/db.conf | grep PASSWD")
        passdb=$(echo "$catpassdb" | awk '{ print $3 }' | sed 's/.$//' | sed -e 's/^"//' -e 's/"$//')
        userdb="$(awk '/^ *USER *=/ {gsub(/(^ *USER *= *)|(")|(, *$)|(, *#.*$)/,""); print}' /etc/one/one.d/db.conf)"
        namedb="$(awk '/^ *DB_NAME *=/ {gsub(/(^ *DB_NAME *= *)|(")|(, *)|( *] *$)|( *] *#.*$)/,""); print}' /etc/one/one.d/db.conf)"
        ssh $server /bin/bash << EOF
            onedb backup --federated $path_to_db/master_db.backup -f -t postgresql -S localhost -u "$userdb" -p "$passdb" -d "$namedb"
EOF
    done
}

function give_base_slave {
    local zone="$1"
    local float_ip="${zone_ips[$zone]}"
    local use_raft="${zone_uses_raft_slave[$zone]}"
    local servers=(${zone_servers[$zone]})
    if [ "${use_raft}" == "y"  ]; then
    echo "Получаем базу для рафт SLAVE_$zone"
        for server in "${servers[@]}";
        do
        # Создаем строку с серверами без пробелов
            ssh "$server" /bin/bash << EOF
            result=\$(sudo -u $user_db psql -d brest -t -c "SELECT body FROM zone_pool WHERE oid = 0;")
            new_xml_output=\$(echo "\$result" | \
                sed "s|<ID>0</ID>|<ID>$zone</ID>|g;
                    s|http://localhost:2633/RPC2|http://$float_ip:2633/RPC2|g;
                    s|<NAME>OpenNebula</NAME>|<NAME>SLAVE_$zone</NAME>|")

            psql_command="UPDATE zone_pool SET body = '\$new_xml_output' WHERE oid=$zone;"
            echo "\$psql_command" > /tmp/psql_command_$zone.txt
EOF
        done
    fi
}

function copy_base {
    #Копируем и восстанавливаем дамп базы на сервера, которые будут участниками федерации.
    local zone="$1"
    local float_ip="${zone_ips[$zone]}"
    local use_raft="${zone_uses_raft_slave[$zone]}"
    local servers=(${zone_servers[$zone]})
    for server in "${servers[@]}";
    do
        scp "$path_to_db/master_db.backup" "$server":"$path_to_db"  > /dev/null 2>&1
        catpassdb=$(ssh "$server" "cat /etc/one/one.d/db.conf | grep PASSWD")
        passdb=$(echo "$catpassdb" | awk '{ print $3 }' | sed 's/.$//' | sed -e 's/^"//' -e 's/"$//')
        userdb="$(awk '/^ *USER *=/ {gsub(/(^ *USER *= *)|(")|(, *$)|(, *#.*$)/,""); print}' /etc/one/one.d/db.conf)"
        ssh $server /bin/bash << EOF
            systemctl stop opennebula
            onedb restore --federated -f $path_to_db/master_db.backup -t postgresql -S localhost -u "$userdb" -p "$passdb" -d "$namedb"
EOF
    done
}


function change_base_slave {
    local zone="$1"
    local float_ip="${zone_ips[$zone]}"
    local use_raft="${zone_uses_raft_slave[$zone]}"
    local servers=(${zone_servers[$zone]})
    if [ "${use_raft}" == "y"  ]; then
        for server in "${servers[@]}";
        do
            echo "Меняем базу для рафт на узле $server на SLAVE_$zone зоне"
            ssh $server /bin/bash << EOF
            psql_master=\$(cat /tmp/psql_command_$zone.txt)
            sudo -u $user_db psql -d brest -c "\$psql_master" 2>/dev/null
            rm -rf /tmp/psql_command_$zone.txt
EOF
        done
    fi
}
function change_base_master {
    local zone="$1"
    local use_raft="${zone_uses_raft_slave[$zone]}"
    local servers=(${zone_servers[$zone]})

    if [ "${use_raft}" == "y"  ]; then
        result=$(ssh ${servers[0]} "sudo -u $user_db psql -d brest -t -c 'SELECT body FROM zone_pool WHERE oid = $zone;'") 2>/dev/null
        psql_command="UPDATE zone_pool SET body = '$result' WHERE oid=$zone;"

        for server in "${master_servers[@]}"
        do
            echo "Меняем базу для рафт Master $server"
            ssh "$server" /bin/bash << EOF
                systemctl stop opennebula
                sudo -u $user_db psql -d brest -c "$psql_command" 2>/dev/null
                systemctl start opennebula
EOF
        done
    fi
}

function copy_one {
#  #Заменяем на хостах файлы авторизации в /var/lib/one/.one
   echo "На Slave копируем с Master файлы авторизации из /var/lib/one/.one"
   local zone="$1"
   local float_ip="${zone_ips[$zone]}"
   local use_raft="${zone_uses_raft_slave[$zone]}"
   local servers=(${zone_servers[$zone]})
    for server in "${servers[@]}"
        do
        scp /var/lib/one/.one/ec2_auth "$server":/var/lib/one/.one
        scp /var/lib/one/.one/one_auth "$server":/var/lib/one/.one
        scp /var/lib/one/.one/oneflow_auth "$server":/var/lib/one/.one
        scp /var/lib/one/.one/onegate_auth "$server":/var/lib/one/.one
        scp /var/lib/one/.one/sunstone_auth "$server":/var/lib/one/.one

        ssh $server /bin/bash << EOF
           chown -R oneadmin:oneadmin /var/lib/one/.one
            systemctl start opennebula
EOF
    done
}

function oneflow_config {
    #Настраиваем показ сервисной информации по Zone Sunstone
    local zone="$1"
    local float_ip="${zone_ips[$zone]}"
    local use_raft="${zone_uses_raft[$zone]}"
    local servers=(${zone_servers[$zone]})
    for server in "${servers[@]}"
        do
        ssh $server /bin/bash << EOF
            sed -i 's/\:host\: 127\.0\.0\.1,/\:host\: 0\.0\.0\.0/' $ONEFLOW
            systemctl restart opennebula-sunstone
EOF
    done
}

function oneflow_config_update {
    local zone="$1"
    local float_ip="${zone_ips[$zone]}"
    local use_raft="${zone_uses_raft_slave[$zone]}"
    local servers=(${zone_servers[$zone]})

    if [ "${use_raft}" == "y"  ]; then
	echo "ENDPOINT = http://$float_ip:2633/RPC2 " >>  /tmp/add_oneflow_support_config_$zone.conf
        echo "ONEFLOW_ENDPOINT = http://$float_ip:2474" >> /tmp/add_oneflow_support_config_$zone.conf
        onezone update $zone /tmp/add_oneflow_support_config_$zone.conf
    fi

    for server in "${master_servers[@]}"
    do
        ssh $server /bin/bash << EOF
            systemctl restart opennebula-sunstone
EOF
    done
}

function check_success {
    if [ $? -eq 0 ]; then
        echo -e "\n${BOLD}Федерация развернута успешно!${NORMAL}\n"
    else
        echo -e "\n${BOLD}Ошибка: Федерация не развернута.${NORMAL}\n"
    fi
}

function process_zone {
    process="$1"
    for zone in "${!zone_servers[@]}"; do
        "$process" "$zone"
    done
}

input_master_servers
input_slave_servers
verify_servers
master_zone
server_id_master
process_zone server_id_slave
backup_base
process_zone give_base_slave
process_zone copy_base
process_zone change_base_slave
process_zone change_base_master
process_zone copy_one
process_zone oneflow_config
process_zone oneflow_config_update
check_success
