#!/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 ""