#!/bin/bash ############################# # CONFIG ############################# # Répertoire contenant tes containers SOURCE_DIR="/home/docker" # Répertoire où stocker les sauvegardes BACKUP_DIR="/home/backup/docker" # Nombre max de backups à conserver MAX_BACKUPS=5 # Date format DATE="$(date +'%Y%m%d_%H%M%S')" # Fichier TAR final BACKUP_FILE="$BACKUP_DIR/docker_backup_${DATE}.tar.gz" # Dossier de configs des compose à côté du TAR CONFIG_DIR="$BACKUP_DIR/config_${DATE}" # Dossiers à exclure (chemins relatifs à $SOURCE_DIR) EXCLUDE_DIRS=( "casaos_data/*" "nginx_proxy/data/logs/*" "yt-dl/video/*" ) # LOGS LOG_FILE="/var/log/docker_backup.log" ############################# # COULEURS ############################# NC="\e[0m" GREEN="\e[32m" YELLOW="\e[33m" CYAN="\e[36m" RED="\e[31m" ############################# # FONCTIONS ############################# log_message() { local msg="$1" local now now="$(date +'%Y-%m-%d %H:%M:%S')" echo "$now - $msg" >> "$LOG_FILE" echo -e "$now - $msg" } format_time() { local t=$1 ((h=t/3600)) ((m=(t%3600)/60)) ((s=t%60)) printf "%02d:%02d:%02d\n" $h $m $s } check_pigz() { if ! command -v pigz >/dev/null 2>&1; then echo -e "${YELLOW}pigz n'est pas installé. Voulez-vous l’installer ? (y/n)${NC}" read rep if [ "$rep" = "y" ]; then sudo apt update && sudo apt install -y pigz || { echo -e "${RED}Échec de l’installation de pigz. Abandon.${NC}" exit 1 } else echo -e "${RED}pigz est requis pour ce script. Abandon.${NC}" exit 1 fi fi } rotate_backups() { log_message "Nettoyage des anciens backups (conserver les $MAX_BACKUPS plus récents)..." # TAR mapfile -t backups_tar < <(ls -1t "$BACKUP_DIR"/docker_backup_*.tar.gz 2>/dev/null || true) if [ "${#backups_tar[@]}" -gt "$MAX_BACKUPS" ]; then for f in "${backups_tar[@]:$MAX_BACKUPS}"; do log_message "Suppression ancien backup tar : $f" rm -f "$f" done fi # Dossiers config_ mapfile -t backups_cfg < <(ls -1td "$BACKUP_DIR"/config_* 2>/dev/null || true) if [ "${#backups_cfg[@]}" -gt "$MAX_BACKUPS" ]; then for d in "${backups_cfg[@]:$MAX_BACKUPS}"; do log_message "Suppression ancien dossier config : $d" rm -rf "$d" done fi } ############################# # DÉBUT SCRIPT ############################# SECONDS=0 check_pigz mkdir -p "$BACKUP_DIR" "$CONFIG_DIR" touch "$LOG_FILE" echo -e "${CYAN}=== Backup Docker + Configs (Portainer / CasaOS) ===${NC}" log_message "Début sauvegarde Docker + configs Portainer & CasaOS..." ############################################## # 1) Sauvegarde TAR via pigz + exclusions ############################################## log_message "Création de l’archive : $BACKUP_FILE" # Construction des arguments d'exclusion dynamiques EXCLUDE_ARGS=( "--exclude=*.log" "--exclude=.composer" "--exclude=.aptitude" "--exclude=.cache" "--exclude=.cmake" "--exclude=.yarn" "--exclude=.w3m" "--exclude=.pip" "--exclude=.pm2" "--exclude=.pm" "--exclude=.bundle" "--exclude=.gem" "--exclude=.cpan" "--exclude=.cpanm" "--exclude=.git" "--exclude=.local" "--exclude=.npm" "--exclude=.nvm" "--exclude=.rvm" "--exclude=node_modules" "--exclude=lost+found" "--exclude=casaos_data" ) for d in "${EXCLUDE_DIRS[@]}"; do EXCLUDE_ARGS+=( "--exclude=$d" ) done tar -cf "$BACKUP_FILE" -I pigz \ --directory="$SOURCE_DIR" \ "${EXCLUDE_ARGS[@]}" \ . 2>> "$LOG_FILE" if [ $? -ne 0 ]; then log_message "ERREUR lors de la création du tar." echo -e "${RED}Erreur lors de la création de l’archive.${NC}" exit 1 fi ############################################## # 2) Extraction des docker-compose # - CasaOS: config_/casaos// # - Docker: config_/docker// ############################################## log_message "Extraction des docker-compose CasaOS..." if [ -d "$SOURCE_DIR/casaos_data" ]; then find "$SOURCE_DIR/casaos_data" -maxdepth 3 -type f -name "docker-compose.y*ml" | while read file; do container_name=$(basename "$(dirname "$file")") dest_dir="$CONFIG_DIR/casaos/$container_name" mkdir -p "$dest_dir" cp "$file" "$dest_dir/" log_message " [CasaOS] → Compose trouvé : $file" done fi log_message "Extraction des docker-compose Docker (hors casaos_data)..." find "$SOURCE_DIR" -type f -name "docker-compose.y*ml" ! -path "$SOURCE_DIR/casaos_data/*" | while read file; do container_name=$(basename "$(dirname "$file")") dest_dir="$CONFIG_DIR/docker/$container_name" mkdir -p "$dest_dir" cp "$file" "$dest_dir/" log_message " [Docker] → Compose trouvé : $file" done ############################################## # 3) Config Portainer (full) ############################################## PORTAINER_CONFIG_DIR="$CONFIG_DIR/portainer/full_config" PORTAINER_FILES=0 if docker volume inspect portainer_data >/dev/null 2>&1; then log_message "Sauvegarde config Portainer..." mkdir -p "$PORTAINER_CONFIG_DIR" cp -r /var/lib/docker/volumes/portainer_data/_data/* "$PORTAINER_CONFIG_DIR/" 2>>"$LOG_FILE" PORTAINER_FILES=$(find "$PORTAINER_CONFIG_DIR" -type f 2>/dev/null | wc -l) else log_message "Portainer non détecté (volume portainer_data absent)." fi ############################################## # 4) Config CasaOS (full) ############################################## CASAOS_CONFIG_DIR="$CONFIG_DIR/casaos/full_config" CASAOS_FILES=0 if [ -d "/var/lib/casaos" ]; then log_message "Sauvegarde config CasaOS (full)..." mkdir -p "$CASAOS_CONFIG_DIR" cp -r /var/lib/casaos/* "$CASAOS_CONFIG_DIR/" 2>>"$LOG_FILE" CASAOS_FILES=$(find "$CONFIG_DIR/casaos" -type f -name "docker-compose.y*ml" ! -path "$CONFIG_DIR/casaos/full_config/*" 2>/dev/null | wc -l) else log_message "CasaOS non détecté (/var/lib/casaos manquant)." fi ############################################## # 5) Comptages finaux (sur le backup courant) ############################################## # Compose dans le backup (docker + casaos compose seulement) CASAOS_COMPOSE_BACKUP=$(find "$CONFIG_DIR/casaos" -maxdepth 3 -type f -name "docker-compose.y*ml" ! -path "$CASAOS_CONFIG_DIR/*" 2>/dev/null | wc -l) DOCKER_COMPOSE_BACKUP=$(find "$CONFIG_DIR/docker" -type f -name "docker-compose.y*ml" 2>/dev/null | wc -l) TOTAL_COMPOSE_BACKUP=$((CASAOS_COMPOSE_BACKUP + DOCKER_COMPOSE_BACKUP)) # Compose dans le dossier source (hors casaos_data) SOURCE_COMPOSE_COUNT=$(find "$SOURCE_DIR" -type f -name "docker-compose.y*ml" ! -path "$SOURCE_DIR/casaos_data/*" 2>/dev/null | wc -l) # Rotation des anciens backups rotate_backups # Nombre total de tar.gz TOTAL_TAR=$(ls -1 "$BACKUP_DIR"/docker_backup_*.tar.gz 2>/dev/null | wc -l) # Oldest / newest après rotation OLDEST_BACKUP=$(ls -1 "$BACKUP_DIR"/docker_backup_*.tar.gz 2>/dev/null | head -n1) NEWEST_BACKUP=$(ls -1 "$BACKUP_DIR"/docker_backup_*.tar.gz 2>/dev/null | tail -n1) DURATION="$(format_time $SECONDS)" ############################################## # 6) RÉSUMÉS ############################################## log_message "----- RÉSUMÉ DOCKER -----" log_message "Compose source (hors CasaOS) : $SOURCE_COMPOSE_COUNT" log_message "Compose backup (config/docker) : $DOCKER_COMPOSE_BACKUP" log_message "Dossier compose Docker backup : $CONFIG_DIR/docker" log_message "----- RÉSUMÉ CASAOS -----" log_message "Compose CasaOS backup (config/casaos/) : $CASAOS_COMPOSE_BACKUP" log_message "Dossier CasaOS backup : $CONFIG_DIR/casaos" log_message "----- RÉSUMÉ PORTAINER -----" log_message "Fichiers config Portainer (full_config) : $PORTAINER_FILES" log_message "Dossier Portainer backup : $PORTAINER_CONFIG_DIR" log_message "----- RÉSUMÉ GLOBAL -----" log_message "Nombre de .tar.gz : $TOTAL_TAR" log_message "Backup le plus ancien : ${OLDEST_BACKUP:-N/A}" log_message "Backup le plus récent : ${NEWEST_BACKUP:-N/A}" log_message "Archive courante : $BACKUP_FILE" log_message "Dossier config courant : $CONFIG_DIR" log_message "Durée totale : $DURATION" echo -e "${GREEN}===== BACKUP TERMINÉ =====${NC}" echo -e "${CYAN}----- DOCKER -----${NC}" echo -e "${CYAN}Compose source (hors CasaOS) : ${GREEN}$SOURCE_COMPOSE_COUNT${NC}" echo -e "${CYAN}Compose backup (Docker) : ${GREEN}$DOCKER_COMPOSE_BACKUP${NC}" echo -e "${CYAN}Dossier compose Docker : ${YELLOW}$CONFIG_DIR/docker${NC}" echo -e "${CYAN}----- CASAOS -----${NC}" echo -e "${CYAN}Compose CasaOS backup : ${GREEN}$CASAOS_COMPOSE_BACKUP${NC}" echo -e "${CYAN}Dossier CasaOS backup : ${YELLOW}$CONFIG_DIR/casaos${NC}" echo -e "${CYAN}----- PORTAINER -----${NC}" echo -e "${CYAN}Fichiers config Portainer : ${GREEN}$PORTAINER_FILES${NC}" echo -e "${CYAN}Dossier Portainer backup : ${YELLOW}$PORTAINER_CONFIG_DIR${NC}" echo -e "${CYAN}----- GLOBAL -----${NC}" echo -e "${CYAN}Nombre de .tar.gz : ${GREEN}$TOTAL_TAR${NC}" echo -e "${CYAN}Oldest backup : ${YELLOW}${OLDEST_BACKUP:-N/A}${NC}" echo -e "${CYAN}Newest backup : ${YELLOW}${NEWEST_BACKUP:-N/A}${NC}" echo -e "${CYAN}Archive courante : ${YELLOW}$BACKUP_FILE${NC}" echo -e "${CYAN}Dossier config courant : ${YELLOW}$CONFIG_DIR${NC}" echo -e "${CYAN}Durée totale : ${GREEN}$DURATION${NC}"