Script de synchronisation PiHole
Vue d'ensemble
Ce script permet de synchroniser une configuration PiHole depuis un serveur Mikrotik (source de vérité) vers deux serveurs Ubuntu. Il gère la copie des fichiers de configuration, des bases de données et des règles DNS personnalisées.
Architecture
Serveurs
'Source de vérité' : Mikrotik (192.168.2.1)- * Stockage :
/usb1/etc/pihole/
et
/usb1/etc/dnsmasq.d/
- * Utilisateur : piholesync
'Serveurs cibles' : Ubuntu (192.168.2.8 et 192.168.2.9)- * Stockage :
/etc/pihole/
et
/etc/dnsmasq.d/
- * Utilisateur : piholesync avec droits sudo
Fichiers synchronisés
* 'PiHole' :
gravity.db (base de données principale)
custom.list (règles personnalisées)
local.list (règles locales)
* 'DNSMasq' :
05-pihole-custom-cname.conf (règles CNAME)
Fonctionnalités
Système de backup
* Localisation :
/var/backup/pihole-sync/
* Rotation automatique : Conservation de 7 jours * Structure : Un sous-dossier par serveur et par date * Contenu : Copie complète de /etc/pihole et /etc/dnsmasq.d
Logging
* Fichiers :
/var/log/pihole-sync/sync.log
* Rotation : Quotidienne avec conservation de 7 jours * Format :
[TIMESTAMP] Message
* Niveaux : Info et Debug (avec –debug)
Sécurité
* Authentification SSH par clés * Utilisateur dédié (piholesync) * Permissions sudo limitées * Vérification des droits avant exécution
Processus de synchronisation
1. Vérifications préalables
# Rotation des logs # Vérification de l'existence des fichiers source # Test des droits sudo sur tous les serveurs # Création des backups
2. Copie depuis Mikrotik
# Création d'un dossier temporaire # Copie des fichiers via SFTP # Vérification de l'intégrité (taille, format SQLite) # Synchronisation du fichier CNAME
3. Déploiement
# Arrêt des services PiHole # Synchronisation des fichiers via rsync # Correction des permissions # Reconstruction des bases gravity # Redémarrage des services
Utilisation
Prérequis
# Configuration SSH avec clés pour piholesync # Droits sudo pour piholesync sur les commandes : #* rsync #* pihole # Répertoires de logs et backups créés avec bonnes permissions
Commandes
<syntaxhighlight lang=“bash”> # Exécution normale ./pihole-sync.sh
# Mode simulation ./pihole-sync.sh –dry-run
# Mode debug ./pihole-sync.sh –debug
# Aide ./pihole-sync.sh –help </syntaxhighlight>
Options
* '–dry-run' : Simulation sans modification
* '–debug' : Affichage des messages de debug
Résolution des problèmes
Logs à vérifier
#
/var/log/pihole-sync/sync.log
# Journaux système (
journalctl
) # Logs PiHole (
/var/log/pihole/pihole.log
)
Problèmes courants
; Erreur SSH : Vérifier les clés et permissions ; Erreur sudo : Contrôler le fichier sudoers ; Fichiers invalides : Vérifier l'espace disque et les permissions ; Services non redémarrés : Relancer manuellement
pihole -g
Maintenance
Tâches quotidiennes
* Vérification des logs * Contrôle des backups * Validation du fonctionnement DNS
Tâches hebdomadaires
* Nettoyage des vieux backups * Vérification de l'espace disque * Test de restauration
Tâches mensuelles
* Rotation des clés SSH * Audit des permissions * Test de recovery complet
#!/bin/bash # Variables de configuration MIKROTIK_MASTER="piholesync@192.168.2.1" LOCAL_HOST="192.168.2.8" OTHER_UBUNTU="piholesync@192.168.2.9" DRY_RUN=0 DEBUG=0 # SSH Options SSH_OPTS="-o BatchMode=yes -o StrictHostKeyChecking=no" SUDO_OPTS=" sudo -n -u piholesync " # Chemins sur le Mikrotik (master) MIKROTIK_PIHOLE_PATH="/usb1/etc" MIKROTIK_DNSMASQ_PATH="/usb1/etc-dnsmasq.d" # Chemins de logs et backups LOG_DIR="/var/log/pihole-sync" LOG_FILE="$LOG_DIR/sync.log" BACKUP_DIR="/var/backup/pihole-sync" MAX_LOGS=7 # Liste des fichiers à exclure EXCLUDE_LIST=( "pihole-FTL-old.db" "gravity_old.db" "*.log" "dhcp.leases" "setupVars.conf.update.bak" "pihole-FTL.conf" "install.log" ) # Fonction pour construire les options rsync build_rsync_opts() { local target="$1" # Paramètre pour identifier la cible local opts="-avz" # Ajouter les exclusions for item in "${EXCLUDE_LIST[@]}"; do opts+=" --exclude=$item" done # Ajouter --dry-run si nécessaire [ $DRY_RUN -eq 1 ] && opts+=" --dry-run" # Option spéciale pour Mikrotik if [[ $target == *"192.168.2.1"* ]]; then opts+=" --rsh='ssh $SSH_OPTS' --rsync-path=/usr/bin/rsync" fi echo "$opts" } # Fonction de debug debug() { if [ $DEBUG -eq 1 ]; then echo "DEBUG: $1" fi } # Fonction de logging log() { local message="$1" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] $message" if [ -w "$LOG_DIR" ]; then echo "[$timestamp] $message" >> "$LOG_FILE" fi } # Fonction de rotation des logs rotate_logs() { if [ -f "$LOG_FILE" ]; then mv "$LOG_FILE" "$LOG_FILE.$(date '+%Y%m%d')" find "$LOG_DIR" -name "sync.log.*" -mtime +$MAX_LOGS -delete fi } # Fonction de vérification des droits sudo check_sudo() { local host="$1" log "Vérification des droits sudo sur $host..." if [[ $host == "localhost" ]]; then # Test local log "Test des permissions rsync..." if $SUDO_OPTS rsync --version >/dev/null 2>&1; then log "✓ Droits sudo OK pour rsync" else log "✗ Droits sudo manquants pour rsync" return 1 fi log "Test des permissions pihole..." if $SUDO_OPTS pihole -v >/dev/null 2>&1; then log "✓ Droits sudo OK pour pihole" else log "✗ Droits sudo manquants pour pihole" return 1 fi else # Test distant avec sudo -u piholesync log "Test des permissions rsync sur $host..." if sudo -u piholesync ssh $SSH_OPTS "$host" "$SUDO_OPTS rsync --version" >/dev/null 2>&1; then log "✓ Droits sudo OK pour rsync sur $host" else log "✗ Droits sudo manquants pour rsync sur $host" return 1 fi log "Test des permissions pihole sur $host..." if sudo -u piholesync ssh $SSH_OPTS "$host" "$SUDO_OPTS pihole -v" >/dev/null 2>&1; then log "✓ Droits sudo OK pour pihole sur $host" else log "✗ Droits sudo manquants pour pihole sur $host" return 1 fi fi log "✓ Tous les droits sudo sont corrects" return 0 } # Fonction de backup modifiée backup_pihole() { local host="$1" local backup_date=$(date '+%Y%m%d_%H%M%S') local backup_path="$BACKUP_DIR/$host/$backup_date" local rsync_opts=$(build_rsync_opts "$host") log "Création d'une sauvegarde pour $host" # Création des répertoires avec les bonnes permissions if ! sudo mkdir -p "$backup_path/pihole" "$backup_path/dnsmasq.d"; then log "Erreur : Impossible de créer les répertoires de backup" return 1 fi # Attribution des permissions à piholesync if ! sudo chown -R piholesync:piholesync "$backup_path"; then log "Erreur : Impossible de modifier les permissions des répertoires de backup" return 1 fi if [[ $host == "localhost" ]]; then if ! sudo -u piholesync rsync $rsync_opts /etc/pihole/ "$backup_path/pihole/"; then log "Erreur : Échec de la sauvegarde des fichiers pihole locaux" return 1 fi if ! sudo -u piholesync rsync $rsync_opts /etc/dnsmasq.d/ "$backup_path/dnsmasq.d/"; then log "Erreur : Échec de la sauvegarde des fichiers dnsmasq locaux" return 1 fi else if ! sudo -u piholesync rsync $rsync_opts -e "ssh $SSH_OPTS" "$host:/etc/pihole/" "$backup_path/pihole/"; then log "Erreur : Échec de la sauvegarde des fichiers pihole distants" return 1 fi if ! sudo -u piholesync rsync $rsync_opts -e "ssh $SSH_OPTS" "$host:/etc/dnsmasq.d/" "$backup_path/dnsmasq.d/"; then log "Erreur : Échec de la sauvegarde des fichiers dnsmasq distants" return 1 fi fi log "Sauvegarde terminée pour $host dans $backup_path" return 0 } # Fonction pour vérifier l'existence d'un fichier distant via SSH # Fonction pour vérifier l'existence d'un fichier distant via SSH check_remote_file() { local host="$1" local file="$2" local description="$3" log "Vérification de $description sur $host..." if [[ $host == *"192.168.2.1"* ]]; then # Cas spécial pour Mikrotik local mikrotik_path=${file#/} local cmd="/file print where name=\"$mikrotik_path\"" debug "Chemin original: $file" debug "Chemin Mikrotik: $mikrotik_path" debug "Commande exécutée: $cmd" local output=$(sudo -u piholesync ssh $SSH_OPTS "$host" "$cmd") debug "Sortie de la commande:" debug "$output" if echo "$output" | grep -q "$mikrotik_path"; then log "✓ $description trouvé" return 0 else log "✗ $description non trouvé ($mikrotik_path)" return 1 fi else # Cas standard pour les serveurs Ubuntu if sudo -u piholesync ssh $SSH_OPTS "$host" "$SUDO_OPTS [ -f $file ]"; then log "✓ $description trouvé" return 0 else log "✗ $description non trouvé à $file" return 1 fi fi } # Fonction pour copier depuis le Mikrotik # Fonction pour copier depuis le Mikrotik copy_from_mikrotik() { local source="$1" local dest="$2" local description="$3" log "Copie de $description depuis Mikrotik..." if [ $DRY_RUN -eq 1 ]; then log "[DRY RUN] Copie de $source vers $dest" return 0 fi # Création d'un répertoire temporaire local temp_dir=$(mktemp -d) chown piholesync "$temp_dir" # Ajout de cette ligne ! local temp_file="$temp_dir/$(basename "$dest")" debug "Tentative de copie avec SFTP..." # Exécution de la commande SFTP if ! sudo -u piholesync sftp "$MIKROTIK_MASTER" <<< "get $source $temp_file"; then log "✗ Erreur lors de la copie de $description via SFTP" rm -rf "$temp_dir" return 1 fi # Vérification de la taille du fichier local file_size=$(stat -c%s "$temp_file" 2>/dev/null || stat -f%z "$temp_file") debug "Taille du fichier temporaire: $file_size octets" # Vérification SQLite pour gravity.db if [[ "$source" == *"gravity.db"* ]]; then if ! sqlite3 "$temp_file" "SELECT name FROM sqlite_master LIMIT 1" > /dev/null 2>&1; then log "✗ Le fichier temporaire $description n'est pas une base SQLite valide" rm -rf "$temp_dir" return 1 fi debug "Vérification SQLite réussie" fi # Déplacement du fichier vers sa destination finale if ! mv "$temp_file" "$dest"; then log "✗ Erreur lors du déplacement de $description vers sa destination finale" rm -rf "$temp_dir" return 1 fi # Nettoyage rm -rf "$temp_dir" # Vérification finale if [ ! -s "$dest" ]; then log "✗ Le fichier $description est vide après copie" return 1 fi log "✓ Copie de $description réussie (taille: $file_size octets)" return 0 } # Fonction pour vérifier la taille d'un fichier sur le Mikrotik check_mikrotik_file_size() { local file="$1" local min_size="$2" log "Vérification de la taille de $file..." # Obtenir les informations du fichier via ssh local file_info=$(sudo -u piholesync ssh $SSH_OPTS "$MIKROTIK_MASTER" "du -b \"$file\" 2>/dev/null || echo '0'") # Extraire la taille (premier champ de la sortie du) local size=$(echo "$file_info" | awk '{print $1}') if [ -z "$size" ] || [ "$size" -lt "$min_size" ]; then log "✗ Le fichier $file sur le Mikrotik est trop petit ou inexistant (${size:-0} octets < ${min_size} octets attendus)" return 1 fi log "✓ Taille du fichier $file sur le Mikrotik: ${size} octets" return 0 } # Fonction pour arrêter/démarrer/reconstruire Pi-hole manage_pihole_service() { local host="$1" local action="$2" local description="$3" log "$description sur $host..." if [[ $host == "localhost" ]]; then case $action in "stop") sudo pihole -f stop ;; "start") sudo pihole -f start ;; "rebuild") sudo pihole -g ;; esac else case $action in "stop") sudo -u piholesync ssh -t "$host" "$SUDO_OPTS pihole -f stop" ;; "start") sudo -u piholesync ssh -t "$host" "$SUDO_OPTS pihole -f start" ;; "rebuild") sudo -u piholesync ssh -t "$host" "$SUDO_OPTS pihole -g" ;; esac fi } sync_to_ubuntu() { local src="$1" local dest="$2" local host="$3" local description="$4" log "Synchronisation de $description vers $host..." if [ $DRY_RUN -eq 1 ]; then log "[DRY RUN] Synchronisation vers $host" return 0 fi if [[ $host == "localhost" ]]; then # Vérification et création du répertoire if [ ! -d "$dest" ]; then sudo mkdir -p "$dest" fi # Exécution de rsync en sudo pour éviter les problèmes de permissions sudo rsync -avz --no-perms --no-owner --no-times "$src" "$dest" || true # Correction des permissions pour gravity.db if [[ -f "$dest/gravity.db" && "$EUID" -eq 0 ]]; then sudo chown pihole:pihole "$dest/gravity.db" sudo chmod 664 "$dest/gravity.db" fi else # Synchronisation distante avec rsync sudo -u piholesync rsync -avz --no-perms --no-owner --no-times "$src" "$host:$dest/" || true # Correction des permissions sur le serveur distant if [[ "$dest" == *"pihole"* ]]; then if [[ -n "$SUDO_OPTS" ]]; then sudo -u piholesync ssh $SSH_OPTS "$host" "$SUDO_OPTS chown pihole:pihole $dest/gravity.db" sudo -u piholesync ssh $SSH_OPTS "$host" "$SUDO_OPTS chmod 664 $dest/gravity.db" else log "⚠️ Impossible de changer les permissions sur $host, SUDO_OPTS est vide." fi fi fi } sync_dnsmasq_cname() { local cname_file="05-pihole-custom-cname.conf" local src_path="usb1/etc/dnsmasq.d/$cname_file" # Correction du chemin ici log "Vérification du fichier CNAME ($cname_file)..." # Vérifie si le fichier existe sur le Mikrotik en utilisant /file get local check_output check_output=$(sudo -u piholesync ssh $SSH_OPTS "$MIKROTIK_MASTER" ":put [/file/get \"$src_path\" contents]" 2>/dev/null) debug "Résultat de la vérification: '$check_output'" if [ -n "$check_output" ] && [[ ! "$check_output" =~ "no such item" ]]; then log "✓ Fichier CNAME trouvé sur le Mikrotik, synchronisation..." if [ $DRY_RUN -eq 0 ]; then # Crée le répertoire temporaire s'il n'existe pas mkdir -p "$TEMP_DIR/dnsmasq.d" # Écrit le contenu directement dans le fichier temporaire echo "$check_output" > "$TEMP_DIR/dnsmasq.d/$cname_file" # Synchronise vers les deux serveurs Ubuntu sync_to_ubuntu "$TEMP_DIR/dnsmasq.d/$cname_file" "/etc/dnsmasq.d/" "localhost" "fichier CNAME local" sync_to_ubuntu "$TEMP_DIR/dnsmasq.d/$cname_file" "/etc/dnsmasq.d/" "$OTHER_UBUNTU" "fichier CNAME distant" else log "[DRY RUN] Le fichier CNAME serait synchronisé" fi else log "! Fichier CNAME non trouvé sur le Mikrotik, suppression sur les serveurs Ubuntu..." if [ $DRY_RUN -eq 0 ]; then # Supprime le fichier sur les deux serveurs Ubuntu s'il existe sudo rm -f "/etc/dnsmasq.d/$cname_file" sudo -u piholesync ssh $SSH_OPTS "$OTHER_UBUNTU" "$SUDO_OPTS rm -f /etc/dnsmasq.d/$cname_file" else log "[DRY RUN] Le fichier CNAME serait supprimé des serveurs Ubuntu" fi fi } # Fonction de rotation des logs rotate_logs() { if [ -f "$LOG_FILE" ]; then mv "$LOG_FILE" "$LOG_FILE.$(date '+%Y%m%d')" find "$LOG_DIR" -name "sync.log.*" -mtime +$MAX_LOGS -delete fi } # Fonction pour effectuer les backups perform_backups() { if [ $DRY_RUN -eq 0 ]; then log "Démarrage des sauvegardes..." backup_pihole "localhost" backup_pihole "$OTHER_UBUNTU" else log "[DRY RUN] Les sauvegardes seraient effectuées ici" fi } # Traitement des arguments while [[ $# -gt 0 ]]; do case $1 in --dry-run) DRY_RUN=1 shift ;; --debug) DEBUG=1 shift ;; --help) echo "Usage: $0 [--dry-run] [--debug]" echo "Options:" echo " --dry-run Simuler la synchronisation sans l'exécuter" echo " --debug Afficher les messages de debug" exit 0 ;; *) echo "Option invalide: $1" echo "Utilisez --help pour voir les options disponibles" exit 1 ;; esac done log "Mode: $([ $DRY_RUN -eq 1 ] && echo 'DRY RUN' || echo 'PRODUCTION')$([ $DEBUG -eq 1 ] && echo ' (DEBUG activé)')" # Rotation des logs au démarrage rotate_logs # Vérification des prérequis log "Vérification des prérequis..." check_remote_file "$MIKROTIK_MASTER" "$MIKROTIK_PIHOLE_PATH/pihole/gravity.db" "gravity.db" || exit 1 check_remote_file "$MIKROTIK_MASTER" "$MIKROTIK_PIHOLE_PATH/pihole/custom.list" "custom.list" || exit 1 check_remote_file "$MIKROTIK_MASTER" "$MIKROTIK_PIHOLE_PATH/pihole/local.list" "local.list" || exit 1 # Vérification des droits sudo log "Vérification des droits sudo..." check_sudo "localhost" || exit 1 check_sudo "$OTHER_UBUNTU" || exit 1 # Effectuer les backups avant synchronisation perform_backups # Création du dossier temporaire TEMP_DIR="/tmp/pihole-sync" mkdir -p "$TEMP_DIR/pihole" mkdir -p "$TEMP_DIR/dnsmasq.d" # Synchronisation depuis le Mikrotik log "Synchronisation depuis $MIKROTIK_MASTER..." # Copie des fichiers depuis le Mikrotik mkdir -p "$TEMP_DIR/pihole" "$TEMP_DIR/dnsmasq.d" copy_from_mikrotik "usb1/etc/pihole/gravity.db" "$TEMP_DIR/pihole/gravity.db" "gravity.db" || exit 1 copy_from_mikrotik "usb1/etc/pihole/custom.list" "$TEMP_DIR/pihole/custom.list" "custom.list" || exit 1 copy_from_mikrotik "usb1/etc/pihole/local.list" "$TEMP_DIR/pihole/local.list" "local.list" || exit 1 # Pour le dossier dnsmasq.d log "Gestion du fichier CNAME..." if [ $DRY_RUN -eq 1 ]; then log "[DRY RUN] Vérification et synchronisation du fichier CNAME" else sync_dnsmasq_cname || exit 1 fi # Dans la partie synchronisation : if [ $DRY_RUN -eq 0 ]; then # Arrêt des services sur les deux serveurs manage_pihole_service "localhost" "stop" "Arrêt du service Pi-hole" manage_pihole_service "$OTHER_UBUNTU" "stop" "Arrêt du service Pi-hole distant" log "Synchronisation vers les serveurs Ubuntu..." # Synchronisation locale sync_to_ubuntu "$TEMP_DIR/pihole/" "/etc/pihole/" "localhost" "fichiers pihole locaux" || exit 1 sync_to_ubuntu "$TEMP_DIR/dnsmasq.d/" "/etc/dnsmasq.d/" "localhost" "dossier dnsmasq.d local" || exit 1 # Synchronisation vers le serveur distant sync_to_ubuntu "$TEMP_DIR/pihole/" "/etc/pihole" "$OTHER_UBUNTU" "fichiers pihole vers $OTHER_UBUNTU" || exit 1 sync_to_ubuntu "$TEMP_DIR/dnsmasq.d/" "/etc/dnsmasq.d" "$OTHER_UBUNTU" "dossier dnsmasq.d vers $OTHER_UBUNTU" || exit 1 # Reconstruction de la base gravity sur les deux serveurs manage_pihole_service "localhost" "rebuild" "Reconstruction de la base gravity" manage_pihole_service "$OTHER_UBUNTU" "rebuild" "Reconstruction de la base gravity distante" # Démarrage des services manage_pihole_service "localhost" "start" "Démarrage du service Pi-hole" manage_pihole_service "$OTHER_UBUNTU" "start" "Démarrage du service Pi-hole distant" fi # Nettoyage rm -rf "$TEMP_DIR" log "Script terminé avec succès!"
