#!/usr/bin/env bash
# =============================================================================
# KDL Assistance — client Linux (dépannage encadré à distance).
#
# Se connecte EN SORTANT vers l'Atelier KDL, obtient un CODE à communiquer au
# technicien, puis exécute UNIQUEMENT des actions d'une liste blanche :
#   - diagnostics en LECTURE SEULE (auto) ;
#   - réparations en ÉCRITURE (installs, nettoyage, redémarrage de services non
#     critiques) qui ne sont poussées QUE si Karim les a approuvées côté Atelier.
#
# Sécurité :
#   * Aucune commande shell arbitraire : chaque action -> une procédure connue.
#   * Le client REFUSE toute action hors de SA propre liste blanche (2e verrou).
#   * Les installs sont limitées à une ALLOWLIST de logiciels grand public.
#   * AUCUNE action réseau/DNS/pare-feu/SSH : ce canal sortant est l'unique lien,
#     on ne prend jamais le risque de le couper (pas de SSH de secours à distance).
#   * Transparence totale : tout s'affiche, arrêt possible à tout moment (Ctrl+C).
#   * Rien n'est installé en tant que service : le script tourne le temps de la
#     session puis rend la main ; la fermeture invalide le credential.
#
# Usage :
#   bash kdl-assistance-linux.sh                       # serveur par défaut
#   bash kdl-assistance-linux.sh --serveur http://IP:4300 --duree 1800
# =============================================================================
set -uo pipefail

SERVEUR="http://192.168.50.6:4300"
DUREE_MAX=1800
while [ $# -gt 0 ]; do
  case "$1" in
    --serveur) SERVEUR="$2"; shift 2;;
    --duree)   DUREE_MAX="$2"; shift 2;;
    *) echo "Option inconnue : $1"; exit 2;;
  esac
done

command -v curl   >/dev/null 2>&1 || { echo "curl est requis."; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "python3 est requis."; exit 1; }

# --- Helpers JSON (python3, aucune dépendance externe) -----------------------
jget()  { python3 -c 'import sys,json;print(json.load(sys.stdin).get(sys.argv[1],""))' "$1"; }
pget()  { python3 -c 'import sys,json;print(json.loads(sys.argv[1]).get(sys.argv[2],""))' "$1" "$2"; }
mkjson(){ python3 -c 'import sys,json
d={}
for kv in sys.argv[1:]:
 k,_,v=kv.partition("=")
 d[k]=True if v=="true" else False if v=="false" else v
print(json.dumps(d,ensure_ascii=False))' "$@"; }

# --- Listes blanches ---------------------------------------------------------
# Logiciels autorisés à l'installation (grand public, sûrs). Extensible par Karim.
ALLOWLIST_PAQUETS="firefox firefox-esr chromium chromium-browser libreoffice \
vlc gimp inkscape audacity htop neofetch git curl wget flatpak timeshift \
gparted gnome-disk-utility thunderbird transmission-gtk keepassxc filezilla \
qbittorrent obs-studio kdenlive shotwell simple-scan"

# Services qu'on ne redémarre JAMAIS (protègent le réseau / le canal / le système).
SERVICES_INTERDITS_REGEX='^(ssh|sshd|NetworkManager|network|networkd|systemd-networkd|systemd-resolved|wpa_supplicant|kdl|dbus|systemd-logind|polkit)'

# --- Exécution d'une action (liste blanche) ----------------------------------
# Retourne un objet JSON sur stdout. Toute action inconnue -> refus.
executer() {
  local action="$1" params="$2"
  case "$action" in
    # ---------- LECTURE SEULE (risque 0) ----------
    diagnostic.system)
      local os; os=$(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-Linux}")
      mkjson ok=true os="$os" kernel="$(uname -r)" hostname="$(hostname)" \
             uptime="$(uptime -p 2>/dev/null)" \
             cpu="$(nproc 2>/dev/null) coeurs" \
             mem="$(free -h 2>/dev/null | awk '/Mem:/{print $3"/"$2}')"
      ;;
    diagnostic.network)
      local ifs; ifs=$(ip -brief -4 addr 2>/dev/null | awk '{print $1"="$3}' | paste -sd'|' -)
      local gw;  gw=$(ip route 2>/dev/null | awk '/default/{print $3; exit}')
      local dns; dns=$( (resolvectl dns 2>/dev/null || cat /etc/resolv.conf 2>/dev/null | awk '/nameserver/{print $2}') | tr '\n' ' ')
      local rok=false; getent hosts kdl-tech.fr >/dev/null 2>&1 && rok=true
      mkjson ok=true interfaces="$ifs" gateway="$gw" dns="$dns" resolution_ok="$rok"
      ;;
    diagnostic.disk)
      local d; d=$(df -h --output=target,size,used,avail,pcent 2>/dev/null | grep -E '^/|^/home|^/$' | head -8 | tr '\n' ';')
      mkjson ok=true volumes="$d"
      ;;
    diagnostic.services)
      local f; f=$(systemctl --failed --no-legend 2>/dev/null | awk '{print $1}' | head -15 | tr '\n' ' ')
      mkjson ok=true services_en_echec="${f:-aucun}"
      ;;

    # ---------- INSTALLATION (risque 2, déjà approuvée côté Atelier) ----------
    repair.package.install)
      local paquet; paquet=$(pget "$params" paquet)
      [ -z "$paquet" ] && { mkjson ok=false raison="paquet manquant"; return; }
      # 2e verrou : allowlist stricte côté client.
      if ! printf ' %s ' $ALLOWLIST_PAQUETS | grep -q " $paquet "; then
        mkjson ok=false refuse=true raison="paquet « $paquet » hors allowlist client"; return
      fi
      local deja=false
      dpkg -s "$paquet" >/dev/null 2>&1 && deja=true
      local src="apt" log rc
      if [ "$deja" = true ]; then
        mkjson ok=true paquet="$paquet" deja_installe=true source="$src" message="déjà présent, rien à faire"; return
      fi
      log=$(sudo -n DEBIAN_FRONTEND=noninteractive apt-get install -y "$paquet" 2>&1); rc=$?
      if [ $rc -ne 0 ] && command -v flatpak >/dev/null 2>&1; then
        log=$(flatpak install -y --noninteractive flathub "$paquet" 2>&1); rc=$?; src="flatpak"
      fi
      if [ $rc -eq 0 ]; then
        mkjson ok=true paquet="$paquet" deja_installe=false source="$src" \
               rollback="sudo apt-get remove -y $paquet" message="installé"
      else
        mkjson ok=false paquet="$paquet" source="$src" erreur="$(echo "$log" | tail -3 | tr '\n' ' ')"
      fi
      ;;

    # ---------- NETTOYAGE réversible (risque 1) ----------
    repair.optim.clean)
      local avant; avant=$(df -h / 2>/dev/null | awk 'NR==2{print $4}')
      sudo -n apt-get clean          >/dev/null 2>&1
      sudo -n journalctl --vacuum-time=7d >/dev/null 2>&1
      rm -rf "$HOME/.cache/thumbnails"/* 2>/dev/null
      rm -rf "$HOME/.local/share/Trash/files"/* "$HOME/.local/share/Trash/info"/* 2>/dev/null
      local apres; apres=$(df -h / 2>/dev/null | awk 'NR==2{print $4}')
      mkjson ok=true nettoye="caches apt, journaux >7j, miniatures, corbeille" \
             espace_libre_avant="$avant" espace_libre_apres="$apres" \
             note="aucune donnée utilisateur supprimée ; caches régénérables"
      ;;

    # ---------- REDÉMARRAGE de service NON critique (risque 2, approuvé) ----------
    repair.service.restart)
      local svc; svc=$(pget "$params" service)
      [ -z "$svc" ] && { mkjson ok=false raison="service manquant"; return; }
      if echo "$svc" | grep -Eiq "$SERVICES_INTERDITS_REGEX"; then
        mkjson ok=false refuse=true raison="service « $svc » protégé (réseau/critique) : jamais redémarré à distance"; return
      fi
      local etat_avant; etat_avant=$(systemctl is-active "$svc" 2>/dev/null)
      sudo -n systemctl restart "$svc" >/dev/null 2>&1
      local etat_apres; etat_apres=$(systemctl is-active "$svc" 2>/dev/null)
      mkjson ok="$([ "$etat_apres" = active ] && echo true || echo false)" \
             service="$svc" etat_avant="$etat_avant" etat_apres="$etat_apres"
      ;;

    *)
      mkjson ok=false refuse=true raison="action « $action » hors liste blanche du client"
      ;;
  esac
}

# Point d'entrée testable : si sourcé avec KDL_TEST=1, on s'arrête avant la
# session (permet de tester la fonction executer sans lancer de connexion).
[ -n "${KDL_TEST:-}" ] && return 0 2>/dev/null || true

# --- Bandeau + consentement --------------------------------------------------
clear 2>/dev/null || true
cat <<'BANNER'
============================================================
   KDL Assistance — dépannage à distance encadré
============================================================
Ce programme va :
  • se connecter à l'atelier KDL et afficher un CODE à communiquer ;
  • exécuter uniquement des actions demandées ET validées par le technicien
    (diagnostics, installation de logiciels courants, nettoyage, réglages) ;
  • NE JAMAIS toucher à votre réseau, votre connexion, ni à vos données.
Vous pouvez tout arrêter à tout moment avec Ctrl+C.
============================================================
BANNER
read -r -p "Démarrer KDL Assistance ? [o/N] " REP
case "$REP" in o|O|oui|Oui) ;; *) echo "Annulé."; exit 0;; esac

echo
echo "KDL Assistance a besoin des droits administrateur (pour installer des"
echo "logiciels et appliquer des réglages). Votre mot de passe reste sur VOTRE PC."
sudo -v || { echo "Droits administrateur refusés — arrêt."; exit 1; }
# Maintient le cache sudo actif pendant la session.
( while kill -0 "$$" 2>/dev/null; do sudo -n true 2>/dev/null; sleep 45; done ) &
SUDO_KEEP=$!

# --- Session -----------------------------------------------------------------
OS=$(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-Linux}")
HOST=$(hostname)
HELLO=$(curl -fsS -X POST "$SERVEUR/assist/hello" -H 'Content-Type: application/json' \
        -d "$(mkjson hostname="$HOST" os="$OS")" 2>/dev/null) \
  || { echo "Impossible de joindre l'atelier ($SERVEUR)."; exit 2; }
CODE=$(echo "$HELLO" | jget code)
SID=$(echo "$HELLO"  | jget session_id)
CTOK=$(echo "$HELLO" | jget client_token)
[ -z "$CTOK" ] && { echo "Réponse invalide de l'atelier."; exit 2; }

echo
echo "  ┌────────────────────────────────────────────┐"
echo "  │  CODE À COMMUNIQUER AU TECHNICIEN : $CODE"
echo "  └────────────────────────────────────────────┘"
echo "Session $SID — en attente d'activation par le technicien."
echo "(Fermez la fenêtre ou Ctrl+C pour tout arrêter.)"
echo

fermer() {
  curl -fsS -X POST "$SERVEUR/assist/close" -H "Authorization: Bearer $CTOK" >/dev/null 2>&1 || true
  kill "$SUDO_KEEP" 2>/dev/null || true
  echo; echo "Session KDL Assistance clôturée. Aucun service résident, credential invalidé."
}
trap 'fermer; exit 0' EXIT INT TERM

# --- Boucle : heartbeat + exécution des actions approuvées -------------------
SECONDS=0
while [ "$SECONDS" -lt "$DUREE_MAX" ]; do
  HB=$(curl -fsS -o /dev/null -w '%{http_code}' -X POST "$SERVEUR/assist/heartbeat" \
       -H "Authorization: Bearer $CTOK" 2>/dev/null)
  if [ "$HB" != "200" ]; then echo "Session terminée par le technicien (ou expirée)."; break; fi

  ACTS=$(curl -fsS "$SERVEUR/assist/actions" -H "Authorization: Bearer $CTOK" 2>/dev/null) || ACTS='{}'
  while IFS=$'\t' read -r AID ACTION PARAMS; do
    [ -z "$AID" ] && continue
    echo "→ Action demandée : $ACTION"
    RES=$(executer "$ACTION" "$PARAMS")
    curl -fsS -X POST "$SERVEUR/assist/actions/$AID/result" -H "Authorization: Bearer $CTOK" \
         -H 'Content-Type: application/json' -d "$(python3 -c 'import sys,json;print(json.dumps({"resultat":json.loads(sys.argv[1])}))' "$RES")" \
         >/dev/null 2>&1
    echo "  ✓ résultat transmis"
  done < <(echo "$ACTS" | python3 -c 'import sys,json
try: d=json.load(sys.stdin)
except Exception: d={}
for a in d.get("actions",[]):
    print(str(a.get("id",""))+"\t"+str(a.get("action",""))+"\t"+json.dumps(a.get("params",{}),ensure_ascii=False))')

  sleep 3
done
