#!/usr/bin/env bash # ct302-self-health.sh โ€” CT 302 (claude-runner) disk self-check โ†’ Discord # # Monitors disk usage on CT 302 itself and alerts to Discord when any # filesystem exceeds the threshold. Closes the blind spot where the # monitoring system cannot monitor itself via external health checks. # # Designed to run silently when healthy (no Discord spam on green). # Only posts when a filesystem is at or above THRESHOLD. # # Usage: # ct302-self-health.sh [--discord-webhook URL] [--threshold N] [--dry-run] [--always-post] # # Environment overrides: # DISCORD_WEBHOOK Discord webhook URL (required unless --dry-run) # DISK_THRESHOLD Disk usage % alert threshold (default: 80) # # Install on CT 302 (daily, 07:00 UTC): # 0 7 * * * /root/scripts/ct302-self-health.sh >> /var/log/ct302-self-health.log 2>&1 set -uo pipefail DISK_THRESHOLD="${DISK_THRESHOLD:-80}" DISCORD_WEBHOOK="${DISCORD_WEBHOOK:-}" DRY_RUN=0 ALWAYS_POST=0 while [[ $# -gt 0 ]]; do case "$1" in --discord-webhook) if [[ $# -lt 2 ]]; then echo "Error: --discord-webhook requires a value" >&2 exit 1 fi DISCORD_WEBHOOK="$2" shift 2 ;; --threshold) if [[ $# -lt 2 ]]; then echo "Error: --threshold requires a value" >&2 exit 1 fi DISK_THRESHOLD="$2" shift 2 ;; --dry-run) DRY_RUN=1 shift ;; --always-post) ALWAYS_POST=1 shift ;; *) echo "Unknown option: $1" >&2 exit 1 ;; esac done if [[ "$DRY_RUN" -eq 0 && -z "$DISCORD_WEBHOOK" ]]; then echo "Error: DISCORD_WEBHOOK not set. Use --discord-webhook URL or set env var." >&2 exit 1 fi log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } # --------------------------------------------------------------------------- # Check disk usage on all real filesystems # --------------------------------------------------------------------------- # df output: Filesystem Use% Mounted-on (skipping tmpfs, devtmpfs, overlay) TRIGGERED=() ALL_FS=() while IFS= read -r line; do fs=$(echo "$line" | awk '{print $1}') pct=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') ALL_FS+=("${pct}% ${mount} (${fs})") if [[ "$pct" -ge "$DISK_THRESHOLD" ]]; then TRIGGERED+=("${pct}% used โ€” ${mount} (${fs})") fi done < <(df -h --output=source,size,used,avail,pcent,target | tail -n +2 | awk '$1 !~ /^(tmpfs|devtmpfs|overlay|udev)/' | awk '{print $1, $5, $6}') HOSTNAME=$(hostname -s) TRIGGERED_COUNT=${#TRIGGERED[@]} log "Disk check complete: ${TRIGGERED_COUNT} filesystem(s) above ${DISK_THRESHOLD}%" # Exit cleanly with no Discord post if everything is healthy if [[ "$TRIGGERED_COUNT" -eq 0 && "$ALWAYS_POST" -eq 0 && "$DRY_RUN" -eq 0 ]]; then log "All filesystems healthy โ€” no alert needed." exit 0 fi # --------------------------------------------------------------------------- # Build Discord payload # --------------------------------------------------------------------------- if [[ "$TRIGGERED_COUNT" -gt 0 ]]; then EMBED_COLOR=15548997 # 0xED4245 red TITLE="๐Ÿ”ด ${HOSTNAME}: Disk usage above ${DISK_THRESHOLD}%" alert_lines=$(printf 'โš ๏ธ %s\n' "${TRIGGERED[@]}") FIELDS=$(jq -n \ --arg name "Filesystems Over Threshold" \ --arg value "$alert_lines" \ '[{"name": $name, "value": $value, "inline": false}]') else EMBED_COLOR=5763719 # 0x57F287 green TITLE="๐ŸŸข ${HOSTNAME}: All filesystems healthy" FIELDS='[]' fi # Add summary of all filesystems all_lines=$(printf '%s\n' "${ALL_FS[@]}") FIELDS=$(echo "$FIELDS" | jq \ --arg name "All Filesystems" \ --arg value "$all_lines" \ '. + [{"name": $name, "value": $value, "inline": false}]') FOOTER="$(date -u '+%Y-%m-%d %H:%M UTC') ยท CT 302 self-health ยท threshold: ${DISK_THRESHOLD}%" PAYLOAD=$(jq -n \ --arg title "$TITLE" \ --argjson color "$EMBED_COLOR" \ --argjson fields "$FIELDS" \ --arg footer "$FOOTER" \ '{ "embeds": [{ "title": $title, "color": $color, "fields": $fields, "footer": {"text": $footer} }] }') if [[ "$DRY_RUN" -eq 1 ]]; then log "DRY RUN โ€” Discord payload:" echo "$PAYLOAD" | jq . exit 0 fi log "Posting to Discord..." HTTP_STATUS=$(curl -s -o /tmp/ct302-self-health-discord.out \ -w "%{http_code}" \ -X POST "$DISCORD_WEBHOOK" \ -H "Content-Type: application/json" \ -d "$PAYLOAD") if [[ "$HTTP_STATUS" -ge 200 && "$HTTP_STATUS" -lt 300 ]]; then log "Discord notification sent (HTTP ${HTTP_STATUS})." else log "Warning: Discord returned HTTP ${HTTP_STATUS}." cat /tmp/ct302-self-health-discord.out >&2 exit 1 fi