From 10d214c2a218db01ef71b8b76fa074da0c8b040e Mon Sep 17 00:00:00 2001 From: mates Date: Sun, 2 Aug 2026 08:50:11 +0200 Subject: [PATCH] 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). --- README.md | 128 +++++++-------- disk-watch.sh | 236 ++++++++++++++-------------- docker-backup.sh | 194 +++++++++++------------ docker-cleanup.sh | 386 +++++++++++++++++++++++----------------------- docker-restore.sh | 382 ++++++++++++++++++++++----------------------- docker-update.sh | 198 ++++++++++++------------ update-macos.sh | 18 +-- 7 files changed, 771 insertions(+), 771 deletions(-) diff --git a/README.md b/README.md index 3ed4b35..ccaefd7 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ Bash skripty pro správu Dockeru na Macu i Linuxu. Jedná se o **přesné kopie** skriptů z tvého lokálního `~/Skripty/docker-domov/`. -> **Jsi začátečník?** Čti od [Začínáme](#zaciname) – vše je krok za krokem. +> **Jsi začátečník?** Čti od [Začínáme](#zaciname) - vše je krok za krokem. --- -## 📦 Co tu je +## Co tu je | Skript | Alias | Co dělá | |---|---|---| @@ -17,13 +17,13 @@ Jedná se o **přesné kopie** skriptů z tvého lokálního `~/Skripty/docker-d | `docker-restore.sh` | `dk-restore` | Obnovuje Docker projekty ze zálohy | | `disk-watch.sh` | `dk-watch` | Sleduje volné místo na Docker datech | | `mount-nas.sh` | `dk-nas` | Připojí/odpojí NAS SMB share do `/Volumes//multimedia` | -| `update-macos.sh` | – | Aktualizace macOS přes Software Update | +| `update-macos.sh` | - | Aktualizace macOS přes Software Update | Všechny skripty jsou **idempotentní** (můžeš spustit víckrát), mají `--help`, podporují **dry-run** a bez potvrzení nic destruktivního neprovádějí. --- -## 🚀 Začínáme +## Začínáme ### Krok 1: Naklonuj repo @@ -73,22 +73,22 @@ Příklad struktury: ``` ~/DockerProjects/ -├── portainer/ -│ ├── docker-compose.yml -│ └── data/ -├── vaultwarden/ -│ ├── docker-compose.yml -│ └── data/ -└── nextcloud/ - └── docker-compose.yml ++-- portainer/ +| +-- docker-compose.yml +| +-- data/ ++-- vaultwarden/ +| +-- docker-compose.yml +| +-- data/ ++-- nextcloud/ + +-- docker-compose.yml ``` **Konfigurace cesty:** ```bash -export DOCKERY="/Volumes//Dockery" # macOS +export DOCKERY="/Volumes//Dockery" # macOS # nebo -export DOCKERY="/home/deploy/docker" # Linux +export DOCKERY="/home/deploy/docker" # Linux ``` ### Krok 4: První použití @@ -104,16 +104,16 @@ dk-backup --dry-run --- -## 📚 Detail ke každému skriptu +## Detail ke každému skriptu -### `docker-update.sh` → `dk-update` +### `docker-update.sh` -> `dk-update` Bezpečná aktualizace Docker Compose projektů. ```bash -dk-update # aktualizuje všechny projekty v DOCKERY -dk-update portainer # aktualizuje jen portainer -dk-update --dry-run # ukáže co by se dělalo, neprovede +dk-update # aktualizuje všechny projekty v DOCKERY +dk-update portainer # aktualizuje jen portainer +dk-update --dry-run # ukáže co by se dělalo, neprovede ``` **Workflow:** @@ -122,15 +122,15 @@ dk-update --dry-run # ukáže co by se dělalo, neprovede 3. Restartuje s novými image 4. Ověří, že vše běží -### `docker-backup.sh` → `dk-backup` +### `docker-backup.sh` -> `dk-backup` Záloha Docker projektů na NAS. ```bash -dk-backup # zalohuj vse -dk-backup --only portainer # jen jeden projekt -dk-backup --dry-run # ukaz co by se delalo -dk-backup --auto # bez dotazovani (cron-friendly) +dk-backup # zalohuj vse +dk-backup --only portainer # jen jeden projekt +dk-backup --dry-run # ukaz co by se delalo +dk-backup --auto # bez dotazovani (cron-friendly) ``` **Výchozí nastavení:** @@ -138,57 +138,57 @@ dk-backup --auto # bez dotazovani (cron-friendly) - Používá `tar --zstd` (moderní komprese) - Staré zálohy se **nemazou** (musíš ručně) -### `docker-cleanup.sh` → `dk-cleanup` +### `docker-cleanup.sh` -> `dk-cleanup` Bezpečné čištění Docker odpadu. ```bash -dk-cleanup # zobrazi plan, zepta se na souhlas -dk-cleanup --dry-run # jen vypise co by smazal -dk-cleanup --auto # bez ptani -dk-cleanup --all # dangling + build cache + sites + tmp +dk-cleanup # zobrazi plan, zepta se na souhlas +dk-cleanup --dry-run # jen vypise co by smazal +dk-cleanup --auto # bez ptani +dk-cleanup --all # dangling + build cache + sites + tmp ``` **Nikdy nemaže:** -- ❌ Používané images -- ❌ Bind-mount ani named volume data -- ❌ Běžící kontejnery +- Používané images +- Bind-mount ani named volume data +- Běžící kontejnery -### `docker-restore.sh` → `dk-restore` +### `docker-restore.sh` -> `dk-restore` Obnova ze zálohy. ```bash -dk-list # seznam dostupnych zaloh -dk-restore # obnov vse (nejnovejsi) -dk-restore --only portainer # jen jeden projekt -dk-restore --from 20240801-120000 # konkretni zaloha +dk-list # seznam dostupnych zaloh +dk-restore # obnov vse (nejnovejsi) +dk-restore --only portainer # jen jeden projekt +dk-restore --from 20240801-120000 # konkretni zaloha ``` -### `disk-watch.sh` → `dk-watch` +### `disk-watch.sh` -> `dk-watch` Sledování volného místa. ```bash -dk-watch # jen info o aktualnim stavu -dk-watch --alert # posli Telegram alert kdyz < 10% volneho +dk-watch # jen info o aktualnim stavu +dk-watch --alert # posli Telegram alert kdyz < 10% volneho ``` -### `mount-nas.sh` → `dk-nas` +### `mount-nas.sh` -> `dk-nas` Připojí NAS SMB share. ```bash -dk-nas-status # je pripojeno? -dk-nas-mount # pripoj (auto = chybejici chybu) -dk-nas --umount # odpoj +dk-nas-status # je pripojeno? +dk-nas-mount # pripoj (auto = chybejici chybu) +dk-nas --umount # odpoj ``` **Konfigurace:** -- `SMB_SERVER` – default `` -- `SMB_SHARE` – default `Multimedia` -- `SMB_USER` – default `mates` -- `TARGET` – default `/Volumes//multimedia` +- `SMB_SERVER` - default `` +- `SMB_SHARE` - default `Multimedia` +- `SMB_USER` - default `mates` +- `TARGET` - default `/Volumes//multimedia` - Heslo je v **macOS keychainu** pod `SMB:/` ### `update-macos.sh` @@ -201,33 +201,33 @@ Spustí `softwareupdate` na macOS. --- -## 🔐 Bezpečnost +## Bezpečnost -- ✅ **Všechny skripty kontrolují `$EUID == 0`** (musíš být root pro sudo) -- ✅ **Všechny skripty podporují `--dry-run`** -- ✅ **Žádné destruktivní akce bez potvrzení** -- ✅ **Hesla v macOS keychainu**, ne v skriptech +- **Všechny skripty kontrolují `$EUID == 0`** (musíš být root pro sudo) +- **Všechny skripty podporují `--dry-run`** +- **Žádné destruktivní akce bez potvrzení** +- **Hesla v macOS keychainu**, ne v skriptech --- -## 🏗 Struktura +## Struktura ``` mates-install-scripts/ -├── README.md # Tento soubor -├── LICENSE # MIT -├── docker-update.sh # Aktualizace Docker -├── docker-backup.sh # Záloha na NAS -├── docker-cleanup.sh # Čištění odpadu -├── docker-restore.sh # Obnova ze zálohy -├── disk-watch.sh # Sledování místa -├── mount-nas.sh # Mount NAS -└── update-macos.sh # Aktualizace macOS ++-- README.md # Tento soubor ++-- LICENSE # MIT ++-- docker-update.sh # Aktualizace Docker ++-- docker-backup.sh # Záloha na NAS ++-- docker-cleanup.sh # Čištění odpadu ++-- docker-restore.sh # Obnova ze zálohy ++-- disk-watch.sh # Sledování místa ++-- mount-nas.sh # Mount NAS ++-- update-macos.sh # Aktualizace macOS ``` --- -## 📚 Související +## Související - **Jotty:** knowledge base (detailní postupy, troubleshooting) - **mates-docker-services:** Docker Compose šablony pro 14 služeb diff --git a/disk-watch.sh b/disk-watch.sh index d1b559a..2f12484 100644 --- a/disk-watch.sh +++ b/disk-watch.sh @@ -1,140 +1,140 @@ #!/usr/bin/env bash # -# disk-watch.sh — sledování volného místa na Docker datech +# disk-watch.sh -- sledování volného místa na Docker datech # # Kontroluje: -# - (Docker data) -# - (zálohy) -# - Docker disk usage (images, containers, volumes, build cache) +# - (Docker data) +# - (zálohy) +# - Docker disk usage (images, containers, volumes, build cache) # # Při podkročení prahu pošle notifikaci (Telegram přes Hermes cron, nebo lokální alert). # V defaultu jen vypíše stav; s --alert pošle zprávu přes Hermes Telegram doručení. # # Použití: -# ./disk-watch.sh # jen výpis -# ./disk-watch.sh --alert # pošle notifikaci při podkročení -# SSDDATA_THRESHOLD=80 ./disk-watch.sh # 80 % prah pro SSD-data +# ./disk-watch.sh # jen výpis +# ./disk-watch.sh --alert # pošle notifikaci při podkročení +# SSDDATA_THRESHOLD=80 ./disk-watch.sh # 80 % prah pro SSD-data # set -euo pipefail SSD_DATA="${SSD_DATA:-}" SSD_BACKUP="${SSD_BACKUP:-}" -SSD_DATA_THRESHOLD="${SSD_DATA_THRESHOLD:-85}" # % -SSD_BACKUP_THRESHOLD="${SSD_BACKUP_THRESHOLD:-85}" # % -DOCKER_THRESHOLD="${DOCKER_THRESHOLD:-80}" # % +SSD_DATA_THRESHOLD="${SSD_DATA_THRESHOLD:-85}" # % +SSD_BACKUP_THRESHOLD="${SSD_BACKUP_THRESHOLD:-85}" # % +DOCKER_THRESHOLD="${DOCKER_THRESHOLD:-80}" # % ALERT=0 for arg in "$@"; do - case "$arg" in - --alert|-a) ALERT=1 ;; - --help|-h) - sed -n '2,15p' "$0" - exit 0 - ;; - *) echo "Unknown arg: $arg" >&2; exit 1 ;; - esac + case "$arg" in + --alert|-a) ALERT=1 ;; + --help|-h) + sed -n '2,15p' "$0" + exit 0 + ;; + *) echo "Unknown arg: $arg" >&2; exit 1 ;; + 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' + 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='' + RED=''; GREEN=''; YELLOW=''; BLUE=''; NC='' fi WARNINGS=() check_disk() { - local path="$1" threshold="$2" label="$3" - if [ ! -d "$path" ]; then - printf '%s%s%s %s — adresář neexistuje\n' "$YELLOW" "⚠" "$NC" "$label" - return - fi - # df v macOS nevrací % použití přímo; parsujeme z výstupu - local line used avail capacity - line=$(df -h "$path" | tail -n +2 | head -n 1) - capacity=$(echo "$line" | awk '{print $5}' | tr -d '%') - used=$(echo "$line" | awk '{print $3}') - avail=$(echo "$line" | awk '{print $4}') - total=$(echo "$line" | awk '{print $2}') + local path="$1" threshold="$2" label="$3" + if [ ! -d "$path" ]; then + printf '%s%s%s %s -- adresář neexistuje\n' "$YELLOW" "" "$NC" "$label" + return + fi + # df v macOS nevrací % použití přímo; parsujeme z výstupu + local line used avail capacity + line=$(df -h "$path" | tail -n +2 | head -n 1) + capacity=$(echo "$line" | awk '{print $5}' | tr -d '%') + used=$(echo "$line" | awk '{print $3}') + avail=$(echo "$line" | awk '{print $4}') + total=$(echo "$line" | awk '{print $2}') - if [ -z "$capacity" ]; then - printf '%s%s%s %s — nelze přečíst\n' "$YELLOW" "⚠" "$NC" "$label" - return - fi + if [ -z "$capacity" ]; then + printf '%s%s%s %s -- nelze přečíst\n' "$YELLOW" "" "$NC" "$label" + return + fi - if [ "$capacity" -ge "$threshold" ]; then - printf '%s●%s %s %s/%s (%s%%) — %sPŘEKROČEN PRAH%s (≥%s%%)\n' \ - "$RED" "$NC" "$label" "$used" "$total" "$capacity" "$RED" "$NC" "$threshold" - WARNINGS+=("$label: ${capacity}% (prah ${threshold}%)") - else - printf '%s●%s %s %s/%s (%s%%) — OK\n' \ - "$GREEN" "$NC" "$label" "$used" "$total" "$capacity" - fi + if [ "$capacity" -ge "$threshold" ]; then + printf '%s%s %s %s/%s (%s%%) -- %sPŘEKROČEN PRAH%s (%s%%)\n' \ + "$RED" "$NC" "$label" "$used" "$total" "$capacity" "$RED" "$NC" "$threshold" + WARNINGS+=("$label: ${capacity}% (prah ${threshold}%)") + else + printf '%s%s %s %s/%s (%s%%) -- OK\n' \ + "$GREEN" "$NC" "$label" "$used" "$total" "$capacity" + fi } check_docker_disk() { - if ! docker info >/dev/null 2>&1; then - printf '%s●%s Docker — není dostupný\n' "$YELLOW" "$NC" - return - fi + if ! docker info >/dev/null 2>&1; then + printf '%s%s Docker -- není dostupný\n' "$YELLOW" "$NC" + return + fi - # Získat velikost images + containers + volumes - local out - out=$(docker system df --format '{{.Type}}\t{{.Size}}\t{{.Reclaimable}}' 2>/dev/null || true) + # Získat velikost images + containers + volumes + local out + out=$(docker system df --format '{{.Type}}\t{{.Size}}\t{{.Reclaimable}}' 2>/dev/null || true) - if [ -z "$out" ]; then - printf '%s●%s Docker disk — nešlo přečíst\n' "$YELLOW" "$NC" - return - fi + if [ -z "$out" ]; then + printf '%s%s Docker disk -- nešlo přečíst\n' "$YELLOW" "$NC" + return + fi - # Spočítat celkové využití Dockeru (obtížné bez du -s na /var/lib/docker; - # protože jsme na Docker Desktop macOS, kontrolujeme jen velikost images) - local images_size_bytes - images_size_bytes=$(docker system df --format '{{.Type}}\t{{.Size}}' \ - | awk -F'\t' '$1 == "Images" {print $2}') + # Spočítat celkové využití Dockeru (obtížné bez du -s na /var/lib/docker; + # protože jsme na Docker Desktop macOS, kontrolujeme jen velikost images) + local images_size_bytes + images_size_bytes=$(docker system df --format '{{.Type}}\t{{.Size}}' \ + | awk -F'\t' '$1 == "Images" {print $2}') - printf '%s●%s Docker images: %s\n' "$BLUE" "$NC" "${images_size_bytes:-neznámé}" + printf '%s%s Docker images: %s\n' "$BLUE" "$NC" "${images_size_bytes:-neznámé}" - # Velké images (>500 MB), které nejsou používané - printf '%s●%s Velké images (>500 MB):\n' "$BLUE" "$NC" - docker images --format '{{.Size}}\t{{.Repository}}:{{.Tag}}\t{{.ID}}' \ - | awk -F'\t' '{ - size = $1 - # převod na MB - if (size ~ /GB/) {mb = size * 1024} else if (size ~ /MB/) {mb = size + 0} else if (size ~ /kB/) {mb = size / 1024} else {mb = 0} - if (mb + 0 > 500) print $2, size - }' \ - | head -10 | while read -r line; do - printf ' %s\n' "$line" - done + # Velké images (>500 MB), které nejsou používané + printf '%s%s Velké images (>500 MB):\n' "$BLUE" "$NC" + docker images --format '{{.Size}}\t{{.Repository}}:{{.Tag}}\t{{.ID}}' \ + | awk -F'\t' '{ + size = $1 + # převod na MB + if (size ~ /GB/) {mb = size * 1024} else if (size ~ /MB/) {mb = size + 0} else if (size ~ /kB/) {mb = size / 1024} else {mb = 0} + if (mb + 0 > 500) print $2, size + }' \ + | head -10 | while read -r line; do + printf ' %s\n' "$line" + done - # Nepoužívané images (pozor, ne "používané" — bezpečnost) - unused=$(docker images --format '{{.Size}}\t{{.Repository}}:{{.Tag}}' \ - | awk -F'\t' ' - /^([0-9.]+[kMG]B)\s/ { - size = $1 - if (size ~ /GB/) mb = size * 1024 - else if (size ~ /MB/) mb = size + 0 - else if (size ~ /kB/) mb = size / 1024 - if (mb + 0 > 200) print size, $2 - }' | head -5) - if [ -n "$unused" ]; then - printf '%s●%s Kandidáti na vyčištění (>200 MB):\n' "$YELLOW" "$NC" - echo "$unused" | while read -r line; do printf ' %s\n' "$line"; done - fi + # Nepoužívané images (pozor, ne "používané" -- bezpečnost) + unused=$(docker images --format '{{.Size}}\t{{.Repository}}:{{.Tag}}' \ + | awk -F'\t' ' + /^([0-9.]+[kMG]B)\s/ { + size = $1 + if (size ~ /GB/) mb = size * 1024 + else if (size ~ /MB/) mb = size + 0 + else if (size ~ /kB/) mb = size / 1024 + if (mb + 0 > 200) print size, $2 + }' | head -5) + if [ -n "$unused" ]; then + printf '%s%s Kandidáti na vyčištění (>200 MB):\n' "$YELLOW" "$NC" + echo "$unused" | while read -r line; do printf ' %s\n' "$line"; done + fi } check_top_offenders() { - if [ ! -d "$SSD_DATA/Dockery" ]; then - return - fi - printf '\n%s●%s Top 5 největších adresářů v :\n' "$BLUE" "$NC" - /bin/du -sh "$SSD_DATA"/Dockery/*/ 2>/dev/null \ - | /usr/bin/sort -rh \ - | /usr/bin/head -5 \ - | /usr/bin/sed 's/^/ /' + if [ ! -d "$SSD_DATA/Dockery" ]; then + return + fi + printf '\n%s%s Top 5 největších adresářů v :\n' "$BLUE" "$NC" + /bin/du -sh "$SSD_DATA"/Dockery/*/ 2>/dev/null \ + | /usr/bin/sort -rh \ + | /usr/bin/head -5 \ + | /usr/bin/sed 's/^/ /' } printf '%s%s%s\n' "$BLUE" "=== DISK WATCH ===" "$NC" @@ -148,38 +148,38 @@ check_docker_disk check_top_offenders if [ "${#WARNINGS[@]}" -gt 0 ]; then - printf '\n%s●%s VAROVÁNÍ:\n' "$RED" "$NC" - for w in "${WARNINGS[@]}"; do - printf ' - %s\n' "$w" - done + printf '\n%s%s VAROVÁNÍ:\n' "$RED" "$NC" + for w in "${WARNINGS[@]}"; do + printf ' - %s\n' "$w" + done fi if [ "$ALERT" = "1" ] && [ "${#WARNINGS[@]}" -gt 0 ]; then - # Alert přes Hermes Telegram cron - MSG="⚠️ Disk Watch — překročen prah + # Alert přes Hermes Telegram cron + MSG="️ Disk Watch -- překročen prah $(printf '%s\n' "${WARNINGS[@]}") Datum: $(date '+%Y-%m-%d %H:%M:%S') Host: $(hostname)" - # Vytvoř one-shot cron pro doručení - CRON_TS=$(date -u -v+1M +%Y-%m-%dT%H:%M:%S 2>/dev/null \ - || date -u -d '+1 minute' +%Y-%m-%dT%H:%M:%S) - PAYLOAD=$(printf '%s' "$MSG" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))') + # Vytvoř one-shot cron pro doručení + CRON_TS=$(date -u -v+1M +%Y-%m-%dT%H:%M:%S 2>/dev/null \ + || date -u -d '+1 minute' +%Y-%m-%dT%H:%M:%S) + PAYLOAD=$(printf '%s' "$MSG" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))') - if command -v hermes >/dev/null 2>&1; then - hermes cron add \ - --name "disk-watch-alert-$(date +%s)" \ - --at "$CRON_TS" \ - --deliver all \ - --message "$MSG" 2>/dev/null \ - && log "Alert naplánován na $CRON_TS" \ - || err "Nepodařilo se naplánovat alert (hermes cron)" - else - # Fallback: jen vypiš, že by se měl alert poslat - printf '\n%s[ALERT by se poslal:]%s\n' "$YELLOW" "$NC" - printf '%s\n' "$MSG" - fi + if command -v hermes >/dev/null 2>&1; then + hermes cron add \ + --name "disk-watch-alert-$(date +%s)" \ + --at "$CRON_TS" \ + --deliver all \ + --message "$MSG" 2>/dev/null \ + && log "Alert naplánován na $CRON_TS" \ + || err "Nepodařilo se naplánovat alert (hermes cron)" + else + # Fallback: jen vypiš, že by se měl alert poslat + printf '\n%s[ALERT by se poslal:]%s\n' "$YELLOW" "$NC" + printf '%s\n' "$MSG" + fi fi exit 0 \ No newline at end of file diff --git a/docker-backup.sh b/docker-backup.sh index c850cb2..12699a5 100644 --- a/docker-backup.sh +++ b/docker-backup.sh @@ -1,30 +1,30 @@ #!/usr/bin/env bash # -# docker-backup.sh — bezpečná záloha Docker projektů a named volumes +# docker-backup.sh -- bezpečná záloha Docker projektů a named volumes # # - vytvoří timestamp složku v / # - pro každý projekt v zastaví kontejnery, -# zálohuje tar.zst, ověří integritu, znovu spustí +# 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 +# ./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\}//' + sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' } for arg in "$@"; do - case "$arg" in - --help|-h) usage; exit 0 ;; - esac + case "$arg" in + --help|-h) usage; exit 0 ;; + esac done DOCKERY="${DOCKERY:-}" @@ -35,9 +35,9 @@ 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' + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' else - RED=''; GREEN=''; YELLOW=''; NC='' + RED=''; GREEN=''; YELLOW=''; NC='' fi log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*"; } @@ -46,31 +46,31 @@ 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 + err "Docker není dostupný." + exit 1 fi if [ ! -d "$DOCKERY" ]; then - err "Dockery adresář neexistuje: $DOCKERY" - exit 1 + err "Dockery adresář neexistuje: $DOCKERY" + exit 1 fi if [ ! -d "$BACKUP_ROOT" ]; then - err "Backup adresář neexistuje: $BACKUP_ROOT" - exit 1 + err "Backup adresář neexistuje: $BACKUP_ROOT" + exit 1 fi mkdir -p "$DEST" # Najdi projekty if [ "$#" -gt 0 ]; then - PROJECTS=("$@") + 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 + 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[@]}" @@ -81,13 +81,13 @@ log "Uchovat posledních: $KEEP záloh" 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 + 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) === @@ -95,78 +95,78 @@ log "" log "=== BACKUP projekty (tar.zst) ===" FAILED=() for project in "${PROJECTS[@]}"; do - cd "$DOCKERY/$project" - ARCHIVE="$DEST/${project}.tar.zst" - log " [$project] tar -c /$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" \ - "/$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 + cd "$DOCKERY/$project" + ARCHIVE="$DEST/${project}.tar.zst" + log " [$project] tar -c /$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" \ + "/$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) + | 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 + 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 + 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" + [ -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 === @@ -175,29 +175,29 @@ 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") + [ -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í" + 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 + 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 " HOTOVO" log "======================================" log "Záloha: $DEST" if [ "${#FAILED[@]}" -gt 0 ]; then - err "Selhalo: ${#FAILED[@]}" - for f in "${FAILED[@]}"; do err " ✗ $f"; done - exit 1 + err "Selhalo: ${#FAILED[@]}" + for f in "${FAILED[@]}"; do err " [FAIL] $f"; done + exit 1 fi log "Vše OK." \ No newline at end of file diff --git a/docker-cleanup.sh b/docker-cleanup.sh index 70c95a4..5fa3c4e 100644 --- a/docker-cleanup.sh +++ b/docker-cleanup.sh @@ -1,27 +1,27 @@ #!/usr/bin/env bash # -# docker-cleanup.sh — bezpečné čištění Docker odpadu +# docker-cleanup.sh -- bezpečné čištění Docker odpadu # # Cíle (vše s výslovným potvrzením nebo dry-run): -# - dangling images (:) -# - starý build cache (docker builder prune -f) -# - opuštěné (orphaned) Docker sítě — nikdy bridge/host/none -# - zastaralé images s tagem ale s referencí (REPO@sha256…) -# - dočasné soubory (XDG_CACHE_HOME pro docker, /tmp/docker-*) +# - dangling images (:) +# - starý build cache (docker builder prune -f) +# - opuštěné (orphaned) Docker sítě -- nikdy bridge/host/none +# - zastaralé images s tagem ale s referencí (REPO@sha256...) +# - dočasné soubory (XDG_CACHE_HOME pro docker, /tmp/docker-*) # # Nikdy nemaže: -# - používané images -# - bind-mount ani named volume data -# - běžící kontejnery +# - používané images +# - bind-mount ani named volume data +# - běžící kontejnery # # Použití: -# ./docker-cleanup.sh # zobrazí plán, zeptá se na souhlas -# ./docker-cleanup.sh --dry-run # jen vypíše co by smazal -# ./docker-cleanup.sh --auto # bez ptaní (default akce) -# ./docker-cleanup.sh --dangling # jen dangling images -# ./docker-cleanup.sh --build-cache # jen build cache -# ./docker-cleanup.sh --networks # jen orphaned sítě -# ./docker-cleanup.sh --all # všechno výše uvedené +# ./docker-cleanup.sh # zobrazí plán, zeptá se na souhlas +# ./docker-cleanup.sh --dry-run # jen vypíše co by smazal +# ./docker-cleanup.sh --auto # bez ptaní (default akce) +# ./docker-cleanup.sh --dangling # jen dangling images +# ./docker-cleanup.sh --build-cache # jen build cache +# ./docker-cleanup.sh --networks # jen orphaned sítě +# ./docker-cleanup.sh --all # všechno výše uvedené # set -euo pipefail @@ -37,42 +37,42 @@ AUTO=0 DRY_RUN=0 usage() { - sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//' + sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//' } for arg in "$@"; do - case "$arg" in - --dry-run|-n) DRY_RUN=1 ;; - --auto|-y) AUTO=1 ;; - --dangling) DO_DANGLING=1 ;; - --build-cache) DO_BUILDER=1 ;; - --networks) DO_NETWORKS=1 ;; - --tmp) DO_TMP=1 ;; - --stopped-containers) DO_STOPPED=1 ;; - --all) DO_ALL=1 ;; - --help|-h) usage; exit 0 ;; - *) echo "Unknown arg: $arg" >&2; exit 1 ;; - esac + case "$arg" in + --dry-run|-n) DRY_RUN=1 ;; + --auto|-y) AUTO=1 ;; + --dangling) DO_DANGLING=1 ;; + --build-cache) DO_BUILDER=1 ;; + --networks) DO_NETWORKS=1 ;; + --tmp) DO_TMP=1 ;; + --stopped-containers) DO_STOPPED=1 ;; + --all) DO_ALL=1 ;; + --help|-h) usage; exit 0 ;; + *) echo "Unknown arg: $arg" >&2; exit 1 ;; + esac done if [ "$DO_ALL" = "1" ]; then - DO_DANGLING=1 - DO_BUILDER=1 - DO_NETWORKS=1 - DO_TMP=1 + DO_DANGLING=1 + DO_BUILDER=1 + DO_NETWORKS=1 + DO_TMP=1 fi # Bez přepínače: nic neprovádět if [ "$DO_DANGLING" = "0" ] && [ "$DO_BUILDER" = "0" ] && [ "$DO_NETWORKS" = "0" ] && [ "$DO_TMP" = "0" ] && [ "$DO_STOPPED" = "0" ]; then - usage - exit 0 + usage + exit 0 fi # 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' + 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='' + RED=''; GREEN=''; YELLOW=''; BLUE=''; NC='' fi log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*"; } @@ -81,179 +81,179 @@ err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%H:%M:%S')" "$NC" "$*"; } # Kontrola dockeru if ! docker info >/dev/null 2>&1; then - err "Docker není dostupný." - exit 1 + err "Docker není dostupný." + exit 1 fi PLANNED_TOTAL=0 PLANNED_ITEMS=() show_plan_header() { - printf '\n%s%s%s\n' "$BLUE" "=== PLÁN ČIŠTĚNÍ ===" "$NC" - printf 'Datum: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" - printf 'Režim: %s\n' "$([ "$DRY_RUN" = "1" ] && echo "DRY-RUN" || echo "SKUTEČNÝ MAZÁNÍ")" - printf '\n' + printf '\n%s%s%s\n' "$BLUE" "=== PLÁN ČIŠTĚNÍ ===" "$NC" + printf 'Datum: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" + printf 'Režim: %s\n' "$([ "$DRY_RUN" = "1" ] && echo "DRY-RUN" || echo "SKUTEČNÝ MAZÁNÍ")" + printf '\n' } # === DANGLING IMAGES === plan_dangling() { - if [ "$DO_DANGLING" = "0" ]; then return; fi - printf '%s● Dangling images (:):%s\n' "$BLUE" "$NC" + if [ "$DO_DANGLING" = "0" ]; then return; fi + printf '%s Dangling images (:):%s\n' "$BLUE" "$NC" - local pure_dangling=0 - local stopped_holding=0 + local pure_dangling=0 + local stopped_holding=0 - while IFS=$'\t' read -r id size; do - [ -z "$id" ] && continue - # Kolik kontejnerů (včetně zastavených) na tento image ukazuje? - local holders - holders=$(docker ps -a --filter "ancestor=$id" --format '{{.Names}}\t{{.Status}}' 2>/dev/null | head -3) - if [ -n "$holders" ]; then - stopped_holding=$((stopped_holding + 1)) - printf ' %s %s ⚠ drží zastavený kontejner: %s\n' \ - "$id" "$size" "$(echo "$holders" | head -1 | awk '{print $1}')" - else - pure_dangling=$((pure_dangling + 1)) - printf ' %s %s\n' "$id" "$size" - fi - done < <(docker images -f "dangling=true" --format '{{.ID}}\t{{.Size}}') + while IFS=$'\t' read -r id size; do + [ -z "$id" ] && continue + # Kolik kontejnerů (včetně zastavených) na tento image ukazuje? + local holders + holders=$(docker ps -a --filter "ancestor=$id" --format '{{.Names}}\t{{.Status}}' 2>/dev/null | head -3) + if [ -n "$holders" ]; then + stopped_holding=$((stopped_holding + 1)) + printf ' %s %s drží zastavený kontejner: %s\n' \ + "$id" "$size" "$(echo "$holders" | head -1 | awk '{print $1}')" + else + pure_dangling=$((pure_dangling + 1)) + printf ' %s %s\n' "$id" "$size" + fi + done < <(docker images -f "dangling=true" --format '{{.ID}}\t{{.Size}}') - if [ "$pure_dangling" -eq 0 ] && [ "$stopped_holding" -eq 0 ]; then - printf ' (žádné)\n' - else - printf ' -> %d čistě dangling, %d drží zastavené kontejnery (docker image prune -f je nesmaže)\n' \ - "$pure_dangling" "$stopped_holding" - if [ "$pure_dangling" -gt 0 ]; then - PLANNED_TOTAL=$((PLANNED_TOTAL + pure_dangling)) - PLANNED_ITEMS+=("dangling-images:$pure_dangling") - fi - if [ "$stopped_holding" -gt 0 ]; then - PLANNED_ITEMS+=("dangling-with-stopped-containers:$stopped_holding") - printf ' 💡 Pro smazání těchto images je nutné nejprve odstranit zastavené kontejnery (--stopped-containers)\n' - fi - fi + if [ "$pure_dangling" -eq 0 ] && [ "$stopped_holding" -eq 0 ]; then + printf ' (žádné)\n' + else + printf ' -> %d čistě dangling, %d drží zastavené kontejnery (docker image prune -f je nesmaže)\n' \ + "$pure_dangling" "$stopped_holding" + if [ "$pure_dangling" -gt 0 ]; then + PLANNED_TOTAL=$((PLANNED_TOTAL + pure_dangling)) + PLANNED_ITEMS+=("dangling-images:$pure_dangling") + fi + if [ "$stopped_holding" -gt 0 ]; then + PLANNED_ITEMS+=("dangling-with-stopped-containers:$stopped_holding") + printf ' Pro smazání těchto images je nutné nejprve odstranit zastavené kontejnery (--stopped-containers)\n' + fi + fi } # === BUILD CACHE === plan_build_cache() { - if [ "$DO_BUILDER" = "0" ]; then return; fi - printf '%s● Build cache:%s\n' "$BLUE" "$NC" + if [ "$DO_BUILDER" = "0" ]; then return; fi + printf '%s Build cache:%s\n' "$BLUE" "$NC" - local size - size=$(docker system df --format '{{.Type}}\t{{.Size}}' 2>/dev/null \ - | awk -F'\t' '$1 == "Build Cache" {print $2}') - if [ -z "$size" ]; then - size="0B" - fi - printf ' Build cache: %s\n' "$size" + local size + size=$(docker system df --format '{{.Type}}\t{{.Size}}' 2>/dev/null \ + | awk -F'\t' '$1 == "Build Cache" {print $2}') + if [ -z "$size" ]; then + size="0B" + fi + printf ' Build cache: %s\n' "$size" - if [ "$size" = "0B" ]; then - printf ' (žádný ke smazání)\n' - else - PLANNED_TOTAL=$((PLANNED_TOTAL + 1)) - PLANNED_ITEMS+=("build-cache:$size") - fi + if [ "$size" = "0B" ]; then + printf ' (žádný ke smazání)\n' + else + PLANNED_TOTAL=$((PLANNED_TOTAL + 1)) + PLANNED_ITEMS+=("build-cache:$size") + fi } # === ORPHANED NETWORKS === plan_networks() { - if [ "$DO_NETWORKS" = "0" ]; then return; fi - printf '%s● Opuštěné Docker sítě:%s\n' "$BLUE" "$NC" + if [ "$DO_NETWORKS" = "0" ]; then return; fi + printf '%s Opuštěné Docker sítě:%s\n' "$BLUE" "$NC" - local count=0 - # Najdi sítě, které NEpoužívá žádný kontejner - while IFS= read -r net; do - [ -z "$net" ] && continue - # Vynech výchozí systémové - case "$net" in - bridge|host|none|docker_gwbridge|docker-desktop*) continue ;; - esac - # Používá ji nějaký kontejner? - if ! docker ps --format '{{.Networks}}' 2>/dev/null \ - | grep -qw "$net"; then - printf ' %s\n' "$net" - count=$((count + 1)) - fi - done < <(docker network ls --format '{{.Name}}') + local count=0 + # Najdi sítě, které NEpoužívá žádný kontejner + while IFS= read -r net; do + [ -z "$net" ] && continue + # Vynech výchozí systémové + case "$net" in + bridge|host|none|docker_gwbridge|docker-desktop*) continue ;; + esac + # Používá ji nějaký kontejner? + if ! docker ps --format '{{.Networks}}' 2>/dev/null \ + | grep -qw "$net"; then + printf ' %s\n' "$net" + count=$((count + 1)) + fi + done < <(docker network ls --format '{{.Name}}') - if [ "$count" -eq 0 ]; then - printf ' (žádné opuštěné)\n' - else - printf ' -> %d opuštěných sítí\n' "$count" - PLANNED_TOTAL=$((PLANNED_TOTAL + count)) - PLANNED_ITEMS+=("networks:$count") - fi + if [ "$count" -eq 0 ]; then + printf ' (žádné opuštěné)\n' + else + printf ' -> %d opuštěných sítí\n' "$count" + PLANNED_TOTAL=$((PLANNED_TOTAL + count)) + PLANNED_ITEMS+=("networks:$count") + fi } # === TMP FILES === plan_tmp() { - if [ "$DO_TMP" = "0" ]; then return; fi - printf '%s● Dočasné soubory:%s\n' "$BLUE" "$NC" + if [ "$DO_TMP" = "0" ]; then return; fi + printf '%s Dočasné soubory:%s\n' "$BLUE" "$NC" - local total_bytes=0 - local paths=() + local total_bytes=0 + local paths=() - # /tmp/docker-* (logy docker-update atd., starší než 7 dní) - if [ -d /tmp ]; then - while IFS= read -r f; do - [ -f "$f" ] || continue - sz=$(stat -f '%z' "$f") - total_bytes=$((total_bytes + sz)) - paths+=("$f") - done < <(/usr/bin/find /tmp -maxdepth 1 -name 'docker-*' -type f -mtime +7 2>/dev/null) - fi + # /tmp/docker-* (logy docker-update atd., starší než 7 dní) + if [ -d /tmp ]; then + while IFS= read -r f; do + [ -f "$f" ] || continue + sz=$(stat -f '%z' "$f") + total_bytes=$((total_bytes + sz)) + paths+=("$f") + done < <(/usr/bin/find /tmp -maxdepth 1 -name 'docker-*' -type f -mtime +7 2>/dev/null) + fi - # Docker cache adresáře - [ -d "$HOME/.docker/buildx" ] && paths+=("$HOME/.docker/buildx") - [ -d "$HOME/.cache/docker" ] && paths+=("$HOME/.cache/docker") + # Docker cache adresáře + [ -d "$HOME/.docker/buildx" ] && paths+=("$HOME/.docker/buildx") + [ -d "$HOME/.cache/docker" ] && paths+=("$HOME/.cache/docker") - if [ "${#paths[@]}" -eq 0 ]; then - printf ' (žádné)\n' - else - for p in "${paths[@]}"; do - printf ' %s\n' "$p" - done - printf ' -> %d položek, cca %d B\n' "${#paths[@]}" "$total_bytes" - PLANNED_TOTAL=$((PLANNED_TOTAL + ${#paths[@]})) - PLANNED_ITEMS+=("tmp-files:${#paths[@]}") - fi + if [ "${#paths[@]}" -eq 0 ]; then + printf ' (žádné)\n' + else + for p in "${paths[@]}"; do + printf ' %s\n' "$p" + done + printf ' -> %d položek, cca %d B\n' "${#paths[@]}" "$total_bytes" + PLANNED_TOTAL=$((PLANNED_TOTAL + ${#paths[@]})) + PLANNED_ITEMS+=("tmp-files:${#paths[@]}") + fi } confirm() { - if [ "$AUTO" = "1" ]; then - return 0 - fi - if [ "$DRY_RUN" = "1" ]; then - return 0 - fi - printf '\n%sPokračovat? [y/N]%s ' "$YELLOW" "$NC" - read -r ans - case "$ans" in - y|Y|yes|YES) return 0 ;; - *) printf 'Zrušeno.\n'; exit 1 ;; - esac + if [ "$AUTO" = "1" ]; then + return 0 + fi + if [ "$DRY_RUN" = "1" ]; then + return 0 + fi + printf '\n%sPokračovat? [y/N]%s ' "$YELLOW" "$NC" + read -r ans + case "$ans" in + y|Y|yes|YES) return 0 ;; + *) printf 'Zrušeno.\n'; exit 1 ;; + esac } # === STOPPED CONTAINERS === plan_stopped() { - if [ "$DO_STOPPED" = "0" ]; then return; fi - printf '%s● Zastavené kontejnery:%s\n' "$BLUE" "$NC" + if [ "$DO_STOPPED" = "0" ]; then return; fi + printf '%s Zastavené kontejnery:%s\n' "$BLUE" "$NC" - local count=0 - while IFS= read -r line; do - [ -z "$line" ] && continue - # Formát: NAME STATUS - printf ' %s\n' "$line" - count=$((count + 1)) - done < <(docker ps -a --filter "status=exited" --format '{{.Names}}\t{{.Status}}' 2>/dev/null) + local count=0 + while IFS= read -r line; do + [ -z "$line" ] && continue + # Formát: NAME STATUS + printf ' %s\n' "$line" + count=$((count + 1)) + done < <(docker ps -a --filter "status=exited" --format '{{.Names}}\t{{.Status}}' 2>/dev/null) - if [ "$count" -eq 0 ]; then - printf ' (žádné)\n' - else - printf ' -> %d zastavených kontejnerů\n' "$count" - PLANNED_TOTAL=$((PLANNED_TOTAL + count)) - PLANNED_ITEMS+=("stopped-containers:$count") - fi + if [ "$count" -eq 0 ]; then + printf ' (žádné)\n' + else + printf ' -> %d zastavených kontejnerů\n' "$count" + PLANNED_TOTAL=$((PLANNED_TOTAL + count)) + PLANNED_ITEMS+=("stopped-containers:$count") + fi } # Spustit plánování @@ -266,14 +266,14 @@ plan_tmp printf '\n%sCelkem plánovaných akcí: %d%s\n' "$BLUE" "$PLANNED_TOTAL" "$NC" if [ "$PLANNED_TOTAL" -eq 0 ]; then - log "Není co čistit." - exit 0 + log "Není co čistit." + exit 0 fi # Bez potvrzení v dry-run režimu if [ "$DRY_RUN" = "1" ]; then - printf '\n%sDRY-RUN: žádná akce neprovedena%s\n' "$YELLOW" "$NC" - exit 0 + printf '\n%sDRY-RUN: žádná akce neprovedena%s\n' "$YELLOW" "$NC" + exit 0 fi confirm @@ -283,39 +283,39 @@ log "" log "=== PROVÁDÍM ČIŠTĚNÍ ===" if [ "$DO_DANGLING" = "1" ]; then - log "docker image prune -f (dangling only)" - docker image prune -f 2>&1 || warn "dangling prune selhal" + log "docker image prune -f (dangling only)" + docker image prune -f 2>&1 || warn "dangling prune selhal" fi if [ "$DO_BUILDER" = "1" ]; then - log "docker builder prune -f" - docker builder prune -f 2>&1 || warn "builder prune selhal" + log "docker builder prune -f" + docker builder prune -f 2>&1 || warn "builder prune selhal" fi if [ "$DO_NETWORKS" = "1" ]; then - log "docker network prune -f (filtrováno na opuštěné)" - # docker network prune umí vyfiltrovat, ale my jsme již identifikovali konkrétní - while IFS= read -r net; do - case "$net" in bridge|host|none|docker_gwbridge|docker-desktop*) continue ;; esac - if ! docker ps --format '{{.Networks}}' 2>/dev/null | grep -qw "$net"; then - log " rm network: $net" - docker network rm "$net" 2>&1 || warn " rm $net selhal" - fi - done < <(docker network ls --format '{{.Name}}') + log "docker network prune -f (filtrováno na opuštěné)" + # docker network prune umí vyfiltrovat, ale my jsme již identifikovali konkrétní + while IFS= read -r net; do + case "$net" in bridge|host|none|docker_gwbridge|docker-desktop*) continue ;; esac + if ! docker ps --format '{{.Networks}}' 2>/dev/null | grep -qw "$net"; then + log " rm network: $net" + docker network rm "$net" 2>&1 || warn " rm $net selhal" + fi + done < <(docker network ls --format '{{.Name}}') fi if [ "$DO_TMP" = "1" ]; then - log "mazání starých /tmp/docker-* (≥7 dní)" - # -print -delete musí být v tomto pořadí; -delete sám o sobě na macOS BSD find někdy selže - /usr/bin/find /tmp -maxdepth 1 -name 'docker-*' -type f -mtime +7 -print 2>&1 | while read -r f; do - log " rm: $f" - /bin/rm -f "$f" 2>&1 || warn " rm $f selhal" - done + log "mazání starých /tmp/docker-* (7 dní)" + # -print -delete musí být v tomto pořadí; -delete sám o sobě na macOS BSD find někdy selže + /usr/bin/find /tmp -maxdepth 1 -name 'docker-*' -type f -mtime +7 -print 2>&1 | while read -r f; do + log " rm: $f" + /bin/rm -f "$f" 2>&1 || warn " rm $f selhal" + done fi if [ "$DO_STOPPED" = "1" ]; then - log "docker container prune -f (zastavené kontejnery)" - docker container prune -f 2>&1 || warn "container prune selhal" + log "docker container prune -f (zastavené kontejnery)" + docker container prune -f 2>&1 || warn "container prune selhal" fi log "" diff --git a/docker-restore.sh b/docker-restore.sh index 49c524f..49b7f61 100644 --- a/docker-restore.sh +++ b/docker-restore.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# docker-restore.sh — bezpečná obnova Docker projektů z tar.zst zálohy +# 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) @@ -11,12 +11,12 @@ # - 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 +# ./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 @@ -30,30 +30,30 @@ TS_ARG="" PROJECTS_RAW=() usage() { - sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//' + 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 + 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' + 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='' + RED=''; GREEN=''; YELLOW=''; BLUE=''; NC='' fi log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*"; } @@ -61,147 +61,147 @@ 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 + 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 + err "Docker není dostupný." + exit 1 fi if [ ! -d "$DOCKERY" ]; then - err "Dockery adresář neexistuje: $DOCKERY" - exit 1 + err "Dockery adresář neexistuje: $DOCKERY" + exit 1 fi if [ ! -d "$BACKUP_ROOT" ]; then - err "Backup adresář neexistuje: $BACKUP_ROOT" - exit 1 + 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 + 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 + 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 + 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 + 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 +/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: .tar.zst v Dockery/ - # Volume: .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 + name=$(basename "$archive" .tar.zst) + # Rozlišení: project archive vs volume archive + # Project: .tar.zst v Dockery/ + # Volume: .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 +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 + 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 + 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 + 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[@]}") + SELECTED=("${PROJECTS_RAW[@]}") fi if [ "${#SELECTED[@]}" -eq 0 ]; then - err "Nebyly vybrány žádné projekty" - exit 1 + 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 '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]" @@ -210,34 +210,34 @@ confirm "Pokračovat v obnově? (PŘEPSOU se současná data projektů!) [y/N]" 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 + 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 + 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 === @@ -245,50 +245,50 @@ 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 + 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//... - # Extrahujeme s --strip-components=3, abychom získali přímo /... - 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 + log " [$project] extrahuji do $DOCKERY/$project/" + # Archiv má cesty jako Volumes/SSD-data/Dockery//... + # Extrahujeme s --strip-components=3, abychom získali přímo /... + 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 + 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" + 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 === @@ -298,45 +298,45 @@ 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 + 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 " SHRNUTÍ OBNOVY" log "======================================" log "Obnoveno:" -for p in "${SELECTED[@]}"; do log " ✓ $p"; done +for p in "${SELECTED[@]}"; do log " [OK] $p"; done if [ "${#FAILED[@]}" -gt 0 ]; then - err "Selhalo:" - for f in "${FAILED[@]}"; do err " ✗ $f"; done + 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 " rm -rf $DOCKERY/_pre-restore-$TS_NOW" log "" if [ "$all_healthy" = "1" ]; then - log "Všechny kontejnery běží." + log "Všechny kontejnery běží." else - warn "Některé kontejnery nejsou v pořádku — zkontrolujte logy." + warn "Některé kontejnery nejsou v pořádku -- zkontrolujte logy." fi \ No newline at end of file diff --git a/docker-update.sh b/docker-update.sh index 5b01f2b..5a51d34 100644 --- a/docker-update.sh +++ b/docker-update.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# docker-update.sh — bezpečná aktualizace Docker Compose projektů +# docker-update.sh -- bezpečná aktualizace Docker Compose projektů # # - zastaví všechny kontejnery, # - stáhne nové image (docker compose pull), @@ -8,36 +8,36 @@ # - spustí kontejnery z nových image, # - zkontroluje health checks, # - ponechá staré image pro případný rollback (docker image prune -f na konci, -# ale jen dangling, ne všechno). +# ale jen dangling, ne všechno). # # Použití: -# ./docker-update.sh # aktualizuje všechny projekty v -# ./docker-update.sh nginx mattermost # aktualizuje vybrané projekty -# ./docker-update.sh --help # nápověda +# ./docker-update.sh # aktualizuje všechny projekty v +# ./docker-update.sh nginx mattermost # aktualizuje vybrané projekty +# ./docker-update.sh --help # nápověda # set -euo pipefail usage() { - sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' + sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' } for arg in "$@"; do - case "$arg" in - --help|-h) usage; exit 0 ;; - esac + case "$arg" in + --help|-h) usage; exit 0 ;; + esac done DOCKERY="${DOCKERY:-}" LOG="${LOG:-/tmp/docker-update-$(date +%Y-%m-%d_%H-%M).log}" -PRUNE_DANGLING="${PRUNE_DANGLING:-1}" # 1 = docker image prune -f (dangling only) -HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-60}" # sekund na health check po startu +PRUNE_DANGLING="${PRUNE_DANGLING:-1}" # 1 = docker image prune -f (dangling only) +HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-60}" # sekund na health check po startu # Barevné výstupy (pokud terminál umí) if [ -t 1 ]; then - RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' else - RED=''; GREEN=''; YELLOW=''; NC='' + RED=''; GREEN=''; YELLOW=''; NC='' fi log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*" | tee -a "$LOG"; } @@ -46,26 +46,26 @@ err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%H:%M:%S')" "$NC" "$*" | tee -a # Kontrola, že docker běží if ! docker info >/dev/null 2>&1; then - err "Docker není dostupný. Spusť Docker Desktop a zkus znovu." - exit 1 + err "Docker není dostupný. Spusť Docker Desktop a zkus znovu." + exit 1 fi # Najdi projekty if [ "$#" -gt 0 ]; then - PROJECTS=("$@") + 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 + 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 if [ "${#PROJECTS[@]}" -eq 0 ]; then - err "Nebyly nalezeny žádné Docker Compose projekty v $DOCKERY" - exit 1 + err "Nebyly nalezeny žádné Docker Compose projekty v $DOCKERY" + exit 1 fi log "Nalezeno ${#PROJECTS[@]} projektů: ${PROJECTS[*]}" @@ -74,108 +74,108 @@ log "Nalezeno ${#PROJECTS[@]} projektů: ${PROJECTS[*]}" log "=== PRE-FLIGHT ===" BEFORE_RUNNING=$(docker ps --format '{{.Names}}') log "Běžící kontejnery před aktualizací:" -printf ' %s\n' $BEFORE_RUNNING +printf ' %s\n' $BEFORE_RUNNING UPDATED=() SKIPPED=() FAILED=() for project in "${PROJECTS[@]}"; do - cd "$DOCKERY/$project" - if ! compgen -G "docker-compose.y*ml" > /dev/null; then - warn "[$project] nemá docker-compose soubor, přeskakuji" - SKIPPED+=("$project") - continue - fi + cd "$DOCKERY/$project" + if ! compgen -G "docker-compose.y*ml" > /dev/null; then + warn "[$project] nemá docker-compose soubor, přeskakuji" + SKIPPED+=("$project") + continue + fi - log "" - log "=== [$project] ===" + log "" + log "=== [$project] ===" - # Zaznamenej aktuální image ID - BEFORE_DIGESTS=$(docker compose ps -q 2>/dev/null \ - | xargs -I{} docker inspect --format='{{.Image}}' {} 2>/dev/null \ - | sort -u) + # Zaznamenej aktuální image ID + BEFORE_DIGESTS=$(docker compose ps -q 2>/dev/null \ + | xargs -I{} docker inspect --format='{{.Image}}' {} 2>/dev/null \ + | sort -u) - # Pull - log "[$project] docker compose pull" - if ! docker compose pull 2>&1 | tee -a "$LOG"; then - err "[$project] pull selhal" - FAILED+=("$project") - continue - fi + # Pull + log "[$project] docker compose pull" + if ! docker compose pull 2>&1 | tee -a "$LOG"; then + err "[$project] pull selhal" + FAILED+=("$project") + continue + fi - # Ověř, že se image změnil - AFTER_DIGESTS=$(docker compose config --images 2>/dev/null \ - | xargs -I{} docker image inspect --format='{{index .RepoDigests 0}}' "{}" 2>/dev/null \ - | sort -u) + # Ověř, že se image změnil + AFTER_DIGESTS=$(docker compose config --images 2>/dev/null \ + | xargs -I{} docker image inspect --format='{{index .RepoDigests 0}}' "{}" 2>/dev/null \ + | sort -u) - CHANGED=0 - for d in $AFTER_DIGESTS; do - if ! grep -qx "$d" <<<"$BEFORE_DIGESTS"; then - CHANGED=1 - break - fi - done + CHANGED=0 + for d in $AFTER_DIGESTS; do + if ! grep -qx "$d" <<<"$BEFORE_DIGESTS"; then + CHANGED=1 + break + fi + done - if [ "$CHANGED" -eq 0 ]; then - log "[$project] žádná změna, přeskakuji up -d" - SKIPPED+=("$project") - continue - fi + if [ "$CHANGED" -eq 0 ]; then + log "[$project] žádná změna, přeskakuji up -d" + SKIPPED+=("$project") + continue + fi - # Up -d (vždy background kvůli shell guardu) - log "[$project] docker compose up -d" - if ! docker compose up -d 2>&1 | tee -a "$LOG"; then - err "[$project] up -d selhal" - FAILED+=("$project") - continue - fi + # Up -d (vždy background kvůli shell guardu) + log "[$project] docker compose up -d" + if ! docker compose up -d 2>&1 | tee -a "$LOG"; then + err "[$project] up -d selhal" + FAILED+=("$project") + continue + fi - # Počkej na health check - log "[$project] čekám na health check (max ${HEALTH_TIMEOUT}s)" - elapsed=0 - healthy=0 - while [ "$elapsed" -lt "$HEALTH_TIMEOUT" ]; do - sleep 5 - elapsed=$((elapsed + 5)) - unhealthy=$(docker compose ps 2>/dev/null \ - | tail -n +2 \ - | awk '{print $NF}' \ - | grep -vE 'healthy|Up' || true) - if [ -z "$unhealthy" ]; then - healthy=1 - break - fi - done + # Počkej na health check + log "[$project] čekám na health check (max ${HEALTH_TIMEOUT}s)" + elapsed=0 + healthy=0 + while [ "$elapsed" -lt "$HEALTH_TIMEOUT" ]; do + sleep 5 + elapsed=$((elapsed + 5)) + unhealthy=$(docker compose ps 2>/dev/null \ + | tail -n +2 \ + | awk '{print $NF}' \ + | grep -vE 'healthy|Up' || true) + if [ -z "$unhealthy" ]; then + healthy=1 + break + fi + done - if [ "$healthy" -eq 1 ]; then - log "[$project] OK" - UPDATED+=("$project") - else - warn "[$project] běží, ale ne všechny health checks prošly (zkontroluj ručně)" - UPDATED+=("$project:health_unknown") - fi + if [ "$healthy" -eq 1 ]; then + log "[$project] OK" + UPDATED+=("$project") + else + warn "[$project] běží, ale ne všechny health checks prošly (zkontroluj ručně)" + UPDATED+=("$project:health_unknown") + fi done # Prune dangling images (bezpečné) if [ "$PRUNE_DANGLING" = "1" ]; then - log "" - log "=== DOCKER IMAGE PRUNE -f (dangling only) ===" - docker image prune -f 2>&1 | tee -a "$LOG" || true + log "" + log "=== DOCKER IMAGE PRUNE -f (dangling only) ===" + docker image prune -f 2>&1 | tee -a "$LOG" || true fi # Report log "" log "======================================" -log " SHRNUTÍ" +log " SHRNUTÍ" log "======================================" log "Aktualizováno: ${#UPDATED[@]} projekt(ů)" -for p in "${UPDATED[@]:-}"; do [ -n "$p" ] && log " ✓ $p"; done +for p in "${UPDATED[@]:-}"; do [ -n "$p" ] && log " [OK] $p"; done log "Přeskočeno (beze změny): ${#SKIPPED[@]}" -for p in "${SKIPPED[@]:-}"; do [ -n "$p" ] && log " - $p"; done +for p in "${SKIPPED[@]:-}"; do [ -n "$p" ] && log " - $p"; done if [ "${#FAILED[@]}" -gt 0 ]; then - log "Selhalo: ${#FAILED[@]}" - for p in "${FAILED[@]}"; do err " ✗ $p"; done + log "Selhalo: ${#FAILED[@]}" + for p in "${FAILED[@]}"; do err " [FAIL] $p"; done fi log "" log "Log: $LOG" \ No newline at end of file diff --git a/update-macos.sh b/update-macos.sh index 15ca625..9403e31 100644 --- a/update-macos.sh +++ b/update-macos.sh @@ -10,35 +10,35 @@ echo "" # Kontrola Homebrew if ! command -v brew >/dev/null 2>&1; then - echo "Homebrew není nainstalovaný." - exit 1 + echo "Homebrew není nainstalovaný." + exit 1 fi -echo "→ Aktualizace Homebrew metadata..." +echo "-> Aktualizace Homebrew metadata..." brew update echo "" -echo "→ Upgrade formulae..." +echo "-> Upgrade formulae..." brew upgrade echo "" -echo "→ Upgrade casks..." +echo "-> Upgrade casks..." brew upgrade --cask echo "" -echo "→ Cleanup..." +echo "-> Cleanup..." brew cleanup echo "" -echo "→ Autoremove..." +echo "-> Autoremove..." brew autoremove echo "" -echo "→ Health check..." +echo "-> Health check..." brew doctor echo "" -echo "→ Outdated packages:" +echo "-> Outdated packages:" brew outdated || true echo ""