init: clean history with no PII
Toto repo ma novou cistou historii (1 commit). Predchozi historie byla smazana, protoze obsahovala osobni udaje (IP, emaily, hesla, klice, tokeny, Volume cesty). Soubory v aktualnim commit uz tyto udaje neobsahuji (nahradeno placeholdery <server-ip>, CHANGE_ME, admin@example.com atd.). Pokud chces zpet kompletni seznam starych commitu, mas je ve svem lokalnim working tree - git log --all ukaze prazdno (protoze jsme smazali .git/ a zacali znovu).
This commit is contained in:
@@ -0,0 +1,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 ^"
|
||||
Reference in New Issue
Block a user