最終更新 1 month ago

Full backup zip + all compose file ! Compose Docker + Report Html

修正履歴 b06531d6ba92e11cc1d542b2cf320c1ad4b3e6d0

Backup-Home-docker.tar.gz.sh Raw
1#!/bin/bash
2
3#############################
4# CONFIG
5#############################
6
7# Répertoire contenant tes containers
8SOURCE_DIR="/home/docker"
9
10# Répertoire où stocker les sauvegardes
11BACKUP_DIR="/home/backup/docker"
12
13# Nombre max de backups à conserver
14MAX_BACKUPS=5
15
16# Date format
17DATE="$(date +'%Y%m%d_%H%M%S')"
18
19# Fichier TAR final
20BACKUP_FILE="$BACKUP_DIR/docker_backup_${DATE}.tar.gz"
21
22# Dossier de configs des compose à côté du TAR
23CONFIG_DIR="$BACKUP_DIR/config_${DATE}"
24
25# Dossiers à exclure (chemins relatifs à $SOURCE_DIR)
26EXCLUDE_DIRS=(
27 "docker_var_lib"
28 "casaos_data/*"
29 "nginx_proxy/data/logs/*"
30 "yt-dl/video/*"
31)
32
33# LOGS
34LOG_FILE="/var/log/docker_backup.log"
35
36#############################
37# COULEURS
38#############################
39NC="\e[0m"
40GREEN="\e[32m"
41YELLOW="\e[33m"
42CYAN="\e[36m"
43RED="\e[31m"
44
45#############################
46# FONCTIONS
47#############################
48
49log_message() {
50 local msg="$1"
51 local now
52 now="$(date +'%Y-%m-%d %H:%M:%S')"
53 echo "$now - $msg" >> "$LOG_FILE"
54 echo -e "$now - $msg"
55}
56
57format_time() {
58 local t=$1
59 ((h=t/3600))
60 ((m=(t%3600)/60))
61 ((s=t%60))
62 printf "%02d:%02d:%02d\n" $h $m $s
63}
64
65check_pigz() {
66 if ! command -v pigz >/dev/null 2>&1; then
67 echo -e "${YELLOW}pigz n'est pas installé. Voulez-vous l’installer ? (y/n)${NC}"
68 read rep
69 if [ "$rep" = "y" ]; then
70 sudo apt update && sudo apt install -y pigz || {
71 echo -e "${RED}Échec de l’installation de pigz. Abandon.${NC}"
72 exit 1
73 }
74 else
75 echo -e "${RED}pigz est requis pour ce script. Abandon.${NC}"
76 exit 1
77 fi
78 fi
79}
80
81rotate_backups() {
82 log_message "Nettoyage des anciens backups (conserver les $MAX_BACKUPS plus récents)..."
83
84 # TAR
85 mapfile -t backups_tar < <(ls -1t "$BACKUP_DIR"/docker_backup_*.tar.gz 2>/dev/null || true)
86 if [ "${#backups_tar[@]}" -gt "$MAX_BACKUPS" ]; then
87 for f in "${backups_tar[@]:$MAX_BACKUPS}"; do
88 log_message "Suppression ancien backup tar : $f"
89 rm -f "$f"
90 done
91 fi
92
93 # Dossiers config_
94 mapfile -t backups_cfg < <(ls -1td "$BACKUP_DIR"/config_* 2>/dev/null || true)
95 if [ "${#backups_cfg[@]}" -gt "$MAX_BACKUPS" ]; then
96 for d in "${backups_cfg[@]:$MAX_BACKUPS}"; do
97 log_message "Suppression ancien dossier config : $d"
98 rm -rf "$d"
99 done
100 fi
101}
102
103human_date() {
104 # Convertit 20251203_172811 → "03/12/2025 17:28:11"
105 local d="$1"
106 local date_part="${d:0:8}"
107 local time_part="${d:9:6}"
108 echo "${date_part:6:2}/${date_part:4:2}/${date_part:0:4} ${time_part:0:2}:${time_part:2:2}:${time_part:4:2}"
109}
110
111get_size() {
112 # Taille lisible
113 du -sh "$1" 2>/dev/null | awk '{print $1}'
114}
115
116#############################
117# DÉBUT SCRIPT
118#############################
119
120SECONDS=0
121
122check_pigz
123
124mkdir -p "$BACKUP_DIR" "$CONFIG_DIR"
125touch "$LOG_FILE"
126
127echo -e "${CYAN}=== Backup Docker + Configs (Portainer / CasaOS) ===${NC}"
128log_message "Début sauvegarde Docker + configs Portainer & CasaOS..."
129
130##############################################
131# 1) Sauvegarde TAR via pigz + exclusions
132##############################################
133log_message "Création de l’archive : $BACKUP_FILE"
134
135# Construction des arguments d'exclusion dynamiques pour le TAR
136EXCLUDE_ARGS=(
137 "--exclude=*.log"
138 "--exclude=.composer"
139 "--exclude=.aptitude"
140 "--exclude=.cache"
141 "--exclude=.cmake"
142 "--exclude=.yarn"
143 "--exclude=.w3m"
144 "--exclude=.pip"
145 "--exclude=.pm2"
146 "--exclude=.pm"
147 "--exclude=.bundle"
148 "--exclude=.gem"
149 "--exclude=.cpan"
150 "--exclude=.cpanm"
151 "--exclude=.git"
152 "--exclude=.local"
153 "--exclude=.npm"
154 "--exclude=.nvm"
155 "--exclude=.rvm"
156 "--exclude=node_modules"
157 "--exclude=lost+found"
158)
159
160for d in "${EXCLUDE_DIRS[@]}"; do
161 EXCLUDE_ARGS+=( "--exclude=$d" )
162done
163
164tar -cf "$BACKUP_FILE" -I pigz \
165 --directory="$SOURCE_DIR" \
166 "${EXCLUDE_ARGS[@]}" \
167 . 2>> "$LOG_FILE"
168
169if [ $? -ne 0 ]; then
170 log_message "ERREUR lors de la création du tar."
171 echo -e "${RED}Erreur lors de la création de l’archive.${NC}"
172 exit 1
173fi
174
175##############################################
176# 2) Extraction des docker-compose
177# - CasaOS: config_<date>/casaos/<container>/
178# - Docker: config_<date>/docker/<container>/
179##############################################
180
181log_message "Extraction des docker-compose CasaOS..."
182if [ -d "$SOURCE_DIR/casaos_data" ]; then
183 find "$SOURCE_DIR/casaos_data" -maxdepth 3 -type f -name "docker-compose.y*ml" | while read file; do
184 container_name=$(basename "$(dirname "$file")")
185 dest_dir="$CONFIG_DIR/casaos/$container_name"
186 mkdir -p "$dest_dir"
187 cp "$file" "$dest_dir/"
188 log_message " [CasaOS] → Compose trouvé : $file"
189 done
190fi
191
192log_message "Extraction des docker-compose Docker (hors casaos_data & dossiers exclus)..."
193find "$SOURCE_DIR" -mindepth 2 -maxdepth 2 -type f -name "docker-compose.y*ml" \
194 ! -path "$SOURCE_DIR/casaos_data/*" \
195 ! -path "$SOURCE_DIR/docker_var_lib/*" \
196 | while read file; do
197 container_name=$(basename "$(dirname "$file")")
198 dest_dir="$CONFIG_DIR/docker/$container_name"
199 mkdir -p "$dest_dir"
200 cp "$file" "$dest_dir/"
201 log_message " [Docker] → Compose trouvé : $file"
202 done
203
204##############################################
205# 3) Config Portainer (full)
206##############################################
207PORTAINER_CONFIG_DIR="$CONFIG_DIR/portainer/full_config"
208PORTAINER_FILES=0
209
210if docker volume inspect portainer_data >/dev/null 2>&1; then
211 log_message "Sauvegarde config Portainer..."
212 mkdir -p "$PORTAINER_CONFIG_DIR"
213 cp -r /var/lib/docker/volumes/portainer_data/_data/* "$PORTAINER_CONFIG_DIR/" 2>>"$LOG_FILE"
214 PORTAINER_FILES=$(find "$PORTAINER_CONFIG_DIR" -type f 2>/dev/null | wc -l)
215else
216 PORTAINER_CONFIG_DIR=""
217fi
218
219##############################################
220# 4) Config CasaOS (full)
221##############################################
222CASAOS_CONFIG_DIR="$CONFIG_DIR/casaos/full_config"
223CASAOS_FILES=0
224
225if [ -d "/var/lib/casaos" ]; then
226 log_message "Sauvegarde config CasaOS (full)..."
227 mkdir -p "$CASAOS_CONFIG_DIR"
228 cp -r /var/lib/casaos/* "$CASAOS_CONFIG_DIR/" 2>>"$LOG_FILE"
229fi
230
231##############################################
232# 5) Comptages finaux (sur le backup courant)
233##############################################
234
235# Compose dans le backup (docker + casaos compose seulement)
236CASAOS_COMPOSE_BACKUP=$(find "$CONFIG_DIR/casaos" -maxdepth 3 -type f -name "docker-compose.y*ml" ! -path "$CASAOS_CONFIG_DIR/*" 2>/dev/null | wc -l)
237DOCKER_COMPOSE_BACKUP=$(find "$CONFIG_DIR/docker" -type f -name "docker-compose.y*ml" 2>/dev/null | wc -l)
238TOTAL_COMPOSE_BACKUP=$((CASAOS_COMPOSE_BACKUP + DOCKER_COMPOSE_BACKUP))
239
240# Compose dans le dossier source (uniquement /home/docker/<container>/docker-compose.*)
241SOURCE_COMPOSE_COUNT=$(find "$SOURCE_DIR" -mindepth 2 -maxdepth 2 -type f -name "docker-compose.y*ml" \
242 ! -path "$SOURCE_DIR/casaos_data/*" \
243 ! -path "$SOURCE_DIR/docker_var_lib/*" \
244 2>/dev/null | wc -l)
245
246# Containers Docker "valides" (dossiers de premier niveau, hors exclusions évidentes)
247CONTAINERS_DOCKER=$(find "$SOURCE_DIR" -mindepth 1 -maxdepth 1 -type d \
248 ! -path "$SOURCE_DIR/casaos_data" \
249 ! -path "$SOURCE_DIR/docker_var_lib" \
250 ! -path "$SOURCE_DIR/portainer" 2>/dev/null | wc -l)
251
252# Rotation des anciens backups
253rotate_backups
254
255# Nombre total de tar.gz
256TOTAL_TAR=$(ls -1 "$BACKUP_DIR"/docker_backup_*.tar.gz 2>/dev/null | wc -l)
257
258# Oldest / newest après rotation
259OLDEST_BACKUP=$(ls -1 "$BACKUP_DIR"/docker_backup_*.tar.gz 2>/dev/null | head -n1)
260NEWEST_BACKUP=$(ls -1 "$BACKUP_DIR"/docker_backup_*.tar.gz 2>/dev/null | tail -n1)
261
262DURATION="$(format_time $SECONDS)"
263
264# Tailles des backups
265TAR_SIZE=$(get_size "$BACKUP_FILE")
266CONFIG_SIZE=$(get_size "$CONFIG_DIR")
267
268# Docker compose directory size
269DOCKER_CONFIG_SIZE=$(get_size "$CONFIG_DIR/docker")
270
271# CasaOS full config size
272if [ -d "$CASAOS_CONFIG_DIR" ]; then
273 CASAOS_CONFIG_SIZE=$(get_size "$CASAOS_CONFIG_DIR")
274else
275 CASAOS_CONFIG_SIZE=""
276fi
277
278# Portainer full config size
279if [ -n "$PORTAINER_CONFIG_DIR" ] && [ -d "$PORTAINER_CONFIG_DIR" ]; then
280 PORTAINER_CONFIG_SIZE=$(get_size "$PORTAINER_CONFIG_DIR")
281else
282 PORTAINER_CONFIG_SIZE=""
283fi
284
285# Dates humaines
286HUMAN_OLDEST=$( [ -n "$OLDEST_BACKUP" ] && human_date "$(basename "$OLDEST_BACKUP" | sed 's/docker_backup_//; s/.tar.gz//')" )
287HUMAN_NEWEST=$( [ -n "$NEWEST_BACKUP" ] && human_date "$(basename "$NEWEST_BACKUP" | sed 's/docker_backup_//; s/.tar.gz//')" )
288
289##############################################
290# 6) RÉSUMÉS
291##############################################
292
293echo -e "${GREEN}===== BACKUP TERMINÉ =====${NC}"
294
295##############################################
296# LISTE DES DOSSIERS DOCKER PAR TAILLE (ASCII)
297##############################################
298
299echo -e "\n${CYAN}----- LISTE/TAILLE DES RÉPERTOIRES DOCKER -----${NC}"
300
301DOCKER_DIRS=$(find "$SOURCE_DIR" -mindepth 1 -maxdepth 1 -type d \
302 ! -path "$SOURCE_DIR/casaos_data" \
303 ! -path "$SOURCE_DIR/docker_var_lib" \
304 ! -path "$SOURCE_DIR/portainer" 2>/dev/null)
305
306if [ -z "$DOCKER_DIRS" ]; then
307 echo -e "${YELLOW}(Aucun dossier Docker trouvé)${NC}"
308else
309 echo -e "┌──────────────────────────┬─────────┬────────────────────────────────────────────┐"
310 printf "│ %-24s │ %-7s │ %-38s │\n" "Container" "Taille" "Chemin"
311 echo -e "├──────────────────────────┼─────────┼────────────────────────────────────────────┤"
312 du -sh $DOCKER_DIRS 2>/dev/null | sort -hr | while read -r size path; do
313 name=$(basename "$path")
314 printf "│ %-24s │ %-7s │ %-38s │\n" "$name" "$size" "$path"
315 done
316 echo -e "└──────────────────────────┴─────────┴────────────────────────────────────────────┘"
317fi
318
319#################################
320# DOCKER
321#################################
322echo -e "${CYAN}\n----- DOCKER -----${NC}"
323echo -e "${CYAN}Containers Docker détectés : ${GREEN}$CONTAINERS_DOCKER${NC}"
324echo -e "${CYAN}Compose source (Docker) : ${GREEN}$SOURCE_COMPOSE_COUNT${NC}"
325echo -e "${CYAN}Compose backup (Docker) : ${GREEN}$DOCKER_COMPOSE_BACKUP${NC}"
326echo -e "${CYAN}Dossier compose Docker : ${YELLOW}$CONFIG_DIR/docker${NC}"
327echo -e "${CYAN}Taille compose Docker : ${GREEN}$DOCKER_CONFIG_SIZE${NC}"
328
329#################################
330# CASAOS (si présent)
331#################################
332if [ -d "$CASAOS_CONFIG_DIR" ] || [ "$CASAOS_COMPOSE_BACKUP" -gt 0 ]; then
333 echo -e "${CYAN}\n----- CASAOS -----${NC}"
334 echo -e "${CYAN}Compose CasaOS backup : ${GREEN}$CASAOS_COMPOSE_BACKUP${NC}"
335 echo -e "${CYAN}Dossier CasaOS backup : ${YELLOW}$CONFIG_DIR/casaos${NC}"
336 [ -n "$CASAOS_CONFIG_SIZE" ] && echo -e "${CYAN}Taille config CasaOS (full) : ${GREEN}$CASAOS_CONFIG_SIZE${NC}"
337fi
338
339#################################
340# PORTAINER (si présent)
341#################################
342if [ -n "$PORTAINER_CONFIG_DIR" ] && [ -d "$PORTAINER_CONFIG_DIR" ]; then
343 echo -e "${CYAN}\n----- PORTAINER -----${NC}"
344 echo -e "${CYAN}Fichiers config Portainer : ${GREEN}$PORTAINER_FILES${NC}"
345 echo -e "${CYAN}Dossier Portainer backup : ${YELLOW}$PORTAINER_CONFIG_DIR${NC}"
346 [ -n "$PORTAINER_CONFIG_SIZE" ] && echo -e "${CYAN}Taille Portainer (full) : ${GREEN}$PORTAINER_CONFIG_SIZE${NC}"
347fi
348
349#################################
350# GLOBAL
351#################################
352echo -e "${CYAN}\n----- GLOBAL -----${NC}"
353echo -e "${CYAN}Taille archive tar.gz : ${GREEN}$TAR_SIZE${NC}"
354echo -e "${CYAN}Taille dossier config : ${GREEN}$CONFIG_SIZE${NC}"
355echo -e "${CYAN}Nombre de .tar.gz : ${GREEN}$TOTAL_TAR${NC}"
356echo -e "${CYAN}Oldest backup : ${YELLOW}$OLDEST_BACKUP${NC}"
357echo -e "${CYAN}Oldest backup (human) : ${GREEN}${HUMAN_OLDEST:-N/A}${NC}"
358echo -e "${CYAN}Newest backup : ${YELLOW}$NEWEST_BACKUP${NC}"
359echo -e "${CYAN}Newest backup (human) : ${GREEN}${HUMAN_NEWEST:-N/A}${NC}"
360echo -e "${CYAN}Archive courante : ${YELLOW}$BACKUP_FILE${NC}"
361echo -e "${CYAN}Dossier config courant : ${YELLOW}$CONFIG_DIR${NC}"
362echo -e "${CYAN}Durée totale : ${GREEN}$DURATION${NC}"
363
364#################################
365# JSON (optionnel : ./script.sh --json)
366#################################
367if [ "$1" = "--json" ]; then
368 echo ""
369 echo "{"
370 echo " \"date\": \"$DATE\","
371 echo " \"backup_file\": \"$BACKUP_FILE\","
372 echo " \"backup_size\": \"$TAR_SIZE\","
373 echo " \"config_dir\": \"$CONFIG_DIR\","
374 echo " \"config_size\": \"$CONFIG_SIZE\","
375 echo " \"docker\": {"
376 echo " \"containers\": $CONTAINERS_DOCKER,"
377 echo " \"compose_source\": $SOURCE_COMPOSE_COUNT,"
378 echo " \"compose_backup\": $DOCKER_COMPOSE_BACKUP"
379 echo " },"
380 echo " \"casaos\": {"
381 echo " \"compose_backup\": $CASAOS_COMPOSE_BACKUP"
382 echo " },"
383 echo " \"portainer\": {"
384 echo " \"files\": $PORTAINER_FILES"
385 echo " },"
386 echo " \"tar_count\": $TOTAL_TAR,"
387 echo " \"oldest_backup\": \"$OLDEST_BACKUP\","
388 echo " \"oldest_backup_human\": \"${HUMAN_OLDEST:-N/A}\","
389 echo " \"newest_backup\": \"$NEWEST_BACKUP\","
390 echo " \"newest_backup_human\": \"${HUMAN_NEWEST:-N/A}\","
391 echo " \"duration\": \"$DURATION\""
392 echo "}"
393fi
394