#!/bin/bash
# Copyright (C) 2023 Rusbitech-Astra <support@rusbitech.ru>
exec > >(systemd-cat -t brest-kub-host -p info) 2>&1
# shellcheck disable=SC2034
# EXIT CODES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
readonly EC_PREPARED=0               # Prepared for ansible
readonly EC_ASTRA_MODE_ERROR=1        # Catch generic serrors
readonly EC_INTERNAL_ERROR=2         # Invalid script usage
readonly EC_WAITING=3                # Check state later
readonly EC_PREPARE_FAILED=4         # Configuring by KUB failed
readonly EC_IP_UNAVAILABLE=5         # Ping failed
readonly EC_SSH_FAILED=6             # Cannot login via ssh
readonly EC_SUDO_ERROR=7             # User has no sudo rights OR sudo is under password
readonly EC_NETWORK_MANAGER_ERROR=8  # Network manager is not running
readonly EC_AVAILABLE=9              # Available for preparation
# Unknown state - initial

exit-code() {
    local RC=$1
    shift
    echo "Exit ${RC}: $*"
    exit "${RC}"
}
# USAGE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if [[ $# -lt 2 ]]; then
    echo "Usage:
    brest-kub-host <cluster> <check/configure/hosts> <hostname> [force]

    Description:
    This script preconfigures single host
    force - used to reconfigure already configured host"
    exit-code "${EC_INTERNAL_ERROR}" "Invalid usage"
fi

export BR_CLUSTER=$1 # used in source script
BR_CMD=$2
BR_HOSTNAME=$3
STATUS_STRING="${BR_HOSTNAME} "

# REMOTE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
host.send-key() {
    if [[ ! -f ~/.ssh/id_rsa ]]; then
        echo "Key is missing, generating new one"
        ssh-keygen -q -N '' -f ~/.ssh/id_rsa
    fi

    sshpass -p "${BR_PASSWORD}" ssh-copy-id -o StrictHostKeyChecking=no "${BR_USER}@${BR_IP}" || return 1
}

host.check-key() {
    ssh -o ConnectTimeout=4 -o PubkeyAuthentication=yes -o PasswordAuthentication=no -o KbdInteractiveAuthentication=no -o BatchMode=yes -q "${BR_USER}@${BR_IP}" exit || return 1
}

host.scp() {
    if [[ $# -lt 1 ]]; then
        echo "Usage: host.scp <scp args>"
        exit-code "${EC_INTERNAL_ERROR}" "Invalid usage"
    fi
    scp -o StrictHostKeyChecking=no "$@" || return 1
}

host.simple-cmd () {
    if [[ $# -lt 1 ]]; then
        echo "Usage: host.simple-cmd <command>"
        exit-code "${EC_INTERNAL_ERROR}" "Invalid usage"
    fi
    ssh -o StrictHostKeyChecking=no "${BR_REMOTE}" "$@" || return 1
}

host.sshpass () {
    if [[ $# -lt 1 ]]; then
        echo "Usage: host.cmd <command>"
        exit-code "${EC_INTERNAL_ERROR}" "Invalid usage"
    fi
    sshpass -p "${BR_PASSWORD}" ssh -o StrictHostKeyChecking=no -q "${BR_USER}@${BR_IP}" "$@" || return 1
}


host.cmd () {
    if [[ $# -lt 1 ]]; then
        echo "Usage: host.cmd <command>"
        exit-code "${EC_INTERNAL_ERROR}" "Invalid usage"
    fi
    ssh -o StrictHostKeyChecking=no "${BR_REMOTE}" -t "bash -l -c \"$*\"" || return 1
}

host.sudo-cmd () {
    if [[ $# -lt 1 ]]; then
        echo "Usage: host.cmd <command>"
        exit-code "${EC_INTERNAL_ERROR}" "Invalid usage"
    fi
    ssh -o StrictHostKeyChecking=no "${BR_REMOTE}" -t "sudo bash -l -c \"$*\"" || return 1
}

host.enable-root-login() {
    host.sudo-cmd  "chpasswd <<<'root:${BR_ROOT_PASSWORD}'"
    host.cmd       "sudo sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config"
    host.cmd       "sudo sed -i 's/PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config"
    host.sudo-cmd  "systemctl restart ssh"
    sleep 10
}

host.reboot() {
    export BR_REMOTE_REBOOT_DURATION=60
    echo "Reboot device and wait ${BR_REMOTE_REBOOT_DURATION}"
    host.cmd "sudo reboot now"
    sleep ${BR_REMOTE_REBOOT_DURATION}
}

host.configure-sources-list() {
    echo "Configure /etc/apt/sources.list and update sources:"
    host.sudo-cmd "grep -q 'Verify-Peer false' /etc/apt/apt.conf.d/99verify-peer.conf || echo 'Acquire { https::Verify-Peer false }' >> /etc/apt/apt.conf.d/99verify-peer.conf"
    host.scp       "${SOURCES_LIST}" "${BR_CRED}:/tmp" || return 1
    host.sudo-cmd  "cat /tmp/sources.list > /etc/apt/sources.list" || return 1
    host.cmd       "cat /etc/apt/sources.list" || return 1
    host.sudo-cmd  "apt update" || return 1
}

host.configure-packages() {
    echo "Configure packages"
    host.sudo-cmd "sed -i 's/LAST_SYSTEM_GID=999/LAST_SYSTEM_GID=2000000000/' /etc/adduser.conf"
    host.sudo-cmd "apt update --yes" || return 1
    host.sudo-cmd "apt install -y astra-update" # do not exit on error, ignore
    host.sudo-cmd "cp /etc/adduser.conf  /etc/adduser_conf_$(date +%y%m%d%H%M%S).backup"
    host.sudo-cmd "sed -i 's/LAST_SYSTEM_GID=999/LAST_SYSTEM_GID=2000000000/' /etc/adduser.conf"
    host.sudo-cmd "apt-get dist-upgrade --yes" # "astra-update -A -r -T" cannot be used anymore (see BREST-4177, BT-69993)
    # shellcheck disable=SC2181
    if [[ $? -ne 0 ]]; then # Most likely astra-update not installed
        host.sudo-cmd "sudo apt-get -y upgrade" || return 1
    fi
    host.sudo-cmd "apt-get check" || return 1
    host.sudo-cmd "apt --yes install" || return 1
    host.sudo-cmd "apt install -y python-lxml &> /dev/null" # Ignore error
    host.sudo-cmd "apt install -y python-psycopg2 &> /dev/null" # Ignore error
    host.sudo-cmd "apt install -y python-cryptography &> /dev/null" # Ignore error
    host.sudo-cmd "apt install -y python3-apt &> /dev/null" # Ignore error
    host.sudo-cmd "apt install -y syslog-ng-mod-astra" # Ignore error
    host.sudo-cmd "apt install -y libwxbase3.0-0v5 &> /dev/null" # Ignore error
    host.sudo-cmd "apt install -y libwxbase3.2-1  &> /dev/null" # Ignore error

    # Brest Agent
    host.sudo-cmd "apt install -y cockpit/brest &> /dev/null" # Ignore error
    host.sudo-cmd "apt install -y cockpit-networkmanager/brest &> /dev/null" # Ignore error
    #host.sudo-cmd "apt install -y cockpit-storaged" #TODO: to enable it, we need fix udisks2 dependecy
    host.sudo-cmd "/bin/systemctl --system daemon-reload"
    host.sudo-cmd "/bin/systemctl --system restart cockpit.socket"
    return 0
}

host.configure-services() {
    echo "Configure services"
    if [[ "${BR_TYPE}" == *D* ]] || [[ "${BR_TYPE}" == *R* ]]; then
        echo " Disabling dnsmasq"
        host.sudo-cmd "systemctl stop dnsmasq" # do not check for error
        host.sudo-cmd "systemctl disable dnsmasq" # do not check for error
        host.sudo-cmd "systemctl mask dnsmasq" # do not check for error
    fi
    host.sudo-cmd "systemctl enable ssh"
    host.sudo-cmd "systemctl start ssh"
    host.sudo-cmd "systemctl stop firewalld"
    host.sudo-cmd "systemctl disable firewalld"
    host.sudo-cmd "systemctl mask firewalld"
}

host.configure-hosts-line() {
    local HNAME=$1
    local HIP=$2
    host.sudo-cmd "grep -q ${HIP} /etc/hosts || echo ${HIP} ${FQDN} ${HNAME} >> /etc/hosts"
}

configure-etc-hosts() {
    for HNAME in "${!BR_CONFIG[@]}"; do
        # shellcheck disable=SC2086
        host.configure-hosts-line "${HNAME}" ${BR_CONFIG[$HNAME]} # DO NOT QUOTE BR_CONFIG
    done
}

require-network-manager() {
    echo "Check Network Manager"
    if host.sshpass "systemctl is-active NetworkManager" &> /dev/null ; then
        return 0
    fi
    return 1
}

require-sudo() {
    echo "Check sudo"
    if host.sshpass "SUDO_ASKPASS=/bin/false sudo -A whoami" &> /dev/null ; then
        return 0
    fi
    return 1
}

require-smolensk() {
    echo "Check for Smolensk OS mode"
    if host.sshpass '/usr/sbin/astra-modeswitch get | grep -q "^2$"'; then
        return 0
    fi
    return 1
}

add-status() {
    if [[ $# -ne 2 ]]; then
        exit-code "${EC_INTERNAL_ERROR}" "Invalid status use"
    fi
    local PARAMETER=$1
    local VALUE=$2
    STATUS_STRING="${STATUS_STRING} ${PARAMETER}=${VALUE}"
}

check-os() {
    local ASTRA_VERSION="NONE"
    ASTRA_VERSION=$(host.sshpass "cat /etc/astra/build_version" || echo "UNKNOWN")
    add-status "ASTRA_VERSION" "${ASTRA_VERSION}"
}

check-kernel() {
    local KERNEL_VERSION="NONE"
    KERNEL_VERSION=$(host.sshpass "uname -r" || echo "UNKNOWN")
    add-status "KERNEL_VERSION" "${KERNEL_VERSION}"
}

check-domain() {
    local DOMAIN_ROLE="NONE"
    if host.sshpass "astra-freeipa-server -i 2>/dev/null | grep -q 'Обнаружен настроенный домен'"; then
        DOMAIN_ROLE="SERVER"
    fi

    if host.sshpass "astra-freeipa-client -i 2>/dev/null | grep -q 'Обнаружен настроенный клиент в домене'"; then
        DOMAIN_ROLE="CLIENT"
    fi
    add-status "DOMAIN_ROLE" "${DOMAIN_ROLE}"
}

check-front() {
    local FRONT="NONE"
    if host.sudo-cmd 'onezone show 0 2>/dev/null | grep -q "${HOSTNAME}.*leader"'; then
        FRONT="LEADER"
    elif host.sudo-cmd 'onezone show 0 2>/dev/null | grep -q "${HOSTNAME}.*follower"'; then
        FRONT="FOLLOWER"
    elif host.sshpass "dpkg -l brestcloud-ipa" &> /dev/null; then
        FRONT="TRUE"
    fi

    add-status "FRONT" "${FRONT}"
}

check-virt() {
    local VIRT="FALSE"
    if host.sshpass "dpkg -l ipa-libvirt-qemu" &> /dev/null; then
        VIRT="TRUE"
    fi
    add-status "VIRT" "${VIRT}"
}

check-kub() {
    local KUB="NONE"
    if host.sshpass "dpkg -l brest-manager" &> /dev/null; then
        KUB="SERVER"
    elif host.sshpass "dpkg -l cockpit" &> /dev/null; then
        KUB="CLIENT"
    fi

    add-status "KUB" "${KUB}"
    if host.sshpass "dpkg -l brest-manager" &> /dev/null; then
        KUB="SERVER"
    elif host.sshpass "dpkg -l cockpit" &> /dev/null; then
        KUB="CLIENT"
    fi
}

# MAIN ################################################################
(
    flock -n 200 || exit-code "${EC_WAITING}" "Failed to take lock"
    # shellcheck disable=SC1091
    source /var/lib/brest-kub/scripts/init-user-env.sh

    BR_HOST_ARGS=${BR_CONFIG[$BR_HOSTNAME]}
    # shellcheck disable=SC2086
    host.parse-host-params ${BR_HOST_ARGS}
    BR_FQDN="${BR_HOSTNAME}.${BR_DOMAIN}"

    # Pre-conditions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ping -c 1 -W 4 "${BR_IP}" >/dev/null || exit-code "${EC_IP_UNAVAILABLE}" "Host unavailable"

    if [[ "${BR_TYPE}" == *E* ]]; then # do not configure external hosts
        exit-code "${EC_PREPARED}" "Skip external host checks and modifications"
    fi

    ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "${BR_IP}"
    sshpass -p "${BR_PASSWORD}" ssh -o StrictHostKeyChecking=no -q "${BR_USER}@${BR_IP}" exit || exit-code "${EC_SSH_FAILED}" "Cannot login via ssh"

    # Collect states ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    check-os
    check-kernel
    check-domain
    check-front
    check-virt
    check-kub

    echo "Sending state string ${STATUS_STRING}"
    send_command_to_server "host set-state ${STATUS_STRING}"

    # Requirements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    require-sudo || exit-code "${EC_SUDO_ERROR}" "Sudo check failed"
    require-smolensk || exit-code "${EC_ASTRA_MODE_ERROR}" "Smolensk check failed"
    require-network-manager || exit-code "${EC_NETWORK_MANAGER_ERROR}" "Network manager check failed"

    # SSH key handling/check ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    if [[ "$BR_CMD" == "configure" ]]; then
        host.send-key || exit-code "${EC_PREPARE_FAILED}" "Failed to send ssh key"
    fi
    host.check-key || exit-code "${EC_AVAILABLE}" "Cannot login via ssh key"

    # Exit on readonly ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    if [[ "$BR_CMD" == "check" ]]; then
        if host.sshpass "dpkg -l cockpit" &> /dev/null ; then
            exit-code "${EC_PREPARED}" "Host is already configured"
        else
            exit-code "${EC_AVAILABLE}" "Host is ready to configure" # reached this point, means host available
        fi
    fi

    # Prepare hosts file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    if [[ "$BR_CMD" == "hosts" ]]; then
        echo "Fixing /etc/hosts"
        configure-etc-hosts
        exit-code "${EC_PREPARED}" "Hosts updated"
    fi

    host.configure-sources-list || exit-code "${EC_PREPARE_FAILED}" "Preparing sources.list failed"
    host.configure-packages || exit-code "${EC_PREPARE_FAILED}" "Preparing packages failed"
    host.configure-services || exit-code "${EC_PREPARE_FAILED}" "Preparing services failed"
    host.reboot # do not check for error
    exit-code "${EC_PREPARED}" "Host configured"

) 200>"/var/lock/${BR_HOSTNAME}.lock"
