#!/usr/bin/env bash
# diskcheck.sh - alert when a disk is getting full, and (optionally) when
# your backups have stopped. Alerts once per problem, not every run.
#
#   ./diskcheck.sh                           # every mounted disk, alert at 90% used
#   ./diskcheck.sh -p 80                     # alert at 80% used instead
#   ./diskcheck.sh -b /backups -h 26         # also: newest file in /backups must be
#                                            #  less than 26 hours old
# Daily with cron (crontab -e):
#   0 7 * * * /home/you/diskcheck.sh -b /backups -h 26
#
# Remembers what it already alerted about in ~/.useralertx_diskcheck and
# sends a "back to normal" alert when a problem clears.
# API key: USERALERTX_API_KEY env var, else ~/.useralertx_key (create it
# once with uxsend.sh). USERALERTX_DRYRUN=1 prints instead of sending.

URL_API="${USERALERTX_URL:-https://useralertx.com/cgi-bin/itmessages.cgi}"
PCT=90; BACKUP=""; HOURS=26
while getopts "p:b:h:" o; do case "$o" in p) PCT="$OPTARG";; b) BACKUP="$OPTARG";; h) HOURS="$OPTARG";;
  *) echo "usage: diskcheck.sh [-p percent] [-b backup_dir -h max_age_hours]"; exit 2;; esac; done

send() {
  if [ -n "${USERALERTX_DRYRUN:-}" ]; then echo "[dry run] would send: $1"; return 0; fi
  local key="${USERALERTX_API_KEY:-}"
  [ -z "$key" ] && [ -f "$HOME/.useralertx_key" ] && key="$(tr -d '[:space:]' < "$HOME/.useralertx_key")"
  [ -z "$key" ] && { echo "diskcheck: no API key (set USERALERTX_API_KEY or run uxsend.sh once to save it)"; return 1; }
  curl -s -o /dev/null -A 'UserAlertX-example-diskcheck/1.0' -H "X-Api-Key: $key" --data-urlencode "body=$1" "$URL_API"
}

STATE="$HOME/.useralertx_diskcheck"; touch "$STATE"
HOST="$(hostname -s 2>/dev/null || hostname)"
problems=()

# disks: real devices only (/dev/...), skips tmpfs, overlays, snaps (works on Linux and Mac)
while read -r use mnt; do
  u="${use%\%}"
  [ "$u" -ge "$PCT" ] && problems+=("disk $mnt ${u}% full")
done < <(df -P 2>/dev/null | awk 'NR>1 && $1 ~ /^\/dev\// && $6 !~ /^\/snap\// {print $5, $6}')

# backups: newest file's age
if [ -n "$BACKUP" ]; then
  newest_file="$(find "$BACKUP" -type f -exec ls -t {} + 2>/dev/null | head -1)"
  if [ -z "$newest_file" ]; then
    problems+=("no backup files in $BACKUP")
  else
    age_h=$(( ( $(date +%s) - $(date -r "$newest_file" +%s) ) / 3600 ))
    [ "$age_h" -ge "$HOURS" ] && problems+=("newest backup in $BACKUP is ${age_h}h old")
  fi
fi

# alert only on change
NOW="$(printf '%s\n' "${problems[@]}" | sort)"
PREV="$(sort "$STATE")"
if [ "$NOW" != "$PREV" ]; then
  if [ ${#problems[@]} -gt 0 ]; then
    send "$HOST: $(printf '%s; ' "${problems[@]}" | sed 's/; $//')"
  else
    send "$HOST: disks and backups back to normal"
  fi
  printf '%s\n' "${problems[@]}" | sed '/^$/d' > "$STATE"
fi
[ ${#problems[@]} -gt 0 ] && printf 'problem: %s\n' "${problems[@]}" || echo "all OK"
