#!/bin/bash

#Скрипт рекомендуется запускать от пользователя root.
#На узлах, которые будут настраиваться и входить в RAFT,
#от пользователя 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
BCKCONF=/var/lib/one/backups/config/raft.conf.before_raft
HOSTNAME=`hostname -s`
DOMAIN=`hostname -d`
FQDN=$HOSTNAME.$DOMAIN
a2conf="/etc/apache2/sites-available/ipa-one-apache2.conf"
a2confcp="/etc/apache2/sites-available/ipa-one-apache2-float.conf"
brest_version=$(cat /etc/brest_version)
certdir=/etc/one/ssl
KDC="$(awk '{if ($1 == "server") print $3}' '/etc/ipa/default.conf')"

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"

echo "Количество серверов OpenNebula участвующих в RAFT должно быть минимум три или 2N+1"

function digit {
read -p "Укажите количество серверов (число от 3 до 2N+1): " n
}

digit

re='^[0-9]+$'
if ! [[ $n =~ $re ]] ; then
    echo "ОШИБКА: Это должно быть цело число >=3" >&2
    digit
fi

function configure_network {
echo "В системе присутствуют следующие интерфейсы:"
interface_list="$( ip -br link show up | awk '{ print $1 }' )"
echo
echo $interface_list
echo
echo  "Укажите интерфейс для плавающего IP-адреса,"
echo  "предназначенный для синхронизации между нодами:"

while [ -z "$eth_interface" ]; do
    echo "интерфейс по умолчанию eth0"
    read eth_interface
    if [ -z "$eth_interface" ]; then
        eth_interface="eth0"
        echo $eth_interface
    fi
    if [[ "$interface_list" != *"$eth_interface"* ]]; then
        echo -ne "ОШИБКА:  нет соответствующего интерфейса\n"
        eth_interface=
        continue
    fi
done
echo -n "Укажите плавающий IP адрес, предназначеный для лидера RAFT,"
echo -n "по умолчанию, IP будет 10.10.10.100/24:"
read float_ip
if [ -z "$float_ip" ]; then
    float_ip="10.10.10.100/24"
    echo $float_ip
fi
}

declare -a servers=("$server01")
function input_servers {
echo "Построчно введите имена (hostname) серверов, пример ниже, этот сервер уже введен:"
echo "Server01:" $HOSTNAME

for ((a=2; a <= "$n" ; a++))
do
    i=$((i+1))
    read -p "Server0""$a: " server
    servers["$i"]="$server.$DOMAIN"
done

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

read -p "Все верно? y/n: " -n 1 -r
echo
if [[  $REPLY =~ ^[Yy]$ ]]; then
    for server in "${servers[@]}"
    do
        if ! ssh -o BatchMode=yes -o ConnectTimeout=5 ${server} "exit 0"; then
            echo "Ошибка: Нет доступа по ssh к \"${server}\""
            error_ssh=1
        else
            remote_brest_version=$(ssh ${server} "cat /etc/brest_version")
            if [ "$brest_version" != "$remote_brest_version" ]; then
                echo "Ошибка: Версия не сходится на узле \"${server}\""
                error_ssh=1
            fi
        fi
    done
else
    unset server
    servers=("$server01")
    input_servers
fi
    if [ -n "$error_ssh" ]; then
        error_ssh=
	unset server
        servers=("$server01")
        input_servers
    fi
echo "Начинается настройка RAFT."
}

function local_zone {
#Запускаем сервис OpenNebula и добавляем сервер в существующую или новую зону.
cp "$ONECONF" "$BCKCONF"
chown :oneadmin "$ONECONF"
if [[ ! -f "$onelock" ]]; then
    echo "Запускаем сервис OpenNebula и добавляем сервер в существующую или новую зону."
    systemctl start opennebula
    while [[ ! -f "$onelock"  ]] ;
    do
        echo "Ждем старта сервиса opennebula"
        sleep 2
    done
fi
echo "Добавляем первый сервер в зону"
onezone server-add 0 --name "$server01" --rpc http://"$server01":2633/RPC2
echo "Останавливаем сервис opennebula"
systemctl stop opennebula
}

function server_id {
#В raft.conf изменяем параметр "SERVER_ID" с -1 на X, где X - это ID сервера
for server in "${servers[@]}"
do
    if [ "${server}" == "$server01"  ]; then
        sed -i 's/SERVER_ID\     \=\ -1\,/SERVER_ID\     \=\ 0\,/' $ONECONF
    else
        id=$((id+1))
        ssh "$server" /bin/bash << EOF
            systemctl stop opennebula
            cp "$ONECONF" "$BCKCONF"
            chown :oneadmin "$ONECONF"
            sed -i 's/SERVER_ID\     \=\ -1\,/SERVER_ID\     \=\ '$id'\,/' $ONECONF
EOF
    fi
done
}

function raft_hook {
echo "Активируем Raft-хуки для того, чтобы добавить плавающий адрес в кластер."
for server in ${servers[@]}
do
ssh "$server" /bin/bash << EOF
sed -i '/\# RAFT_LEADER_HOOK/,/\#\ \]/d' $ONECONF

sed -i '/\#\ Executed\ when\ a\ server\ transits\ from\ follower/a\
 RAFT_LEADER_HOOK = [\
     COMMAND = "raft/vip.sh",\
     ARGUMENTS = "leader '$eth_interface' '$float_ip'"\
 ]\
' $ONECONF

sed -i '/\# RAFT_FOLLOWER_HOOK/,/\#\ \]/d' $ONECONF

sed -i '/\#\ Executed\ when\ a\ server\ transits\ from\ leader/a\
 RAFT_FOLLOWER_HOOK = [\
     COMMAND = "raft/vip.sh",\
     ARGUMENTS = "follower '$eth_interface' '$float_ip'"\
 ]\
' $ONECONF
EOF
done
systemctl start opennebula
while [[ ! -f "$onelock"  ]] ;
do
    echo "Ждем старта сервиса opennebula"
    sleep 2
done
systemctl restart opennebula-sunstone
}

function backup_base {
#На Leader создаем полный бэкап актуальной базы Postgres.
echo "Создаем backup БД Opennebula"
#catpassdb=$(cat /etc/one/one.d/db.conf | grep PASSWD | awk '{ print $3 }' | sed 's/.$//')
#passdb=$(sed -e 's/^"//' -e 's/"$//' <<<"$catpassdb")
#onedb backup $path_to_db/leader_db.backup -f -t postgresql -S localhost -u oneadmin -p "$passdb" -d opennebula
passdb="$(awk '/^ *PASSWD *=/ {gsub(/(^ *PASSWD *= *)|(")|(, *$)|(, *#.*$)/,""); print}' /etc/one/one.d/db.conf)"
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)"
onedb backup $path_to_db/leader_db.backup -f -t postgresql -S localhost -u "$userdb"  -p "$passdb" -d "$namedb"
}

function copy_base {
#Копируем и восстанавливаем дамп базы на сервера, которые будут участниками кластера.
for server in ${servers[@]}
do
    if [ "${server}" != "$server01"  ]; then
        scp "$path_to_db/leader_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/"$//')
        ssh $server /bin/bash << EOF
            systemctl stop opennebula
            onedb restore -f $path_to_db/leader_db.backup -t postgresql -S localhost -u "$userdb" -p "$passdb" -d "$namedb"
EOF

    fi
done
}

function copy_ssh {

keys=()

function ssh_and_save {
  server=$1

  keys+=("$(ssh -o StrictHostKeyChecking=no $server 'cat /var/lib/one/.ssh/id_rsa.pub')$'\n'")
}

for server in ${servers[@]}
do
  ssh_and_save $server
done

for server in ${servers[@]}
do
  ssh -o StrictHostKeyChecking=no $server "echo -e "${keys[@]}" > /var/lib/one/.ssh/authorized_keys; echo -e "${keys[@]}" > /var/lib/one/.ssh/brest_keys"
done
}

function copy_one {
#Удаляем на хостах директорию /var/lib/one/.one и копируем её же с Leader-сервера
echo "Удаляем на хостах директорию /var/lib/one/.one и копируем её же с Leader-сервера"
for server in ${servers[@]}
do
    if [ "${server}" != "$server01"  ]; then
        ssh $server /bin/bash << EOF
            rm -rf /var/lib/one/.one
            rm -rf /var/lib/one/homes
EOF
        scp -r /var/lib/one/.one/ "$server":/var/lib/one/
        scp -r /var/lib/one/homes/ "$server":/var/lib/one/homes/
    fi
done
}

function server_zone {
#Запускаем сервис OpenNebula и добавляем сервер в существующую или новую зону.
echo "Запускаем сервис OpenNebula и добавляем сервер в существующую или новую зону."


while : ; do
    get_leader=$(onezone show 0 -x | xmlstarlet sel -t -v '/ZONE/SERVER_POOL/SERVER[STATE="3"]/NAME')
    echo "Ждем на $server01 статус leader..."
    if  [ "$get_leader" == "$server01" ]; then
        break
    fi
    sleep 10
done
echo "Получили статус leader для $get_leader"

while true; do
    read -p "Введите логин администратора Бреста: " brestadmin_name

    if [ -z "$brestadmin_name" ]; then
        echo "Имя пользователя не может быть пустым."
    else
        if id -u "$brestadmin_name" >/dev/null 2>&1; then
            break
        else
            echo "Имя пользователя $brestadmin_name не найдено в системе."
        fi
    fi
done

for server in ${servers[@]}
do
    if [ "${server}" != "$server01"  ]; then
        onezone server-add 0 --name "$server" --rpc http://"$server":2633/RPC2
        echo "Добавляем в RAFT" "$server"
        ssh $server /bin/bash << EOF
            chown -R oneadmin:oneadmin /var/lib/one/.one/
            chown oneadmin:oneadmin /var/lib/one/homes/
            chown -R $brestadmin_name:oneadmin /var/lib/one/homes/*
            systemctl start opennebula
            while [ ! -f "$onelock"  ] ;
            do
                echo "Ждем старта сервиса opennebula"
                sleep 2
            done
            systemctl restart opennebula-scheduler opennebula-sunstone
EOF
    fi
done
}

#rbt: float-FQDN configure
function get_names {
echo "Настраивается плавающий FQDN для узлов на плавающий IP: $float_ip"
echo -n "Введите плавающее имя (hostname), по умолчанию - leader:"
read  float_name
if [ -z "$float_name" ]; then
        float_name="leader"
        fi
}

function copying_certs {
echo "Генерация сертификата для apache2:"
openssl req -x509 -nodes -newkey rsa:2048 -days 3650 -keyout "$certdir"/$float_name.key -out "$certdir"/$float_name.crt -subj "/C=RU/ST=Moscow/L=Moscow/O=COMPANY/OU=root/CN="$float_name"/emailAddress=webmaster@localhost"

echo "Копируем сертификат по узлам:"
for server in ${servers[@]}
do
    scp "$certdir"/$float_name.crt $server:"$certdir"/$float_name.crt
    scp "$certdir"/$float_name.key $server:"$certdir"/$float_name.key
    ssh $server /bin/bash << EOF
            chmod 0600 "$certdir"/$float_name.crt
            chmod 0600 "$certdir"/$float_name.key
            chown -R oneadmin:oneadmin "$certdir"/$float_name.crt
            chown -R oneadmin:oneadmin "$certdir"/$float_name.key
            ln -sf "$certdir"/$float_name.crt /usr/lib/one/fireedge/cert/cert.pem
            ln -sf "$certdir"/$float_name.key /usr/lib/one/fireedge/cert/key.pem
            systemctl restart opennebula-fireedge.service
            sed -i "/^ *:vnc_proxy_cert: \/etc\/one\/ssl\/one-apache2.crt/c :vnc_proxy_cert: ${certdir}/${float_name}.crt" /etc/one/sunstone-server.conf
            sed -i "/^ *:vnc_proxy_key: \/etc\/one\/ssl\/one-apache2.key/c :vnc_proxy_key: ${certdir}/${float_name}.key" /etc/one/sunstone-server.conf
            systemctl restart opennebula-novnc.service
EOF
done

echo "Добавление новой конфигурации apache2 для портала на плавающий FQDN:"
for server in ${servers[@]}
do
    ssh $server /bin/bash << EOF
        cp -f $a2conf $a2confcp
        sed -i "s/$server/$float_name.$DOMAIN/" $a2confcp
        sed -i "s/one-apache2.crt/$float_name.crt/" $a2confcp
        sed -i "s/one-apache2.key/$float_name.key/" $a2confcp
        a2ensite ipa-one-apache2-float.conf
        systemctl reload apache2
        systemctl restart apache2
EOF
done

}

function admin_auth {
echo "Добавление хоста, IP и службы на контроллер домена IPA:"
echo -n "Введите имя администратора IPA-сервера (по умолчанию admin): "
read dc_admin
if [ -z "$dc_admin" ]; then
    dc_admin="admin"
fi
echo -n "Введите пароль администратора сервера: "
read -s admin_pass
echo
while [ -z "$admin_pass" ]; do
    echo -n "Введите пароль администратора сервера: "
    read -s admin_pass
    echo
done

echo $admin_pass | kinit $dc_admin || {
        echo -e "\033[91m Вы неправильно ввели имя или пароль, попробуйте ещё раз \033[0m"
        admin_auth
    }
}

function kinit_dc {
ipa host-add $float_name.$DOMAIN --ip-address=$float_ip
ipa service-add HTTP/$float_name.$DOMAIN
ipa service-allow-create-keytab HTTP/$float_name.$DOMAIN --groups=admins
ipa service-allow-retrieve-keytab HTTP/$float_name.$DOMAIN --groups=admins

for server in "${servers[@]}"
do
    ipa service-allow-create-keytab HTTP/$float_name.$DOMAIN --hosts="$server"
    ipa service-allow-retrieve-keytab HTTP/$float_name.$DOMAIN --hosts="$server"
done

echo "Получение keytab's у нового сервиса для apache2:"
for server in "${servers[@]}"
do
    if [[ "$server" == "$server01" ]]; then
        ipa-getkeytab -s "$KDC" -p HTTP/"$float_name.$DOMAIN" -k /etc/apache2/apache2.$float_name.keytab
    else
        ssh "$server" /bin/bash << EOF
            echo "$admin_pass" | kinit "$dc_admin"
            ipa-getkeytab -r -s "$KDC" -p HTTP/"$float_name.$DOMAIN" -k /etc/apache2/apache2.$float_name.keytab
EOF
    fi
done

echo "Объединение keytab's узлов в один для apache2:"
for server in "${servers[@]}"
do
        ssh "$server" /bin/bash << EOF1
            ktutil << EOF2
            rkt /etc/apache2/apache2."$float_name".keytab
            wkt /etc/apache2/apache2.keytab
            q
EOF2
EOF1
        ssh "$server" /bin/bash << EOF
            chown www-data:www-data /etc/apache2/apache2.keytab
            chmod 440 /etc/apache2/apache2.keytab
            systemctl restart apache2
EOF
done
}

configure_network
input_servers
local_zone
server_id
raft_hook
backup_base
copy_base
copy_ssh
copy_one
server_zone
get_names
copying_certs
admin_auth
kinit_dc
