#!/usr/bin/env bash # # disk-watch.sh — sledování volného místa na Docker datech # # Kontroluje: # - (Docker data) # - (zálohy) # - Docker disk usage (images, containers, volumes, build cache) # # 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_BACKUP="${SSD_BACKUP:-}" 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 :\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