#!/bin/bash
# Utilitaire exécutable par les utilisateurs pour la mise à jour de leur poste
# Usage : scibian-update

# Gestion des couleurs
BLUE='\033[0;34m'
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'

echo_green() {
    echo -e "${GREEN}$*${NC}"
}

echo_red() {
    echo -e "${RED}$*${NC}"
}

# Vérifie si le script s'exécute en root, sinon le rééxécute avec sudo
root_or_sudo() {
    if [ "$EUID" -ne 0 ]; then
        sudo "$(readlink -f "${BASH_SOURCE[0]}")" "$@"
        exit
    fi
}

# Pose une question à l'utilisateur, quitte le programme s'il ne répond pas "o"
check_consent() {
    read -r -p "$* (o/n) " ISOK
    if [ ! "$ISOK" ] || [ "$ISOK" != "o" ]; then
        echo "Arrêt du script..."
        exit
    fi
}

display_last_update() {
    sLastUpdateMinutes=$(sciutils-update get_minutes_since_update --apt)
    #conversion en jours et heures
    sLastUpdateHours=$((sLastUpdateMinutes / 60))
    sLastUpdateDays=$((sLastUpdateHours / 24))
    sLastUpdateHoursModulo=$((sLastUpdateHours % 24))
    echo -e "Dernière mise à jour réalisée il y a : ${BLUE}${sLastUpdateDays} ${NC}jours et ${BLUE}${sLastUpdateHoursModulo} ${NC}heures"
}

# Bloque l'exécution du script tant que le poste n'est pas branché au secteur.
check_ac_power() {
    # On récupère un code de retour potentiellement non-nul sans perturber set -e(rrexit)
    # https://stackoverflow.com/a/15844901
    on_ac_power && power_status=$? || power_status=$?

    # power_status = 255 -> fixe, OK
    # power_status = 0 -> laptop branché, OK
    # power_status = 1 -> laptop pas branché, KO
    if [ "$power_status" -eq 1 ]; then
        echo_red "La mise à jour commencera quand votre poste sera branché au secteur (Ctrl-C pour quitter)."
        while ! on_ac_power; do
            sleep 5
        done
    fi
}

# Test de la connectivité concernant l'accès aux dépôts
# Quitte le programme si pas d'accès
check_edf_network() {
    if ! sciutils-networking is_edf_network > /dev/null 2>&1; then
        echo_red "Attention, vous devez être connecté sur le réseau EDF ou en VPN pour activer les mises à jour."
        sciutils-update add_history_failure --apt -m "Scibian-update -> poste hors RIN/VPN"
        exit 1
    fi
    echo_red "Merci de laisser le poste connecté au réseau jusqu'à la fin de la mise à jour."
}

# Si la màj concerne un paquet PulseSecure, avertit l'utilisateur et lui permet d'arrêter.
check_pulse() {
    local upgradable_packages="$1"
    if grep -q -E "pulsesecure" <<< "$upgradable_packages"; then
        echo_red "Attention, une mise à jour de PulseSecure est prévue. Cette action coupera la connexion VPN le temps de la mise à jour."
        check_consent "Êtes-vous sûr de vouloir effectuer la mise à jour maintenant ?"
    fi
}

# Si la màj concerne un paquet wpa, avertit l'utilisateur que la connexion Wi-Fi va être interrompu et lui permet d'arrêter au besoin.s
check_wpa() {
    local upgradable_packages="$1"
    if grep -q -E "wpasupplicant" <<< "$upgradable_packages"; then
        echo_red "Attention, une mise à jour de wpasupplicant est prévue. Cette action va interrompre temporairement la connexion Wi-Fi le temps de la mise à jour"
        check_consent "Êtes-vous sûr de vouloir effectuer la mise à jour maintenant ?"
    fi
}

# Si la màj concerne un paquet VBox, avertit l'utilisateur et lui permet d'arrêter.
# S'il veut continuer, attend que plus aucun process VBox ne tourne pour poursuivre la màj.
# A noter : un process "VirtualBox" peut persister un certain temps après l'extinction de la VM
# TODO: supprimer après la migration
check_vbox() {
    local upgradable_packages="$1"
    if grep -q -E "virtualbox|scibian-neptune-vb" <<< "$upgradable_packages"; then
        echo_red "Attention, une mise à jour de VirtualBox est prévue. Cette action ne peut se faire que si votre VM est arrêtée.\nMerci de bien vouloir éteindre celle-ci afin de pouvoir procéder à la mise à jour."
        check_consent "Êtes-vous sûr de vouloir effectuer la mise à jour maintenant ?"
        echo_green "La mise à jour va se poursuivre dès qu'il n'y aura plus de process de VirtualBox en cours."

        # Vérifier si les processus VBoxXPCOMIPCD, VBoxSVC ou VirtualBox sont en cours d'exécution
        if pgrep -f "VBoxXPCOMIPCD|VBoxSVC|VirtualBox" > /dev/null; then

            # Vérifier s'il y a des processus VirtualBox parmi ceux-ci
            if pgrep -f "VirtualBox" > /dev/null; then
                while [[ $(pgrep -f "VirtualBox" | wc -l) != "0" ]]; do
                    echo "$(pgrep -f "VirtualBox" | wc -l) process encore en cours..."
                    sleep 5
                done
            fi
            # arrêt de virtualbox
            systemctl stop vboxweb
        fi

        echo "Démarrage de la mise à jour..."

        # Vérifier le statut du paquet "virtualbox", paquet déprécié (version 6.0). Utilisation a présent des paquets nommés virtualbox-<version>
        package_status=$(dpkg-query -W -f='${Status}' virtualbox 2> /dev/null)

        if [[ $package_status == "install ok"* ]]; then
            apt remove -y virtualbox virtualbox-qt virtualbox-dkms
            apt install -y -o Dpkg::Options::="--force-confnew" scibian-neptune-vb
        fi

        # Forcer l'installation de scibian-neptune-vb en cas de nouvelle dépendnce car par défaut APT ne le fait pas
        if grep -q "scibian-neptune-vb" <<< "$upgradable_packages"; then
            snv_version=$(apt-cache policy scibian-neptune-vb | grep Candidat | awk '{print $2 }')
            apt install -y scibian-neptune-vb="$snv_version"
        fi

    fi
}

# Si la màj concerne les paquets de conf de la vdi : xendesktopvda ou scibian-nvidia-vdi avertit l'utilisateur et lui permet d'arrêter.
check_vdi() {
    local upgradable_packages="$1"
    # Vérifie si les paquets xendesktopvda ou scibian-nvidia-vdi sont dans les paquets upgradables
    if grep -q -E "^Inst xendesktopvda|^Inst scibian-nvidia-vdi" <<< "$upgradable_packages"; then
        # La mis-à-jour se lance seulement en ssh et s'il n'y a pas d'autres session connectée
        if [[ $(pstree -s $$) = *sshd* ]] && [ "$(users | wc -w)" -eq 1 ]; then
            echo_red "Attention, une mise à jour de la VDI Citrix est prévue. Veuillez attendre la fin de la mise-à-jour avant de pouvoir relancer une session VDI avec le client Citrix."
            check_consent "Êtes-vous sûr de vouloir effectuer la mise à jour maintenant ?"
            need_reboot=true
            # enlève un apt hold qui aurait pu être mis lors d'une éxécution précédente
            apt-mark unhold scibian-nvidia-vdi xendesktopvda > /dev/null 2>&1

            # Permet de remettre la configuration en place après maj du xendesktopvda
            if apt list --upgradable | grep -e '^xendesktopvda'; then
                need_reconfigure_xendesktopvda_conf=true
            fi
        else
            echo_red "Attention, une mise à jour de la VDI Citrix est nécessaire. \nVous devez fermer la session graphique Citrix et garder qu'une seule session SSH d'ouverte. \nPuis relancer scibian-update depuis cette dernière."
            check_consent "Voulez-vous néanmoins mettre à jour les AUTRES paquets ?"
            sciutils-update add_history_failure --apt -m "Mise-à-jour des paquets liés à VDI nécessaire. En attente de fermeture de session graphique et lancement en ssh de scibian-update"
            apt-mark hold xendesktopvda scibian-nvidia-vdi > /dev/null 2>&1
        fi
    fi
}

update_upgrade() {
    echo "Installation en cours..."

    # La commande suivante interdit tout downgrade et ce sans prompt user (pas de décisions, comportement égal pour tous).
    # L'interdiction de downgrade se fait au niveau des options DPKG en fin de commande. Cett interdiction intervient au moment de l'installation des paquets.
    # Le no-prompt existe grâce à l'association de l'option -y ainsi que de '--allow-downgrades' qui est nécessaire UNIQUEMENT pour pour que le no-prompt soit effectif
    # même si son nom peut porter à confusion. Elle ne signife pas que les downgrades seront effectifs! Pas possible de faire autrement si on veut un no-prompt.
    echo "Mise à jour des paquets en cours. Merci de bien vouloir patienter."
    reponse=$(DEBIAN_FRONTEND=noninteractive apt -o "Apt::Cmd::Disable-Script-Warning=1" upgrade -y --allow-downgrades -o Dpkg::Options::="--force-confnew" -o Dpkg::Options::="--refuse-downgrade" | tee /dev/tty)
    if (echo "$reponse" | grep "apt --fix-broken install"); then
        apt -y --fix-broken install
    fi

    # On reconfigure scibian-xendesktopvda après la maj de xendesktopvda pour réappliquer les configurations du VDA qui sautent après MAJ
    if [ "${need_reconfigure_xendesktopvda_conf:-}" = "true" ] ; then 
        dpkg-reconfigure scibian-xendesktopvda
    fi

    sciutils-update add_history_success --apt -m "scibian-update -> complète"
    echo "Fin de la mise à jour des paquets."

    # enlève un éventuel apt hold qui génère une erreur puppet
    apt-mark unhold scibian-nvidia-vdi xendesktopvda > /dev/null 2>&1

    # Attente que le poste se reconnecte au réseau si maj de NetworkManager ou wpasupplicant par exemple
    if ! sciutils-networking is_edf_network > /dev/null 2>&1; then
        echo_red "Veuillez vous reconnecter au réseau EDF pour terminer la mise à jour (Ctrl-C pour quitter)"
        # Attente indéfiniment que le poste se connecte au réseau EDF
        while ! sciutils-networking is_edf_network > /dev/null 2>&1; do
            sleep 1
        done
    fi  

    # Propose un redémarrage si nécessaire
    if [ "${need_reboot:-}" = "true" ] ; then 
        echo_red "Vous devez redémarrer COMPLETEMENT votre vdi."
        check_consent "Voulez-vous le faire maintenant ?"
        shutdown -r now
    fi
}

main() {
    root_or_sudo "$@"
    display_last_update
    check_ac_power
    check_edf_network
    apt update
    upgradable_packages=$(apt -o "Apt::Cmd::Disable-Script-Warning=1" --simulate upgrade 2> /dev/null)
    listing=""
    nblisting=0
    listing=$(apt -o "Apt::Cmd::Disable-Script-Warning=1" list --upgradable)
    nblisting=$(echo -e "$listing" | wc -l)
    if [ "$nblisting" -gt 1 ]; then
        echo -e "Voici la liste des paquets en attente de mise à jour :"
        echo -e "$listing"
        check_consent "Voulez-vous déclencher la mise à jour maintenant ?"
        check_pulse "$upgradable_packages"
        check_wpa "$upgradable_packages"
        check_vbox "$upgradable_packages"
        check_vdi "$upgradable_packages"
        update_upgrade
    else
        echo "Il n'y a aucune mise à jour à effectuer."
        sciutils-update add_history_success --apt -m "scibian-update -> pas de maj en attente"
    fi

    # Ajout de l'execution de Puppet dans la foulée afin de corriger certaines mises à jour tel vscode qui rajoute son propre dépôt. Le if vérifie au préalable si puppet est installé pour les sites surs.
    if dpkg -l | grep puppet | grep -q "^ii"; then
        echo "Exécution de Puppet, merci de bien vouloir patienter."
        puppet agent -t
        echo "Fin de son exécution."
    fi

    echo_green "Appuyez sur la touche 'Entrée' pour terminer le programme" && read -r
}

main "$@"
