最終更新 1 month ago

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

Backup-Home-docker.tar.gz.sh Raw
1#!/bin/bash
2
3# Backup complet Docker + (CasaOS si présent) + Portainer
4# + export docker-compose + rapport HTML + index
5# + last/ + rotation + publication WEB (HTML only)
6
7#############################
8# CONFIG
9#############################
10
11SOURCE_DIR="/home/docker"
12
13# Base des backups datés
14BACKUP_DIR="/home/backup/docker/compose"
15MAX_BACKUPS=3
16
17DATE_RAW="$(date +'%Y%m%d_%H%M%S')"
18DATE_DIR="$(date +'%Y.%m.%d_%H-%M-%S')"
19
20# Dossier daté du run courant
21RUN_DIR="$BACKUP_DIR/$DATE_DIR"
22
23# Dossier "dernier backup"
24LAST_DIR="$BACKUP_DIR/last"
25
26# Archives dans le dossier daté
27BACKUP_FILE="$RUN_DIR/docker_backup.tar.gz"
28CASAOS_FULL_TAR="$RUN_DIR/casaos_full_backup.tar.gz"
29
30# Dossier de configs / compose dans le run
31CONFIG_DIR="$RUN_DIR"
32
33# Logs
34LOG_FILE="/var/log/docker_backup.log"
35
36# Rapports HTML
37HTML_REPORT="$RUN_DIR/report.html"
38HTML_REPORT_LAST="$LAST_DIR/report.html"
39HTML_INDEX_LAST="$LAST_DIR/index.html"
40
41# Publication WEB (HTML uniquement)
42WEB_PUBLIC_DIR="/home/www-adm1n/docker_compose"
43
44# Dossiers à exclure (chemins relatifs à $SOURCE_DIR)
45EXCLUDE_DIRS=(
46 "docker_var_lib"
47 "1_Backups"
48 "casaos_data/*"
49 "nginx_proxy/data/logs/*"
50 "yt-dl/video/*"
51)
52
53#############################
54# MODE TTY / CRON
55#############################
56
57# Si pas de terminal (cron), on désactive les couleurs
58if [ -t 1 ]; then
59 USE_COLORS=1
60else
61 USE_COLORS=0
62fi
63
64#############################
65# COULEURS
66#############################
67if [ "$USE_COLORS" = "1" ]; then
68 NC="\e[0m"
69 GREEN="\e[32m"
70 YELLOW="\e[33m"
71 CYAN="\e[36m"
72 RED="\e[31m"
73else
74 NC=""
75 GREEN=""
76 YELLOW=""
77 CYAN=""
78 RED=""
79fi
80
81#############################
82# DÉTECTION CASAOS
83#############################
84
85CASAOS_PRESENT=0
86if [ -d "/var/lib/casaos" ] || [ -d "$SOURCE_DIR/casaos_data" ] || command -v casaos >/dev/null 2>&1; then
87 CASAOS_PRESENT=1
88fi
89
90#############################
91# FONCTIONS
92#############################
93
94log_message() {
95 local msg="$1"
96 local now
97 now="$(date +'%Y-%m-%d %H:%M:%S')"
98 echo "$now - $msg" >> "$LOG_FILE"
99 echo -e "$now - $msg"
100}
101
102format_time() {
103 local t=$1
104 ((h=t/3600))
105 ((m=(t%3600)/60))
106 ((s=t%60))
107 printf "%02d:%02d:%02d\n" $h $m $s
108}
109
110check_pigz() {
111 if ! command -v pigz >/dev/null 2>&1; then
112 log_message "pigz n'est pas installé."
113
114 if [ "$USE_COLORS" = "1" ]; then
115 echo -e "${YELLOW}pigz n'est pas installé. Voulez-vous l’installer ? (y/n)${NC}"
116 read -r rep
117 if [ "$rep" = "y" ]; then
118 sudo apt update && sudo apt install -y pigz || {
119 echo -e "${RED}Échec installation pigz. Abandon.${NC}"
120 exit 1
121 }
122 else
123 echo -e "${RED}pigz requis. Abandon.${NC}"
124 exit 1
125 fi
126 else
127 echo "ERREUR: pigz requis (mode cron non-interactif)."
128 exit 1
129 fi
130 fi
131}
132
133rotate_backups() {
134 log_message "Rotation backups (max $MAX_BACKUPS)..."
135
136 # Liste uniquement les dossiers datés, ignore last/
137 mapfile -t backups_dirs < <(
138 find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d \
139 ! -name "last" \
140 -printf "%T@ %p\n" 2>/dev/null \
141 | sort -nr \
142 | awk '{print $2}'
143 )
144
145 if [ "${#backups_dirs[@]}" -gt "$MAX_BACKUPS" ]; then
146 for d in "${backups_dirs[@]:$MAX_BACKUPS}"; do
147 log_message "Suppression ancien backup : $d"
148 rm -rf "$d"
149 done
150 fi
151}
152
153get_size() {
154 du -sh "$1" 2>/dev/null | awk '{print $1}'
155}
156
157html_escape() {
158 sed -e 's/&/\&amp;/g' \
159 -e 's/</\&lt;/g' \
160 -e 's/>/\&gt;/g'
161}
162
163html_line() {
164 # $1=label $2=value $3=emoji $4=class
165 echo "<div class='row $4'><span class='emoji'>$3</span><span class='label'>$1</span><span class='value'>$2</span></div>" >> "$HTML_REPORT"
166}
167
168
169html_container_table() {
170 local dirs
171 dirs=$(find "$SOURCE_DIR" -mindepth 1 -maxdepth 1 -type d \
172 ! -path "$SOURCE_DIR/casaos_data" \
173 ! -path "$SOURCE_DIR/docker_var_lib" \
174 ! -path "$SOURCE_DIR/portainer" \
175 2>/dev/null)
176
177 if [ -z "$dirs" ]; then
178 echo "<div class='empty'>Aucun dossier Docker trouvé.</div>" >> "$HTML_REPORT"
179 return
180 fi
181
182 echo "<div class='tblwrap'><table class='tbl'>" >> "$HTML_REPORT"
183 echo "<thead><tr><th>📦 Container</th><th>📏 Taille</th><th>📁 Chemin</th></tr></thead>" >> "$HTML_REPORT"
184 echo "<tbody>" >> "$HTML_REPORT"
185
186 # tri par taille desc
187 du -sh $dirs 2>/dev/null | sort -hr | while read -r size path; do
188 name="$(basename "$path")"
189 esc_path="$(echo "$path" | html_escape)"
190 echo "<tr><td><b>$name</b></td><td>$size</td><td><code>$esc_path</code></td></tr>" >> "$HTML_REPORT"
191 done
192
193 echo "</tbody></table></div>" >> "$HTML_REPORT"
194}
195
196
197generate_index_html() {
198 local base_dir="$1"
199 local output_file="$2"
200
201 cat > "$output_file" <<EOF
202<!doctype html>
203<html lang="fr">
204<head>
205<meta charset="utf-8">
206<title>Index Backups Docker</title>
207<style>
208body{font-family:Arial,sans-serif;background:#0b1220;color:#e5e7eb;margin:0;padding:24px}
209.card{background:#111827;border:1px solid #1f2937;border-radius:12px;padding:16px;max-width:980px;margin:auto}
210h1{margin:0 0 12px;font-size:22px}
211a{color:#93c5fd;text-decoration:none}
212a:hover{text-decoration:underline}
213hr{border:0;border-top:1px solid #1f2937;margin:12px 0}
214.toolbar{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
215.btn{display:inline-block;padding:8px 12px;border-radius:10px;background:#0f172a;border:1px solid #1f2937}
216.btn:hover{background:#111c33}
217.item{padding:10px;border-radius:12px;margin:8px 0;background:#0f172a;border:1px solid #1f2937}
218.row{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap}
219.left{display:flex;gap:10px;align-items:center}
220.badge{font-size:12px;padding:4px 8px;border-radius:999px;background:#1f2937}
221.badge-ok{background:#052e16;color:#86efac;border:1px solid #14532d}
222.badge-warn{background:#451a03;color:#fcd34d;border:1px solid #92400e}
223.meta{color:#94a3b8;font-size:12px;margin-top:6px}
224small{color:#94a3b8}
225.note{color:#94a3b8;font-size:12px;margin-top:10px}
226</style>
227</head>
228<body>
229<div class="card">
230<h1>📦 Index des Backups Docker</h1>
231<small>Dossier backups: $base_dir</small>
232
233<div class="toolbar">
234 <a class="btn" href="./report.html">📌 Ouvrir le LAST</a>
235</div>
236
237<div class="note">
238ℹ️ Les backups datés ne sont pas exposés sur le web (sécurité).
239Seul le rapport <b>LAST</b> est accessible.
240</div>
241
242<hr>
243EOF
244
245 # Liste dossiers datés (ignore last)
246 find "$base_dir" -mindepth 1 -maxdepth 1 -type d ! -name "last" 2>/dev/null \
247 | sort -r \
248 | while read -r d; do
249
250 name="$(basename "$d")"
251 tarfile="$d/docker_backup.tar.gz"
252 report="$d/report.html"
253
254 mtime="$(date -r "$d" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo 'N/A')"
255
256 if [ -f "$tarfile" ]; then
257 tarsize="$(du -sh "$tarfile" 2>/dev/null | awk '{print $1}')"
258 else
259 tarsize="N/A"
260 fi
261
262 if [ -f "$report" ]; then
263 status="OK"
264 badge="badge-ok"
265 else
266 status="missing report"
267 badge="badge-warn"
268 fi
269
270 cat >> "$output_file" <<EOF
271<div class="item">
272 <div class="row">
273 <div class="left">
274 <span>📄</span>
275 <span><b>$name</b></span>
276 </div>
277 <span class="badge $badge">$status</span>
278 </div>
279 <div class="meta">🕒 $mtime — 🗜️ docker_backup.tar.gz : <b>$tarsize</b></div>
280</div>
281EOF
282 done
283
284 cat >> "$output_file" <<EOF
285</div>
286</body>
287</html>
288EOF
289}
290
291#############################
292# DÉBUT SCRIPT
293#############################
294
295SECONDS=0
296
297check_pigz
298
299mkdir -p "$RUN_DIR"
300mkdir -p "$BACKUP_DIR"
301touch "$LOG_FILE"
302
303echo -e "${CYAN}=== Backup Docker + Configs (Portainer / CasaOS) ===${NC}"
304log_message "Début sauvegarde..."
305log_message "Dossier backup (run) : $RUN_DIR"
306log_message "Dossier backup (last): $LAST_DIR"
307log_message "CasaOS détecté : $CASAOS_PRESENT"
308
309##############################################
310# 1) Archive Docker
311##############################################
312
313log_message "Création archive Docker : $BACKUP_FILE"
314
315EXCLUDE_ARGS=(
316 "--exclude=*.log"
317 "--exclude=.composer"
318 "--exclude=.aptitude"
319 "--exclude=.cache"
320 "--exclude=.cmake"
321 "--exclude=.yarn"
322 "--exclude=.w3m"
323 "--exclude=.pip"
324 "--exclude=.pm2"
325 "--exclude=.pm"
326 "--exclude=.bundle"
327 "--exclude=.gem"
328 "--exclude=.cpan"
329 "--exclude=.cpanm"
330 "--exclude=.git"
331 "--exclude=.local"
332 "--exclude=.npm"
333 "--exclude=.nvm"
334 "--exclude=.rvm"
335 "--exclude=node_modules"
336 "--exclude=lost+found"
337)
338
339for d in "${EXCLUDE_DIRS[@]}"; do
340 EXCLUDE_ARGS+=( "--exclude=$d" )
341done
342
343tar -cf "$BACKUP_FILE" -I pigz \
344 --directory="$SOURCE_DIR" \
345 "${EXCLUDE_ARGS[@]}" \
346 . 2>> "$LOG_FILE"
347
348if [ $? -ne 0 ]; then
349 log_message "ERREUR lors de la création du tar Docker."
350 echo -e "${RED}Erreur lors de la création de l’archive Docker.${NC}"
351 exit 1
352fi
353
354##############################################
355# 2) Extraction docker-compose CasaOS (si présent)
356##############################################
357
358if [ "$CASAOS_PRESENT" = "1" ]; then
359 log_message "Extraction des docker-compose CasaOS..."
360 if [ -d "$SOURCE_DIR/casaos_data" ]; then
361 find "$SOURCE_DIR/casaos_data" -maxdepth 3 -type f -name "docker-compose.y*ml" | while read -r file; do
362 container_name=$(basename "$(dirname "$file")")
363 dest_dir="$CONFIG_DIR/casaos/$container_name"
364 mkdir -p "$dest_dir"
365 cp "$file" "$dest_dir/"
366 log_message " [CasaOS] compose : $file"
367 done
368 fi
369else
370 log_message "CasaOS non détecté → ignoré."
371fi
372
373##############################################
374# 3) Extraction docker-compose Docker (hors CasaOS)
375##############################################
376
377log_message "Extraction des docker-compose Docker..."
378find "$SOURCE_DIR" -mindepth 2 -maxdepth 2 -type f -name "docker-compose.y*ml" \
379 ! -path "$SOURCE_DIR/casaos_data/*" \
380 ! -path "$SOURCE_DIR/docker_var_lib/*" \
381 2>/dev/null | while read -r file; do
382 container_name=$(basename "$(dirname "$file")")
383 dest_dir="$CONFIG_DIR/Compose/$container_name"
384 mkdir -p "$dest_dir"
385 cp "$file" "$dest_dir/"
386 log_message " [Docker] compose : $file"
387 done
388
389##############################################
390# 4) Backup config Portainer (si volume présent)
391##############################################
392
393PORTAINER_CONFIG_DIR="$CONFIG_DIR/portainer"
394PORTAINER_FILES=0
395
396if docker volume inspect portainer_data >/dev/null 2>&1; then
397 log_message "Sauvegarde config Portainer..."
398 mkdir -p "$PORTAINER_CONFIG_DIR"
399 cp -r /var/lib/docker/volumes/portainer_data/_data/* "$PORTAINER_CONFIG_DIR/" 2>>"$LOG_FILE"
400 PORTAINER_FILES=$(find "$PORTAINER_CONFIG_DIR" -type f 2>/dev/null | wc -l)
401else
402 PORTAINER_CONFIG_DIR=""
403fi
404
405##############################################
406# 5) Backup CasaOS (config + FULL TAR) si présent
407##############################################
408
409CASAOS_CONFIG_DIR="$CONFIG_DIR/casaos/full_config"
410
411if [ "$CASAOS_PRESENT" = "1" ]; then
412 if [ -d "/var/lib/casaos" ]; then
413 log_message "Sauvegarde config CasaOS full..."
414 mkdir -p "$CASAOS_CONFIG_DIR"
415 cp -r /var/lib/casaos/* "$CASAOS_CONFIG_DIR/" 2>>"$LOG_FILE"
416 fi
417
418 CASAOS_TAR_SOURCES=()
419 [ -d "/var/lib/casaos" ] && CASAOS_TAR_SOURCES+=( "/var/lib/casaos" )
420 [ -d "$SOURCE_DIR/casaos_data" ] && CASAOS_TAR_SOURCES+=( "$SOURCE_DIR/casaos_data" )
421
422 if [ "${#CASAOS_TAR_SOURCES[@]}" -gt 0 ]; then
423 log_message "Création archive CasaOS FULL : $CASAOS_FULL_TAR"
424 tar -cf "$CASAOS_FULL_TAR" -I pigz "${CASAOS_TAR_SOURCES[@]}" 2>>"$LOG_FILE"
425 fi
426fi
427
428##############################################
429# 6) Stats + rotation
430##############################################
431
432CASAOS_COMPOSE_BACKUP=0
433if [ "$CASAOS_PRESENT" = "1" ]; then
434 CASAOS_COMPOSE_BACKUP=$(find "$CONFIG_DIR/casaos" -maxdepth 3 -type f -name "docker-compose.y*ml" 2>/dev/null | wc -l)
435fi
436
437DOCKER_COMPOSE_BACKUP=$(find "$CONFIG_DIR/Compose" -type f -name "docker-compose.y*ml" 2>/dev/null | wc -l)
438
439SOURCE_COMPOSE_COUNT=$(find "$SOURCE_DIR" -mindepth 2 -maxdepth 2 -type f -name "docker-compose.y*ml" \
440 ! -path "$SOURCE_DIR/casaos_data/*" \
441 ! -path "$SOURCE_DIR/docker_var_lib/*" \
442 2>/dev/null | wc -l)
443
444CONTAINERS_DOCKER=$(find "$SOURCE_DIR" -mindepth 1 -maxdepth 1 -type d \
445 ! -path "$SOURCE_DIR/casaos_data" \
446 ! -path "$SOURCE_DIR/docker_var_lib" \
447 ! -path "$SOURCE_DIR/portainer" 2>/dev/null | wc -l)
448
449rotate_backups
450
451TOTAL_BACKUP_SETS=$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d ! -name "last" 2>/dev/null | wc -l)
452
453mapfile -t CURRENT_SETS < <(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d ! -name "last" 2>/dev/null | sort)
454OLDEST_DIR="${CURRENT_SETS[0]}"
455NEWEST_DIR="${CURRENT_SETS[${#CURRENT_SETS[@]}-1]}"
456
457DURATION="$(format_time $SECONDS)"
458
459TAR_SIZE="$(get_size "$BACKUP_FILE")"
460
461CASAOS_FULL_TAR_SIZE=""
462[ -f "$CASAOS_FULL_TAR" ] && CASAOS_FULL_TAR_SIZE="$(get_size "$CASAOS_FULL_TAR")"
463
464PORTAINER_CONFIG_SIZE=""
465[ -n "$PORTAINER_CONFIG_DIR" ] && [ -d "$PORTAINER_CONFIG_DIR" ] && PORTAINER_CONFIG_SIZE="$(get_size "$PORTAINER_CONFIG_DIR")"
466
467
468##############################################
469# 7) Génération rapport HTML (run daté)
470##############################################
471
472cat > "$HTML_REPORT" <<EOF
473<!doctype html>
474<html lang="fr">
475<head>
476<meta charset="utf-8">
477<meta name="viewport" content="width=device-width, initial-scale=1">
478<title>Backup Docker Report - $DATE_DIR</title>
479<style>
480:root{
481 --bg:#0b1220;
482 --card:#111827;
483 --border:#1f2937;
484 --muted:#94a3b8;
485 --text:#e5e7eb;
486 --blue:#93c5fd;
487 --ok:#22c55e;
488 --warn:#f59e0b;
489 --bad:#ef4444;
490 --row:#0f172a;
491 --row2:#0c1428;
492}
493*{box-sizing:border-box}
494body{font-family:Arial,system-ui,sans-serif;background:var(--bg);color:var(--text);margin:0;padding:22px}
495.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;max-width:1100px;margin:auto}
496h1{margin:0 0 8px;font-size:22px}
497small{color:var(--muted)}
498hr{border:0;border-top:1px solid var(--border);margin:14px 0}
499h2{margin:16px 0 8px;font-size:15px;color:var(--blue)}
500
501.row{display:flex;gap:12px;padding:10px;border-radius:12px;align-items:center}
502.row:nth-child(odd){background:var(--row)}
503.row:nth-child(even){background:var(--row2)}
504.label{flex:1;color:#cbd5e1}
505.value{font-weight:bold;color:#f8fafc}
506.emoji{width:26px;display:inline-block}
507
508.ok{border-left:4px solid var(--ok)}
509.warn{border-left:4px solid var(--warn)}
510.bad{border-left:4px solid var(--bad)}
511.info{border-left:4px solid #3b82f6}
512
513.grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
514@media (max-width:900px){.grid{grid-template-columns:1fr}}
515
516.tblwrap{overflow:auto;border:1px solid var(--border);border-radius:14px}
517.tbl{width:100%;border-collapse:collapse;min-width:720px}
518.tbl th,.tbl td{padding:10px;border-bottom:1px solid var(--border);text-align:left;vertical-align:top}
519.tbl th{color:#cbd5e1;background:#0f172a;position:sticky;top:0}
520.tbl tr:hover td{background:#0a1020}
521code{color:#cbd5e1}
522.empty{padding:10px;color:var(--muted)}
523.footer{margin-top:14px;color:var(--muted);font-size:12px}
524.badge{display:inline-block;padding:3px 8px;border-radius:999px;border:1px solid var(--border);font-size:12px;margin-left:8px}
525.badge-ok{background:#052e16;color:#86efac;border-color:#14532d}
526.badge-warn{background:#451a03;color:#fcd34d;border-color:#92400e}
527</style>
528</head>
529<body>
530<div class="card">
531 <h1>📦 Backup Docker / CasaOS / Portainer</h1>
532 <small>📅 $DATE_DIR — 🕒 $(date +'%Y-%m-%d %H:%M:%S')</small>
533
534 <hr>
535
536 <h2>🔧 Global</h2>
537EOF
538
539# lignes global
540html_line "Dossier backup courant" "$(echo "$RUN_DIR" | html_escape)" "📁" "info"
541html_line "Durée totale" "$DURATION" "⏱️" "info"
542html_line "Nombre de sets de backup" "$TOTAL_BACKUP_SETS" "🗂️" "info"
543
544echo "<h2>🐳 Docker</h2>" >> "$HTML_REPORT"
545html_line "Containers détectés" "$CONTAINERS_DOCKER" "🧱" "ok"
546html_line "Compose source" "$SOURCE_COMPOSE_COUNT" "📄" "info"
547html_line "Compose backup" "$DOCKER_COMPOSE_BACKUP" "📦" "info"
548html_line "Archive Docker (.tar.gz)" "$(echo "$BACKUP_FILE ($TAR_SIZE)" | html_escape)" "🗜️" "ok"
549
550echo "<h2>🏠 CasaOS</h2>" >> "$HTML_REPORT"
551if [ "$CASAOS_PRESENT" = "1" ]; then
552 html_line "CasaOS détecté" "OUI" "✅" "ok"
553 html_line "Compose CasaOS backup" "$CASAOS_COMPOSE_BACKUP" "📄" "info"
554 if [ -f "$CASAOS_FULL_TAR" ]; then
555 html_line "Archive CasaOS FULL (.tar.gz)" "$(echo "$CASAOS_FULL_TAR ($CASAOS_FULL_TAR_SIZE)" | html_escape)" "🗜️" "ok"
556 else
557 html_line "Archive CasaOS FULL (.tar.gz)" "Non générée" "⚠️" "warn"
558 fi
559else
560 html_line "CasaOS détecté" "NON (ignoré)" "🚫" "warn"
561fi
562
563echo "<h2>🧭 Portainer</h2>" >> "$HTML_REPORT"
564html_line "Fichiers config Portainer" "$PORTAINER_FILES" "📁" "info"
565[ -n "$PORTAINER_CONFIG_SIZE" ] && html_line "Taille config Portainer" "$PORTAINER_CONFIG_SIZE" "📏" "info"
566
567echo "<h2>📊 Containers / Tailles (dossiers dans $SOURCE_DIR)</h2>" >> "$HTML_REPORT"
568html_container_table
569
570cat >> "$HTML_REPORT" <<EOF
571 <div class="footer">
572 ✅ Rapport généré automatiquement par le script de backup.<br>
573 ⚠️ Ne partage pas ce rapport publiquement : il révèle des chemins et des infos de structure.
574 </div>
575</div>
576</body>
577</html>
578EOF
579
580log_message "Rapport HTML généré : $HTML_REPORT"
581
582##################
583
584
585##############################################
586# 8) MAJ last/ (SANS copier les archives .gz)
587##############################################
588
589log_message "Mise à jour du dossier LAST : $LAST_DIR"
590
591rm -rf "$LAST_DIR"
592mkdir -p "$LAST_DIR"
593
594# HTML
595cp -f "$HTML_REPORT" "$LAST_DIR/report.html"
596
597# Compose / configs (pas d'archives)
598[ -d "$CONFIG_DIR/Compose" ] && cp -a "$CONFIG_DIR/Compose" "$LAST_DIR/"
599[ -d "$CONFIG_DIR/portainer" ] && cp -a "$CONFIG_DIR/portainer" "$LAST_DIR/"
600[ "$CASAOS_PRESENT" = "1" ] && [ -d "$CONFIG_DIR/casaos" ] && cp -a "$CONFIG_DIR/casaos" "$LAST_DIR/"
601
602# Index HTML last/
603generate_index_html "$BACKUP_DIR" "$HTML_INDEX_LAST"
604
605log_message "LAST mis à jour ✅"
606log_message "Dernier rapport HTML : $HTML_REPORT_LAST"
607log_message "Index HTML last : $HTML_INDEX_LAST"
608
609##############################################
610# 9) Publication WEB (HTML UNIQUEMENT)
611##############################################
612
613log_message "Publication web (HTML only) vers : $WEB_PUBLIC_DIR"
614
615rm -rf "$WEB_PUBLIC_DIR"
616mkdir -p "$WEB_PUBLIC_DIR"
617
618
619cp -f "$LAST_DIR/report.html" "$WEB_PUBLIC_DIR/report.html"
620cp -f "$LAST_DIR/index.html" "$WEB_PUBLIC_DIR/" 2>/dev/null || true
621
622log_message "Publication web OK ✅"
623
624##############################################
625# 10) Rapport CONSOLE
626##############################################
627
628echo -e "${GREEN}===== BACKUP TERMINÉ ✅ =====${NC}"
629
630##############################################
631# LISTE DES DOSSIERS DOCKER PAR TAILLE (ASCII)
632##############################################
633
634echo ""
635echo -e "${CYAN}----- LISTE/TAILLE DES RÉPERTOIRES DOCKER -----${NC}"
636
637DOCKER_DIRS=$(find "$SOURCE_DIR" -mindepth 1 -maxdepth 1 -type d \
638 ! -path "$SOURCE_DIR/casaos_data" \
639 ! -path "$SOURCE_DIR/docker_var_lib" \
640 ! -path "$SOURCE_DIR/portainer" \
641 2>/dev/null)
642
643if [ -z "$DOCKER_DIRS" ]; then
644 echo -e "${YELLOW}(Aucun dossier Docker trouvé)${NC}"
645else
646 echo -e "${CYAN}┌──────────────────────────┬─────────┬────────────────────────────────────────┐${NC}"
647 printf "${CYAN}│ %-24s │ %-7s │ %-38s │${NC}\n" "Container" "Taille" "Chemin"
648 echo -e "${CYAN}├──────────────────────────┼─────────┼────────────────────────────────────────┤${NC}"
649
650 # du + tri par taille desc
651 du -sh $DOCKER_DIRS 2>/dev/null | sort -hr | while read -r size path; do
652 name=$(basename "$path")
653 printf "│ %-24s │ %-7s │ %-38s │\n" "$name" "$size" "$path"
654 done
655
656 echo -e "${CYAN}└──────────────────────────┴─────────┴────────────────────────────────────────┘${NC}"
657fi
658
659##############################################
660
661echo -e "${CYAN}Dossier backup courant : ${YELLOW}$RUN_DIR${NC}"
662echo ""
663
664echo -e "${CYAN}----- DOCKER -----${NC}"
665echo "Containers Docker détectés : $CONTAINERS_DOCKER"
666echo "Compose source (Docker) : $SOURCE_COMPOSE_COUNT"
667echo "Compose backup (Docker) : $DOCKER_COMPOSE_BACKUP"
668echo "Archive Docker (run) : $BACKUP_FILE ($TAR_SIZE)"
669echo ""
670
671echo -e "${CYAN}----- CASAOS -----${NC}"
672if [ "$CASAOS_PRESENT" = "1" ]; then
673 echo "CasaOS détecté : OUI"
674 echo "Compose CasaOS backup : $CASAOS_COMPOSE_BACKUP"
675 [ -f "$CASAOS_FULL_TAR" ] && echo "Archive CasaOS FULL (run) : $CASAOS_FULL_TAR ($CASAOS_FULL_TAR_SIZE)"
676else
677 echo "CasaOS détecté : NON (ignoré)"
678fi
679echo ""
680
681echo -e "${CYAN}----- PORTAINER -----${NC}"
682echo "Fichiers config Portainer : $PORTAINER_FILES"
683[ -n "$PORTAINER_CONFIG_SIZE" ] && echo "Taille Portainer (run) : $PORTAINER_CONFIG_SIZE"
684echo ""
685
686echo -e "${CYAN}----- GLOBAL -----${NC}"
687echo "Sets de backup : $TOTAL_BACKUP_SETS"
688echo "Oldest backup : ${OLDEST_DIR:-N/A}"
689echo "Newest backup : ${NEWEST_DIR:-N/A}"
690echo "Durée totale : $DURATION"
691echo ""
692echo "HTML (run) : $HTML_REPORT"
693echo "HTML (last) : $HTML_REPORT_LAST"
694echo "WEB index : $WEB_PUBLIC_DIR/index.html"
695echo "WEB report : $WEB_PUBLIC_DIR/report.html"
696
697##############################################
698# JSON optionnel
699##############################################
700
701if [ "$1" = "--json" ]; then
702 echo ""
703 echo "{"
704 echo " \"date_raw\": \"$DATE_RAW\","
705 echo " \"date_dir\": \"$DATE_DIR\","
706 echo " \"run_dir\": \"$RUN_DIR\","
707 echo " \"last_dir\": \"$LAST_DIR\","
708 echo " \"docker_backup_file\": \"$BACKUP_FILE\","
709 echo " \"docker_backup_size\": \"$TAR_SIZE\","
710 echo " \"casaos_present\": $CASAOS_PRESENT,"
711 echo " \"casaos_full_backup_file\": \"${CASAOS_FULL_TAR:-}\","
712 echo " \"casaos_full_backup_size\": \"${CASAOS_FULL_TAR_SIZE:-}\","
713 echo " \"portainer_files\": $PORTAINER_FILES,"
714 echo " \"backup_sets\": $TOTAL_BACKUP_SETS,"
715 echo " \"duration\": \"$DURATION\","
716 echo " \"html_report_run\": \"$HTML_REPORT\","
717 echo " \"html_report_last\": \"$HTML_REPORT_LAST\","
718 echo " \"web_index\": \"$WEB_PUBLIC_DIR/index.html\","
719 echo " \"web_report\": \"$WEB_PUBLIC_DIR/report.html\""
720 echo "}"
721fi
722