added failover script and other sytem scripts

This commit is contained in:
2026-04-11 17:46:11 -04:00
parent ee107e5df7
commit 55930a1166
7 changed files with 1388 additions and 386 deletions
+198
View File
@@ -0,0 +1,198 @@
#!/bin/bash
# -----------------------------------------------------------------------------------------------
# --------------------------------- WebGUI Watchdog --------------------------------------------
# -----------------------------------------------------------------------------------------------
# Monitors unRAID's WebGUI and restarts it if unresponsive.
# Uses an escalating restart strategy — tries nginx first, then emhttp if needed.
# emhttp is the unRAID management daemon — restarting it is more disruptive than nginx
# but recovers cleanly. Notification sent on any restart so you know what happened.
#
# Escalation path:
# Check WebGUI → unresponsive → restart nginx → recheck
# Still unresponsive → restart emhttp → recheck
# Still unresponsive → notify warning, manual intervention needed
#
# Run every 5-10 minutes via cron/User Scripts plugin.
# All configuration in Master.conf under WebGUI Watchdog section.
# Supports --dry-run to show what would be restarted without acting.
# -----------------------------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../Master.conf"
source "$SCRIPT_DIR/../common.sh"
parse_args "$@"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_GEAR Setup ━━━"
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
success "Running as root"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Status ━━━
# -----------------------------------------------------------------------------------------------
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_WEBGUI URL: $WEBGUI_URL"
echo "$ICON_WEBGUI Curl timeout: ${WEBGUI_TIMEOUT}s"
echo "$ICON_WEBGUI Nginx wait: ${WEBGUI_NGINX_WAIT}s"
echo "$ICON_WEBGUI emhttp wait: ${WEBGUI_EMHTTP_WAIT}s"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
# Show current state
if curl -sf --max-time "$WEBGUI_TIMEOUT" "$WEBGUI_URL" >/dev/null 2>&1; then
echo "$ICON_WEBGUI WebGUI: $ICON_RUNNING responding"
else
echo "$ICON_WEBGUI WebGUI: $ICON_NOT_RUNNING not responding"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no services will be restarted"
# -----------------------------------------------------------------------------------------------
# FUNCTIONS
# -----------------------------------------------------------------------------------------------
# Check if WebGUI is responding
check_webgui() {
curl -sf --max-time "$WEBGUI_TIMEOUT" "$WEBGUI_URL" >/dev/null 2>&1
}
# Restart nginx — lightweight fix, try first
restart_nginx() {
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would restart nginx"
return 0
fi
info "$ICON_WEBGUI Restarting nginx..."
if /etc/rc.d/rc.nginx restart >/dev/null 2>&1; then
success "nginx restarted"
return 0
else
error "nginx restart failed"
return 1
fi
}
# Restart emhttp — heavier fix, escalate if nginx didn't help
# emhttp drives the array, Docker management, shares — recovers cleanly but takes longer
restart_emhttp() {
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would restart emhttp"
return 0
fi
info "$ICON_WEBGUI Restarting emhttp..."
if /etc/rc.d/rc.emhttp restart >/dev/null 2>&1; then
success "emhttp restarted"
return 0
else
error "emhttp restart failed"
return 1
fi
}
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_WEBGUI WebGUI Watchdog ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_WEBGUI WebGUI Watchdog — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
echo "$ICON_WEBGUI URL: $WEBGUI_URL"
echo ""
START=$(date +%s)
# Initial check
info "Checking WebGUI..."
if check_webgui; then
success "$ICON_WEBGUI WebGUI is responding — nothing to do"
echo ""
echo "━━━━━ $ICON_SUMMARY WEBGUI WATCHDOG SUMMARY ━━━━━"
echo "$ICON_WEBGUI Status: $ICON_RUNNING HEALTHY"
echo "$ICON_TIME Duration: $(format_duration $(($(date +%s) - START)))"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
# WebGUI not responding — begin escalation
warn "$ICON_WEBGUI WebGUI is not responding at $WEBGUI_URL"
# ── Step 1: Restart nginx ──
echo ""
echo "━━━ $ICON_WEBGUI Step 1 — Nginx Restart ━━━"
restart_nginx
if [[ "$DRY_RUN" == false ]]; then
info "Waiting ${WEBGUI_NGINX_WAIT}s for nginx to recover..."
sleep "$WEBGUI_NGINX_WAIT"
if check_webgui; then
success "$ICON_WEBGUI WebGUI recovered after nginx restart"
notify "WebGUI recovered on $(hostname) after nginx restart" "WebGUI Watchdog" "warning"
echo ""
echo "━━━━━ $ICON_SUMMARY WEBGUI WATCHDOG SUMMARY ━━━━━"
echo "$ICON_WEBGUI Status: $ICON_SUCCESS RECOVERED via nginx restart"
echo "$ICON_TIME Duration: $(format_duration $(($(date +%s) - START)))"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
warn "WebGUI still not responding after nginx restart — escalating to emhttp"
fi
# ── Step 2: Restart emhttp ──
echo ""
echo "━━━ $ICON_WEBGUI Step 2 — emhttp Restart ━━━"
warn "Restarting emhttp — this is the unRAID management daemon"
warn "Array, Docker management and shares remain running but WebGUI will be briefly unavailable"
restart_emhttp
if [[ "$DRY_RUN" == false ]]; then
info "Waiting ${WEBGUI_EMHTTP_WAIT}s for emhttp to recover..."
sleep "$WEBGUI_EMHTTP_WAIT"
if check_webgui; then
success "$ICON_WEBGUI WebGUI recovered after emhttp restart"
notify "WebGUI recovered on $(hostname) after emhttp restart — check system health" "WebGUI Watchdog" "warning"
echo ""
echo "━━━━━ $ICON_SUMMARY WEBGUI WATCHDOG SUMMARY ━━━━━"
echo "$ICON_WEBGUI Status: $ICON_SUCCESS RECOVERED via emhttp restart"
echo "$ICON_TIME Duration: $(format_duration $(($(date +%s) - START)))"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
fi
# ── Both restarts failed ──
END=$(date +%s)
echo ""
echo "━━━━━ $ICON_SUMMARY WEBGUI WATCHDOG SUMMARY ━━━━━"
echo "$ICON_WEBGUI Status: $ICON_ERROR UNRECOVERED — manual intervention needed"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ "$DRY_RUN" == false ]]; then
notify "WebGUI unrecovered on $(hostname) after nginx and emhttp restart — manual intervention needed" "WebGUI Watchdog" "warning"
exit 1
fi
exit 0
+188 -61
View File
@@ -2,9 +2,16 @@
# -----------------------------------------------------------------------------------------------
# --------------------------------- ZFS Memory Snapshot ----------------------------------------
# -----------------------------------------------------------------------------------------------
# Captures a point-in-time snapshot of ZFS ARC statistics and system memory usage.
# Read-only diagnostic tool — no changes are made to the system regardless of flags.
# Dry run mode still collects and displays data since no modifications occur.
# Weekly ZFS pool health and memory diagnostic report.
# Combines ZFS pool status, ARC statistics, memory summary, Docker memory usage
# and kernel pressure into a single report. Informational only — no action taken.
# system_watchdog.sh handles threshold-based intervention.
#
# Output goes to both console and ZFS_REPORT_LOG for later review.
# Notifies if any warning thresholds are exceeded.
#
# All configuration in Master.conf under ZFS Memory Snapshot section.
# Supports --dry-run (preview only, no log write) and --status.
# -----------------------------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -14,14 +21,24 @@ source "$SCRIPT_DIR/../common.sh"
parse_args "$@"
# Tee output to log file unless dry run
if [[ "$DRY_RUN" == false ]]; then
mkdir -p "$(dirname "$ZFS_REPORT_LOG")"
exec > >(tee -a "$ZFS_REPORT_LOG") 2>&1
fi
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_GEAR Setup ━━━"
info "$ICON_ZFS ZFS ARC + Memory Snapshot"
info "$ICON_TIME $(date)"
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
success "Running as root"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Status ━━━
@@ -29,65 +46,167 @@ info "$ICON_TIME $(date)"
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_ZFS Mode: Read-only diagnostics"
echo "$ICON_MEM Source: ZFS ARC + system memory"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "$ICON_ZFS Log file: $ZFS_REPORT_LOG"
echo "$ICON_ZFS ARC warn: ${ZFS_REPORT_ARC_WARN_PCT}%"
echo "$ICON_MEM Free RAM warn: ${ZFS_REPORT_FREE_WARN_GB}GB"
echo "$ICON_MEM Avail RAM warn: ${ZFS_REPORT_AVAIL_WARN_GB}GB"
echo "$ICON_CONTAINERS Docker top: $ZFS_REPORT_DOCKER_TOP"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
# -----------------------------------------------------------------------------------------------
# FUNCTIONS
# -----------------------------------------------------------------------------------------------
# Reads ZFS ARC statistics from the kernel stats interface.
# Filters for the most useful ARC metrics — size, hits, misses and metadata.
# Skips gracefully if ZFS is not available on this system.
show_zfs_arc() {
echo ""
echo "━━━ $ICON_ZFS ZFS ARC Stats ━━━"
if [[ ! -r /proc/spl/kstat/zfs/arcstats ]]; then
warn "ZFS arcstats not available on this system — is ZFS loaded?"
return
fi
grep -iE '^(c|size|hits|misses|arc_meta_used|demand_metadata_misses|mru_ghost_metadata|mfu_ghost_metadata)' \
/proc/spl/kstat/zfs/arcstats 2>/dev/null || warn "Unable to read ARC stats"
}
# Reads current system memory and swap usage using free.
# Skips gracefully if free is not available.
show_memory_status() {
echo ""
echo "━━━ $ICON_MEM Memory Status ━━━"
if ! command -v free >/dev/null 2>&1; then
warn "free command not available on this system"
return
fi
free -h | awk '
NR==1 { print $0 }
/Mem:/ { print $0 }
/Swap:/ { print $0 }'
}
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — output will not be written to log"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_ZFS Snapshot ━━━
# Tracking
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_ZFS Snapshot ━━━"
echo "$ICON_ZFS Source: ZFS ARC + system memory"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo ""
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — this script is read-only, data will still be collected"
WARNINGS=()
START=$(date +%s)
DATE=$(date +"%Y-%m-%d %H:%M:%S")
show_zfs_arc
show_memory_status
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " $ICON_ZFS ZFS WEEKLY HEALTH REPORT — $DATE"
echo " $ICON_HOST Host: $(hostname)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_ZFS ZFS Pool Health ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_ZFS ZFS Pool Health ━━━"
if ! command -v zpool >/dev/null 2>&1; then
warn "ZFS not available on this system — skipping pool checks"
else
info "Pool status:"
zpool status 2>/dev/null | grep -E "pool:|state:|status:|errors:|scan:" | while IFS= read -r line; do
echo " $line"
done
echo ""
info "Pool overview:"
zpool list 2>/dev/null | while IFS= read -r line; do
echo " $line"
done
UNHEALTHY=$(zpool list -H -o health 2>/dev/null | grep -v ONLINE || true)
if [[ -n "$UNHEALTHY" ]]; then
error "One or more ZFS pools are NOT ONLINE"
WARNINGS+=("ZFS pool unhealthy")
else
success "All ZFS pools are ONLINE"
fi
fi
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_ZFS ARC Statistics ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_ZFS ARC Statistics ━━━"
if [[ ! -f /proc/spl/kstat/zfs/arcstats ]]; then
warn "ZFS arcstats not available — skipping ARC section"
else
ARC_MAX=$(cat /sys/module/zfs/parameters/zfs_arc_max 2>/dev/null || \
awk '/^c_max / {print $3}' /proc/spl/kstat/zfs/arcstats)
ARC_SIZE=$(awk '/^size / {print $3}' /proc/spl/kstat/zfs/arcstats)
ARC_META_USED=$(awk '/^arc_meta_used / {print $3}' /proc/spl/kstat/zfs/arcstats)
ARC_MAX_GB=$(awk "BEGIN {printf \"%.1f\", $ARC_MAX / 1073741824}")
ARC_CUR_GB=$(awk "BEGIN {printf \"%.1f\", $ARC_SIZE / 1073741824}")
ARC_META_GB=$(awk "BEGIN {printf \"%.1f\", $ARC_META_USED / 1073741824}")
ARC_PCT=$(awk "BEGIN {printf \"%.1f\", $ARC_SIZE * 100 / $ARC_MAX}")
ARC_PCT_INT=$(printf "%.0f" "$ARC_PCT")
echo " $ICON_ZFS ARC Max: ${ARC_MAX_GB}GB"
echo " $ICON_ZFS ARC Current: ${ARC_CUR_GB}GB"
echo " $ICON_ZFS ARC Meta Used: ${ARC_META_GB}GB"
echo " $ICON_ZFS ARC Utilization: ${ARC_PCT}%"
if [[ "$ARC_PCT_INT" -ge "$ZFS_REPORT_ARC_WARN_PCT" ]]; then
warn "ARC utilization ${ARC_PCT}% — above ${ZFS_REPORT_ARC_WARN_PCT}% threshold"
WARNINGS+=("ARC high: ${ARC_PCT}%")
else
success "ARC utilization ${ARC_PCT}% — within threshold"
fi
echo ""
info "Metadata pressure:"
META_MRU_GHOST=$(awk '/^mru_ghost_metadata / {print $3}' /proc/spl/kstat/zfs/arcstats 2>/dev/null || echo 0)
META_MFU_GHOST=$(awk '/^mfu_ghost_metadata / {print $3}' /proc/spl/kstat/zfs/arcstats 2>/dev/null || echo 0)
META_MISSES=$(awk '/^demand_metadata_misses / {print $3}' /proc/spl/kstat/zfs/arcstats 2>/dev/null || echo 0)
MRU_GB=$(awk "BEGIN {printf \"%.2f\", $META_MRU_GHOST / 1073741824}")
MFU_GB=$(awk "BEGIN {printf \"%.2f\", $META_MFU_GHOST / 1073741824}")
echo " $ICON_ZFS MRU Ghost: ${MRU_GB}GB"
echo " $ICON_ZFS MFU Ghost: ${MFU_GB}GB"
echo " $ICON_ZFS Metadata Misses: ${META_MISSES}"
fi
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_MEM Memory Status ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_MEM Memory Status ━━━"
FREE_HUMAN=$(free -h | awk '/Mem:/ {print $4}')
AVAIL_HUMAN=$(free -h | awk '/Mem:/ {print $7}')
TOTAL_HUMAN=$(free -h | awk '/Mem:/ {print $2}')
FREE_GB=$(free -g | awk '/Mem:/ {print $4}')
AVAIL_GB=$(free -g | awk '/Mem:/ {print $7}')
echo " $ICON_MEM Total RAM: $TOTAL_HUMAN"
echo " $ICON_MEM Free RAM: $FREE_HUMAN"
echo " $ICON_MEM Available RAM: $AVAIL_HUMAN"
if [[ "$FREE_GB" -lt "$ZFS_REPORT_FREE_WARN_GB" ]]; then
warn "Free RAM ${FREE_HUMAN} — below ${ZFS_REPORT_FREE_WARN_GB}GB threshold"
WARNINGS+=("Low free RAM: ${FREE_HUMAN}")
else
success "Free RAM ${FREE_HUMAN} — within threshold"
fi
if [[ "$AVAIL_GB" -lt "$ZFS_REPORT_AVAIL_WARN_GB" ]]; then
warn "Available RAM ${AVAIL_HUMAN} — below ${ZFS_REPORT_AVAIL_WARN_GB}GB threshold"
WARNINGS+=("Low available RAM: ${AVAIL_HUMAN}")
else
success "Available RAM ${AVAIL_HUMAN} — within threshold"
fi
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_CONTAINERS Docker Memory ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_CONTAINERS Top $ZFS_REPORT_DOCKER_TOP Docker Memory Users ━━━"
if ! command -v docker >/dev/null 2>&1; then
warn "Docker not available — skipping container memory section"
else
docker stats --no-stream \
--format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}" \
2>/dev/null | head -n $(( ZFS_REPORT_DOCKER_TOP + 1 )) | while IFS= read -r line; do
echo " $line"
done
fi
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Kernel Pressure ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_GEAR Kernel Pressure ━━━"
if ! command -v vmstat >/dev/null 2>&1; then
warn "vmstat not available — skipping kernel pressure section"
else
info "vmstat snapshot (3 samples):"
vmstat 1 3 2>/dev/null | while IFS= read -r line; do
echo " $line"
done
fi
END=$(date +%s)
@@ -95,9 +214,17 @@ END=$(date +%s)
# ━━━ $ICON_SUMMARY Summary ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━━━ $ICON_SUMMARY SNAPSHOT SUMMARY ━━━━━"
echo "$ICON_ZFS Source: ZFS ARC + system memory"
echo "$ICON_GEAR Mode: READ-ONLY — no changes made"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "━━━━━ $ICON_SUMMARY ZFS REPORT SUMMARY ━━━━━"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
echo "$ICON_ZFS Log: $ZFS_REPORT_LOG"
echo ""
if [[ ${#WARNINGS[@]} -eq 0 ]]; then
success "Report complete — no warnings"
else
echo "$ICON_WARN Warnings: ${#WARNINGS[@]}"
for w in "${WARNINGS[@]}"; do
echo " $ICON_WARN $w"
done
notify "ZFS weekly report on $(hostname)${#WARNINGS[@]} warning(s): ${WARNINGS[*]}" "ZFS Report" "warning"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"