init: clean history with no PII
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).
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# docker-restore.sh — bezpečná obnova Docker projektů z tar.zst zálohy
|
||||
#
|
||||
# - zobrazí dostupné zálohy v $BACKUP_ROOT
|
||||
# - vyžádá potvrzení (nebo --auto)
|
||||
# - zastaví vybrané projekty
|
||||
# - přesune současná data do _pre-restore-<TS>/ (NE rm -rf!)
|
||||
# - obnoví data ze zálohy
|
||||
# - vyčistí AppleDouble _* před docker compose up -d
|
||||
# - spustí kontejnery a ověří zdraví
|
||||
#
|
||||
# Použití:
|
||||
# ./docker-restore.sh # interaktivně: vybere zálohu + projekty
|
||||
# ./docker-restore.sh --list # jen seznam dostupných záloh
|
||||
# ./docker-restore.sh 2026-07-29_19-21-00 # konkrétní záloha, interaktivní výběr projektů
|
||||
# ./docker-restore.sh 2026-07-29_19-21-00 nginx vaultwarden # konkrétní záloha + projekty
|
||||
# ./docker-restore.sh --auto 2026-07-29_19-21-00 nginx # bez ptaní
|
||||
# ./docker-restore.sh --help # nápověda
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DOCKERY="${DOCKERY:-<docker-projects-root>}"
|
||||
BACKUP_ROOT="${BACKUP_ROOT:-<backup-root>}"
|
||||
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-60}"
|
||||
AUTO=0
|
||||
LIST_ONLY=0
|
||||
TS_ARG=""
|
||||
PROJECTS_RAW=()
|
||||
|
||||
usage() {
|
||||
sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h) usage; exit 0 ;;
|
||||
--auto|-y) AUTO=1 ;;
|
||||
--list|-l) LIST_ONLY=1 ;;
|
||||
--*) echo "Unknown flag: $arg" >&2; exit 1 ;;
|
||||
*)
|
||||
if [ -z "$TS_ARG" ]; then
|
||||
TS_ARG="$arg"
|
||||
else
|
||||
PROJECTS_RAW+=("$arg")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Barevné výstupy
|
||||
if [ -t 1 ]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; BLUE=''; 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" "$*"; }
|
||||
|
||||
confirm() {
|
||||
if [ "$AUTO" = "1" ]; then return 0; fi
|
||||
printf '%s%s%s ' "$YELLOW" "$1" "$NC"
|
||||
read -r ans
|
||||
case "$ans" in
|
||||
y|Y|yes|YES) return 0 ;;
|
||||
*) printf 'Zrušeno.\n'; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
# === Seznam dostupných záloh ===
|
||||
list_backups() {
|
||||
printf '%s%s%s\n' "$BLUE" "=== DOSTUPNÉ ZÁLOHY ===" "$NC"
|
||||
found=0
|
||||
while IFS= read -r d; do
|
||||
[ -d "$d" ] || continue
|
||||
ts=$(basename "$d")
|
||||
archives=$(/bin/ls "$d"/*.tar.zst 2>/dev/null | /usr/bin/wc -l | /usr/bin/tr -d ' ')
|
||||
size=$(/usr/bin/du -sh "$d" 2>/dev/null | /usr/bin/awk '{print $1}')
|
||||
printf ' %s %s archivů %s\n' "$ts" "$archives" "$size"
|
||||
found=$((found + 1))
|
||||
done < <(/usr/bin/find "$BACKUP_ROOT" -maxdepth 1 -mindepth 1 -type d \
|
||||
| /usr/bin/sort -r)
|
||||
if [ "$found" -eq 0 ]; then
|
||||
printf ' (žádné)\n'
|
||||
fi
|
||||
}
|
||||
|
||||
# Zobrazit seznam, pokud --list
|
||||
if [ "$LIST_ONLY" = "1" ]; then
|
||||
list_backups
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# === Vyber zálohu ===
|
||||
if [ -z "$TS_ARG" ]; then
|
||||
list_backups
|
||||
printf '\n%sZadejte timestamp zálohy (např. 2026-07-29_19-21-00):%s ' "$YELLOW" "$NC"
|
||||
read -r TS_ARG
|
||||
fi
|
||||
|
||||
BACKUP_DIR="$BACKUP_ROOT/$TS_ARG"
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
err "Záloha neexistuje: $BACKUP_DIR"
|
||||
list_backups
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Vybraná záloha: $BACKUP_DIR"
|
||||
log "Archivy v záloze:"
|
||||
/bin/ls -1 "$BACKUP_DIR"/*.tar.zst 2>/dev/null | /usr/bin/sed 's/^/ /' || {
|
||||
err "Záloha neobsahuje žádné archivy"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# === Detekce projektů v záloze ===
|
||||
DETECTED=()
|
||||
for archive in "$BACKUP_DIR"/*.tar.zst; do
|
||||
name=$(basename "$archive" .tar.zst)
|
||||
# Rozlišení: project archive vs volume archive
|
||||
# Project: <project>.tar.zst v Dockery/
|
||||
# Volume: <volname>.tar.zst v named volumes
|
||||
if [ -d "$DOCKERY/$name" ] && compgen -G "$DOCKERY/$name/docker-compose.y*ml" > /dev/null; then
|
||||
DETECTED+=("project:$name")
|
||||
else
|
||||
# Může to být volume; docker volume ls to ověří
|
||||
if docker volume inspect "$name" >/dev/null 2>&1; then
|
||||
DETECTED+=("volume:$name")
|
||||
else
|
||||
DETECTED+=("unknown:$name")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log ""
|
||||
log "Položky v záloze:"
|
||||
for d in "${DETECTED[@]}"; do printf ' %s\n' "$d"; done
|
||||
|
||||
# === Vyber projekty k obnově ===
|
||||
if [ "${#PROJECTS_RAW[@]}" -eq 0 ]; then
|
||||
PROJECTS=()
|
||||
for d in "${DETECTED[@]}"; do
|
||||
type=${d%%:*}
|
||||
name=${d#*:}
|
||||
if [ "$type" = "project" ]; then
|
||||
PROJECTS+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#PROJECTS[@]}" -eq 0 ]; then
|
||||
err "V záloze nebyly nalezeny žádné projekty k obnově"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '\n%sDostupné projekty v této záloze:%s\n' "$BLUE" "$NC"
|
||||
for i in "${!PROJECTS[@]}"; do
|
||||
printf ' %d) %s\n' $((i+1)) "${PROJECTS[$i]}"
|
||||
done
|
||||
printf '%sVyberte projekty (např. 1 3 nebo "all"):%s ' "$YELLOW" "$NC"
|
||||
read -r sel
|
||||
case "$sel" in
|
||||
all|"") SELECTED=("${PROJECTS[@]}") ;;
|
||||
*)
|
||||
SELECTED=()
|
||||
for n in $sel; do
|
||||
idx=$((n - 1))
|
||||
if [ "$idx" -ge 0 ] && [ "$idx" -lt "${#PROJECTS[@]}" ]; then
|
||||
SELECTED+=("${PROJECTS[$idx]}")
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
else
|
||||
SELECTED=("${PROJECTS_RAW[@]}")
|
||||
fi
|
||||
|
||||
if [ "${#SELECTED[@]}" -eq 0 ]; then
|
||||
err "Nebyly vybrány žádné projekty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# === Plán obnovy ===
|
||||
TS_NOW="$(date +%Y-%m-%d_%H-%M-%S)"
|
||||
|
||||
printf '\n%s%s%s\n' "$BLUE" "=== PLÁN OBNOVY ===" "$NC"
|
||||
printf 'Záloha: %s\n' "$BACKUP_DIR"
|
||||
printf 'Projekty: %s\n' "${SELECTED[*]}"
|
||||
printf 'Pre-restore: %s/_pre-restore-%s/\n' "$DOCKERY" "$TS_NOW"
|
||||
printf '\n'
|
||||
|
||||
confirm "Pokračovat v obnově? (PŘEPSOU se současná data projektů!) [y/N]"
|
||||
|
||||
# === Stop kontejnerů vybraných projektů ===
|
||||
log ""
|
||||
log "=== STOP: zastavuji kontejnery vybraných projektů ==="
|
||||
for project in "${SELECTED[@]}"; do
|
||||
cd "$DOCKERY/$project"
|
||||
if docker compose ps -q 2>/dev/null | grep -q .; then
|
||||
log " [$project] docker compose stop"
|
||||
docker compose stop 2>&1 || warn " [$project] stop selhal"
|
||||
fi
|
||||
done
|
||||
|
||||
# === Move-aside současných dat ===
|
||||
log ""
|
||||
log "=== MOVE-ASIDE: přesouvám současná data do _pre-restore-$TS_NOW/ ==="
|
||||
for project in "${SELECTED[@]}"; do
|
||||
SRC="$DOCKERY/$project"
|
||||
DEST="$DOCKERY/_pre-restore-$TS_NOW/$project"
|
||||
mkdir -p "$DEST"
|
||||
# Najdi velké podadresáře a přesuň je (NEVYMAŽ!)
|
||||
for sub in $(/bin/ls -A "$SRC" 2>/dev/null); do
|
||||
# Přeskoč .DS_Store, samotný _pre-restore-*, docker-compose soubory
|
||||
case "$sub" in
|
||||
_pre-restore-*) continue ;;
|
||||
docker-compose.y*ml) continue ;;
|
||||
.env|.env.*) continue ;; # .env necháme — je to runtime konfigurace
|
||||
.DS_Store) continue ;;
|
||||
esac
|
||||
if [ -e "$SRC/$sub" ]; then
|
||||
log " [$project] mv $sub → _pre-restore-$TS_NOW/$project/"
|
||||
mv "$SRC/$sub" "$DEST/$sub" 2>&1 || warn " mv $sub selhal"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# === Restore ze zálohy ===
|
||||
log ""
|
||||
log "=== RESTORE: extrahuji ze zálohy ==="
|
||||
FAILED=()
|
||||
for project in "${SELECTED[@]}"; do
|
||||
ARCHIVE="$BACKUP_DIR/${project}.tar.zst"
|
||||
if [ ! -f "$ARCHIVE" ]; then
|
||||
warn "[$project] archiv neexistuje: $ARCHIVE (přeskakuji)"
|
||||
FAILED+=("$project:no-archive")
|
||||
continue
|
||||
fi
|
||||
|
||||
log " [$project] extrahuji do $DOCKERY/$project/"
|
||||
# Archiv má cesty jako Volumes/SSD-data/Dockery/<project>/...
|
||||
# Extrahujeme s --strip-components=3, abychom získali přímo <project>/...
|
||||
cd "$DOCKERY"
|
||||
if /opt/homebrew/bin/zstd -d -c "$ARCHIVE" \
|
||||
| /usr/bin/tar --strip-components=3 -xf - 2>>"$BACKUP_DIR/.restore-errors-$TS_NOW"; then
|
||||
log " OK"
|
||||
else
|
||||
err " extrakce selhala pro $project"
|
||||
FAILED+=("$project:extract-failed")
|
||||
fi
|
||||
done
|
||||
|
||||
# === Vyčistit AppleDouble _* (prevence MariaDB/PG smyček) ===
|
||||
log ""
|
||||
log "=== CLEAN APPLE DOUBLE: mažu ._* před startem ==="
|
||||
for project in "${SELECTED[@]}"; do
|
||||
count=$(/usr/bin/find "$DOCKERY/$project" -name '._*' -type f 2>/dev/null | /usr/bin/wc -l | /usr/bin/tr -d ' ')
|
||||
if [ "$count" -gt 0 ]; then
|
||||
log " [$project] nalezeno $count ._* souborů, mažu"
|
||||
/usr/bin/find "$DOCKERY/$project" -name '._*' -type f -delete 2>/dev/null || true
|
||||
fi
|
||||
# .DS_Store
|
||||
dscount=$(/usr/bin/find "$DOCKERY/$project" -name '.DS_Store' -type f 2>/dev/null | /usr/bin/wc -l | /usr/bin/tr -d ' ')
|
||||
if [ "$dscount" -gt 0 ]; then
|
||||
log " [$project] nalezeno $dscount .DS_Store, mažu"
|
||||
/usr/bin/find "$DOCKERY/$project" -name '.DS_Store' -type f -delete 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# === Up kontejnerů ===
|
||||
log ""
|
||||
log "=== UP: spouštím kontejnery ==="
|
||||
for project in "${SELECTED[@]}"; do
|
||||
cd "$DOCKERY/$project"
|
||||
log " [$project] docker compose up -d"
|
||||
docker compose up -d 2>&1 | /usr/bin/head -10 || warn " [$project] up -d selhal"
|
||||
done
|
||||
|
||||
# === Health check ===
|
||||
log ""
|
||||
log "=== HEALTH CHECK: čekám na zdraví (max ${HEALTH_TIMEOUT}s) ==="
|
||||
sleep 10
|
||||
elapsed=10
|
||||
all_healthy=1
|
||||
while [ "$elapsed" -lt "$HEALTH_TIMEOUT" ]; do
|
||||
sleep 5
|
||||
elapsed=$((elapsed + 5))
|
||||
for project in "${SELECTED[@]}"; do
|
||||
for c in $(docker compose -f "$DOCKERY/$project/docker-compose.yml" ps -a --format '{{.Names}}' 2>/dev/null); do
|
||||
status=$(docker inspect --format='{{.State.Status}}' "$c" 2>/dev/null)
|
||||
health=$(docker inspect --format='{{.State.Health.Status}}' "$c" 2>/dev/null)
|
||||
if [ "$status" != "running" ]; then
|
||||
log " [$project/$c] state=$status health=$health"
|
||||
all_healthy=0
|
||||
fi
|
||||
done
|
||||
done
|
||||
# Pokud všechny kontejnery running a žádné health check nehlásí starting/unhealthy, ukončíme
|
||||
starting=$(docker ps --format '{{.Names}}' \
|
||||
| while read -r c; do docker inspect --format='{{.State.Health.Status}}' "$c" 2>/dev/null; done \
|
||||
| /usr/bin/grep -c '^starting$' || true)
|
||||
if [ "$starting" = "0" ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# === Report ===
|
||||
log ""
|
||||
log "======================================"
|
||||
log " SHRNUTÍ OBNOVY"
|
||||
log "======================================"
|
||||
log "Obnoveno:"
|
||||
for p in "${SELECTED[@]}"; do log " ✓ $p"; done
|
||||
if [ "${#FAILED[@]}" -gt 0 ]; then
|
||||
err "Selhalo:"
|
||||
for f in "${FAILED[@]}"; do err " ✗ $f"; done
|
||||
fi
|
||||
log ""
|
||||
log "Současná data jsou v: $DOCKERY/_pre-restore-$TS_NOW/"
|
||||
log "Po ověření můžete tento adresář ručně smazat:"
|
||||
log " rm -rf $DOCKERY/_pre-restore-$TS_NOW"
|
||||
log ""
|
||||
if [ "$all_healthy" = "1" ]; then
|
||||
log "Všechny kontejnery běží."
|
||||
else
|
||||
warn "Některé kontejnery nejsou v pořádku — zkontrolujte logy."
|
||||
fi
|
||||
Reference in New Issue
Block a user