Script : post_install_tech_tools.sh

#!/usr/bin/env bash
# =============================================================================
#  post_install_tech_tools.sh
#  Post-installation et configuration des outils Tech
#
#  À exécuter après install_tech_tools.sh pour configurer les services,
#  groupes utilisateurs, bases de données et modules noyau.
#
#  Usage :
#    chmod +x post_install_tech_tools.sh
#    sudo ./post_install_tech_tools.sh [--dry-run] [--section SECTION]
#
#  Options :
#    --dry-run            Affiche les commandes sans les exécuter
#    --section SECTION    N'applique qu'une section
#    --help               Affiche cette aide
#
#  Sections disponibles :
#    groups | services | databases | kernel | configs | ai | osint | all
# =============================================================================

set -euo pipefail

# ─── Couleurs ────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GRN='\033[0;32m'; YLW='\033[1;33m'
BLU='\033[0;34m'; CYN='\033[0;36m'; BOLD='\033[1m'; RST='\033[0m'

DRY_RUN=false
SECTION_FILTER=""
LOG_FILE="/var/log/tech_tools_postinstall_$(date +%Y%m%d_%H%M%S).log"
NEEDS_LOGOUT=false
TARGET_USER="${SUDO_USER:-$(logname 2>/dev/null || echo "$USER")}"

# ─── Parsing arguments ───────────────────────────────────────────────────────
usage() {
  grep '^#  ' "$0" | sed 's/#  //'
  exit 0
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --dry-run)   DRY_RUN=true; shift ;;
    --section)   SECTION_FILTER="$2"; shift 2 ;;
    --help|-h)   usage ;;
    *)           echo "Option inconnue : $1"; usage ;;
  esac
done

# ─── Helpers ─────────────────────────────────────────────────────────────────
info()    { echo -e "${BLU}[INFO]${RST}  $*" | tee -a "$LOG_FILE"; }
success() { echo -e "${GRN}[OK]${RST}    $*" | tee -a "$LOG_FILE"; }
warn()    { echo -e "${YLW}[WARN]${RST}  $*" | tee -a "$LOG_FILE"; }
error()   { echo -e "${RED}[ERR]${RST}   $*" | tee -a "$LOG_FILE"; }
skip()    { echo -e "${CYN}[SKIP]${RST}  $*" | tee -a "$LOG_FILE"; }
title()   { echo -e "\n${BOLD}${CYN}══════════ $* ══════════${RST}" | tee -a "$LOG_FILE"; }

run_section() {
  [[ -z "$SECTION_FILTER" || "$SECTION_FILTER" == "$1" || "$SECTION_FILTER" == "all" ]]
}

run() {
  if $DRY_RUN; then
    echo -e "${YLW}[DRY-RUN]${RST} $*" | tee -a "$LOG_FILE"
  else
    eval "$@" 2>&1 | tee -a "$LOG_FILE"
  fi
}

# ─── Vérifications ───────────────────────────────────────────────────────────
if [[ $EUID -ne 0 ]] && ! $DRY_RUN; then
  error "Ce script doit être exécuté en root (sudo)."
  exit 1
fi

title "Post-installation Tech Tools"
info "Utilisateur cible : $TARGET_USER"
info "Log : $LOG_FILE"
info "Mode dry-run : $DRY_RUN"
[[ -n "$SECTION_FILTER" ]] && info "Filtre section : $SECTION_FILTER"

# ═══════════════════════════════════════════════════════════════════════════════
#  1. GROUPES UTILISATEUR
# ═══════════════════════════════════════════════════════════════════════════════
configure_groups() {
  run_section "groups" || return 0
  title "1. Groupes utilisateur"

  local current_groups
  current_groups=$(id -nG "$TARGET_USER" 2>/dev/null)

  add_to_group() {
    local group="$1"
    local reason="$2"
    if ! getent group "$group" &>/dev/null; then
      skip "$group : groupe inexistant (paquet non installé ?)"
      return
    fi
    if echo "$current_groups" | grep -qw "$group"; then
      skip "$TARGET_USER déjà dans le groupe $group"
    else
      info "Ajout de $TARGET_USER au groupe $group ($reason)"
      run "usermod -aG $group $TARGET_USER"
      NEEDS_LOGOUT=true
    fi
  }

  # Wireshark — dpkg-reconfigure nécessaire pour créer le groupe + setcap dumpcap
  if command -v dumpcap &>/dev/null; then
    if ! getent group wireshark &>/dev/null; then
      info "Configuration de Wireshark pour capture non-root..."
      if $DRY_RUN; then
        echo -e "${YLW}[DRY-RUN]${RST} dpkg-reconfigure wireshark-common (répondre Oui)"
      else
        echo "wireshark-common wireshark-common/install-setuid boolean true" | debconf-set-selections
        DEBIAN_FRONTEND=noninteractive dpkg-reconfigure wireshark-common 2>&1 | tee -a "$LOG_FILE"
      fi
    fi
    add_to_group "wireshark" "capture réseau sans root"
  fi

  add_to_group "docker"    "utiliser Docker sans sudo"
  add_to_group "vboxusers" "USB passthrough VirtualBox"
  add_to_group "dialout"   "accès ports série /dev/ttyUSB*"
  add_to_group "libvirt"   "gestion VMs libvirt"
  add_to_group "kvm"       "accélération KVM"
  add_to_group "scanner"   "accès scanner SANE"

  if $NEEDS_LOGOUT; then
    warn "Déconnexion/reconnexion nécessaire pour appliquer les groupes."
  fi
  success "Groupes configurés."
}

configure_groups

# ═══════════════════════════════════════════════════════════════════════════════
#  2. SERVICES SYSTEMD
# ═══════════════════════════════════════════════════════════════════════════════
configure_services() {
  run_section "services" || return 0
  title "2. Services systemd"

  enable_service() {
    local svc="$1"
    local desc="$2"
    if ! systemctl list-unit-files "$svc" &>/dev/null 2>&1; then
      skip "$svc : service inexistant"
      return
    fi
    if systemctl is-enabled --quiet "$svc" 2>/dev/null; then
      skip "$svc déjà activé ($desc)"
    else
      info "Activation de $svc ($desc)"
      run "systemctl enable $svc"
    fi
  }

  start_service() {
    local svc="$1"
    local desc="$2"
    if ! systemctl list-unit-files "$svc" &>/dev/null 2>&1; then
      return
    fi
    if systemctl is-active --quiet "$svc" 2>/dev/null; then
      skip "$svc déjà démarré ($desc)"
    else
      info "Démarrage de $svc ($desc)"
      run "systemctl start $svc"
    fi
  }

  # ClamAV — mise à jour automatique des signatures
  enable_service "clamav-freshclam.service" "mise à jour signatures antivirus"
  start_service  "clamav-freshclam.service" "mise à jour signatures antivirus"

  # ClamAV daemon
  enable_service "clamav-daemon.service" "daemon antivirus ClamAV"

  # Docker
  enable_service "docker.service"    "Docker engine"
  enable_service "containerd.service" "Container runtime"

  # Libvirt
  enable_service "libvirtd.service" "gestion virtualisation"

  # AppArmor
  enable_service "apparmor.service" "contrôle d'accès AppArmor"

  # Chrony (NTP)
  enable_service "chrony.service" "synchronisation NTP"

  # Smartmontools
  enable_service "smartd.service" "monitoring disques SMART"

  # chkrootkit timer
  if systemctl list-unit-files "chkrootkit.timer" &>/dev/null 2>&1; then
    enable_service "chkrootkit.timer" "scan rootkit périodique"
  fi

  # plocate DB update
  if systemctl list-unit-files "plocate-updatedb.timer" &>/dev/null 2>&1; then
    enable_service "plocate-updatedb.timer" "mise à jour base locate"
  fi

  success "Services configurés."
}

configure_services

# ═══════════════════════════════════════════════════════════════════════════════
#  3. BASES DE DONNÉES & INITIALISATIONS
# ═══════════════════════════════════════════════════════════════════════════════
configure_databases() {
  run_section "databases" || return 0
  title "3. Bases de données & initialisations"

  # ClamAV — mise à jour initiale des signatures si absentes
  if command -v freshclam &>/dev/null; then
    if [[ ! -f /var/lib/clamav/main.cvd && ! -f /var/lib/clamav/main.cld ]]; then
      info "Téléchargement initial des signatures ClamAV..."
      run "freshclam"
    else
      skip "Signatures ClamAV déjà présentes"
    fi
  fi

  # rkhunter — mise à jour base + empreintes fichiers
  if command -v rkhunter &>/dev/null; then
    # WEB_CMD="/bin/false" par défaut sur Debian/Ubuntu bloque --update
    if grep -q 'WEB_CMD="/bin/false"' /etc/rkhunter.conf 2>/dev/null; then
      info "Correction de WEB_CMD dans /etc/rkhunter.conf..."
      if ! $DRY_RUN; then
        sed -i 's|WEB_CMD="/bin/false"|WEB_CMD=""|' /etc/rkhunter.conf
      fi
    fi
    info "Mise à jour de la base rkhunter..."
    run "rkhunter --update" || true
    info "Calcul des empreintes de référence rkhunter..."
    run "rkhunter --propupd"
    success "rkhunter initialisé."
  fi

  # lm-sensors — détection des capteurs hardware
  if command -v sensors-detect &>/dev/null; then
    info "Détection automatique des capteurs hardware..."
    if $DRY_RUN; then
      echo -e "${YLW}[DRY-RUN]${RST} yes | sensors-detect --auto"
    else
      # --auto répond aux questions de scan mais pas à l'écriture /etc/modules → pipe yes
      yes "" | sensors-detect --auto 2>&1 | tee -a "$LOG_FILE" || true
      if systemctl list-unit-files "lm-sensors.service" &>/dev/null 2>&1; then
        systemctl restart lm-sensors 2>&1 | tee -a "$LOG_FILE" || true
      fi
    fi
    success "Capteurs détectés et modules ajoutés à /etc/modules."
  fi

  # plocate — construction initiale de la base si absente
  if command -v updatedb &>/dev/null; then
    if [[ ! -f /var/lib/plocate/plocate.db ]]; then
      info "Construction de la base plocate..."
      run "updatedb"
    else
      skip "Base plocate existante"
    fi
  fi

  # Snapper — création d'une config root si aucune n'existe
  if command -v snapper &>/dev/null; then
    if [[ ! -d /etc/snapper/configs ]] || [[ -z "$(ls -A /etc/snapper/configs/ 2>/dev/null)" ]]; then
      local root_fs
      root_fs=$(findmnt -n -o FSTYPE / 2>/dev/null)
      if [[ "$root_fs" == "btrfs" ]]; then
        info "Création de la configuration Snapper pour / (btrfs)..."
        run "snapper -c root create-config /"
        success "Snapper configuré pour /."
      else
        skip "Snapper : / n'est pas en btrfs ($root_fs) — config non créée"
      fi
    else
      skip "Snapper : configuration déjà existante"
    fi
  fi

  # fscrypt — initialisation si pas encore fait
  if command -v fscrypt &>/dev/null; then
    if [[ ! -d /.fscrypt ]]; then
      info "Initialisation de fscrypt..."
      if $DRY_RUN; then
        echo -e "${YLW}[DRY-RUN]${RST} fscrypt setup && fscrypt setup /"
      else
        # Répondre 'y' automatiquement à la question d'accès utilisateur
        echo "y" | fscrypt setup 2>&1 | tee -a "$LOG_FILE" || warn "fscrypt setup échoué"
        # Setup du filesystem racine si supporté
        if ! fscrypt status / 2>/dev/null | grep -q "supported"; then
          echo "y" | fscrypt setup / 2>&1 | tee -a "$LOG_FILE" || true
        fi
      fi
      success "fscrypt initialisé."
    else
      skip "fscrypt déjà initialisé"
    fi
  fi

  success "Bases de données et initialisations terminées."
}

configure_databases

# ═══════════════════════════════════════════════════════════════════════════════
#  4. MODULES NOYAU
# ═══════════════════════════════════════════════════════════════════════════════
configure_kernel() {
  run_section "kernel" || return 0
  title "4. Modules noyau"

  load_module() {
    local mod="$1"
    local desc="$2"
    if lsmod | grep -qw "$mod" 2>/dev/null; then
      skip "Module $mod déjà chargé ($desc)"
    else
      info "Chargement du module $mod ($desc)"
      run "modprobe $mod" || warn "Impossible de charger $mod"
    fi
  }

  # KVM — le module arch (kvm_intel/kvm_amd) charge kvm automatiquement
  if grep -qE "vmx|svm" /proc/cpuinfo 2>/dev/null; then
    if grep -q "vmx" /proc/cpuinfo; then
      load_module "kvm_intel" "virtualisation Intel VT-x (charge kvm automatiquement)"
    elif grep -q "svm" /proc/cpuinfo; then
      load_module "kvm_amd" "virtualisation AMD-V (charge kvm automatiquement)"
    fi
  fi

  # VirtualBox
  if command -v VBoxManage &>/dev/null; then
    # Si le module vboxdrv n'existe pas pour ce noyau, tenter un rebuild DKMS
    if ! modinfo vboxdrv &>/dev/null 2>&1; then
      if command -v dkms &>/dev/null; then
        local vbox_ver
        vbox_ver=$(VBoxManage --version 2>/dev/null | cut -d'r' -f1 | cut -d'_' -f1)
        if [[ -n "$vbox_ver" ]] && dkms status vboxhost 2>/dev/null | grep -q "added\|built"; then
          info "Recompilation des modules VirtualBox via DKMS pour le noyau $(uname -r)..."
          run "dkms autoinstall" || warn "DKMS autoinstall échoué"
        else
          warn "VirtualBox : modules DKMS non disponibles — installer linux-headers-$(uname -r) puis relancer"
        fi
      else
        warn "VirtualBox : DKMS non installé — modules noyau indisponibles"
      fi
    fi
    if modinfo vboxdrv &>/dev/null 2>&1; then
      load_module "vboxdrv" "VirtualBox host driver"
      load_module "vboxnetflt" "VirtualBox network filter"
      load_module "vboxnetadp" "VirtualBox host-only adapter"
    else
      warn "VirtualBox : modules noyau introuvables pour $(uname -r) — vérifier linux-headers et dkms"
    fi
  fi

  # nbd (utile pour qemu-nbd / montage d'images disque)
  if command -v qemu-nbd &>/dev/null; then
    if ! lsmod | grep -qw nbd 2>/dev/null; then
      info "Chargement du module nbd (montage images QEMU)"
      run "modprobe nbd max_part=16"
      # Persistance
      if ! grep -qw "nbd" /etc/modules 2>/dev/null; then
        run "echo 'nbd' >> /etc/modules"
        info "Module nbd ajouté à /etc/modules"
      fi
    else
      skip "Module nbd déjà chargé"
    fi
  fi

  success "Modules noyau configurés."
}

configure_kernel

# ═══════════════════════════════════════════════════════════════════════════════
#  5. FICHIERS DE CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════════
configure_configs() {
  run_section "configs" || return 0
  title "5. Fichiers de configuration"

  # Docker daemon.json — log rotation
  if command -v docker &>/dev/null; then
    if [[ ! -f /etc/docker/daemon.json ]]; then
      info "Création de /etc/docker/daemon.json (log rotation)..."
      if ! $DRY_RUN; then
        mkdir -p /etc/docker
        cat > /etc/docker/daemon.json << 'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF
        systemctl reload docker 2>/dev/null || true
      else
        echo "[DRY-RUN] Création /etc/docker/daemon.json"
      fi
      success "Docker daemon.json créé."
    else
      skip "Docker daemon.json existe déjà"
    fi
  fi

  # iptables-persistent — sauvegarde des règles actuelles
  if dpkg -l iptables-persistent &>/dev/null 2>&1; then
    if [[ ! -d /etc/iptables ]]; then
      info "Création du répertoire /etc/iptables..."
      run "mkdir -p /etc/iptables"
    fi
    if [[ ! -f /etc/iptables/rules.v4 ]]; then
      info "Sauvegarde des règles iptables actuelles..."
      run "iptables-save > /etc/iptables/rules.v4"
      run "ip6tables-save > /etc/iptables/rules.v6"
      success "Règles iptables sauvegardées."
    else
      skip "Règles iptables déjà sauvegardées"
    fi
  fi

  # darkstat — configuration de base si pas configuré
  if command -v darkstat &>/dev/null; then
    if [[ -f /etc/darkstat/init.cfg ]]; then
      if grep -q 'START_DARKSTAT=no' /etc/darkstat/init.cfg 2>/dev/null; then
        local default_iface
        default_iface=$(ip route show default 2>/dev/null | awk '/default/ {print $5}' | head -1)
        if [[ -n "$default_iface" ]]; then
          info "Configuration de darkstat sur l'interface $default_iface..."
          if ! $DRY_RUN; then
            sed -i "s/START_DARKSTAT=no/START_DARKSTAT=yes/" /etc/darkstat/init.cfg
            sed -i "s/^INTERFACE=.*/INTERFACE=\"-i $default_iface\"/" /etc/darkstat/init.cfg
          else
            echo "[DRY-RUN] darkstat : activation sur $default_iface"
          fi
          success "darkstat configuré."
        fi
      else
        skip "darkstat déjà configuré"
      fi
    fi
  fi

  # macchanger — reconfiguration debconf (auto MAC sur ifup)
  if command -v macchanger &>/dev/null; then
    if dpkg -l macchanger &>/dev/null 2>&1; then
      info "macchanger : la randomisation MAC automatique est désactivée par défaut."
      info "  Pour l'activer : sudo dpkg-reconfigure macchanger"
      skip "macchanger : configuration manuelle recommandée (dpkg-reconfigure)"
    fi
  fi

  # AnonSurf — vérification de l'installation
  if command -v anonsurf &>/dev/null; then
    local anonsurf_ok=true
    for f in /usr/lib/anonsurf/anondaemon /usr/lib/anonsurf/make-torrc /usr/bin/dnstool; do
      if [[ ! -x "$f" ]]; then
        warn "AnonSurf : fichier manquant $f"
        anonsurf_ok=false
      fi
    done
    if [[ ! -f /lib/systemd/system/anonsurfd.service ]]; then
      warn "AnonSurf : service systemd manquant"
      anonsurf_ok=false
    fi
    if [[ ! -d /etc/anonsurf ]]; then
      warn "AnonSurf : répertoire /etc/anonsurf manquant"
      anonsurf_ok=false
    fi
    if $anonsurf_ok; then
      success "AnonSurf : installation vérifiée et complète."
    else
      error "AnonSurf : installation incomplète — relancer install_tech_tools.sh --section anonymization"
    fi
  fi

  # Ollama — vérification du service
  if command -v ollama &>/dev/null; then
    if systemctl list-unit-files "ollama.service" &>/dev/null 2>&1; then
      if systemctl is-active --quiet ollama 2>/dev/null; then
        success "Ollama : service actif."
      else
        info "Activation et démarrage du service Ollama..."
        run "systemctl enable ollama"
        run "systemctl start ollama"
      fi
    else
      skip "Ollama : installé mais pas de service systemd détecté"
    fi
  fi

  # Claude Code — vérification
  if command -v claude &>/dev/null; then
    success "Claude Code : installé ($(claude --version 2>/dev/null || echo 'version inconnue'))."
  fi

  # Open WebUI — vérification du conteneur Docker
  if command -v docker &>/dev/null; then
    if docker image inspect ghcr.io/open-webui/open-webui:main &>/dev/null 2>&1; then
      if docker ps --format '{{.Names}}' | grep -q "open-webui" 2>/dev/null; then
        success "Open WebUI : conteneur actif."
      else
        info "Open WebUI : image présente mais conteneur non démarré."
        info "  Lancer avec : docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui ghcr.io/open-webui/open-webui:main"
      fi
    fi
  fi

  # OSINT — vérification des outils
  local osint_tools=("sherlock" "holehe" "maigret" "amass" "phoneinfoga" "exiftool" "mat2" "theharvester" "recon-ng")
  local osint_found=false
  for tool in "${osint_tools[@]}"; do
    if command -v "$tool" &>/dev/null; then
      osint_found=true
      break
    fi
  done
  if $osint_found; then
    info "Outils OSINT détectés :"
    for tool in "${osint_tools[@]}"; do
      if command -v "$tool" &>/dev/null; then
        success "  $tool : installé"
      else
        skip "  $tool : non installé"
      fi
    done
  fi

  # Tor — vérification du torrc
  if command -v tor &>/dev/null; then
    if [[ -f /etc/tor/torrc ]]; then
      skip "Tor : torrc présent"
    else
      warn "Tor : /etc/tor/torrc absent"
    fi
  fi

  success "Fichiers de configuration vérifiés."
}

configure_configs

# ═══════════════════════════════════════════════════════════════════════════════
#  6. VÉRIFICATIONS FINALES
# ═══════════════════════════════════════════════════════════════════════════════
final_checks() {
  run_section "all" && true  # toujours exécuté sauf si filtre actif et != all
  title "6. Vérifications finales"

  info "Groupes actuels de $TARGET_USER :"
  id "$TARGET_USER" 2>/dev/null | tee -a "$LOG_FILE"
  echo ""

  # Résumé des services critiques
  info "État des services critiques :"
  for svc in clamav-freshclam clamav-daemon docker tor anonsurfd apparmor smartd libvirtd chrony ollama; do
    local status
    if systemctl is-active --quiet "$svc" 2>/dev/null; then
      status="${GRN}actif${RST}"
    elif systemctl is-enabled --quiet "$svc" 2>/dev/null; then
      status="${YLW}activé (non démarré)${RST}"
    elif systemctl list-unit-files "$svc.service" &>/dev/null 2>&1; then
      status="${RED}inactif${RST}"
    else
      status="${CYN}non installé${RST}"
      svc="$svc"
    fi
    printf "  %-25s %b\n" "$svc" "$status" | tee -a "$LOG_FILE"
  done

  echo ""

  # AnonSurf
  if command -v anonsurf &>/dev/null; then
    info "AnonSurf disponible : sudo anonsurf {start|stop|status|myip|changeid}"
  fi

  # Outils IA
  if command -v ollama &>/dev/null; then
    info "Ollama disponible : ollama run llama3 | ollama list"
  fi
  if command -v claude &>/dev/null; then
    info "Claude Code disponible : claude"
  fi

  # OSINT
  if command -v sherlock &>/dev/null; then
    info "OSINT disponible : sherlock, holehe, maigret, amass, phoneinfoga, exiftool..."
  fi

  success "Vérifications terminées."
}

final_checks

# ═══════════════════════════════════════════════════════════════════════════════
#  RÉSUMÉ FINAL
# ═══════════════════════════════════════════════════════════════════════════════
title "Post-installation terminée"
echo -e "${GRN}${BOLD}"
echo "  ╔══════════════════════════════════════════════════════╗"
echo "  ║    Post-installation Tech Tools : terminée !       ║"
echo "  ╚══════════════════════════════════════════════════════╝"
echo -e "${RST}"
info "Log complet : $LOG_FILE"

if $NEEDS_LOGOUT; then
  echo ""
  warn "╔══════════════════════════════════════════════════════════╗"
  warn "║  IMPORTANT : Déconnexion/reconnexion nécessaire pour    ║"
  warn "║  appliquer les nouveaux groupes utilisateur.             ║"
  warn "╚══════════════════════════════════════════════════════════╝"
fi

echo ""
echo -e "${BLU}Actions manuelles restantes (optionnelles) :${RST}"
echo "  • macchanger auto  → sudo dpkg-reconfigure macchanger"
echo "  • Pare-feu UFW     → sudo ufw enable (si pas déjà actif)"
echo "  • AppArmor enforce → sudo aa-enforce /etc/apparmor.d/<profil>"
echo "  • AnonSurf boot    → sudo anonsurf enable-boot"
echo "  • Bacula director  → éditer /etc/bacula/bacula-fd.conf"
echo "  • Ollama models   → ollama pull llama3 / mistral / codellama"
echo "  • Open WebUI      → docker start open-webui (port 3000)"
echo "  • Claude Code     → claude (nécessite une clé API Anthropic)"
echo ""