Toto repo ma novou cistou historii (1 commit). Predchozi historie byla smazana, protoze obsahovala osobni udaje (IP, emaily, hesla, klice, tokeny, Volume cesty). Soubory v aktualnim commit uz tyto udaje neobsahuji (nahradeno placeholdery <server-ip>, CHANGE_ME, admin@example.com atd.). Pokud chces zpet kompletni seznam starych commitu, mas je ve svem lokalnim working tree - git log --all ukaze prazdno (protoze jsme smazali .git/ a zacali znovu).
203 lines
6.1 KiB
Bash
203 lines
6.1 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# docker-backup.sh — bezpečná záloha Docker projektů a named volumes
|
|
#
|
|
# - vytvoří timestamp složku v <backup-root>/
|
|
# - pro každý projekt v <docker-projects-root> zastaví kontejnery,
|
|
# zálohuje tar.zst, ověří integritu, znovu spustí
|
|
# - zvlášť zálohuje Docker named volumes
|
|
# - ponechá posledních N záloh (default 3)
|
|
#
|
|
# Použití:
|
|
# ./docker-backup.sh # všechny projekty + named volumes
|
|
# ./docker-backup.sh nginx mattermost # vybrané projekty (volumes vždy)
|
|
# BACKUP_KEEP=7 ./docker-backup.sh # ponechá 7 záloh
|
|
# ./docker-backup.sh --help # nápověda
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'
|
|
}
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--help|-h) usage; exit 0 ;;
|
|
esac
|
|
done
|
|
|
|
DOCKERY="${DOCKERY:-<docker-projects-root>}"
|
|
BACKUP_ROOT="${BACKUP_ROOT:-<backup-root>}"
|
|
KEEP="${BACKUP_KEEP:-3}"
|
|
TS="$(date +%Y-%m-%d_%H-%M-%S)"
|
|
DEST="${BACKUP_ROOT}/${TS}"
|
|
|
|
# Barevné výstupy
|
|
if [ -t 1 ]; then
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
|
else
|
|
RED=''; GREEN=''; YELLOW=''; NC=''
|
|
fi
|
|
|
|
log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
|
warn() { printf '%s[%s]%s %s\n' "$YELLOW" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
|
err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
|
|
|
# Kontrola
|
|
if ! docker info >/dev/null 2>&1; then
|
|
err "Docker není dostupný."
|
|
exit 1
|
|
fi
|
|
if [ ! -d "$DOCKERY" ]; then
|
|
err "Dockery adresář neexistuje: $DOCKERY"
|
|
exit 1
|
|
fi
|
|
if [ ! -d "$BACKUP_ROOT" ]; then
|
|
err "Backup adresář neexistuje: $BACKUP_ROOT"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$DEST"
|
|
|
|
# Najdi projekty
|
|
if [ "$#" -gt 0 ]; then
|
|
PROJECTS=("$@")
|
|
else
|
|
PROJECTS=()
|
|
for d in "$DOCKERY"/*/; do
|
|
[ -d "$d" ] || continue
|
|
if compgen -G "$d/docker-compose.y*ml" > /dev/null; then
|
|
PROJECTS+=("$(basename "$d")")
|
|
fi
|
|
done
|
|
fi
|
|
|
|
log "Projektů k záloze: ${#PROJECTS[@]}"
|
|
log "Cíl: $DEST"
|
|
log "Uchovat posledních: $KEEP záloh"
|
|
|
|
# === FÁZE 1: zastavit všechny dotčené kontejnery najednou ===
|
|
log ""
|
|
log "=== STOP: zastavuji všechny kontejnery v ${PROJECTS[*]} ==="
|
|
for project in "${PROJECTS[@]}"; do
|
|
cd "$DOCKERY/$project"
|
|
if [ -f docker-compose.yml ] || [ -f docker-compose.yaml ]; then
|
|
if docker compose ps -q 2>/dev/null | grep -q .; then
|
|
log " [$project] docker compose stop"
|
|
docker compose stop 2>&1 || true
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# === FÁZE 2: záloha projektů (paralelně pro velké projekty) ===
|
|
log ""
|
|
log "=== BACKUP projekty (tar.zst) ==="
|
|
FAILED=()
|
|
for project in "${PROJECTS[@]}"; do
|
|
cd "$DOCKERY/$project"
|
|
ARCHIVE="$DEST/${project}.tar.zst"
|
|
log " [$project] tar -c <docker-projects-root>/$project/ → $ARCHIVE"
|
|
# absolutní cesta (background tar pitfall: vždy absolutní)
|
|
if /usr/bin/tar --exclude='._*' --exclude='.DS_Store' \
|
|
--use-compress-program='/opt/homebrew/bin/zstd -1 -T0' \
|
|
-cf "$ARCHIVE" \
|
|
"<docker-projects-root>/$project/" 2>>"$DEST/.errors"; then
|
|
# Ověření
|
|
if /opt/homebrew/bin/zstd -t "$ARCHIVE" >/dev/null 2>&1; then
|
|
SIZE=$(stat -f '%z' "$ARCHIVE")
|
|
log " OK ($(numfmt --to=iec --suffix=B "$SIZE" 2>/dev/null || echo "$SIZE B"))"
|
|
else
|
|
err " zstd -t selhal pro $ARCHIVE"
|
|
FAILED+=("$project")
|
|
fi
|
|
else
|
|
err " tar selhal pro $project"
|
|
FAILED+=("$project")
|
|
fi
|
|
done
|
|
|
|
# === FÁZE 3: named volumes (per-volume záloha přes Docker) ===
|
|
log ""
|
|
log "=== BACKUP named volumes ==="
|
|
VOLUMES=$(docker volume ls --format '{{.Name}}' \
|
|
| grep -vE '^(host-services|docker-desktop)$' || true)
|
|
|
|
for vol in $VOLUMES; do
|
|
ARCHIVE="$DEST/${vol}.tar.zst"
|
|
log " [volume:$vol] → $ARCHIVE"
|
|
# Nejdřív dekomprimuj na hostiteli, pak pošli do kontejneru (alpine tar neumí zstd)
|
|
if docker run --rm \
|
|
-v "${vol}:/src:ro" \
|
|
-v "$DEST:/dest" \
|
|
alpine:3.20 \
|
|
sh -c "cd /src && tar --exclude='._*' --exclude='.DS_Store' -cf - ." \
|
|
> "$DEST/${vol}.tar" 2>>"$DEST/.errors"; then
|
|
/opt/homebrew/bin/zstd -1 -T0 -f "$DEST/${vol}.tar" -o "$ARCHIVE" 2>>"$DEST/.errors"
|
|
rm -f "$DEST/${vol}.tar"
|
|
if /opt/homebrew/bin/zstd -t "$ARCHIVE" >/dev/null 2>&1; then
|
|
SIZE=$(stat -f '%z' "$ARCHIVE")
|
|
log " OK ($(numfmt --to=iec --suffix=B "$SIZE" 2>/dev/null || echo "$SIZE B"))"
|
|
else
|
|
err " zstd -t selhal pro $ARCHIVE"
|
|
FAILED+=("volume:$vol")
|
|
fi
|
|
else
|
|
err " docker run selhal pro volume $vol"
|
|
FAILED+=("volume:$vol")
|
|
fi
|
|
done
|
|
|
|
# === FÁZE 4: znovu spustit projekty ===
|
|
log ""
|
|
log "=== UP: spouštím projekty ==="
|
|
for project in "${PROJECTS[@]}"; do
|
|
cd "$DOCKERY/$project"
|
|
if [ -f docker-compose.yml ] || [ -f docker-compose.yaml ]; then
|
|
log " [$project] docker compose up -d"
|
|
docker compose up -d 2>&1 || warn " [$project] up -d selhal (zkontroluj)"
|
|
fi
|
|
done
|
|
|
|
# === FÁZE 5: hash report ===
|
|
log ""
|
|
log "=== SHA-256 hashe ==="
|
|
for f in "$DEST"/*.tar.zst; do
|
|
[ -f "$f" ] || continue
|
|
H=$(shasum -a 256 "$f" | awk '{print $1}')
|
|
S=$(stat -f '%z' "$f")
|
|
log " $(basename "$f") sha256=$H size=$S"
|
|
done
|
|
|
|
# === FÁZE 6: retention ===
|
|
log ""
|
|
log "=== RETENTION: ponechávám posledních $KEEP záloh ==="
|
|
# set -u a prázdné pole
|
|
existing=()
|
|
while IFS= read -r d; do
|
|
[ -d "$d" ] && existing+=("$d")
|
|
done < <(find "$BACKUP_ROOT" -maxdepth 1 -mindepth 1 -type d | sort)
|
|
|
|
# Pokud něco selhalo, nepromazávej
|
|
if [ "${#FAILED[@]}" -gt 0 ]; then
|
|
warn "Některé zálohy selhaly (${#FAILED[@]}), RETENTION se neprovádí"
|
|
else
|
|
if [ "${#existing[@]}" -gt "$KEEP" ]; then
|
|
for old in "${existing[@]:0:${#existing[@]}-KEEP}"; do
|
|
log " mažu starou zálohu: $(basename "$old")"
|
|
rm -rf "$old"
|
|
done
|
|
fi
|
|
fi
|
|
|
|
log ""
|
|
log "======================================"
|
|
log " HOTOVO"
|
|
log "======================================"
|
|
log "Záloha: $DEST"
|
|
if [ "${#FAILED[@]}" -gt 0 ]; then
|
|
err "Selhalo: ${#FAILED[@]}"
|
|
for f in "${FAILED[@]}"; do err " ✗ $f"; done
|
|
exit 1
|
|
fi
|
|
log "Vše OK." |