82 lines
2.9 KiB
Text
82 lines
2.9 KiB
Text
|
|
#!/bin/sh
|
||
|
|
# disk-guard [--warn PCT] [--crit PCT] [--volume PATH] [--quiet]
|
||
|
|
#
|
||
|
|
# Check free space on the data volume and raise a macOS notification when it
|
||
|
|
# runs low. Meant to run unattended (launchd), but safe to run by hand.
|
||
|
|
#
|
||
|
|
# Unlike `disk-reclaim` (which only reports and is read-only), this is the
|
||
|
|
# *alarm*: it makes low disk visible BEFORE the volume hits ~97% full and
|
||
|
|
# things start failing. It never deletes anything.
|
||
|
|
#
|
||
|
|
# Flags:
|
||
|
|
# --warn PCT warn when free space drops below PCT percent (default 15)
|
||
|
|
# --crit PCT critical when free space drops below PCT percent (default 7)
|
||
|
|
# --volume PATH volume to check (default /System/Volumes/Data)
|
||
|
|
# --quiet no stdout; only notify + log (for launchd)
|
||
|
|
#
|
||
|
|
# Exit status: 0 ok, 1 warn, 2 critical. Always logs to
|
||
|
|
# ~/Library/Logs/disk-reclaim.log so boot snapshots and alarms share a trail.
|
||
|
|
|
||
|
|
set -eu
|
||
|
|
|
||
|
|
warn_pct=15
|
||
|
|
crit_pct=7
|
||
|
|
volume=/System/Volumes/Data
|
||
|
|
quiet=0
|
||
|
|
|
||
|
|
die() { echo "disk-guard: $*" >&2; exit 64; }
|
||
|
|
|
||
|
|
while [ $# -gt 0 ]; do
|
||
|
|
case "$1" in
|
||
|
|
-h|--help) sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;;
|
||
|
|
--warn) [ $# -ge 2 ] || die "--warn needs a value"; warn_pct=$2; shift 2 ;;
|
||
|
|
--crit) [ $# -ge 2 ] || die "--crit needs a value"; crit_pct=$2; shift 2 ;;
|
||
|
|
--volume) [ $# -ge 2 ] || die "--volume needs a value"; volume=$2; shift 2 ;;
|
||
|
|
--quiet) quiet=1; shift ;;
|
||
|
|
*) die "unknown arg: $1" ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
# df -k: portable. Columns: Filesystem 1024-blocks Used Available Capacity ...
|
||
|
|
# Available is field 4, in KiB. Compute free percent ourselves (Capacity is
|
||
|
|
# "used %" and rounds oddly on APFS shared containers).
|
||
|
|
read -r avail_kb total_kb <<EOF
|
||
|
|
$(df -k "$volume" | awk 'NR==2 {print $4, $2}')
|
||
|
|
EOF
|
||
|
|
|
||
|
|
[ -n "${total_kb:-}" ] && [ "$total_kb" -gt 0 ] || die "could not read df for $volume"
|
||
|
|
|
||
|
|
free_pct=$(( avail_kb * 100 / total_kb ))
|
||
|
|
avail_gb=$(awk -v k="$avail_kb" 'BEGIN { printf "%.1f", k/1048576 }')
|
||
|
|
|
||
|
|
log="$HOME/Library/Logs/disk-reclaim.log"
|
||
|
|
mkdir -p "$(dirname "$log")"
|
||
|
|
stamp=$(date '+%Y-%m-%d %H:%M:%S %z')
|
||
|
|
|
||
|
|
notify() {
|
||
|
|
# Best-effort macOS banner; ignore failure (e.g. headless/ssh).
|
||
|
|
osascript -e "display notification \"$2\" with title \"$1\" sound name \"Basso\"" \
|
||
|
|
>/dev/null 2>&1 || true
|
||
|
|
}
|
||
|
|
|
||
|
|
if [ "$free_pct" -lt "$crit_pct" ]; then
|
||
|
|
level=CRITICAL; rc=2
|
||
|
|
notify "Disk critically full" "${avail_gb}G free (${free_pct}%) on $volume. Run: disk-reclaim"
|
||
|
|
elif [ "$free_pct" -lt "$warn_pct" ]; then
|
||
|
|
level=WARN; rc=1
|
||
|
|
notify "Disk getting full" "${avail_gb}G free (${free_pct}%) on $volume. Run: disk-reclaim"
|
||
|
|
else
|
||
|
|
level=OK; rc=0
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "=== $stamp (guard) $level: ${avail_gb}G free (${free_pct}%) on $volume ===" >> "$log"
|
||
|
|
|
||
|
|
# On WARN/CRIT, append a reclaim snapshot so the log shows what to delete.
|
||
|
|
if [ "$rc" -ne 0 ]; then
|
||
|
|
script_dir=$(cd "$(dirname "$0")" && pwd -P)
|
||
|
|
"$script_dir/disk-reclaim" "$HOME" --min 1G >> "$log" 2>&1 || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
[ "$quiet" = 1 ] || echo "$level: ${avail_gb}G free (${free_pct}%) on $volume"
|
||
|
|
exit "$rc"
|