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).
This commit is contained in:
2026-08-02 08:50:11 +02:00
parent 09d5ccb4b1
commit 10d214c2a2
7 changed files with 771 additions and 771 deletions
+64 -64
View File
@@ -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/<ssd-data-mount>/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/<ssd-data-mount>/Dockery" # macOS
export DOCKERY="/Volumes/<ssd-data-mount>/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 `<nas-ip>`
- `SMB_SHARE` default `Multimedia`
- `SMB_USER` default `mates`
- `TARGET` default `/Volumes/<ssd-data-mount>/multimedia`
- `SMB_SERVER` - default `<nas-ip>`
- `SMB_SHARE` - default `Multimedia`
- `SMB_USER` - default `mates`
- `TARGET` - default `/Volumes/<ssd-data-mount>/multimedia`
- Heslo je v **macOS keychainu** pod `SMB:<server>/<share>`
### `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
+118 -118
View File
@@ -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:
# - <ssd-data-mount> (Docker data)
# - <backup-disk-mount> (zálohy)
# - Docker disk usage (images, containers, volumes, build cache)
# - <ssd-data-mount> (Docker data)
# - <backup-disk-mount> (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-data-mount>}"
SSD_BACKUP="${SSD_BACKUP:-<backup-disk-mount>}"
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 <docker-projects-root>:\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 <docker-projects-root>:\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
+97 -97
View File
@@ -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 <backup-root>/
# - pro každý projekt v <docker-projects-root> 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:-<docker-projects-root>}"
@@ -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 <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
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)
| 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."
+193 -193
View File
@@ -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 (<none>:<none>)
# - starý build cache (docker builder prune -f)
# - opuštěné (orphaned) Docker sítě nikdy bridge/host/none
# - zastaralé images s tagem <none> ale s referencí (REPO@sha256)
# - dočasné soubory (XDG_CACHE_HOME pro docker, /tmp/docker-*)
# - dangling images (<none>:<none>)
# - starý build cache (docker builder prune -f)
# - opuštěné (orphaned) Docker sítě -- nikdy bridge/host/none
# - zastaralé images s tagem <none> 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 (<none>:<none>):%s\n' "$BLUE" "$NC"
if [ "$DO_DANGLING" = "0" ]; then return; fi
printf '%s Dangling images (<none>:<none>):%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 ""
+191 -191
View File
@@ -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: <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
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
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/<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
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
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
+99 -99
View File
@@ -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-projects-root>
# ./docker-update.sh nginx mattermost # aktualizuje vybrané projekty
# ./docker-update.sh --help # nápověda
# ./docker-update.sh # aktualizuje všechny projekty v <docker-projects-root>
# ./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:-<docker-projects-root>}"
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"
+9 -9
View File
@@ -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 ""