Files
mates 10d214c2a2 fix: odstranit emoji ze vsech souboru
Kompletne odstraneno:
- Emoji (smajliky, symboly, boxes, arrows) z README
- Emoji ze skriptu (sipky, symboly)
- Emoji z docker-compose (adresare)

Nahrano za:
- Textove alternativy (sipky -> '->', boxes -> '+', atd.)
- ASCII pouze kde to slo

Ciste vsechny 7 repo (overeno Python skriptem).
2026-08-02 08:50:11 +02:00

342 lines
9.6 KiB
Bash

#!/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 " [OK] $p"; done
if [ "${#FAILED[@]}" -gt 0 ]; then
err "Selhalo:"
for f in "${FAILED[@]}"; do err " [FAIL] $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