Outils pour utilisateurs

Outils du site


synchronisation_d_un_serveur_sur_deux_autres

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

Documentation PiHole Scripts

synchronisation_d_un_serveur_sur_deux_autres.txt · Dernière modification : de inc002