init: clean history with no PII
Toto repo ma novou cistou historii (1 commit). Predchozi historie byla smazana, protoze obsahovala osobni udaje (IP, emaily, hesla, klice, tokeny, Volume cesty). Soubory v aktualnim commit uz tyto udaje neobsahuji (nahradeno placeholdery <server-ip>, CHANGE_ME, admin@example.com atd.). Pokud chces zpet kompletni seznam starych commitu, mas je ve svem lokalnim working tree - git log --all ukaze prazdno (protoze jsme smazali .git/ a zacali znovu).
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
||||
# Secrets - nikdy v gitu
|
||||
.env
|
||||
*.env
|
||||
!.env.example
|
||||
|
||||
# Zálohy a dočasné soubory
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
|
||||
# Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
*.swp
|
||||
|
||||
# Build artefakty
|
||||
*.log
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 mates
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,26 @@
|
||||
# mates-install-scripts
|
||||
|
||||
Bash skripty pro správu Dockeru.
|
||||
Jedná se o **přesné kopie** skriptů z `~/Skripty/docker-domov/`.
|
||||
|
||||
## Skripty
|
||||
|
||||
| Skript | Alias | Účel |
|
||||
|---|---|---|
|
||||
| `docker-update.sh` | `dk-update` | Aktualizace Docker Compose projektů |
|
||||
| `docker-backup.sh` | `dk-backup` | Záloha projektů a named volumes |
|
||||
| `docker-cleanup.sh` | `dk-cleanup` | Čištění odpadu (s potvrzením) |
|
||||
| `docker-restore.sh` | `dk-restore` | Obnova ze zálohy |
|
||||
| `disk-watch.sh` | `dk-watch` | Sledování místa |
|
||||
| `mount-nas.sh` | – | Připojení NAS SMB share |
|
||||
| `update-macos.sh` | – | Aktualizace macOS |
|
||||
|
||||
Všechny skripty jsou **idempotentní**, mají `--help`, podporují **dry-run** a bez potvrzení nic destruktivního neprovádějí.
|
||||
|
||||
## Detailní návod
|
||||
|
||||
Viz [README.md](README.md) – kompletní dokumentace aliasů a workflow.
|
||||
|
||||
## Licence
|
||||
|
||||
MIT – viz [LICENSE](LICENSE)
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# 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)
|
||||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
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}" # %
|
||||
|
||||
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
|
||||
done
|
||||
|
||||
# Barevné výstupy
|
||||
if [ -t 1 ]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; BLUE=''; NC=''
|
||||
fi
|
||||
|
||||
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}')
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
check_docker_disk() {
|
||||
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)
|
||||
|
||||
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}')
|
||||
|
||||
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
|
||||
|
||||
# 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/^/ /'
|
||||
}
|
||||
|
||||
printf '%s%s%s\n' "$BLUE" "=== DISK WATCH ===" "$NC"
|
||||
printf 'Prahy: SSD-data>%s%%, disk2-data>%s%%\n' "$SSD_DATA_THRESHOLD" "$SSD_BACKUP_THRESHOLD"
|
||||
printf 'Datum: %s\n\n' "$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
check_disk "$SSD_DATA" "$SSD_DATA_THRESHOLD" "SSD-data (Docker)"
|
||||
check_disk "$SSD_BACKUP" "$SSD_BACKUP_THRESHOLD" "disk2-data (zálohy)"
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
if [ "$ALERT" = "1" ] && [ "${#WARNINGS[@]}" -gt 0 ]; then
|
||||
# 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()))')
|
||||
|
||||
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
|
||||
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# docker-backup.sh — bezpečná záloha Docker projektů a named volumes
|
||||
#
|
||||
# - vytvoří timestamp složku v <backup-root>/
|
||||
# - pro každý projekt v <docker-projects-root> zastaví kontejnery,
|
||||
# zálohuje tar.zst, ověří integritu, znovu spustí
|
||||
# - zvlášť zálohuje Docker named volumes
|
||||
# - ponechá posledních N záloh (default 3)
|
||||
#
|
||||
# Použití:
|
||||
# ./docker-backup.sh # všechny projekty + named volumes
|
||||
# ./docker-backup.sh nginx mattermost # vybrané projekty (volumes vždy)
|
||||
# BACKUP_KEEP=7 ./docker-backup.sh # ponechá 7 záloh
|
||||
# ./docker-backup.sh --help # nápověda
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h) usage; exit 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
DOCKERY="${DOCKERY:-<docker-projects-root>}"
|
||||
BACKUP_ROOT="${BACKUP_ROOT:-<backup-root>}"
|
||||
KEEP="${BACKUP_KEEP:-3}"
|
||||
TS="$(date +%Y-%m-%d_%H-%M-%S)"
|
||||
DEST="${BACKUP_ROOT}/${TS}"
|
||||
|
||||
# Barevné výstupy
|
||||
if [ -t 1 ]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; NC=''
|
||||
fi
|
||||
|
||||
log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
warn() { printf '%s[%s]%s %s\n' "$YELLOW" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
|
||||
# Kontrola
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
err "Docker není dostupný."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$DOCKERY" ]; then
|
||||
err "Dockery adresář neexistuje: $DOCKERY"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$BACKUP_ROOT" ]; then
|
||||
err "Backup adresář neexistuje: $BACKUP_ROOT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$DEST"
|
||||
|
||||
# Najdi projekty
|
||||
if [ "$#" -gt 0 ]; then
|
||||
PROJECTS=("$@")
|
||||
else
|
||||
PROJECTS=()
|
||||
for d in "$DOCKERY"/*/; do
|
||||
[ -d "$d" ] || continue
|
||||
if compgen -G "$d/docker-compose.y*ml" > /dev/null; then
|
||||
PROJECTS+=("$(basename "$d")")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
log "Projektů k záloze: ${#PROJECTS[@]}"
|
||||
log "Cíl: $DEST"
|
||||
log "Uchovat posledních: $KEEP záloh"
|
||||
|
||||
# === FÁZE 1: zastavit všechny dotčené kontejnery najednou ===
|
||||
log ""
|
||||
log "=== STOP: zastavuji všechny kontejnery v ${PROJECTS[*]} ==="
|
||||
for project in "${PROJECTS[@]}"; do
|
||||
cd "$DOCKERY/$project"
|
||||
if [ -f docker-compose.yml ] || [ -f docker-compose.yaml ]; then
|
||||
if docker compose ps -q 2>/dev/null | grep -q .; then
|
||||
log " [$project] docker compose stop"
|
||||
docker compose stop 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# === FÁZE 2: záloha projektů (paralelně pro velké projekty) ===
|
||||
log ""
|
||||
log "=== BACKUP projekty (tar.zst) ==="
|
||||
FAILED=()
|
||||
for project in "${PROJECTS[@]}"; do
|
||||
cd "$DOCKERY/$project"
|
||||
ARCHIVE="$DEST/${project}.tar.zst"
|
||||
log " [$project] tar -c <docker-projects-root>/$project/ → $ARCHIVE"
|
||||
# absolutní cesta (background tar pitfall: vždy absolutní)
|
||||
if /usr/bin/tar --exclude='._*' --exclude='.DS_Store' \
|
||||
--use-compress-program='/opt/homebrew/bin/zstd -1 -T0' \
|
||||
-cf "$ARCHIVE" \
|
||||
"<docker-projects-root>/$project/" 2>>"$DEST/.errors"; then
|
||||
# Ověření
|
||||
if /opt/homebrew/bin/zstd -t "$ARCHIVE" >/dev/null 2>&1; then
|
||||
SIZE=$(stat -f '%z' "$ARCHIVE")
|
||||
log " OK ($(numfmt --to=iec --suffix=B "$SIZE" 2>/dev/null || echo "$SIZE B"))"
|
||||
else
|
||||
err " zstd -t selhal pro $ARCHIVE"
|
||||
FAILED+=("$project")
|
||||
fi
|
||||
else
|
||||
err " tar selhal pro $project"
|
||||
FAILED+=("$project")
|
||||
fi
|
||||
done
|
||||
|
||||
# === FÁZE 3: named volumes (per-volume záloha přes Docker) ===
|
||||
log ""
|
||||
log "=== BACKUP named volumes ==="
|
||||
VOLUMES=$(docker volume ls --format '{{.Name}}' \
|
||||
| grep -vE '^(host-services|docker-desktop)$' || true)
|
||||
|
||||
for vol in $VOLUMES; do
|
||||
ARCHIVE="$DEST/${vol}.tar.zst"
|
||||
log " [volume:$vol] → $ARCHIVE"
|
||||
# Nejdřív dekomprimuj na hostiteli, pak pošli do kontejneru (alpine tar neumí zstd)
|
||||
if docker run --rm \
|
||||
-v "${vol}:/src:ro" \
|
||||
-v "$DEST:/dest" \
|
||||
alpine:3.20 \
|
||||
sh -c "cd /src && tar --exclude='._*' --exclude='.DS_Store' -cf - ." \
|
||||
> "$DEST/${vol}.tar" 2>>"$DEST/.errors"; then
|
||||
/opt/homebrew/bin/zstd -1 -T0 -f "$DEST/${vol}.tar" -o "$ARCHIVE" 2>>"$DEST/.errors"
|
||||
rm -f "$DEST/${vol}.tar"
|
||||
if /opt/homebrew/bin/zstd -t "$ARCHIVE" >/dev/null 2>&1; then
|
||||
SIZE=$(stat -f '%z' "$ARCHIVE")
|
||||
log " OK ($(numfmt --to=iec --suffix=B "$SIZE" 2>/dev/null || echo "$SIZE B"))"
|
||||
else
|
||||
err " zstd -t selhal pro $ARCHIVE"
|
||||
FAILED+=("volume:$vol")
|
||||
fi
|
||||
else
|
||||
err " docker run selhal pro volume $vol"
|
||||
FAILED+=("volume:$vol")
|
||||
fi
|
||||
done
|
||||
|
||||
# === FÁZE 4: znovu spustit projekty ===
|
||||
log ""
|
||||
log "=== UP: spouštím projekty ==="
|
||||
for project in "${PROJECTS[@]}"; do
|
||||
cd "$DOCKERY/$project"
|
||||
if [ -f docker-compose.yml ] || [ -f docker-compose.yaml ]; then
|
||||
log " [$project] docker compose up -d"
|
||||
docker compose up -d 2>&1 || warn " [$project] up -d selhal (zkontroluj)"
|
||||
fi
|
||||
done
|
||||
|
||||
# === FÁZE 5: hash report ===
|
||||
log ""
|
||||
log "=== SHA-256 hashe ==="
|
||||
for f in "$DEST"/*.tar.zst; do
|
||||
[ -f "$f" ] || continue
|
||||
H=$(shasum -a 256 "$f" | awk '{print $1}')
|
||||
S=$(stat -f '%z' "$f")
|
||||
log " $(basename "$f") sha256=$H size=$S"
|
||||
done
|
||||
|
||||
# === FÁZE 6: retention ===
|
||||
log ""
|
||||
log "=== RETENTION: ponechávám posledních $KEEP záloh ==="
|
||||
# set -u a prázdné pole
|
||||
existing=()
|
||||
while IFS= read -r d; do
|
||||
[ -d "$d" ] && existing+=("$d")
|
||||
done < <(find "$BACKUP_ROOT" -maxdepth 1 -mindepth 1 -type d | sort)
|
||||
|
||||
# Pokud něco selhalo, nepromazávej
|
||||
if [ "${#FAILED[@]}" -gt 0 ]; then
|
||||
warn "Některé zálohy selhaly (${#FAILED[@]}), RETENTION se neprovádí"
|
||||
else
|
||||
if [ "${#existing[@]}" -gt "$KEEP" ]; then
|
||||
for old in "${existing[@]:0:${#existing[@]}-KEEP}"; do
|
||||
log " mažu starou zálohu: $(basename "$old")"
|
||||
rm -rf "$old"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
log ""
|
||||
log "======================================"
|
||||
log " HOTOVO"
|
||||
log "======================================"
|
||||
log "Záloha: $DEST"
|
||||
if [ "${#FAILED[@]}" -gt 0 ]; then
|
||||
err "Selhalo: ${#FAILED[@]}"
|
||||
for f in "${FAILED[@]}"; do err " ✗ $f"; done
|
||||
exit 1
|
||||
fi
|
||||
log "Vše OK."
|
||||
@@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# 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-*)
|
||||
#
|
||||
# Nikdy nemaže:
|
||||
# - 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é
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Default akce: žádná, vyžaduje explicitní přepínač
|
||||
DO_DANGLING=0
|
||||
DO_BUILDER=0
|
||||
DO_NETWORKS=0
|
||||
DO_TMP=0
|
||||
DO_ALL=0
|
||||
DO_STOPPED=0
|
||||
AUTO=0
|
||||
DRY_RUN=0
|
||||
|
||||
usage() {
|
||||
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
|
||||
done
|
||||
|
||||
if [ "$DO_ALL" = "1" ]; then
|
||||
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
|
||||
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'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; BLUE=''; NC=''
|
||||
fi
|
||||
|
||||
log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
warn() { printf '%s[%s]%s %s\n' "$YELLOW" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
|
||||
# Kontrola dockeru
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
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'
|
||||
}
|
||||
|
||||
# === DANGLING IMAGES ===
|
||||
plan_dangling() {
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
# === BUILD CACHE ===
|
||||
plan_build_cache() {
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
# === TMP FILES ===
|
||||
plan_tmp() {
|
||||
if [ "$DO_TMP" = "0" ]; then return; fi
|
||||
printf '%s● Dočasné soubory:%s\n' "$BLUE" "$NC"
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
# === STOPPED CONTAINERS ===
|
||||
plan_stopped() {
|
||||
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)
|
||||
|
||||
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í
|
||||
show_plan_header
|
||||
plan_dangling
|
||||
plan_build_cache
|
||||
plan_networks
|
||||
plan_stopped
|
||||
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
|
||||
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
|
||||
fi
|
||||
|
||||
confirm
|
||||
|
||||
# === EXECUTE ===
|
||||
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"
|
||||
fi
|
||||
|
||||
if [ "$DO_BUILDER" = "1" ]; then
|
||||
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}}')
|
||||
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
|
||||
fi
|
||||
|
||||
if [ "$DO_STOPPED" = "1" ]; then
|
||||
log "docker container prune -f (zastavené kontejnery)"
|
||||
docker container prune -f 2>&1 || warn "container prune selhal"
|
||||
fi
|
||||
|
||||
log ""
|
||||
log "=== STAV PO ČIŠTĚNÍ ==="
|
||||
docker system df 2>&1 | head -20
|
||||
|
||||
log ""
|
||||
log "HOTOVO. Uvolněno viz výše ^"
|
||||
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# docker-restore.sh — bezpečná obnova Docker projektů z tar.zst zálohy
|
||||
#
|
||||
# - zobrazí dostupné zálohy v $BACKUP_ROOT
|
||||
# - vyžádá potvrzení (nebo --auto)
|
||||
# - zastaví vybrané projekty
|
||||
# - přesune současná data do _pre-restore-<TS>/ (NE rm -rf!)
|
||||
# - obnoví data ze zálohy
|
||||
# - vyčistí AppleDouble _* před docker compose up -d
|
||||
# - spustí kontejnery a ověří zdraví
|
||||
#
|
||||
# Použití:
|
||||
# ./docker-restore.sh # interaktivně: vybere zálohu + projekty
|
||||
# ./docker-restore.sh --list # jen seznam dostupných záloh
|
||||
# ./docker-restore.sh 2026-07-29_19-21-00 # konkrétní záloha, interaktivní výběr projektů
|
||||
# ./docker-restore.sh 2026-07-29_19-21-00 nginx vaultwarden # konkrétní záloha + projekty
|
||||
# ./docker-restore.sh --auto 2026-07-29_19-21-00 nginx # bez ptaní
|
||||
# ./docker-restore.sh --help # nápověda
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DOCKERY="${DOCKERY:-<docker-projects-root>}"
|
||||
BACKUP_ROOT="${BACKUP_ROOT:-<backup-root>}"
|
||||
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-60}"
|
||||
AUTO=0
|
||||
LIST_ONLY=0
|
||||
TS_ARG=""
|
||||
PROJECTS_RAW=()
|
||||
|
||||
usage() {
|
||||
sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h) usage; exit 0 ;;
|
||||
--auto|-y) AUTO=1 ;;
|
||||
--list|-l) LIST_ONLY=1 ;;
|
||||
--*) echo "Unknown flag: $arg" >&2; exit 1 ;;
|
||||
*)
|
||||
if [ -z "$TS_ARG" ]; then
|
||||
TS_ARG="$arg"
|
||||
else
|
||||
PROJECTS_RAW+=("$arg")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Barevné výstupy
|
||||
if [ -t 1 ]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; BLUE=''; NC=''
|
||||
fi
|
||||
|
||||
log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
warn() { printf '%s[%s]%s %s\n' "$YELLOW" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%H:%M:%S')" "$NC" "$*"; }
|
||||
|
||||
confirm() {
|
||||
if [ "$AUTO" = "1" ]; then return 0; fi
|
||||
printf '%s%s%s ' "$YELLOW" "$1" "$NC"
|
||||
read -r ans
|
||||
case "$ans" in
|
||||
y|Y|yes|YES) return 0 ;;
|
||||
*) printf 'Zrušeno.\n'; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Kontrola
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
err "Docker není dostupný."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$DOCKERY" ]; then
|
||||
err "Dockery adresář neexistuje: $DOCKERY"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$BACKUP_ROOT" ]; then
|
||||
err "Backup adresář neexistuje: $BACKUP_ROOT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# === Seznam dostupných záloh ===
|
||||
list_backups() {
|
||||
printf '%s%s%s\n' "$BLUE" "=== DOSTUPNÉ ZÁLOHY ===" "$NC"
|
||||
found=0
|
||||
while IFS= read -r d; do
|
||||
[ -d "$d" ] || continue
|
||||
ts=$(basename "$d")
|
||||
archives=$(/bin/ls "$d"/*.tar.zst 2>/dev/null | /usr/bin/wc -l | /usr/bin/tr -d ' ')
|
||||
size=$(/usr/bin/du -sh "$d" 2>/dev/null | /usr/bin/awk '{print $1}')
|
||||
printf ' %s %s archivů %s\n' "$ts" "$archives" "$size"
|
||||
found=$((found + 1))
|
||||
done < <(/usr/bin/find "$BACKUP_ROOT" -maxdepth 1 -mindepth 1 -type d \
|
||||
| /usr/bin/sort -r)
|
||||
if [ "$found" -eq 0 ]; then
|
||||
printf ' (žádné)\n'
|
||||
fi
|
||||
}
|
||||
|
||||
# Zobrazit seznam, pokud --list
|
||||
if [ "$LIST_ONLY" = "1" ]; then
|
||||
list_backups
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# === Vyber zálohu ===
|
||||
if [ -z "$TS_ARG" ]; then
|
||||
list_backups
|
||||
printf '\n%sZadejte timestamp zálohy (např. 2026-07-29_19-21-00):%s ' "$YELLOW" "$NC"
|
||||
read -r TS_ARG
|
||||
fi
|
||||
|
||||
BACKUP_DIR="$BACKUP_ROOT/$TS_ARG"
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
err "Záloha neexistuje: $BACKUP_DIR"
|
||||
list_backups
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Vybraná záloha: $BACKUP_DIR"
|
||||
log "Archivy v záloze:"
|
||||
/bin/ls -1 "$BACKUP_DIR"/*.tar.zst 2>/dev/null | /usr/bin/sed 's/^/ /' || {
|
||||
err "Záloha neobsahuje žádné archivy"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# === Detekce projektů v záloze ===
|
||||
DETECTED=()
|
||||
for archive in "$BACKUP_DIR"/*.tar.zst; do
|
||||
name=$(basename "$archive" .tar.zst)
|
||||
# Rozlišení: project archive vs volume archive
|
||||
# Project: <project>.tar.zst v Dockery/
|
||||
# Volume: <volname>.tar.zst v named volumes
|
||||
if [ -d "$DOCKERY/$name" ] && compgen -G "$DOCKERY/$name/docker-compose.y*ml" > /dev/null; then
|
||||
DETECTED+=("project:$name")
|
||||
else
|
||||
# Může to být volume; docker volume ls to ověří
|
||||
if docker volume inspect "$name" >/dev/null 2>&1; then
|
||||
DETECTED+=("volume:$name")
|
||||
else
|
||||
DETECTED+=("unknown:$name")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log ""
|
||||
log "Položky v záloze:"
|
||||
for d in "${DETECTED[@]}"; do printf ' %s\n' "$d"; done
|
||||
|
||||
# === Vyber projekty k obnově ===
|
||||
if [ "${#PROJECTS_RAW[@]}" -eq 0 ]; then
|
||||
PROJECTS=()
|
||||
for d in "${DETECTED[@]}"; do
|
||||
type=${d%%:*}
|
||||
name=${d#*:}
|
||||
if [ "$type" = "project" ]; then
|
||||
PROJECTS+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#PROJECTS[@]}" -eq 0 ]; then
|
||||
err "V záloze nebyly nalezeny žádné projekty k obnově"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '\n%sDostupné projekty v této záloze:%s\n' "$BLUE" "$NC"
|
||||
for i in "${!PROJECTS[@]}"; do
|
||||
printf ' %d) %s\n' $((i+1)) "${PROJECTS[$i]}"
|
||||
done
|
||||
printf '%sVyberte projekty (např. 1 3 nebo "all"):%s ' "$YELLOW" "$NC"
|
||||
read -r sel
|
||||
case "$sel" in
|
||||
all|"") SELECTED=("${PROJECTS[@]}") ;;
|
||||
*)
|
||||
SELECTED=()
|
||||
for n in $sel; do
|
||||
idx=$((n - 1))
|
||||
if [ "$idx" -ge 0 ] && [ "$idx" -lt "${#PROJECTS[@]}" ]; then
|
||||
SELECTED+=("${PROJECTS[$idx]}")
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
else
|
||||
SELECTED=("${PROJECTS_RAW[@]}")
|
||||
fi
|
||||
|
||||
if [ "${#SELECTED[@]}" -eq 0 ]; then
|
||||
err "Nebyly vybrány žádné projekty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# === Plán obnovy ===
|
||||
TS_NOW="$(date +%Y-%m-%d_%H-%M-%S)"
|
||||
|
||||
printf '\n%s%s%s\n' "$BLUE" "=== PLÁN OBNOVY ===" "$NC"
|
||||
printf 'Záloha: %s\n' "$BACKUP_DIR"
|
||||
printf 'Projekty: %s\n' "${SELECTED[*]}"
|
||||
printf 'Pre-restore: %s/_pre-restore-%s/\n' "$DOCKERY" "$TS_NOW"
|
||||
printf '\n'
|
||||
|
||||
confirm "Pokračovat v obnově? (PŘEPSOU se současná data projektů!) [y/N]"
|
||||
|
||||
# === Stop kontejnerů vybraných projektů ===
|
||||
log ""
|
||||
log "=== STOP: zastavuji kontejnery vybraných projektů ==="
|
||||
for project in "${SELECTED[@]}"; do
|
||||
cd "$DOCKERY/$project"
|
||||
if docker compose ps -q 2>/dev/null | grep -q .; then
|
||||
log " [$project] docker compose stop"
|
||||
docker compose stop 2>&1 || warn " [$project] stop selhal"
|
||||
fi
|
||||
done
|
||||
|
||||
# === Move-aside současných dat ===
|
||||
log ""
|
||||
log "=== MOVE-ASIDE: přesouvám současná data do _pre-restore-$TS_NOW/ ==="
|
||||
for project in "${SELECTED[@]}"; do
|
||||
SRC="$DOCKERY/$project"
|
||||
DEST="$DOCKERY/_pre-restore-$TS_NOW/$project"
|
||||
mkdir -p "$DEST"
|
||||
# Najdi velké podadresáře a přesuň je (NEVYMAŽ!)
|
||||
for sub in $(/bin/ls -A "$SRC" 2>/dev/null); do
|
||||
# Přeskoč .DS_Store, samotný _pre-restore-*, docker-compose soubory
|
||||
case "$sub" in
|
||||
_pre-restore-*) continue ;;
|
||||
docker-compose.y*ml) continue ;;
|
||||
.env|.env.*) continue ;; # .env necháme — je to runtime konfigurace
|
||||
.DS_Store) continue ;;
|
||||
esac
|
||||
if [ -e "$SRC/$sub" ]; then
|
||||
log " [$project] mv $sub → _pre-restore-$TS_NOW/$project/"
|
||||
mv "$SRC/$sub" "$DEST/$sub" 2>&1 || warn " mv $sub selhal"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# === Restore ze zálohy ===
|
||||
log ""
|
||||
log "=== RESTORE: extrahuji ze zálohy ==="
|
||||
FAILED=()
|
||||
for project in "${SELECTED[@]}"; do
|
||||
ARCHIVE="$BACKUP_DIR/${project}.tar.zst"
|
||||
if [ ! -f "$ARCHIVE" ]; then
|
||||
warn "[$project] archiv neexistuje: $ARCHIVE (přeskakuji)"
|
||||
FAILED+=("$project:no-archive")
|
||||
continue
|
||||
fi
|
||||
|
||||
log " [$project] extrahuji do $DOCKERY/$project/"
|
||||
# Archiv má cesty jako Volumes/SSD-data/Dockery/<project>/...
|
||||
# Extrahujeme s --strip-components=3, abychom získali přímo <project>/...
|
||||
cd "$DOCKERY"
|
||||
if /opt/homebrew/bin/zstd -d -c "$ARCHIVE" \
|
||||
| /usr/bin/tar --strip-components=3 -xf - 2>>"$BACKUP_DIR/.restore-errors-$TS_NOW"; then
|
||||
log " OK"
|
||||
else
|
||||
err " extrakce selhala pro $project"
|
||||
FAILED+=("$project:extract-failed")
|
||||
fi
|
||||
done
|
||||
|
||||
# === Vyčistit AppleDouble _* (prevence MariaDB/PG smyček) ===
|
||||
log ""
|
||||
log "=== CLEAN APPLE DOUBLE: mažu ._* před startem ==="
|
||||
for project in "${SELECTED[@]}"; do
|
||||
count=$(/usr/bin/find "$DOCKERY/$project" -name '._*' -type f 2>/dev/null | /usr/bin/wc -l | /usr/bin/tr -d ' ')
|
||||
if [ "$count" -gt 0 ]; then
|
||||
log " [$project] nalezeno $count ._* souborů, mažu"
|
||||
/usr/bin/find "$DOCKERY/$project" -name '._*' -type f -delete 2>/dev/null || true
|
||||
fi
|
||||
# .DS_Store
|
||||
dscount=$(/usr/bin/find "$DOCKERY/$project" -name '.DS_Store' -type f 2>/dev/null | /usr/bin/wc -l | /usr/bin/tr -d ' ')
|
||||
if [ "$dscount" -gt 0 ]; then
|
||||
log " [$project] nalezeno $dscount .DS_Store, mažu"
|
||||
/usr/bin/find "$DOCKERY/$project" -name '.DS_Store' -type f -delete 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# === Up kontejnerů ===
|
||||
log ""
|
||||
log "=== UP: spouštím kontejnery ==="
|
||||
for project in "${SELECTED[@]}"; do
|
||||
cd "$DOCKERY/$project"
|
||||
log " [$project] docker compose up -d"
|
||||
docker compose up -d 2>&1 | /usr/bin/head -10 || warn " [$project] up -d selhal"
|
||||
done
|
||||
|
||||
# === Health check ===
|
||||
log ""
|
||||
log "=== HEALTH CHECK: čekám na zdraví (max ${HEALTH_TIMEOUT}s) ==="
|
||||
sleep 10
|
||||
elapsed=10
|
||||
all_healthy=1
|
||||
while [ "$elapsed" -lt "$HEALTH_TIMEOUT" ]; do
|
||||
sleep 5
|
||||
elapsed=$((elapsed + 5))
|
||||
for project in "${SELECTED[@]}"; do
|
||||
for c in $(docker compose -f "$DOCKERY/$project/docker-compose.yml" ps -a --format '{{.Names}}' 2>/dev/null); do
|
||||
status=$(docker inspect --format='{{.State.Status}}' "$c" 2>/dev/null)
|
||||
health=$(docker inspect --format='{{.State.Health.Status}}' "$c" 2>/dev/null)
|
||||
if [ "$status" != "running" ]; then
|
||||
log " [$project/$c] state=$status health=$health"
|
||||
all_healthy=0
|
||||
fi
|
||||
done
|
||||
done
|
||||
# Pokud všechny kontejnery running a žádné health check nehlásí starting/unhealthy, ukončíme
|
||||
starting=$(docker ps --format '{{.Names}}' \
|
||||
| while read -r c; do docker inspect --format='{{.State.Health.Status}}' "$c" 2>/dev/null; done \
|
||||
| /usr/bin/grep -c '^starting$' || true)
|
||||
if [ "$starting" = "0" ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# === Report ===
|
||||
log ""
|
||||
log "======================================"
|
||||
log " SHRNUTÍ OBNOVY"
|
||||
log "======================================"
|
||||
log "Obnoveno:"
|
||||
for p in "${SELECTED[@]}"; do log " ✓ $p"; done
|
||||
if [ "${#FAILED[@]}" -gt 0 ]; then
|
||||
err "Selhalo:"
|
||||
for f in "${FAILED[@]}"; do err " ✗ $f"; done
|
||||
fi
|
||||
log ""
|
||||
log "Současná data jsou v: $DOCKERY/_pre-restore-$TS_NOW/"
|
||||
log "Po ověření můžete tento adresář ručně smazat:"
|
||||
log " rm -rf $DOCKERY/_pre-restore-$TS_NOW"
|
||||
log ""
|
||||
if [ "$all_healthy" = "1" ]; then
|
||||
log "Všechny kontejnery běží."
|
||||
else
|
||||
warn "Některé kontejnery nejsou v pořádku — zkontrolujte logy."
|
||||
fi
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# docker-update.sh — bezpečná aktualizace Docker Compose projektů
|
||||
#
|
||||
# - zastaví všechny kontejnery,
|
||||
# - stáhne nové image (docker compose pull),
|
||||
# - ověří, že se image skutečně změnil (jinak přeskočí),
|
||||
# - 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).
|
||||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h) usage; exit 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
DOCKERY="${DOCKERY:-<docker-projects-root>}"
|
||||
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
|
||||
|
||||
# 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'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; NC=''
|
||||
fi
|
||||
|
||||
log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%H:%M:%S')" "$NC" "$*" | tee -a "$LOG"; }
|
||||
warn() { printf '%s[%s]%s %s\n' "$YELLOW" "$(date '+%H:%M:%S')" "$NC" "$*" | tee -a "$LOG"; }
|
||||
err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%H:%M:%S')" "$NC" "$*" | tee -a "$LOG"; }
|
||||
|
||||
# Kontrola, že docker běží
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
err "Docker není dostupný. Spusť Docker Desktop a zkus znovu."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Najdi projekty
|
||||
if [ "$#" -gt 0 ]; then
|
||||
PROJECTS=("$@")
|
||||
else
|
||||
PROJECTS=()
|
||||
for d in "$DOCKERY"/*/; do
|
||||
[ -d "$d" ] || continue
|
||||
if compgen -G "$d/docker-compose.y*ml" > /dev/null; then
|
||||
PROJECTS+=("$(basename "$d")")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "${#PROJECTS[@]}" -eq 0 ]; then
|
||||
err "Nebyly nalezeny žádné Docker Compose projekty v $DOCKERY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Nalezeno ${#PROJECTS[@]} projektů: ${PROJECTS[*]}"
|
||||
|
||||
# Pre-flight: ulož si, co běží
|
||||
log "=== PRE-FLIGHT ==="
|
||||
BEFORE_RUNNING=$(docker ps --format '{{.Names}}')
|
||||
log "Běžící kontejnery před aktualizací:"
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
# Report
|
||||
log ""
|
||||
log "======================================"
|
||||
log " SHRNUTÍ"
|
||||
log "======================================"
|
||||
log "Aktualizováno: ${#UPDATED[@]} projekt(ů)"
|
||||
for p in "${UPDATED[@]:-}"; do [ -n "$p" ] && log " ✓ $p"; done
|
||||
log "Přeskočeno (beze změny): ${#SKIPPED[@]}"
|
||||
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
|
||||
fi
|
||||
log ""
|
||||
log "Log: $LOG"
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# mount-nas.sh — připojení/odpojení NAS SMB share do <multimedia-mount>
|
||||
#
|
||||
# Tiché (nobrowse), vhodné pro ruční i automatizované použití.
|
||||
#
|
||||
# Konfigurace přes env proměnné (mají defaulty):
|
||||
# SMB_SERVER default <nas-ip>
|
||||
# SMB_SHARE default Multimedia
|
||||
# SMB_USER default mates
|
||||
# TARGET default <multimedia-mount>
|
||||
# KEYCHAIN_SERVICE default SMB:<nas-ip>/Multimedia
|
||||
# NET_CHECK_TIMEOUT default 5 (sekund)
|
||||
#
|
||||
# Akce (default = auto):
|
||||
# --auto mount pokud není, skončí úspěšně pokud je již připojen
|
||||
# --mount mount (chyba pokud již připojen)
|
||||
# --umount umount (chyba pokud není připojen)
|
||||
# --status vypíše aktuální stav, exit 0/1 podle toho zda je mount aktivní
|
||||
# --help nápověda
|
||||
#
|
||||
# Exit kódy:
|
||||
# 0 OK (mount proběhl nebo již připojen / umount proběhl)
|
||||
# 10 cílový mount bod (nadřazený adresář) neexistuje
|
||||
# 11 záznam v Klíčence chybí
|
||||
# 12 záznam v Klíčence je prázdný
|
||||
# 20 SMB server nedostupný (TCP 445)
|
||||
# 30 mount selhal (auth, mount error, ...)
|
||||
# 31 umount selhal (resource busy, ...)
|
||||
#
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# ===== Konfigurace =====
|
||||
SMB_SERVER="${SMB_SERVER:-<nas-ip>}"
|
||||
SMB_SHARE="${SMB_SHARE:-Multimedia}"
|
||||
SMB_USER="${SMB_USER:-mates}"
|
||||
TARGET="${TARGET:-<multimedia-mount>}"
|
||||
KEYCHAIN_SERVICE="${KEYCHAIN_SERVICE:-SMB:$SMB_SERVER/$SMB_SHARE}"
|
||||
NET_CHECK_TIMEOUT="${NET_CHECK_TIMEOUT:-5}"
|
||||
LOG_FILE="${LOG_FILE:-/tmp/mount-nas.log}"
|
||||
PASSWORD_FILE="${PASSWORD_FILE:-}"
|
||||
|
||||
# ===== Barvy (jen pokud terminál) =====
|
||||
if [ -t 1 ]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; NC=''
|
||||
fi
|
||||
|
||||
# ===== Pomocné funkce =====
|
||||
log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%Y-%m-%d %H:%M:%S')" "$NC" "$*" | tee -a "$LOG_FILE"; }
|
||||
warn() { printf '%s[%s]%s %s\n' "$YELLOW" "$(date '+%Y-%m-%d %H:%M:%S')" "$NC" "$*" | tee -a "$LOG_FILE"; }
|
||||
err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%Y-%m-%d %H:%M:%S')" "$NC" "$*" | tee -a "$LOG_FILE"; }
|
||||
die() { err "$@"; exit "$1"; }
|
||||
|
||||
usage() {
|
||||
sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
# ===== Parsování argumentů =====
|
||||
# Podpora: --password-file CESTA (s mezerou)
|
||||
# --password-file=CESTA (s rovnítkem)
|
||||
# Argumenty s hodnotou je nutné ručně posunout v poli, protože 'for arg in "$@"'
|
||||
# neumožní 'shift 2' uvnitř smyčky bez komplikací. Přepíšeme do další proměnné.
|
||||
ACTION="auto"
|
||||
PASSWORD_FILE_ARG=""
|
||||
ARGS=()
|
||||
while [ "$#" -gt 0 ]; do
|
||||
arg="$1"
|
||||
case "$arg" in
|
||||
--help|-h) usage; exit 0 ;;
|
||||
--auto) ACTION="auto" ;;
|
||||
--mount) ACTION="mount" ;;
|
||||
--umount) ACTION="umount" ;;
|
||||
--status) ACTION="status" ;;
|
||||
--password-file)
|
||||
PASSWORD_FILE_ARG="$2"; shift 2; continue ;;
|
||||
--password-file=*)
|
||||
PASSWORD_FILE_ARG="${arg#--password-file=}"; shift ;;
|
||||
*) err "Unknown arg: $arg"; usage; exit 1 ;;
|
||||
esac
|
||||
ARGS+=("$arg")
|
||||
shift
|
||||
done
|
||||
|
||||
# ===== Detekce aktuálního stavu =====
|
||||
is_mounted() {
|
||||
mount 2>/dev/null | grep -qE "//$SMB_USER@$SMB_SERVER/$SMB_SHARE on $TARGET \(smbfs,"
|
||||
}
|
||||
|
||||
# ===== 1. Kontrola mount root =====
|
||||
MOUNT_ROOT="$(dirname "$TARGET")"
|
||||
if [ ! -d "$MOUNT_ROOT" ]; then
|
||||
die 10 "mount root neexistuje: $MOUNT_ROOT (čeká se na SSD)"
|
||||
fi
|
||||
|
||||
# ===== Akce: status =====
|
||||
if [ "$ACTION" = "status" ]; then
|
||||
if is_mounted; then
|
||||
log "OK: $TARGET je připojen ($SMB_SERVER/$SMB_SHARE)"
|
||||
mount | grep -E "$TARGET"
|
||||
exit 0
|
||||
else
|
||||
warn "NEpřipojen: $TARGET"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ===== Akce: umount =====
|
||||
if [ "$ACTION" = "umount" ]; then
|
||||
if ! is_mounted; then
|
||||
die 1 "$TARGET není připojen"
|
||||
fi
|
||||
log "Umount $TARGET"
|
||||
if /sbin/umount "$TARGET" 2>>"$LOG_FILE"; then
|
||||
log "OK: umount úspěšný"
|
||||
exit 0
|
||||
else
|
||||
die 31 "umount selhal (resource busy? zavři aplikace/kontejnery)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ===== Akce: mount nebo auto =====
|
||||
if [ "$ACTION" = "auto" ] && is_mounted; then
|
||||
log "OK: $TARGET je již připojen"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$ACTION" = "mount" ] && is_mounted; then
|
||||
die 30 "$TARGET je již připojen (použij --auto nebo --umount)"
|
||||
fi
|
||||
|
||||
# ===== 2. Test dostupnosti SMB serveru =====
|
||||
log "Test dostupnosti SMB serveru $SMB_SERVER:445 (timeout ${NET_CHECK_TIMEOUT}s)"
|
||||
if ! /usr/bin/nc -z -G "$NET_CHECK_TIMEOUT" "$SMB_SERVER" 445 2>/dev/null; then
|
||||
die 20 "SMB server $SMB_SERVER nedostupný (TCP 445)"
|
||||
fi
|
||||
log "OK: SMB server odpovídá"
|
||||
|
||||
# ===== 3. Heslo z Klíčenky nebo ze souboru =====
|
||||
# Priorita:
|
||||
# 1. --password-file <cesta> (heslo v souboru s chmod 600)
|
||||
# 2. $PASSWORD_FILE env proměnná
|
||||
# 3. Klíčenka macOS (service = KEYCHAIN_SERVICE)
|
||||
|
||||
PASSWORD=""
|
||||
if [ -n "${PASSWORD_FILE_ARG:-}" ] && [ -n "$PASSWORD_FILE_ARG" ]; then
|
||||
if [ ! -f "$PASSWORD_FILE_ARG" ]; then
|
||||
die 11 "soubor s heslem neexistuje: $PASSWORD_FILE_ARG"
|
||||
fi
|
||||
if [ ! -r "$PASSWORD_FILE_ARG" ]; then
|
||||
die 11 "soubor s heslem nelze číst (oprávnění): $PASSWORD_FILE_ARG"
|
||||
fi
|
||||
PASSWORD=$(cat "$PASSWORD_FILE_ARG")
|
||||
PASSWORD="${PASSWORD%%$'\n'}" # odřízne trailing newline
|
||||
if [ -z "$PASSWORD" ]; then
|
||||
die 12 "soubor s heslem je prázdný: $PASSWORD_FILE_ARG"
|
||||
fi
|
||||
log "Heslo načteno ze souboru: $PASSWORD_FILE_ARG"
|
||||
elif [ -n "$PASSWORD_FILE" ] && [ -f "$PASSWORD_FILE" ]; then
|
||||
PASSWORD=$(cat "$PASSWORD_FILE")
|
||||
PASSWORD="${PASSWORD%%$'\n'}"
|
||||
log "Heslo načteno z PASSWORD_FILE: $PASSWORD_FILE"
|
||||
else
|
||||
if ! PASSWORD=$(/usr/bin/security find-generic-password -a "$SMB_USER" -s "$KEYCHAIN_SERVICE" -w 2>/dev/null); then
|
||||
die 11 "Klíčenka chybí ($KEYCHAIN_SERVICE)"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$PASSWORD" ]; then
|
||||
die 12 "Heslo je prázdné"
|
||||
fi
|
||||
|
||||
# ===== 4. Vlastní mount přes mount_smbfs s heslem v URL =====
|
||||
#
|
||||
# mount_smbfs podporuje syntaxi:
|
||||
# //user:password@server/share /mount/point
|
||||
# To znamená, že heslo se předá přímo, bez expect a bez interaktivního promptu.
|
||||
# (Bezpečnost: heslo je vidět v 'ps' po dobu trvání mountu, což jsou sekundy.)
|
||||
|
||||
log "Spouštím mount_smbfs //$SMB_USER@$SMB_SERVER/$SMB_SHARE -> $TARGET"
|
||||
|
||||
# Sestav URL s heslem. Pro hesla se speciálními znaky (%, @, :) musíme
|
||||
# escapovat, ale heslo z klíčenky většinou tyto znaky nemá.
|
||||
# Pro jistotu zakódujeme heslo pomocí printf %q a pak odstraníme quotes.
|
||||
|
||||
# mount_smbfs -N zakáže prompt. Pokud heslo v URL je neplatné, mount selže.
|
||||
# Nekombinujeme -N s heslem v URL — buď heslo v URL, nebo -N bez hesla.
|
||||
ESCAPED_URL="//$SMB_USER:$PASSWORD@$SMB_SERVER/$SMB_SHARE"
|
||||
|
||||
# Pro mount_smbfs chceme tichý režim (-s), ale chyby potřebujeme v logu.
|
||||
if /sbin/mount_smbfs -o nobrowse,nosuid "$ESCAPED_URL" "$TARGET" 2>>"$LOG_FILE"; then
|
||||
log "OK: mount_smbfs exit 0"
|
||||
else
|
||||
rc=$?
|
||||
# Heslo v URL může selhat s různými chybami:
|
||||
# 1 = chyba argumentu (heslo obsahuje nepovolené znaky)
|
||||
# 64 = mount_smbfs obecná chyba (auth, share neexistuje, ...)
|
||||
die 30 "mount_smbfs selhal (exit $rc)"
|
||||
fi
|
||||
|
||||
# Bezpečnost: ihned smaž heslo z paměti
|
||||
unset PASSWORD
|
||||
unset ESCAPED_URL
|
||||
|
||||
# ===== 5. Ověření, že mount opravdu proběhl =====
|
||||
if ! is_mounted; then
|
||||
die 30 "mount exit 0 ale SMB share není v /sbin/mount"
|
||||
fi
|
||||
log "OK: mount úspěšný ($SMB_SERVER/$SMB_SHARE -> $TARGET)"
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo " macOS + Homebrew Update"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
# Kontrola Homebrew
|
||||
if ! command -v brew >/dev/null 2>&1; then
|
||||
echo "Homebrew není nainstalovaný."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "→ Aktualizace Homebrew metadata..."
|
||||
brew update
|
||||
|
||||
echo ""
|
||||
echo "→ Upgrade formulae..."
|
||||
brew upgrade
|
||||
|
||||
echo ""
|
||||
echo "→ Upgrade casks..."
|
||||
brew upgrade --cask
|
||||
|
||||
echo ""
|
||||
echo "→ Cleanup..."
|
||||
brew cleanup
|
||||
|
||||
echo ""
|
||||
echo "→ Autoremove..."
|
||||
brew autoremove
|
||||
|
||||
echo ""
|
||||
echo "→ Health check..."
|
||||
brew doctor
|
||||
|
||||
echo ""
|
||||
echo "→ Outdated packages:"
|
||||
brew outdated || true
|
||||
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo " Hotovo"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user