#!/usr/bin/env bash
#
# auto-reboot.sh — Reboot conditionnel d'une machine Debian
#
# Logique :
#   1. Y a-t-il un reboot requis (paquets/kernel) ? Sinon -> fin.
#   2. Si oui, on vérifie une série de garde-fous (users connectés,
#      uptime minimal, ping, script de vie custom, onduleur).
#   3. Si tous les garde-fous sont OK -> reboot direct. Sinon -> on annule,
#      on log pourquoi, et on notifie par mail si configuré.
#
# Dépendances : display.sh (affichage/log) et lockfile.sh (anti double-run).
# Prévu pour tourner via cron / systemd timer, en root.

set -o pipefail

LOCKFILE_AUTO_LOCK=yes

# shellcheck disable=SC1091
. display.sh
# shellcheck disable=SC1091
. lockfile.sh

# ============================================================
# CONFIGURATION (modifiable ici, ou via un fichier externe -
# voir CONFIG_FILE plus bas qui écrase ces valeurs par défaut)
# ============================================================

# Fichier standard Debian/Ubuntu indiquant qu'un reboot est nécessaire
REBOOT_REQUIRED_FILE="/var/run/reboot-required"

# --- Garde-fou : utilisateurs connectés ---
CHECK_USERS=true                # true/false : bloquer le reboot si des users sont loggés

# --- Garde-fou : uptime minimal ---
CHECK_MIN_UPTIME=true           # true/false
MIN_UPTIME_HOURS=24             # ne pas rebooter si la machine a moins de X h d'uptime

# --- Garde-fou : ping (liste vide = désactivé) ---
PING_HOSTS=()                   # ex: ("8.8.8.8" "1.1.1.1") -- si non vide, le check est actif
PING_COUNT=2
PING_TIMEOUT=3                  # secondes par ping

# --- Garde-fou : script de vie personnalisé/évolutif (vide = désactivé) ---
LIVENESS_SCRIPT=""              # ex: "/usr/local/bin/liveness-check.sh" -- doit exit 0 = OK

# --- Garde-fou : onduleur (chaque variable non vide active son propre check,
#     les deux peuvent être utilisées en même temps) ---
UPS_NUT_NAME=""                 # ex: "ups@localhost" -- si non vide, check via NUT (upsc)
UPS_CUSTOM_CMD=""               # ex: une commande custom -- exit 0 = secteur, sinon = batterie

# --- Comportement général ---
DRY_RUN=false                   # true = on simule, on ne reboote jamais réellement

# --- Notification mail (uniquement en cas d'annulation, ou juste avant le reboot) ---
# NOTIFY_EMAIL_TO vide = notifications désactivées, non-vide = activées
NOTIFY_EMAIL_TO=""
NOTIFY_EMAIL_SUBJECT_PREFIX="[auto-reboot]"

# Fichier de conf externe optionnel : permet de surcharger les variables
# ci-dessus sans toucher au script (ex: /etc/auto-reboot.conf)
CONFIG_FILE="/etc/auto-reboot-bugland.conf"
if [[ -f "$CONFIG_FILE" ]]; then
    # shellcheck disable=SC1091
    . "$CONFIG_FILE"
fi

# ============================================================
# UTILITAIRES
# ============================================================

require_root() {
    if [[ "$EUID" -ne 0 ]]; then
        display_error "Ce script doit être lancé en root."
        exit 1
    fi
}

# Envoie un mail si NOTIFY_EMAIL_ENABLED=true. Ne bloque jamais le script
# si `mail` n'est pas installé : on log juste un warning.
send_mail() {
    local subject="$1"
    local body="$2"

    [[ -z "$NOTIFY_EMAIL_TO" ]] && return 0

    if ! command -v mail &>/dev/null; then
        display_warning "Notification mail demandée mais la commande 'mail' est introuvable (installer mailutils/bsd-mailx)."
        return 0
    fi

    echo "$body" | mail -s "${NOTIFY_EMAIL_SUBJECT_PREFIX} ${subject}" "$NOTIFY_EMAIL_TO"
}

# ============================================================
# ÉTAPE 1 : reboot nécessaire ?
# ============================================================

require_root

if [[ ! -f "$REBOOT_REQUIRED_FILE" ]]; then
    display_informational "Aucun reboot requis. Fin."
    exit 0
fi

display_informational "Reboot requis détecté (%s). Vérification des garde-fous..." "$REBOOT_REQUIRED_FILE"

# On accumule les raisons de blocage pour un mail/log clair à la fin
BLOCKERS=()

# ============================================================
# GARDE-FOU : utilisateurs connectés
# ============================================================

if [[ "$CHECK_USERS" == true ]]; then
    CONNECTED_USERS=$(who | wc -l)
    if [[ "$CONNECTED_USERS" -gt 0 ]]; then
        BLOCKERS+=("utilisateurs connectés (${CONNECTED_USERS}) : $(who | awk '{print $1}' | sort -u | tr '\n' ' ')")
    else
        display_informational "OK : aucun utilisateur connecté."
    fi
fi

# ============================================================
# GARDE-FOU : uptime minimal depuis le dernier reboot
# ============================================================

if [[ "$CHECK_MIN_UPTIME" == true ]]; then
    UPTIME_SECONDS=$(awk '{print int($1)}' /proc/uptime)
    UPTIME_HOURS=$(( UPTIME_SECONDS / 3600 ))
    if [[ "$UPTIME_HOURS" -lt "$MIN_UPTIME_HOURS" ]]; then
        BLOCKERS+=("uptime trop récent (${UPTIME_HOURS}h < ${MIN_UPTIME_HOURS}h requis)")
    else
        display_informational "OK : uptime de %sh (>= %sh requis)." "$UPTIME_HOURS" "$MIN_UPTIME_HOURS"
    fi
fi

# ============================================================
# GARDE-FOU : ping (actif si PING_HOSTS non vide)
# ============================================================

if [[ ${#PING_HOSTS[@]} -gt 0 ]]; then
    PING_FAILED=()
    for host in "${PING_HOSTS[@]}"; do
        if ping -c "$PING_COUNT" -W "$PING_TIMEOUT" "$host" &>/dev/null; then
            display_informational "OK : ping réussi vers %s." "$host"
        else
            PING_FAILED+=("$host")
        fi
    done
    if [[ ${#PING_FAILED[@]} -gt 0 ]]; then
        BLOCKERS+=("ping échoué vers : ${PING_FAILED[*]}")
    fi
fi


# ============================================================
# GARDE-FOU : script de vie personnalisé (actif si LIVENESS_SCRIPT non vide)
# ============================================================

if [[ -n "$LIVENESS_SCRIPT" ]]; then
    if [[ ! -x "$LIVENESS_SCRIPT" ]]; then
        BLOCKERS+=("script de vie introuvable ou non exécutable : ${LIVENESS_SCRIPT}")
    elif "$LIVENESS_SCRIPT"; then
        display_informational "OK : script de vie (%s) a répondu OK." "$LIVENESS_SCRIPT"
    else
        BLOCKERS+=("script de vie KO : ${LIVENESS_SCRIPT} (exit != 0)")
    fi
fi

# ============================================================
# GARDE-FOU : onduleur (actif si UPS_NUT_NAME et/ou UPS_CUSTOM_CMD non vide)
# ============================================================

ON_BATTERY=false

if [[ -n "$UPS_NUT_NAME" ]]; then
    if command -v upsc &>/dev/null; then
        STATUS=$(upsc "$UPS_NUT_NAME" ups.status 2>/dev/null)
        if [[ "$STATUS" == *OB* ]]; then
            ON_BATTERY=true
        else
            display_informational "OK (NUT) : alimentation secteur normale."
        fi
    else
        display_warning "upsc introuvable, impossible de vérifier l'onduleur via NUT (paquet nut-client non installé ?)."
    fi
fi

if [[ -n "$UPS_CUSTOM_CMD" ]]; then
    if eval "$UPS_CUSTOM_CMD" &>/dev/null; then
        display_informational "OK (custom) : alimentation secteur normale."
    else
        ON_BATTERY=true
    fi
fi

if [[ "$ON_BATTERY" == true ]]; then
    BLOCKERS+=("machine sur onduleur/batterie (secteur coupé)")
fi

# ============================================================
# GARDE-FOU : une mise à jour Debian est en cours
# ============================================================
 
APT_BUSY=false
 
# Les locks apt/dpkg sont détenus tant qu'une opération est en cours
for lock in /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock; do
    if [[ -f "$lock" ]] && fuser "$lock" &>/dev/null; then
        APT_BUSY=true
        break
    fi
done

if [[ "$APT_BUSY" == true ]]; then
    BLOCKERS+=("une mise à jour apt/dpkg est en cours")
else
    display_informational "OK : aucune mise à jour apt/dpkg en cours."
fi

# ============================================================
# DÉCISION FINALE
# ============================================================

if [[ ${#BLOCKERS[@]} -gt 0 ]]; then
    REASONS=""
    display_warning "Reboot annulé. Raisons :"
    for b in "${BLOCKERS[@]}"; do
        display_warning "  - %s" "$b"
        REASONS+="- ${b}"$'\n'
    done
    send_mail "Reboot annulé sur $(hostname)" "Le reboot a été annulé pour la/les raison(s) suivante(s) :

${REASONS}"
    exit 0
fi

display_notice "Tous les garde-fous sont au vert. Reboot immédiat."

if [[ "$DRY_RUN" == true ]]; then
    display_informational "[DRY-RUN] Reboot simulé, aucune action réelle effectuée."
    exit 0
fi

send_mail "Reboot en cours sur $(hostname)" "Tous les garde-fous sont au vert, la machine $(hostname) redémarre maintenant."

reboot
