feat: watchdog architecture v2 — resource manager + single-pass orchestrator

Introduce a four-layer self-healing stack replacing the continuous-loop watchdogs:

- resource_manager.sh (new): single-pass pressure reduction layer; throttles
  SABnzbd/qBit at level 1, docker-pauses background containers at level 2,
  docker-stops optional containers and signals docker_watchdog to defer at
  level 3; graduated recovery with hysteresis

- watchdog_orchestrator.sh (new, Orchestrators/): runs resource_manager →
  docker_watchdog → system_watchdog in sequence; intended for per-minute cron
  via User Scripts; startup grace, acquire_lock to prevent pile-up, heartbeat

- docker_watchdog.sh: de-looped to single-pass; daemon strikes persisted to
  state file across runs; cross-script coordination reads RM_STATE_FILE instead
  of SYS_WATCHDOG_STATE_FILE

- system_watchdog.sh: de-looped to single-pass; stripped of all container
  management (shutdown_non_essential_containers removed); reboot-only last resort

- master.conf: removed system_watchdog and docker_watchdog from
  ARRAY_START_SCRIPTS; added WATCHDOG ORCHESTRATOR and RESOURCE MANAGER sections

- master_host1.conf: added RM_PAUSE_CONTAINERS and RM_STOP_CONTAINERS arrays

- common.sh: aliased RM_PAUSE_CONTAINERS and RM_STOP_CONTAINERS via detect_hosts()

- continuous_scripts_status.sh: moved to Tools/ (preserved for future use)

- sunday_morning_coffee_report.sh: watchdog section updated to use state file
  mtime checks instead of is_running; added Resource Manager subsection;
  fixed mem_shutdown grep filter pointing to wrong state file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gmer4Lfe
2026-05-12 17:41:59 -04:00
co-authored by Claude Sonnet 4.6
parent f16c962ac0
commit 309546e615
9 changed files with 925 additions and 277 deletions
+86 -36
View File
@@ -278,7 +278,9 @@
# Scripts launched by array_started.sh when the array comes online.
# Launched in order — each as a background process.
# One-shot scripts (ramdisk, syslog, fpm, inotify, network) run and exit naturally.
# Continuous scripts (watchdogs, failover) run until array stops.
# Continuous scripts (failover) run until array stops.
# Watchdogs (resource_manager, docker_watchdog, system_watchdog) are cronned via
# watchdog_orchestrator.sh — NOT launched here.
ARRAY_START_SCRIPTS=(
"git_pull_execute.sh" # pull latest scripts before anything starts
"Transcodes/ramdisk_setup.sh" # creates ramdisk + symlink before Emby starts
@@ -286,9 +288,7 @@
"unRAID_Essentials/php_fpm_max_children.sh" # WebGUI performance tuning
"unRAID_Essentials/inotify_tuning.sh" # bump inotify limits — containers miss events if exhausted
"Docker_Essentials/docker_network_connect.sh" # ensure networks exist + connect containers
"unRAID_Essentials/system_watchdog.sh" # system health monitor — continuous loop
"Docker_Essentials/docker_watchdog.sh" # container health monitor — continuous loop
"Fallback/fallback.sh" # mutual failover — HOST2 back online
"Fallback/fallback.sh" # mutual failover — continuous
)
# ━━━ Intermediate Sync Maintenance ━━━
@@ -1114,12 +1114,85 @@
EMBY_REPORT_DAYS=7 # days to include in the report period
EMBY_REPORT_TOP_N=10 # number of top content items to show
# ==============================================================================================
# ── WATCHDOG ORCHESTRATOR ─────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Runs resource_manager → docker_watchdog → system_watchdog in sequence each cron cycle.
# Schedule: * * * * * (every minute via User Scripts plugin)
# NOT in ARRAY_START_SCRIPTS — has its own cron entry.
WATCHDOG_ORCHESTRATOR_HEARTBEAT=true
WATCHDOG_ORCHESTRATOR_HEARTBEAT_HOURS=1
# ==============================================================================================
# ── RESOURCE MANAGER ──────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Pressure reduction layer — keeps the system comfortable before things break.
# Called by watchdog_orchestrator.sh. Single-pass, not a continuous loop.
#
# ── PRESSURE LEVELS ───────────────────────────────────────────────────────────────────────────
# Level 1 (soft) — throttle SABnzbd + qBit download speeds
# Level 2 (medium) — further throttle + docker pause background containers
# Level 3 (hard) — docker stop optional containers, signal docker_watchdog to defer
#
# ── PER-HOST CONTAINER LISTS ──────────────────────────────────────────────────────────────────
# HOST*_RM_PAUSE_CONTAINERS — docker pause at medium pressure (in master_host*.conf)
# HOST*_RM_STOP_CONTAINERS — docker stop at hard pressure (in master_host*.conf)
RM_ENABLED=true
RM_STATE_FILE="/tmp/resource_manager_state.db"
# ━━━ Pressure Thresholds ━━━
# Graduated RAM response — resource_manager acts before system_watchdog reboots.
# RM_RAM_SOFT_GB > RM_RAM_MEDIUM_GB > RM_RAM_HARD_GB > SYS_WATCHDOG_MEM_GB always
RM_RAM_SOFT_GB=12 # throttle start — reduce background load
RM_RAM_MEDIUM_GB=8 # pause background containers
RM_RAM_HARD_GB=6 # stop optional containers (was SYS_WATCHDOG_MEM_SHUTDOWN_GB)
RM_RAM_RECOVER_GB=20 # RAM must reach this before restoring hard-stopped containers
# Load average thresholds — multiplier × core count
RM_LOAD_SOFT_MULTIPLIER=2.0 # soft pressure: 2× cores sustained
RM_LOAD_MEDIUM_MULTIPLIER=3.0 # medium pressure: 3× cores sustained
# Consecutive runs at lower pressure before de-escalating
RM_RECOVER_CYCLES=3
# ━━━ SABnzbd Throttle ━━━
# Speed values: "50M" = 50 MB/s, "0" = unlimited
RM_SABNZBD_ENABLED=true
RM_SABNZBD_SPEED_SOFT="50M"
RM_SABNZBD_SPEED_MEDIUM="10M"
# ━━━ qBittorrent Throttle ━━━
# KB/s — 0 = unlimited
RM_QBIT_ENABLED=true
RM_QBIT_DL_SOFT=51200 # 50 MB/s
RM_QBIT_DL_MEDIUM=10240 # 10 MB/s
# ━━━ Critical Containers ━━━
# Never paused or stopped regardless of pressure level.
# Keep DNS, auth, media serving, and live TV always running.
RM_CRITICAL_CONTAINERS=(
"NginxProxyManager" # reverse proxy — internet access
"Authelia" # auth — nothing accessible without it
"Authelia-Secondary"
"Mariadb-Authelia" # Authelia dependency
"Mariadb-Authelia-Secondary"
"Redis-Authelia" # Authelia dependency
"Redis-Authelia-Secondary"
"AdGuard-Home" # DNS — all LAN resolution
"Emby" # media server — Live TV buffering
"Dispatcharr" # Live TV scheduler — loses state if stopped
"Dispatcharr-Basic"
"Dispatcharr-Iptv-Users"
)
# ==============================================================================================
# ── SYSTEM WATCHDOG ───────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Continuous system health monitoring — last line of defense before a crash.
# Started by array_started.sh — runs until array stops.
# Re-sources all three conf files each cycle — config changes take effect on next cycle.
# Single-pass system health check — last line of defense before a crash.
# Called by watchdog_orchestrator.sh every minute. NOT started by array_started.sh.
# Re-sources config at each orchestrator run — config changes take effect immediately.
#
# ── THREE-TIER RESPONSE SYSTEM ────────────────────────────────────────────────────────────────
# CRITICAL — bypass ALL strikes, reboot immediately
@@ -1132,16 +1205,9 @@
# STANDARD — strike system (N consecutive failures → reboot)
# RAM tiers, high load, CPU temp, zombies, /var/log, /tmp, containers
#
# ── RAM TIERS ─────────────────────────────────────────────────────────────────────────────────
# MEM_WARN_GB — warn + notify only (informational)
# MEM_SHUTDOWN_GB — stop non-essential containers, recover above MEM_RECOVER_GB
# MEM_GB — strike system → reboot (or bypass with OOM)
#
# ── CONTAINER SHUTDOWN ────────────────────────────────────────────────────────────────────────
# At MEM_SHUTDOWN_GB: stop all containers NOT in MEM_SHUTDOWN_EXCLUDED list
# Excluded containers stay running — DNS, auth, Emby, Dispatcharr
# Stopped containers stay stopped until RAM recovers above MEM_RECOVER_GB
# Strike list applied — doesn't flip-flop every cycle
# ── RAM ───────────────────────────────────────────────────────────────────────────────────────
# SYS_WATCHDOG_MEM_GB — strike system → reboot (or OOM bypass)
# Warn/shutdown/recover RAM tiers are handled by resource_manager.sh
# ━━━ State Files ━━━
SYS_WATCHDOG_STATE_FILE="/tmp/system_watchdog_state.db" # /tmp — resets on reboot ✅
@@ -1167,26 +1233,10 @@
SYSTEM_WATCHDOG_HEARTBEAT=true
SYSTEM_WATCHDOG_HEARTBEAT_HOURS=1
# ━━━ RAM Tiers ━━━
# Three-level RAM response — graduated action instead of single threshold.
# HOST1 has 128GB, HOST2 has 64GB — adjust accordingly.
# MEM_WARN_GB > MEM_SHUTDOWN_GB > MEM_GB always
SYS_WATCHDOG_MEM_WARN_GB=10 # warn + notify — informational only
SYS_WATCHDOG_MEM_SHUTDOWN_GB=6 # stop non-essential containers
# ━━━ RAM Reboot Threshold ━━━
# Reboot trigger only — warn/shutdown/recover handled by resource_manager.sh
# RM_RAM_HARD_GB > SYS_WATCHDOG_MEM_GB always (RM acts before watchdog reboots)
SYS_WATCHDOG_MEM_GB=4 # strike system → reboot
SYS_WATCHDOG_MEM_RECOVER_GB=30 # RAM must recover above this before restarting containers
# Containers excluded from RAM emergency shutdown.
# These stay running regardless of RAM pressure.
# DNS and auth must stay up, Emby and Dispatcharr for Live TV continuity.
SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED=(
"NginxProxyManager" # DNS / reverse proxy — internet access
"Authelia" # auth — without this nothing is accessible
"Mariadb" # Authelia dependency
"Redis" # Authelia dependency
"Emby" # media server — Live TV buffering
"Dispatcharr" # Live TV scheduler — loses state if stopped
)
# ━━━ OOM Bypass Settings ━━━
# OOM bypass: if RAM is critically low AND kernel OOM kills exceed this threshold