feat: slskd reconnect guard in downloaders_reset, mass v2 sync

- downloaders_reset: connection check block before slskd API sections;
  triggers PUT /api/v0/server reconnect if disconnected, polls 60s,
  gates Stuck Searches and Dead Transfer Records on SLSKD_CONNECTED
- Sync all modified/new/deleted files from v2 refactor across Docker_Essentials,
  Media, Monitors, Partnership, Rsync, Tools, Transcodes, unRAID_Essentials,
  common.sh, master confs, and new Manual/README docs
This commit is contained in:
Gmer4Lfe
2026-05-19 20:00:10 -04:00
parent 5cb16d4b18
commit e13f2fa14f
81 changed files with 12164 additions and 10656 deletions
@@ -0,0 +1,768 @@
# ━━━━━ UNRAID ESSENTIALS — Manual ━━━━━
Configuration reference, operational procedures, and troubleshooting for
system-level scripts. Read the ARRAY_START_SCRIPTS order section before
adding or reordering scripts at array start.
---
## ━━━ CONTENTS ━━━
- [ARRAY_START_SCRIPTS Order](#array_start_scripts-order)
- [system_watchdog.sh](#system_watchdogsh)
- [resource_watchdog.sh](#resource_watchdogsh)
- [webgui_restart.sh](#webgui_restartsh)
- [inotify_tuning.sh](#inotify_tuningsh)
- [php_fpm_max_children.sh](#php_fpm_max_childrensh)
- [docker_syslog_filter.sh](#docker_syslog_filtersh)
- [clear_logs.sh](#clear_logssh)
- [mover_stop.sh](#mover_stopsh)
- [rsync_stop.sh](#rsync_stopsh)
- [user_scripts_stop.sh](#user_scripts_stopsh)
- [server_reboot.sh](#server_rebootsh)
- [Full Configuration Reference](#full-configuration-reference)
- [Troubleshooting](#troubleshooting)
---
## ARRAY_START_SCRIPTS Order
> **The order of scripts in ARRAY_START_SCRIPTS matters for three of these
> scripts.** Getting it wrong causes subtle failures that don't show up
> immediately.
```bash
# master.conf
ARRAY_START_SCRIPTS=(
"inotify_tuning.sh" # 1 — FIRST: kernel limits must be set before
# any container starts. Containers inherit
# inotify limits at launch, not dynamically.
"docker_syslog_filter.sh" # 2 — SECOND: before any veth interfaces are
# created. If a container starts first, its
# veth creation is already in syslog.
"php_fpm_max_children.sh" # 3 — before WebGUI is under load
"ramdisk_setup.sh" # (from Transcodes/) before Emby starts
...
"system_watchdog.sh" # LAST or near-last — starts background loop
)
```
Why inotify FIRST: If Code-Server starts before limits are raised, it inherits
the old low limits. The limits are kernel-wide — a restart of Code-Server picks
up the new values, but it's a manual step. Avoid by running inotify_tuning.sh first.
Why docker_syslog_filter SECOND: The filter must be in place before any container
starts creating veth interfaces. The first container start after array start
generates veth messages — these will appear in syslog if the filter isn't active.
---
## system_watchdog.sh
### Three-Tier Response
The watchdog categorizes every failure into one of three tiers:
**Tier 1 — CRITICAL (immediate reboot, no strikes)**
| Condition | Threshold | Why immediate |
|-----------|-----------|---------------|
| Docker daemon unresponsive | N/A | Nothing can be healed; every docker command hangs |
| rootfs usage | SYS_WATCHDOG_ROOTFS_CRITICAL_PCT (99%) | SSH stops; state files fail silently |
| Kernel oops/BUG in dmesg | delta > 0 | Kernel running with corrupted state |
| File descriptor exhaustion | SYS_WATCHDOG_FD_CRITICAL_PCT (95%) | New connections silently failing |
| /boot read-only unexpectedly | write test fails | Config writes silently failing |
**Tier 2 — URGENT (bypass strikes with OOM confirmation)**
RAM below MEM_GB AND OOM kills this cycle >= SYS_WATCHDOG_OOM_LIMIT.
Both conditions required — RAM alone without OOM uses the standard strike system.
OOM confirms the system is dying faster than watchdogs can heal.
**Tier 3 — STANDARD (SYS_WATCHDOG_STRIKES consecutive failures → reboot)**
| Check | Threshold |
|-------|-----------|
| Free RAM | MEM_WARN_GB → MEM_SHUTDOWN_GB → MEM_GB |
| Load average | SYS_WATCHDOG_LOAD_MULTIPLIER × cpu_count |
| CPU temperature | SYS_WATCHDOG_CPU_TEMP |
| Zombie processes | SYS_WATCHDOG_ZOMBIES |
| /var/log usage | SYS_WATCHDOG_VAR_LOG_PCT |
| /tmp usage | SYS_WATCHDOG_TMP_PCT |
| Array disk errors | mdstat error delta > 0 |
| NIC state | interface operstate != "up" |
| Required containers | containers in SYS_WATCHDOG_REQUIRED_CONTAINERS |
### RAM Tiers
```
MEM_WARN_GB (10GB) → warn + notify, no action
MEM_SHUTDOWN_GB (6GB) → stop non-essential containers, wait for recovery
MEM_GB (4GB) → strike → reboot (URGENT bypass with OOM)
MEM_RECOVER_GB (30GB) → RAM must reach this before stopped containers restart
```
At MEM_SHUTDOWN_GB, all containers NOT listed in
`SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED` are stopped. Excluded containers by default:
NginxProxyManager, Authelia, Mariadb, Redis, Emby, Dispatcharr. Adjust in
master.conf for your critical services.
### Abort Conditions
These conditions prevent a reboot — running them would cause data loss:
```bash
SYS_WATCHDOG_ABORT_ON_ZFS_UNHEALTHY=true # ZFS pool degraded/faulted
SYS_WATCHDOG_ABORT_ON_PARITY=true # Parity check/rebuild running
SYS_WATCHDOG_ABORT_ON_MOVER=true # Mover running
```
CRITICAL tier bypasses all abort conditions — an imminent crash outweighs
data safety concerns.
### Strike System
The strike count tracks consecutive failures. A single recovery resets strikes
to 0. The reboot only fires after SYS_WATCHDOG_STRIKES consecutive failures on
the same check — transient spikes (a brief load burst, a momentary RAM dip) don't
trigger reboots.
### Reboot Rate Limit
```bash
SYS_WATCHDOG_REBOOT_WINDOW_HRS=2 # window in hours
SYS_WATCHDOG_MAX_REBOOTS=3 # max reboots within the window
```
If the server has rebooted SYS_WATCHDOG_MAX_REBOOTS times within the window,
system_watchdog stops rebooting and notifies instead. This prevents a boot loop
where the watchdog reboots → something crashes again immediately → reboot again.
### Usage
```bash
system_watchdog.sh # start continuous monitoring loop
system_watchdog.sh --dry-run # run detection logic without rebooting
system_watchdog.sh --status # show thresholds, current state, strike counts
system_watchdog.sh --log # verbose per-cycle output
```
### Verify Running
```bash
pgrep -a -f system_watchdog.sh
# Expected: shows PID and path
```
---
## resource_watchdog.sh
### Pressure Levels
```bash
# master.conf
RW_RAM_SOFT_GB=20 # Level 1 trigger — throttle downloaders
RW_RAM_MEDIUM_GB=15 # Level 2 trigger — throttle + pause containers
RW_RAM_HARD_GB=10 # Level 3 trigger — stop containers
RW_RAM_RECOVER_GB=25 # recover to this before un-stopping at level 3
RW_LOAD_SOFT_MULTIPLIER=2.0 # load > 2× cpu count = level 1
RW_LOAD_MEDIUM_MULTIPLIER=3.0 # load > 3× cpu count = level 2
RW_RECOVER_CYCLES=3 # consecutive under-threshold runs before de-escalating
```
### Per-Host Container Lists
```bash
# master_host1.conf
HOST1_RW_PAUSE_CONTAINERS=("Tdarr" "HandBrake") # paused at medium pressure
HOST1_RW_STOP_CONTAINERS=("LocalAI" "Satisfactory") # stopped at hard pressure
```
Containers in `RW_CRITICAL_CONTAINERS` are never paused or stopped regardless of
pressure level. Default includes: Emby, NginxProxyManager, Authelia, Mariadb, Redis.
### docker_watchdog Coordination
At level 3, resource_watchdog writes `mem_shutdown_active=true` to `RW_STATE_FILE`.
docker_watchdog.sh reads this flag each cycle and skips all container restart logic
while it is set. Without this coordination, docker_watchdog would immediately
restart containers that resource_watchdog just stopped to free RAM.
The flag is cleared when level 3 pressure resolves and containers are restarted.
### Usage
```bash
resource_watchdog.sh # single pass (called by watchdog_orchestrator.sh)
resource_watchdog.sh --dry-run # show what would be throttled/paused/stopped
resource_watchdog.sh --status # current level, active actions, recovery cycle count
resource_watchdog.sh --log # verbose per-check output
```
---
## webgui_restart.sh
### Escalation Logic
```
curl $WEBGUI_URL → 200 OK → exit 0 (silent)
Not responding:
1. /etc/rc.d/rc.nginx restart
wait WEBGUI_NGINX_WAIT (15s) → recheck
→ recovered: notify, exit 0
2. /etc/rc.d/rc.php-fpm restart
wait WEBGUI_PHP_WAIT (10s) → recheck
→ recovered: notify, exit 0
3. /usr/local/sbin/emhttp stop && start
wait WEBGUI_EMHTTP_WAIT (30s) → recheck
→ recovered: notify, exit 0
All three failed → notify warning, exit 1
```
### Configuration
```bash
WEBGUI_URL="http://localhost" # URL to check
WEBGUI_TIMEOUT=5 # curl timeout in seconds
WEBGUI_NGINX_WAIT=15 # seconds after nginx restart before recheck
WEBGUI_PHP_WAIT=10 # seconds after php-fpm restart before recheck
WEBGUI_EMHTTP_WAIT=30 # seconds after emhttp restart before recheck
```
### WebGUI Frozen — Manual Recovery
```bash
# Check which services are running:
webgui_restart.sh --status
# Try manual restart sequence (same as the script):
/etc/rc.d/rc.nginx restart
# wait 15s, then:
curl -sf --max-time 5 http://localhost >/dev/null && echo "OK" || echo "still down"
# If nginx didn't fix it, php-fpm:
/etc/rc.d/rc.php-fpm restart
# If still down, emhttp:
/usr/local/sbin/emhttp stop && /usr/local/sbin/emhttp start
# If all three failed:
server_reboot.sh --status # check for active sessions first
```
---
## inotify_tuning.sh
### What It Sets
```bash
INOTIFY_MAX_INSTANCES=1024 # max inotify fd objects per user (default: 128)
INOTIFY_MAX_WATCHES=1048576 # max watches shared across all users (default: 8192)
INOTIFY_MAX_QUEUED_EVENTS=32768 # max buffered events (default: 16384)
```
Verify current values:
```bash
inotify_tuning.sh --status
# Shows current vs target for each limit, active instance count, top consumers
```
### If Code-Server Shows "Unable to Watch for File Changes"
```bash
# 1. Verify limits are set:
sysctl fs.inotify.max_user_watches
# Expected: 1048576
# 2. Check total usage across all containers:
find /proc/*/fd -lname 'anon_inode:inotify' 2>/dev/null | wc -l
# 3. If limits are set but Code-Server still shows the error:
docker restart Code-Server
# Running containers inherit limits at launch. Restart picks up the new values.
# 4. If limits are NOT set (inotify_tuning.sh hasn't run yet):
inotify_tuning.sh --log
```
---
## php_fpm_max_children.sh
### What It Sets
```bash
PHP_MAX_CHILDREN=250 # target pm.max_children (default: 4-8 on unRAID)
PHP_CONF="/etc/php83/php-fpm.d/www.conf"
```
250 workers × ~2MB per worker = ~500MB total. On 128GB this is trivially small.
The default of 48 saturates immediately under load on a busy server.
### Verify
```bash
php_fpm_max_children.sh --status
# Shows current value vs target, PHP-FPM worker count
# Manual verify:
grep "^pm.max_children" /etc/php83/php-fpm.d/www.conf
# Expected: pm.max_children = 250
```
### If WebGUI Is Slow Despite the Setting
```bash
# Check PHP-FPM worker utilization (requires system_tuning_monitor.sh in Monitors/):
# Look at the webgui_restart.sh escalation — step 2 (php-fpm restart) is specifically
# for worker exhaustion. If webgui_restart.sh is regularly hitting step 2, the
# pm.max_children value may still be too low, or there's a PHP worker leak.
# Check running worker count:
pgrep -fc php-fpm
# Compare to pm.max_children — if equal, workers are saturated
# Increase if needed:
# master.conf: PHP_MAX_CHILDREN=350
# Then: php_fpm_max_children.sh --log (will update and restart php-fpm)
```
---
## docker_syslog_filter.sh
### What It Creates
```
/etc/rsyslog.d/ignore-docker-veth.conf:
if ($msg contains "veth" or $msg contains "docker0") then {
stop
}
```
This drops any syslog message containing "veth" or "docker0" before it reaches
any output target, including the log file. The drop rule is applied at rsyslog
level — not at the log viewer level.
### Verify
```bash
docker_syslog_filter.sh --status
# Shows filter file content and rsyslog process state
# Manual verify:
cat /etc/rsyslog.d/ignore-docker-veth.conf
pgrep -x rsyslogd && echo "rsyslog running" || echo "rsyslog NOT running"
# Test the filter is active (should produce no syslog output):
logger "test veth message"
grep "test veth" /var/log/syslog 2>/dev/null || echo "filtered correctly"
```
### If Syslog Still Has Veth Noise
```bash
# 1. Verify filter file exists with correct content:
docker_syslog_filter.sh --status
# 2. If content differs — re-apply:
docker_syslog_filter.sh --log
# 3. Verify rsyslog is using the conf.d directory:
grep -r "IncludeConfig" /etc/rsyslog.conf
# Expected: IncludeConfig /etc/rsyslog.d/*.conf (or similar)
```
---
## clear_logs.sh
### Thresholds
```bash
LOG_MIN_SIZE_MB=10 # skip system log if under this — keep recent history
LOG_DOCKER_MAX_MB=100 # clear Docker container log only if over this
LOG_FILES=(
"/var/log/syslog"
"/var/log/messages"
"/var/log/dmesg"
)
```
### Why Truncation Not Deletion
unRAID writes logs to tmpfs (`/var/log`). Truncation (`: > file`) keeps the file
descriptor open and valid while emptying content — syslogd continues writing to
the same fd without interruption. Deleting the file would orphan the file
descriptor and syslog would stop writing until restarted.
### Identifying Large Docker Logs
```bash
clear_logs.sh --status
# Shows top 10 Docker logs by size, current size vs threshold
# Find the biggest log manually:
du -sh /var/lib/docker/containers/*/*.log 2>/dev/null | sort -rh | head -5
```
---
## mover_stop.sh
### Stop Sequence
```
1. Check if mover is running (pgrep "emhttp.*Mover") → exit cleanly if not
2. Wall message to all logged-in terminal users
3. Wait MOVER_STOP_TIMEOUT seconds (default: 30)
4. SIGTERM — mover finishes its current file operation, then stops
5. Wait 5 seconds → verify stopped
6. SIGKILL if still running — forced stop, partial files possible
7. Final verify — error if still running after SIGKILL
```
SIGTERM first because the mover can finish the file it is currently moving,
leaving no partial copies split across cache and array. SIGKILL is a last resort.
### Configuration
```bash
MOVER_STOP_TIMEOUT=30 # seconds between wall warning and SIGTERM
```
### Usage
```bash
mover_stop.sh # check and stop if running
mover_stop.sh --status # show current mover state and PID
mover_stop.sh --dry-run # show what would happen without stopping
```
---
## rsync_stop.sh
### Auto-Detection Logic
rsync_stop.sh detects whether an orchestrator script (daily/weekly/critical sync)
is the parent of the running rsync process by scanning lock files in `$LOCK_DIR`.
**Default behavior (orchestrator detected):**
Kills only the rsync subprocess. The orchestrator sees rsync died, moves to the
next share or exits cleanly. The orchestrator is NOT killed — it can still clean up.
**Default behavior (no orchestrator):**
Kills rsync directly (standalone rsync.sh run).
**--full-stop:**
Kills the orchestrator first, then kills rsync. Nothing continues after this.
Use when everything needs to stop immediately.
### Container Recovery
After killing rsync, the script checks all containers in `PROFILE_CRITICAL_CONTAINER_NAMES`
for any that were stopped by the interrupted rsync session and restarts them.
Remote containers are left for docker_watchdog.sh to recover.
Skip container recovery with `--rsync-only` — used when called by other scripts
that handle recovery themselves.
### Usage
```bash
rsync_stop.sh # smart stop (auto-detect orchestrator)
rsync_stop.sh --full-stop # kill orchestrator + rsync
rsync_stop.sh --rsync-only # kill rsync, skip container recovery
rsync_stop.sh --status # show local and remote rsync state
rsync_stop.sh --dry-run # preview without changes
rsync_stop.sh --full-stop --dry-run # preview full stop
```
---
## user_scripts_stop.sh
### Process Identification
Scans `/proc/*/cmdline` for any process whose command line contains
`/tmp/user.scripts`. The unRAID User Scripts plugin stages all scripts in
`/tmp/user.scripts/` before execution — this signature is reliable regardless of
what the script is named or how it was launched.
Script names are extracted from the path for display: you see which scripts are
being stopped, not just PIDs.
### Self-Exclusion
If this script is run via the User Scripts plugin, it would find its own PID in
the scan. It excludes both `$$` (its own PID) and `$PPID` (its parent process)
from the kill list.
### Usage
```bash
user_scripts_stop.sh # stop all User Script processes
user_scripts_stop.sh --status # show running scripts with names and elapsed time
user_scripts_stop.sh --dry-run # show what would be stopped
```
---
## server_reboot.sh
### Full Shutdown Sequence
```
1. Pre-flight checks (warn only — do not block):
- rsync running? → warn, suggest rsync_stop.sh first
- mover running? → warn, suggest mover_stop.sh first
- Emby sessions? → warn (active streams will be interrupted)
2. Wall message to all logged-in terminal users
3. unRAID dashboard notification
4. Wait REBOOT_SLEEP seconds (default: 30)
5. Graceful VM shutdown:
virsh shutdown <each VM> (ACPI signal — clean shutdown)
Wait REBOOT_VM_WAIT seconds (default: 30) for VMs to respond
6. /etc/rc.d/rc.libvirt stop (VM Manager)
7. /etc/rc.d/rc.docker stop (all containers stop)
8. sync (flush filesystem buffers to disk)
9. /sbin/reboot
```
### Configuration
```bash
REBOOT_SLEEP=30 # seconds between warning and shutdown sequence
REBOOT_VM_WAIT=30 # seconds to wait for VMs to shut down gracefully
```
### Recommended Pre-Reboot Sequence
For a clean reboot when services are active:
```bash
rsync_stop.sh # stop any active rsync (smart mode)
mover_stop.sh # stop mover gracefully
server_reboot.sh --status # check what's still running
server_reboot.sh --reason="planned maintenance"
```
### Usage
```bash
server_reboot.sh # reboot with 30s warning
server_reboot.sh --dry-run # walk through without rebooting
server_reboot.sh --status # show what would be affected
server_reboot.sh --reason="disk work" # include reason in notification
```
---
## Full Configuration Reference
```bash
# master.conf
# ── System Watchdog ────────────────────────────────────────────────────────────
SYS_WATCHDOG_INTERVAL=300 # seconds between check cycles
SYS_WATCHDOG_STRIKES=3 # consecutive failures before reboot
SYS_WATCHDOG_REBOOT_WINDOW_HRS=2 # rate limit window
SYS_WATCHDOG_MAX_REBOOTS=3 # max reboots in window
SYS_WATCHDOG_OOM_LIMIT=3 # OOM kills/cycle for URGENT bypass
MEM_WARN_GB=10 # warn + notify
MEM_SHUTDOWN_GB=6 # stop non-essential containers
MEM_GB=4 # strike → reboot
MEM_RECOVER_GB=30 # recovery threshold
SYS_WATCHDOG_ROOTFS_CRITICAL_PCT=99 # Tier 1 trigger
SYS_WATCHDOG_FD_CRITICAL_PCT=95 # Tier 1 trigger
SYS_WATCHDOG_LOAD_MULTIPLIER=4 # Tier 3 — ×cpu_count
SYS_WATCHDOG_CPU_TEMP=85 # Tier 3 — Celsius
SYS_WATCHDOG_ZOMBIES=20 # Tier 3 — process count
SYS_WATCHDOG_VAR_LOG_PCT=80 # Tier 3 — percent full
SYS_WATCHDOG_TMP_PCT=85 # Tier 3 — percent full
SYS_WATCHDOG_ABORT_ON_ZFS_UNHEALTHY=true
SYS_WATCHDOG_ABORT_ON_PARITY=true
SYS_WATCHDOG_ABORT_ON_MOVER=true
SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED=(
"NginxProxyManager" "Authelia" "Mariadb" "Redis" "Emby" "Dispatcharr"
)
SYS_WATCHDOG_REQUIRED_CONTAINERS=() # containers that must be running
# State files:
SYS_WATCHDOG_STATE_FILE="/tmp/sys_watchdog_state.db"
SYS_WATCHDOG_REBOOT_LOG="/tmp/sys_watchdog_reboots.db"
SYS_WATCHDOG_FAILED_FILE="/tmp/sys_watchdog_failed.db"
SYS_WATCHDOG_OOM_FILE="/tmp/sys_watchdog_oom.db"
# ── Resource Watchdog ──────────────────────────────────────────────────────────
RW_ENABLED=true
RW_STATE_FILE="/tmp/resource_watchdog_state.db"
RW_RAM_SOFT_GB=20
RW_RAM_MEDIUM_GB=15
RW_RAM_HARD_GB=10
RW_RAM_RECOVER_GB=25
RW_LOAD_SOFT_MULTIPLIER=2.0
RW_LOAD_MEDIUM_MULTIPLIER=3.0
RW_RECOVER_CYCLES=3
RW_SABNZBD_ENABLED=true
RW_SABNZBD_SPEED_SOFT="50M"
RW_SABNZBD_SPEED_MEDIUM="10M"
RW_QBIT_ENABLED=true
RW_QBIT_DL_SOFT=51200 # KB/s
RW_QBIT_DL_MEDIUM=10240
RW_CRITICAL_CONTAINERS=("Emby" "NginxProxyManager" "Authelia" "Mariadb" "Redis")
# ── Per-Host (master_host*.conf) ───────────────────────────────────────────────
HOST1_RW_PAUSE_CONTAINERS=("Tdarr" "HandBrake")
HOST1_RW_STOP_CONTAINERS=("LocalAI" "Satisfactory")
HOST1_SABNZBD_URL="http://localhost:8080"
HOST1_SABNZBD_API_KEY="your-api-key"
HOST1_QBIT_URL="http://localhost:8090"
HOST1_QBIT_USERNAME="admin"
HOST1_QBIT_PASSWORD="your-password"
# ── WebGUI Watchdog ────────────────────────────────────────────────────────────
WEBGUI_URL="http://localhost"
WEBGUI_TIMEOUT=5
WEBGUI_NGINX_WAIT=15
WEBGUI_PHP_WAIT=10
WEBGUI_EMHTTP_WAIT=30
# ── inotify Tuning ─────────────────────────────────────────────────────────────
INOTIFY_MAX_INSTANCES=1024
INOTIFY_MAX_WATCHES=1048576
INOTIFY_MAX_QUEUED_EVENTS=32768
# ── PHP-FPM ────────────────────────────────────────────────────────────────────
PHP_MAX_CHILDREN=250
PHP_CONF="/etc/php83/php-fpm.d/www.conf"
# ── Syslog Filter ──────────────────────────────────────────────────────────────
FILTER_FILE="/etc/rsyslog.d/ignore-docker-veth.conf"
# ── Log Cleaner ────────────────────────────────────────────────────────────────
LOG_FILES=("/var/log/syslog" "/var/log/messages" "/var/log/dmesg")
LOG_MIN_SIZE_MB=10
LOG_DOCKER_MAX_MB=100
# ── Mover Stop ─────────────────────────────────────────────────────────────────
MOVER_STOP_TIMEOUT=30
# ── Server Reboot ──────────────────────────────────────────────────────────────
REBOOT_SLEEP=30
REBOOT_VM_WAIT=30
```
---
## Troubleshooting
### system_watchdog.sh Not Starting
```bash
# Check if already running (acquire_lock prevents second instance):
pgrep -a -f system_watchdog.sh
# Check state file permissions:
ls -la /tmp/sys_watchdog_*.db
# Run with --log to see startup:
system_watchdog.sh --log
```
### system_watchdog Rebooted Unexpectedly
```bash
# Check reboot log:
cat /tmp/sys_watchdog_reboots.db
# Shows timestamp and reason for each watchdog-triggered reboot
# Check what condition triggered it:
# Look in /var/log/syslog for "system_watchdog" near the reboot time
grep "system_watchdog" /var/log/syslog | tail -20
```
### resource_watchdog Paused Containers It Shouldn't Have
```bash
# Check current state:
resource_watchdog.sh --status
# Add the container to RW_CRITICAL_CONTAINERS in master.conf:
RW_CRITICAL_CONTAINERS=("Emby" "NginxProxyManager" "Authelia" "Mariadb" "Redis" "MyContainer")
# Un-pause manually if needed:
docker unpause MyContainer
```
### rsync_stop Killed the Wrong Thing
If --full-stop killed an orchestrator you didn't intend to kill:
```bash
# Next time use default mode (no --full-stop) to kill only rsync subprocess.
# To verify what would be killed before running:
rsync_stop.sh --status # shows running rsync and detected orchestrators
rsync_stop.sh --dry-run # shows smart mode decision
rsync_stop.sh --full-stop --dry-run # shows full-stop decision
```
### WebGUI Recovery After All Three Steps Failed
```bash
# Check if processes are running:
pgrep -x nginx && echo "nginx: yes" || echo "nginx: no"
pgrep emhttpd && echo "emhttp: yes" || echo "emhttp: no"
pgrep -f php-fpm && echo "php-fpm: yes" || echo "php-fpm: no"
# Check recent nginx errors:
cat /var/log/nginx/error.log | tail -20
# Check emhttp log:
tail -20 /var/log/syslog | grep emhttp
# Last resort — reboot:
server_reboot.sh --reason="WebGUI unrecoverable"
```
### PHP-FPM Config Not Found After unRAID Update
unRAID updates occasionally change the PHP version. If `php_fpm_max_children.sh`
errors with "config file not found":
```bash
# Find the new config path:
find /etc -name "www.conf" 2>/dev/null
# Update PHP_CONF in master.conf:
PHP_CONF="/etc/php84/php-fpm.d/www.conf" # example for php84
# Verify with:
php_fpm_max_children.sh --status
```
+153 -784
View File
@@ -1,807 +1,176 @@
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 🖥️ UNRAID ESSENTIALS
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ━━━━━ UNRAID ESSENTIALS ━━━━━
**System-level scripts that act on the unRAID server itself — not containers, not
media, not monitoring.** Keeping the server stable under load, recovering a frozen
WebGUI, tuning kernel limits, suppressing log noise, and handling graceful shutdowns
and reboots with proper warning sequences.
**System-level scripts that act on the unRAID server itself — not containers,
not media, not monitoring.** Keeping the server stable under load, recovering a
frozen WebGUI, tuning kernel limits, suppressing log noise, and handling graceful
shutdowns with proper warning sequences.
---
## ━━━ THE PROBLEM THAT BUILT THIS ━━━
**Server Getting Into Unstable States With No Recovery Path**
A container has a memory leak. RAM drops to 2GB. The system starts swapping. Docker
watchdog tries to restart the container — but Docker itself is barely responding.
The restart hangs. The server needs a reboot, but nothing in the ecosystem is
authorized to call one. Or: rootfs fills to 99%. SSH stops working. Docker can't
write log files. The server is functionally dead but still technically running.
Fix: `system_watchdog.sh` — three-tier response: immediate reboot on critical
failures, OOM-confirmed bypass for RAM crises, strike system for sustained
threshold breaches. Last line of defense before a hard crash.
**WebGUI Freezing and Nobody Noticing**
The WebGUI becomes unresponsive. Nginx gets into a bad state, or PHP-FPM workers
are saturated, or emhttp has frozen. From a user perspective: dashboard doesn't
load, settings don't save, containers can't be started or stopped via the UI. No
container-level alert fires because this isn't a container problem — it's a web
server problem. By the time someone notices it may have been broken for hours.
Fix: `webgui_restart.sh` — checks every 10 minutes, escalates through nginx →
php-fpm → emhttp. Lightest fix first. Silent when healthy.
**50+ Containers Starting and Filling Syslog With Veth Noise**
Array starts. 50+ containers come up simultaneously. Docker creates a virtual
network interface for each one. Each interface generates multiple syslog entries.
In the first minute after array start, syslog is buried under 200400 lines of
`veth renamed from eth0` and `docker0: port entered forwarding state`. Real events
— a failed mount, a permission error, a service that didn't start — are invisible.
Fix: `docker_syslog_filter.sh` — creates an rsyslog drop rule before any container
starts. Applied at array start. Idempotent — silent when already correct.
**WebGUI Queuing Requests Under Load Without Explanation**
The WebGUI feels slow. Clicking a button takes 5 seconds. Nothing in the logs
explains it. The cause: PHP-FPM's `pm.max_children` defaults to 48 workers. With
multiple users, active plugins, and 50+ containers potentially hitting the WebGUI,
those workers saturate immediately. New requests queue behind active ones.
Fix: `php_fpm_max_children.sh` — sets `pm.max_children=250` at array start.
250 workers × ~2MB = ~500MB total. On 128GB this is trivially small.
**inotify Exhaustion Producing Unexplained Failures**
When inotify limits are exhausted, containers silently stop receiving filesystem
events. Arrs don't detect completed downloads. VSCode shows "unable to watch for
file changes." Code-Server with node_modules alone can consume 100K200K watches,
and all containers share the same pool.
Fix: `inotify_tuning.sh` — raises all three inotify limits at array start. Must
run FIRST in ARRAY_START_SCRIPTS before any containers start.
**Mover Getting Killed Mid-Transfer Leaving Files Inconsistent**
The mover is running — moving a large batch of files from cache to array. A reboot
is triggered. The mover stops mid-file. The file exists partially on both cache and
array simultaneously. unRAID's deduplication layer is confused.
Fix: `mover_stop.sh` — warns users via wall message, waits the configured timeout,
SIGTERM (graceful — finishes current file), SIGKILL only if needed.
---
## ━━━ WHAT THIS FOLDER DOES ━━━
```
unRAID_Essentials/ ← acts on the server itself (this folder)
Docker_Essentials/ ← acts on containers
Media/ ← acts on the library
Monitors/ ← observes and reports
```
> **The escalation chain matters here.** Docker_Essentials handles container-level
> problems. unRAID_Essentials handles server-level problems. The watchdogs are
> designed to work together — docker_watchdog.sh heals containers first,
> system_watchdog.sh reboots only when healing has failed.
---
## ━━━ THE PROBLEM THAT BUILT THIS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
---
### 🔴 Server Getting Into Unstable States With No Recovery Path
A container has a severe memory leak. RAM drops to 2GB. The system starts swapping.
Everything slows down. Docker watchdog tries to restart the container — but Docker
itself is barely responding. The restart hangs. The watchdog is stuck. Nothing is
getting better. The server needs a reboot, but nothing in the ecosystem is authorised
to call one.
Or: rootfs fills to 99%. SSH stops working. Docker can't write log files. The WebGUI
shows nothing useful. The server is functionally dead but still technically running.
Again — needs a reboot, nothing calls one.
The fix: `system_watchdog.sh` — the last line of defense. Three-tier response:
immediate reboot on critical failures, OOM-confirmed bypass for RAM crises, and a
strike system for sustained threshold breaches. When everything else has failed,
system_watchdog reboots cleanly before a hard crash happens.
---
### 🔴 WebGUI Freezing and Nobody Noticing
The WebGUI becomes unresponsive. Nginx gets into a bad state. Or PHP-FPM workers are
saturated and new requests are queueing indefinitely. Or emhttp itself has frozen.
From a user perspective: dashboard doesn't load, settings don't save, containers
can't be started or stopped via the UI.
Nothing in the container stack alerts on a frozen WebGUI — it's not a container
problem, it's a web server problem. The only way to know is if someone tries to use
the UI and notices. By which point it may have been broken for hours.
The fix: `webgui_restart.sh` — checks every 10 minutes, escalates through nginx →
php-fpm → emhttp. Lightest fix first. Notifies on any restart so you know it happened.
Silent when healthy — 144 runs per day with no output is the correct behaviour.
---
### 🔴 50+ Containers Starting and Filling Syslog With Veth Noise
Array starts. 50+ containers come up simultaneously. Docker creates a virtual network
interface for each one. Each creation generates multiple syslog entries. In the first
few minutes after array start the syslog is buried under hundreds of lines of:
```
kernel: veth2a3b4c5: renamed from eth0
kernel: docker0: port 1(veth2a3b4c5) entered blocking state
kernel: docker0: port 1(veth2a3b4c5) entered forwarding state
```
Real events — a failed mount, a permission error, a service that didn't start —
are invisible in this noise. And on a busy server that restarts containers regularly,
this noise continues throughout the day.
The fix: `docker_syslog_filter.sh` — creates an rsyslog drop rule before any
container starts. Applied at array start, idempotent, silent when already correct.
---
### 🔴 WebGUI Queueing Requests Under Load Without Explanation
The WebGUI feels slow. Clicking a button takes 5 seconds. Saving settings seems to
hang. Nothing in the logs explains it. Container starts from the UI timeout. The
server itself is not under load — CPU is fine, RAM is fine.
The cause: PHP-FPM's `pm.max_children` defaults to 4-8 workers. On a server with
multiple users, active plugins, automated tools polling the API, and 50+ containers
all potentially hitting the WebGUI simultaneously, those 4-8 workers saturate
immediately. New requests queue behind active ones. Everything feels slow.
The fix: `php_fpm_max_children.sh` — sets `pm.max_children=250` at array start.
250 workers × ~40MB = ~10GB worst case. On 128GB this is trivially small. The
WebGUI becomes responsive immediately. Idempotent — silent when already correct.
---
### 🔴 inotify Exhaustion Producing Unexplained Failures
Already documented in README-Monitors.md (system_tuning_monitor.sh section). Short
version: when inotify limits are exhausted, containers silently stop receiving file
system events. Downloads complete but arrs don't detect them. The kernel hits the
limit and new watches fail silently. VSCode shows "unable to watch for file changes"
and misses edits.
The fix: `inotify_tuning.sh` — raises all three inotify limits at array start.
1M watches (raised from 512K — Code-Server with node_modules needs this), 1024
instances, 32768 queued events. The startup race note: if Code-Server starts before
this runs, it inherits old limits. Restart Code-Server if the VSCode error appears
after limits are applied.
---
### 🔴 Mover Getting Killed Mid-Transfer Leaving Files Inconsistent
The mover is running — moving a large batch of files from cache to array. Someone
clicks reboot from the UI. Or a script kills the mover process directly. The mover
stops mid-file. The file exists partially on both cache and array simultaneously.
unRAID's deduplication layer is confused. The file is inaccessible.
The fix: `mover_stop.sh` — warns logged-in users via wall message, waits the
configured timeout, then sends SIGTERM (graceful) and verifies. The mover gets to
finish its current file operation before stopping. SIGKILL is a last resort with a
warning that partial files may exist.
---
## ━━━ WHAT THIS FOLDER DOES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Four distinct server-level roles:
```
🛡️ Last-resort stability system_watchdog.sh — reboots before crash
🌐 WebGUI availability webgui_restart.sh — recovers frozen UI
⚙️ Kernel tuning inotify_tuning.sh — file watch limits
php_fpm_max_children.sh — PHP worker count
🔇 Log hygiene docker_syslog_filter.sh — suppress veth noise
clear_logs.sh — weekly log trimming
🔄 Graceful operations mover_stop.sh — clean mover stop
server_reboot.sh — clean reboot with warning
user_scripts_stop.sh — stop running scripts
Last-resort stability system_watchdog.sh — reboots before crash
Pressure reduction resource_watchdog.sh — throttle/pause/stop under load
WebGUI availability webgui_restart.sh — nginx → php-fpm → emhttp escalation
Kernel tuning inotify_tuning.sh — file watch limits
php_fpm_max_children.sh — PHP worker count
Log hygiene docker_syslog_filter.sh — suppress veth noise at start
clear_logs.sh — weekly log trimming
Graceful operations mover_stop.sh — clean mover stop
rsync_stop.sh — smart rsync stop (orchestrator-aware)
user_scripts_stop.sh — stop running User Scripts
server_reboot.sh — clean reboot with pre-flight warnings
```
---
## ━━━ SCRIPTS AT A GLANCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## ━━━ RELATIONSHIP TO OTHER FOLDERS ━━━
| Script | Purpose | When |
|--------|---------|------|
| `system_watchdog.sh` | Three-tier last-resort stability watchdog | Continuous background loop |
| `webgui_restart.sh` | WebGUI availability — nginx → php-fpm → emhttp escalation | Every 10 minutes |
| `inotify_tuning.sh` | Raise inotify kernel limits | At array start |
| `php_fpm_max_children.sh` | Set PHP-FPM worker count | At array start |
| `docker_syslog_filter.sh` | Suppress Docker veth log noise | At array start |
| `clear_logs.sh` | Size-threshold weekly log cleanup | Weekly via maintenance window |
| `mover_stop.sh` | Clean mover stop with SIGTERM → SIGKILL | Manual |
| `server_reboot.sh` | Graceful reboot with pre-flight warnings | Manual or called by system_watchdog |
| `user_scripts_stop.sh` | Stop all running User Script processes | Manual or called by server_reboot |
```
Orchestrators/
array_start.sh ─────────────────────────► inotify_tuning.sh (first in sequence)
─────────────────────────► docker_syslog_filter.sh (second)
─────────────────────────► php_fpm_max_children.sh
─────────────────────────► system_watchdog.sh (background loop)
---
watchdog_orchestrator.sh ───────────────► resource_watchdog.sh (every minute)
weekly_maintenance.sh ──────────────────► clear_logs.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 🛡️ system_watchdog.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
server_reboot.sh ────────────────────────► user_scripts_stop.sh (called internally)
The last line of defense. Reboots the system cleanly before it crashes uncleanly.
Three-tier response system — critical failures bypass everything and reboot immediately,
OOM-confirmed crises bypass the strike system, sustained threshold breaches use strikes.
Runs continuously as a background process started by `array_start.sh`.
> Full architecture documentation in `README-Docker_Essentials.md` under
> "Relationship to System Watchdog". This section covers the system_watchdog itself.
```bash
# Started by: array_start.sh (continuous background process)
# Interval: SYSTEM_WATCHDOG_INTERVAL=300 (5 minutes)
Docker_Essentials/
docker_watchdog.sh ◄─── reads ──────────── resource_watchdog.sh state
(mem_shutdown_active flag)
```
`system_watchdog.sh` and `docker_watchdog.sh` (in Docker_Essentials/) are
designed to work together — docker_watchdog heals containers first,
system_watchdog reboots only when healing has failed. `resource_watchdog.sh`
coordinates with docker_watchdog via the `mem_shutdown_active` state flag to
prevent docker_watchdog from restarting containers that resource_watchdog just
stopped to free RAM.
---
### ── Three-Tier Response System ──────────────────────────────────────────────
## ━━━ SCRIPTS IN THIS FOLDER ━━━
```bash
# ─────────────────────────────────────────────────────────────────────────────
# TIER 1 — CRITICAL (bypass ALL strikes, reboot immediately)
# These failures are acute — the system is not recoverable by waiting.
# Single detection = immediate reboot. No confirmation window.
#
# Docker daemon unresponsive:
# Attempt /etc/rc.d/rc.docker restart first.
# Wait 15 seconds. Verify daemon responding.
# If still hung → CRITICAL reboot.
# A hung daemon cannot be healed — every subsequent docker command hangs.
#
# rootfs at ROOTFS_CRITICAL_PCT (99%+):
# Writes are failing. SSH may stop. Logs can't be written.
# Nothing can be fixed from this state without a reboot.
#
# Kernel BUG/Oops in dmesg:
# Kernel running with corrupted state.
# Delta-based: new oops since last cycle → reboot.
#
# File descriptor exhaustion (FD_CRITICAL_PCT=95%):
# New connections failing. Docker can't spawn processes. SSH drops.
#
# /boot read-only:
# State files and config writes silently failing.
# Write test on /boot every cycle.
| Script | Role | When It Runs |
|--------|------|-------------|
| `system_watchdog.sh` | Three-tier last-resort stability watchdog | Continuous background loop via array_start.sh |
| `resource_watchdog.sh` | Pressure reduction — throttle/pause/stop under load | Every minute via watchdog_orchestrator.sh |
| `webgui_restart.sh` | WebGUI availability — nginx → php-fpm → emhttp | Every 10 min via User Scripts |
| `inotify_tuning.sh` | Raise inotify kernel limits | At array start — FIRST |
| `php_fpm_max_children.sh` | Set PHP-FPM max worker count | At array start |
| `docker_syslog_filter.sh` | Suppress Docker veth syslog noise | At array start — before containers |
| `clear_logs.sh` | Size-threshold log cleanup | Weekly via weekly_maintenance.sh |
| `mover_stop.sh` | Clean mover stop with SIGTERM → SIGKILL | Manual / before reboot |
| `rsync_stop.sh` | Orchestrator-aware rsync stop | Manual |
| `user_scripts_stop.sh` | Stop all running User Script processes | Manual / called by server_reboot.sh |
| `server_reboot.sh` | Graceful reboot with pre-flight warnings | Manual |
# TIER 2 — URGENT (bypass strikes when OOM confirms crisis)
# RAM < MEM_GB (4GB) AND OOM kills >= OOM_LIMIT (3) in this cycle
# Without OOM confirmation → standard strike system applies.
# Rationale: 1-2 OOM kills = docker_watchdog.sh handles it.
# 3+ kills while RAM critical = system dying faster than watchdogs heal.
# OOM victims from dmesg included in reboot message (diagnostic context).
---
## ━━━ HOW THE SCRIPTS RELATE ━━━
# TIER 3 — STANDARD (strike system — N consecutive failures → reboot)
# Everything else: RAM tiers, load, CPU temp, zombies, /var/log,
# /tmp, containers, NIC, mdstat, sshd
# ─────────────────────────────────────────────────────────────────────────────
```
Array starts
├─ inotify_tuning.sh ← FIRST — kernel limits inherited at container launch
├─ docker_syslog_filter.sh ← SECOND — before any veth interfaces are created
├─ php_fpm_max_children.sh ← before WebGUI is under load
└─ system_watchdog.sh ← starts background loop
---
### ── RAM Tiers ────────────────────────────────────────────────────────────────
Every minute (watchdog_orchestrator.sh):
└─ resource_watchdog.sh
Level 1 (soft): throttle SABnzbd + qBit download speeds
Level 2 (medium): further throttle + docker pause non-essential containers
Level 3 (hard): docker stop optional services + set mem_shutdown_active=true
docker_watchdog.sh reads mem_shutdown_active — defers restarts
```bash
# master.conf
# ─────────────────────────────────────────────────────────────────────────────
# Three-level graduated response — not a single threshold.
#
SYS_WATCHDOG_MEM_WARN_GB=10 # warn + notify once — informational only
SYS_WATCHDOG_MEM_SHUTDOWN_GB=6 # stop non-essential containers, wait for recovery
SYS_WATCHDOG_MEM_GB=4 # strike system → reboot (or bypass if OOM confirms)
SYS_WATCHDOG_MEM_RECOVER_GB=30 # RAM must reach this before restarting containers
#
# Containers excluded from RAM emergency shutdown — these stay running:
SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED=(
"NginxProxyManager" # external access — stop this and users lose everything
"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
)
#
# Coordination with docker_watchdog.sh:
# system_watchdog writes mem_shutdown_active=true to SYS_WATCHDOG_STATE_FILE.
# docker_watchdog reads this flag and defers ALL container restart logic.
# Without this: both watchdogs fight — system_watchdog stops containers,
# docker_watchdog restarts them, RAM never recovers.
# With this: docker_watchdog stands down until mem_shutdown_active clears.
# ─────────────────────────────────────────────────────────────────────────────
Every 10 minutes (User Scripts):
└─ webgui_restart.sh
WebGUI OK → silent exit
Not responding:
Step 1: restart nginx → recheck
Step 2: restart php-fpm → recheck
Step 3: restart emhttp → recheck
All failed → notify, exit 1
Weekly (weekly_maintenance.sh):
└─ clear_logs.sh
System logs: clear if > LOG_MIN_SIZE_MB
Docker logs: clear per-container if > LOG_DOCKER_MAX_MB
Manual operations:
mover_stop.sh → wall → SIGTERM → SIGKILL → verify stopped
rsync_stop.sh → detect orchestrator → kill rsync (or orchestrator+rsync)
user_scripts_stop.sh → scan /proc → SIGTERM → SIGKILL per process
server_reboot.sh → pre-flight → wall → wait → VMs → Docker → sync → reboot
```
---
### ── Per-Host Check Toggles ───────────────────────────────────────────────────
```bash
# master_host1.conf (all checks in master_host*.conf — not master.conf)
# ─────────────────────────────────────────────────────────────────────────────
# Each check is independently toggleable per server.
# HOST1 and HOST2 may have different hardware and different workloads.
# detect_hosts() aliases HOST*_SYS_WATCHDOG_CHECK_* → SYS_WATCHDOG_CHECK_*
#
# Tier 1 — Critical (bypass strikes):
HOST1_SYS_WATCHDOG_CHECK_DOCKER_DAEMON=true
HOST1_SYS_WATCHDOG_CHECK_ROOTFS=true
HOST1_SYS_WATCHDOG_CHECK_KERNEL_OOPS=true
HOST1_SYS_WATCHDOG_CHECK_FD=true
HOST1_SYS_WATCHDOG_CHECK_BOOT=true
# Tier 2 — Urgent (OOM bypass):
HOST1_SYS_WATCHDOG_CHECK_OOM=true
HOST1_SYS_WATCHDOG_CHECK_RAM=true
# Tier 3 — Standard (strike system):
HOST1_SYS_WATCHDOG_CHECK_LOG=true
HOST1_SYS_WATCHDOG_CHECK_ARC=true # HOST1 runs ZFS — enable
HOST1_SYS_WATCHDOG_CHECK_CPU_TEMP=true
HOST1_SYS_WATCHDOG_CHECK_LOAD=false # disabled — transcoding causes normal spikes
HOST1_SYS_WATCHDOG_CHECK_ZOMBIES=true
HOST1_SYS_WATCHDOG_CHECK_CONTAINERS=true # monitors docker_watchdog persistent skip list
HOST1_SYS_WATCHDOG_CHECK_TMP=true
HOST1_SYS_WATCHDOG_CHECK_MDSTAT=true
HOST1_SYS_WATCHDOG_CHECK_NETWORK=true
HOST1_SYS_WATCHDOG_CHECK_SSHD=true
HOST1_SYS_WATCHDOG_CHECK_RUNAWAY=false # disabled — may false positive during encoding
HOST1_SYS_WATCHDOG_NIC="eth0" # verify: ip link show | grep "^[0-9]"
```
---
### ── Abort Conditions ─────────────────────────────────────────────────────────
```bash
# master.conf
# ─────────────────────────────────────────────────────────────────────────────
# Conditions that prevent a reboot even when a threshold is hit.
# CRITICAL tier bypasses these — truly critical conditions reboot regardless.
#
SYS_WATCHDOG_ABORT_ON_ZFS_UNHEALTHY=true # rebooting with bad pool risks data loss
SYS_WATCHDOG_ABORT_ON_PARITY=false # aborting parity is better than crashing
SYS_WATCHDOG_ABORT_ON_MOVER=false # aborting mover is better than crashing
#
# Philosophy: a graceful reboot before a crash is always better than a hard crash.
# The abort conditions protect against the cases where a reboot itself causes harm
# (data loss from bad ZFS pool). Parity and mover can be restarted after reboot.
```
---
### ── Reboot Loop Protection ───────────────────────────────────────────────────
```bash
# master.conf
# ─────────────────────────────────────────────────────────────────────────────
# If the server keeps rebooting, something is wrong that rebooting isn't fixing.
# After REBOOT_LIMIT reboots in REBOOT_WINDOW_HRS → shutdown instead.
# Shutdown prevents: hardware damage, filesystem corruption from repeated reboots,
# infinite loop that never lets you investigate.
# State file: /boot/config/system_watchdog_reboots.db — survives reboots.
#
SYS_WATCHDOG_REBOOT_LIMIT=3 # reboots before shutdown instead
SYS_WATCHDOG_REBOOT_WINDOW_HRS=12 # rolling window in hours
```
---
### ── State File Heartbeat ─────────────────────────────────────────────────────
```bash
# ─────────────────────────────────────────────────────────────────────────────
# system_watchdog writes watchdog_cycle=N to SYS_WATCHDOG_STATE_FILE every cycle.
# This keeps the file's modification time current.
#
# docker_watchdog.sh uses the state file mtime as a stale guard — if the file
# is more than 2 hours old while mem_shutdown_active=true is set, system_watchdog
# may have stopped running. docker_watchdog resumes normal operation rather than
# being silenced indefinitely by a stale flag.
#
# Without this heartbeat: if all standard checks pass and no state writes happen
# (e.g. CHECK_KERNEL_OOPS=false AND CHECK_MDSTAT=false), the file mtime could go
# stale even with the watchdog running.
```
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
system_watchdog.sh # normal (continuous — started by array_start.sh)
system_watchdog.sh --dry-run # trigger detection without rebooting
system_watchdog.sh --status # show all tiers, thresholds, active strikes, RAM state
system_watchdog.sh --log # verbose per-cycle output
```
---
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 🌐 webgui_restart.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Monitors the unRAID WebGUI availability and recovers it automatically when unresponsive.
Three-step escalating strategy — lightest fix first, heaviest last. Silent when healthy.
```bash
# Scheduled: */10 * * * * (every 10 minutes)
```
---
### ── Three-Step Escalation ────────────────────────────────────────────────────
```bash
# ─────────────────────────────────────────────────────────────────────────────
# Step 1 — nginx restart
# Lightest fix — handles most WebGUI failures.
# nginx is the web server layer. Crash, worker stuck, connection timeout.
# Wait WEBGUI_NGINX_WAIT seconds → curl recheck.
#
# Step 2 — php-fpm restart
# Added because WebGUI can appear frozen due to PHP worker exhaustion.
# pm.max_children workers all occupied → new requests queue → dashboard hangs.
# php-fpm restart far less disruptive than emhttp.
# Wait WEBGUI_PHP_WAIT seconds → curl recheck.
# Note: system_tuning_monitor.sh tracks worker saturation over time.
#
# Step 3 — emhttp restart
# Heaviest fix. emhttp is the core unRAID management daemon.
# Array, Docker, shares continue running — only WebGUI management restarts.
# Takes longer to recover — WEBGUI_EMHTTP_WAIT gives it time.
# Wait WEBGUI_EMHTTP_WAIT seconds → curl recheck.
#
# If all three fail → notify warning — manual intervention needed.
# Guidance: pgrep -x nginx emhttp | check journalctl | consider server_reboot.sh
# ─────────────────────────────────────────────────────────────────────────────
```
---
### ── Configuration ────────────────────────────────────────────────────────────
```bash
# master.conf
WEBGUI_URL="http://localhost" # adjust if non-standard port
WEBGUI_TIMEOUT=5 # seconds before curl times out
WEBGUI_NGINX_WAIT=15 # seconds after nginx restart before recheck
WEBGUI_PHP_WAIT=10 # seconds after php-fpm restart before recheck
WEBGUI_EMHTTP_WAIT=30 # seconds after emhttp restart — takes longer
```
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
webgui_restart.sh # check once — silent if healthy, escalates if not
webgui_restart.sh --dry-run # walk through escalation without restarting anything
webgui_restart.sh --status # show WebGUI state + nginx/php-fpm/emhttp process state
webgui_restart.sh --log # verbose — show each escalation step
```
---
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 📡 inotify_tuning.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Raises Linux inotify kernel limits at array start. Idempotent — completely silent
when values are already correct.
```bash
# Scheduled: At Startup of Array (via array_start.sh — FIRST in ARRAY_START_SCRIPTS)
# Must run before containers start — containers inherit limits at startup
```
---
### ── Three Limits ─────────────────────────────────────────────────────────────
```bash
# master.conf
# ─────────────────────────────────────────────────────────────────────────────
INOTIFY_MAX_INSTANCES=1024 # default: 128 — max inotify instances per user
# 1024 handles ~20-30 containers watching files
INOTIFY_MAX_WATCHES=1048576 # default: 8192 — SHARED budget across ALL users/containers
# Was 512K — raised to 1M (1048576)
# VSCode/Code-Server alone needs ~50K-200K for large workspaces
# with node_modules. All arr containers + Emby + VSCode share
# this budget. 1M safe on 128GB RAM (~128MB kernel memory)
# If VSCode shows "unable to watch for file changes" → too low
INOTIFY_MAX_QUEUED_EVENTS=32768 # default: 16384 — events buffered before dropping
```
---
### ── Startup Race Condition ───────────────────────────────────────────────────
```bash
# ─────────────────────────────────────────────────────────────────────────────
# inotify limits are kernel-wide — they take effect immediately on sysctl write.
# But containers that have ALREADY started inherit the OLD limits at startup.
# Those containers keep their inherited (low) limits until restarted.
#
# This is why inotify_tuning.sh must be FIRST in ARRAY_START_SCRIPTS — before
# any container starts. If Code-Server starts before limits are raised:
# → Code-Server inherits old 8192 watch limit
# → VSCode shows "unable to watch for file changes"
# → Fix: docker restart Code-Server (picks up current kernel limits on start)
#
# The script warns if it changed any values:
# "If Code-Server is running: docker restart Code-Server"
# ─────────────────────────────────────────────────────────────────────────────
```
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
inotify_tuning.sh # normal run (idempotent — silent when correct)
inotify_tuning.sh --dry-run # show what would change without changing
inotify_tuning.sh --status # current values vs targets + top inotify consumers
inotify_tuning.sh --log # verbose — show each sysctl write
```
---
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## ⚙️ php_fpm_max_children.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Raises PHP-FPM `pm.max_children` at array start to prevent WebGUI queueing under
load. Idempotent — completely silent when already correct. No PHP-FPM restart unless
value actually changed.
```bash
# Scheduled: At Startup of Array (via array_start.sh)
```
---
### ── Why 250 Workers ──────────────────────────────────────────────────────────
```bash
# master.conf
# ─────────────────────────────────────────────────────────────────────────────
PHP_MAX_CHILDREN=250 # default: 4-8 — far too low for a busy server
# Each worker: ~2MB resident memory at idle
# 250 workers × 2MB = ~500MB — trivial on 128GB
# Worst case (all active): ~250 × 40MB = ~10GB
# In practice: rarely all active simultaneously
# On 64GB (HOST2): still appropriate — 250 × 40MB
# = 10GB worst case = 15% of RAM, acceptable
#
# Without this fix:
# 5 users hit the WebGUI simultaneously → 8 workers exhausted → 5 more queue
# Each queued request waits for a worker to free → 5-10 second response times
# Looks like a slow server — it's just a queue
#
PHP_CONF="/etc/php83/php-fpm.d/www.conf" # path may change with PHP version
```
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
php_fpm_max_children.sh # normal run (idempotent)
php_fpm_max_children.sh --dry-run # show what would change
php_fpm_max_children.sh --status # show current value vs target + worker count
php_fpm_max_children.sh --log # verbose — show config write + restart
```
---
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 🔇 docker_syslog_filter.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Creates an rsyslog drop rule for Docker veth/docker0 interface noise. Idempotent —
silent when filter already correct, only writes + restarts rsyslog when something changed.
```bash
# Scheduled: At Startup of Array (via array_start.sh — before containers start)
```
---
### ── What Gets Suppressed ─────────────────────────────────────────────────────
```bash
# ─────────────────────────────────────────────────────────────────────────────
# These kernel messages are generated on every container start and stop:
#
# kernel: veth2a3b4c5: renamed from eth0
# kernel: docker0: port 1(veth2a3b4c5) entered blocking state
# kernel: docker0: port 1(veth2a3b4c5) entered forwarding state
# kernel: docker0: port 1(veth2a3b4c5) entered disabled state
#
# 50+ containers at array start = 200-400 lines of this in the first minute.
# Containers restart throughout the day = continuous noise.
# Real events buried and invisible in syslog.
# ─────────────────────────────────────────────────────────────────────────────
```
---
### ── Idempotent Design ────────────────────────────────────────────────────────
```bash
# ─────────────────────────────────────────────────────────────────────────────
# Runs at every array start but only changes something when needed:
# Filter file exists and content is correct → exit 0 silently
# Filter file missing or content changed → write + restart rsyslog
#
# Expected content compared exactly — single source of truth:
EXPECTED_FILTER='if ($msg contains "veth" or $msg contains "docker0") then {
stop
}'
# ─────────────────────────────────────────────────────────────────────────────
```
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
docker_syslog_filter.sh # normal run (idempotent — silent when correct)
docker_syslog_filter.sh --dry-run # show what would be written without writing
docker_syslog_filter.sh --status # show current filter file + rsyslog state
```
---
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 🗑️ clear_logs.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Clears system logs and Docker container logs using size thresholds. Only clears logs
large enough to be worth clearing — preserves recent diagnostic context on small logs.
```bash
# Called by: weekly_sync_maintenance.sh via WEEKLY_MAINTENANCE_SCRIPTS (Sunday 2:30am)
```
---
### ── Size Threshold Approach ──────────────────────────────────────────────────
```bash
# master.conf
# ─────────────────────────────────────────────────────────────────────────────
# Why thresholds instead of clearing everything:
# A 2MB syslog contains useful recent history — not worth clearing.
# A 500MB Docker log is consuming rootfs and contains mostly noise — clear it.
# Blind truncation destroys diagnostic context for no benefit.
#
LOG_MIN_SIZE_MB=10 # skip system log if under this size — keep history
LOG_DOCKER_MAX_MB=100 # clear Docker container log only if over this size
# Active containers (Emby, SABnzbd) grow fastest
# 100MB × 30 containers = 3GB before any clearing kicks in
LOG_FILES=(/var/log/syslog /var/log/messages /var/log/dmesg)
#
# Truncation not logrotate:
# unRAID writes to tmpfs (/var/log) — logrotate's compress + archive approach
# would consume more tmpfs space, not less.
# : > file keeps the file descriptor valid while emptying content.
# Safe for running services (syslogd continues writing to the same fd).
# ─────────────────────────────────────────────────────────────────────────────
```
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
clear_logs.sh # normal run — silent if all logs under threshold
clear_logs.sh --dry-run # show what would be cleared and sizes
clear_logs.sh --status # show current log sizes vs thresholds
clear_logs.sh --log # verbose — show each file evaluated
```
---
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## ⏹️ mover_stop.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Stops the unRAID mover cleanly — wall warning, configurable timeout, SIGTERM → verify
→ SIGKILL sequence. Safe to run when mover is not running — exits cleanly with a log.
---
### ── Stop Sequence ────────────────────────────────────────────────────────────
```bash
# ─────────────────────────────────────────────────────────────────────────────
# 1. Check if mover is running — exit cleanly if not
# 2. Wall message: "HOST1 (unRAID-Gmer4Lfe) — mover stopping in 30s"
# MY_ID included — on shared terminal it's clear which server
# 3. Wait MOVER_STOP_TIMEOUT seconds
# 4. SIGTERM — allows mover to finish its current file before stopping
# No partial files — the mover completes what it's working on
# 5. Wait 5 seconds — verify if stopped
# 6. If still running → SIGKILL (forced)
# Warning: partial files possible — same as a hard crash
# 7. Final verify — error if still running
# ─────────────────────────────────────────────────────────────────────────────
```
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
mover_stop.sh # stop mover with configured timeout
mover_stop.sh --dry-run # show what would happen
mover_stop.sh --status # show mover state (running, PID, start time)
```
---
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 🔁 server_reboot.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Graceful reboot with pre-flight warnings, wall message, unRAID notification, VM
graceful shutdown, then Docker and services stop, sync, reboot.
> Full documentation in `README-Tools.md` — `server_reboot.sh` section. This is a
> quick reference.
---
### ── Shutdown Sequence ────────────────────────────────────────────────────────
```bash
# ─────────────────────────────────────────────────────────────────────────────
# 1. Pre-flight warnings (warn not block):
# rsync running, mover running, active Emby sessions
# Warnings show in summary — you chose to reboot, these are for context
# 2. Wall message + unRAID notification — MY_ID included
# 3. Wait REBOOT_SLEEP seconds (default 30)
# 4. virsh shutdown each VM → wait REBOOT_VM_WAIT seconds for graceful exit
# 5. Stop libvirt (VM Manager)
# 6. Stop Docker service
# 7. sync — flush filesystem buffers
# 8. /sbin/reboot
# ─────────────────────────────────────────────────────────────────────────────
```
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
server_reboot.sh # reboot with 30s warning
server_reboot.sh --dry-run # full sequence walkthrough without rebooting
server_reboot.sh --status # show running processes that would be affected
server_reboot.sh --reason="maintenance" # include reason in wall + notification
server_reboot.sh --log # verbose per-step output
```
---
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 🛑 user_scripts_stop.sh
## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Stops all running User Script processes. Identifies by `/tmp/user.scripts` path
signature. Shows script names not PIDs. SIGTERM → verify → SIGKILL with self-exclusion.
> Full documentation in `README-Tools.md` — `user_scripts_stop.sh` section.
---
### ── Usage ───────────────────────────────────────────────────────────────────
```bash
user_scripts_stop.sh # stop all — SIGTERM → SIGKILL if needed
user_scripts_stop.sh --dry-run # show which scripts would be stopped, by name
user_scripts_stop.sh --status # show running scripts with PID and runtime
user_scripts_stop.sh --log # verbose per-process output
```
---
## ━━━ STARTUP SEQUENCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```bash
# master.conf — ARRAY_START_SCRIPTS (order matters)
# ─────────────────────────────────────────────────────────────────────────────
ARRAY_START_SCRIPTS=(
# ── One-shot — run and exit ───────────────────────────────────────────────
"unRAID_Essentials/inotify_tuning.sh" # FIRST — raise limits before
# containers inherit old values
"unRAID_Essentials/docker_syslog_filter.sh" # SECOND — before containers
# create veth interfaces
"unRAID_Essentials/php_fpm_max_children.sh" # before WebGUI serves requests
"Transcodes/ramdisk_setup.sh" # before Emby starts transcoding
"Docker_Essentials/docker_network_connect.sh" # before watchdogs check states
# ── Continuous — run until array stops ───────────────────────────────────
"unRAID_Essentials/system_watchdog.sh" # before docker_watchdog —
# writes state file docker_watchdog reads
"Docker_Essentials/docker_watchdog.sh" # before failover — containers
# must be healthy for failover decisions
"Failover/failover.sh" # last — needs everything stable
)
# ─────────────────────────────────────────────────────────────────────────────
# array_start.sh is the ONLY "At Startup of Array" entry in User Scripts.
# It launches everything above in order.
```
---
## ━━━ FULL SCHEDULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```bash
# At Startup of Array — via array_start.sh:
# inotify_tuning.sh
# docker_syslog_filter.sh
# php_fpm_max_children.sh
# (ramdisk_setup.sh — in Transcodes/)
# system_watchdog.sh (continuous)
# Every 10 minutes:
*/10 * * * * webgui_restart.sh # silent when healthy — escalates when not
# Weekly — via weekly_sync_maintenance.sh:
# clear_logs.sh # Sunday 2:30am via WEEKLY_MAINTENANCE_SCRIPTS
# Manual:
# mover_stop.sh — before array ops that need mover stopped
# server_reboot.sh — planned maintenance reboots
# user_scripts_stop.sh — emergency script stop or pre-reboot cleanup
```
+66 -36
View File
@@ -2,51 +2,81 @@
# ==============================================================================================
# ================================= Clear Logs =================================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Clears system and Docker container logs to prevent rootfs fill over time.
# Runs weekly via WEEKLY_MAINTENANCE_SCRIPTS — Sunday 2:30am.
# Uses size thresholds — only clears logs that have grown large enough to matter.
# Called weekly via WEEKLY_MAINTENANCE_SCRIPTS. Uses size thresholds — only
# clears logs large enough to be worth clearing. Small logs are left intact,
# preserving recent diagnostic context.
#
# ── WHAT IT CLEARS ────────────────────────────────────────────────────────────────────────────
# System logs — LOG_FILES from master.conf (/var/log/syslog, messages, dmesg)
# Cleared if size exceeds LOG_MIN_SIZE_MB
# These grow continuously — weekly clearing keeps rootfs healthy
# System logs (LOG_FILES): cleared if size exceeds LOG_MIN_SIZE_MB.
# Docker logs (/var/lib/docker/containers/**/*-json.log): cleared only if the
# individual container log exceeds LOG_DOCKER_MAX_MB. Active containers (Emby,
# SABnzbd) grow fastest — inactive containers typically remain small.
#
# Docker logs — /var/lib/docker/containers/**/*-json.log
# Cleared only if individual container log exceeds LOG_DOCKER_MAX_MB
# Active containers (Emby, SABnzbd) grow fastest — 100MB+ easily
# Inactive containers not cleared — their logs are typically small
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# ── SIZE THRESHOLD APPROACH ───────────────────────────────────────────────────────────────────
# Truncating everything blindly destroys useful diagnostic context.
# A 2MB log is not worth clearing — it contains useful recent history.
# A 500MB log is consuming rootfs and contains mostly noise — clear it.
# Size Thresholds, Not Blind Truncation
# A 2MB syslog contains useful recent diagnostic history — not worth clearing.
# A 500MB Docker log is consuming rootfs and contains mostly noise — clear it.
# Blind truncation destroys diagnostic context for no benefit.
#
# LOG_MIN_SIZE_MB — system logs under this size are left alone
# LOG_DOCKER_MAX_MB — Docker logs under this size are left alone
# Truncation, Not Logrotate
# unRAID writes logs to tmpfs (/var/log). Logrotate's compress + archive approach
# would consume more tmpfs space, not less. Truncation (`: > file`) keeps the
# file descriptor open and valid while emptying content — syslogd continues
# writing to the same fd without interruption.
#
# ── WHY NOT LOGROTATE ─────────────────────────────────────────────────────────────────────────
# unRAID writes to tmpfs (/var/log) — logrotate's compress + archive approach
# would consume even more tmpfs space. Truncation (: > file) keeps the file
# descriptor open and valid while emptying content — safe for running services.
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# acquire_lock prevents concurrent runs corrupting logs
# Root check — truncating system logs requires root
# Size thresholds — only clears logs that have grown large enough
# Byte tracking — reports MB freed for weekly digest
# validate_unraid — notify validated before use
# Silent on clean — small logs = nothing to clear = no output ✅
# Single Instance Lock
# acquire_lock prevents concurrent runs from corrupting logs.
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# LOG_FILES — system log paths to check and clear
# LOG_MIN_SIZE_MB — minimum system log size before clearing (default 10MB)
# LOG_DOCKER_MAX_MB — clear Docker log only if above this size (default 100MB)
# Root Required
# Truncating system logs requires root.
#
# Size Thresholds
# Each file checked against its threshold before clearing.
#
# Silent When Clean
# All logs below threshold = no visible output.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
# LOG_FILES
# System log paths to check and clear. (default: /var/log/syslog /var/log/messages /var/log/dmesg)
#
# LOG_MIN_SIZE_MB
# Skip system log if under this size — keep recent history. (default: 10)
#
# LOG_DOCKER_MAX_MB
# Clear Docker container log only if over this size. (default: 100)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# clear_logs.sh
# Check all configured logs. Clear those above threshold. Silent when all are small.
#
# clear_logs.sh --dry-run
# Show which logs would be cleared and their current sizes. No clearing.
#
# clear_logs.sh --status
# Show current log sizes vs thresholds.
#
# clear_logs.sh --log
# Verbose output — show each file evaluated, its size, and action taken.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# clear_logs.sh — normal run (threshold-based clearing)
# clear_logs.sh --dry-run — show what would be cleared and sizes
# clear_logs.sh --status — show current log sizes
# clear_logs.sh --log — verbose output per file
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+63 -36
View File
@@ -2,49 +2,76 @@
# ==============================================================================================
# ============================= Docker Syslog Filter ===========================================
# ==============================================================================================
# Suppresses noisy Docker veth/docker0 interface messages from syslog.
# Run once at array start via ARRAY_START_SCRIPTS in master.conf.
# Idempotent — completely silent when filter is already correct.
#
# ── WHY THIS EXISTS ───────────────────────────────────────────────────────────────────────────
# Every time Docker creates or destroys a container network interface it logs messages like:
# kernel: veth2a3b4c5: renamed from eth0
# kernel: docker0: port 1(veth2a3b4c5) entered blocking state
# kernel: docker0: port 1(veth2a3b4c5) entered forwarding state
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Suppresses Docker veth/docker0 network interface messages from syslog. Run
# once at array start via ARRAY_START_SCRIPTS before any containers start.
# Idempotent — completely silent when the filter is already in place.
#
# On a busy server creating and restarting many containers these fill syslog rapidly —
# hundreds of entries per minute on container restarts, completely masking real events.
# The filter tells rsyslog to drop these before they reach the log file.
# Every container start/stop generates kernel messages like:
# "veth2a3b4c5: renamed from eth0"
# "docker0: port 1(veth...) entered forwarding state"
# 50+ containers at array start = 200400 lines of noise in the first minute,
# completely masking real events.
#
# ── HOW IT WORKS ──────────────────────────────────────────────────────────────────────────────
# Creates /etc/rsyslog.d/ignore-docker-veth.conf (FILTER_FILE in master.conf).
# rsyslog processes .conf files in /etc/rsyslog.d/ automatically on startup.
# Filter uses rsyslog's RainerScript to match messages containing "veth" or "docker0"
# and calls stop — the message is dropped before reaching any output target.
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# ── IDEMPOTENT DESIGN ─────────────────────────────────────────────────────────────────────────
# On every array start: checks if filter file already exists with correct content.
# If already correct → completely silent no rsyslog restart, no output.
# Only writes + restarts rsyslog if filter is missing or content has changed.
# This prevents unnecessary rsyslog restarts on every boot.
# Idempotent Content Check
# Checks whether the filter file already exists with exactly the expected content.
# If already correct → silent exit, no rsyslog restart. Only writes and restarts
# rsyslog when the filter is missing or content has changed. Running at every boot
# without this would cause an unnecessary rsyslog restart on every array start.
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# Root check — writing to /etc/rsyslog.d/ requires root
# acquire_lock — prevents concurrent runs at array start
# Idempotent check — only restarts rsyslog when filter actually changed
# Directory creation — mkdir -p /etc/rsyslog.d/ before writing
# rsyslog verify — checks rsyslog running after restart
# validate_unraid — notify validated before use
# Silent on success — runs every boot, no noise when already correct
# rsyslog Drop Rule
# Creates FILTER_FILE (/etc/rsyslog.d/ignore-docker-veth.conf).
# Uses RainerScript `stop` action — messages matching "veth" or "docker0" are
# dropped before reaching any output target, including the log file.
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# FILTER_FILE — path for rsyslog drop filter (default /etc/rsyslog.d/ignore-docker-veth.conf)
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
# Writing to /etc/rsyslog.d/ requires root.
#
# Single Instance Lock
# acquire_lock prevents concurrent runs at array start.
#
# rsyslog Process Verify
# Confirms rsyslog is running after restart.
#
# Silent on Success
# Runs every boot — no noise when filter is already correct.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
# FILTER_FILE
# Path for the rsyslog drop filter config file.
# (default: /etc/rsyslog.d/ignore-docker-veth.conf)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# docker_syslog_filter.sh
# Check filter file. Write and restart rsyslog only if changed. Silent if correct.
#
# docker_syslog_filter.sh --dry-run
# Show what would be written without writing or restarting rsyslog.
#
# docker_syslog_filter.sh --status
# Show current filter file content and rsyslog process state.
#
# docker_syslog_filter.sh --log
# Verbose output showing the content comparison and any changes applied.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# docker_syslog_filter.sh — normal run (idempotent)
# docker_syslog_filter.sh --dry-run — show what would change
# docker_syslog_filter.sh --status — show filter file state and rsyslog status
# docker_syslog_filter.sh --log — verbose output
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+72 -50
View File
@@ -2,65 +2,87 @@
# ==============================================================================================
# ================================= inotify Tuning ============================================
# ==============================================================================================
# Raises Linux inotify limits at array start to prevent exhaustion across the container stack.
# Run once at array start via ARRAY_START_SCRIPTS in master.conf.
# Settings are lost on reboot — this script reapplies them on every array start.
#
# ── THREE INOTIFY LIMITS ──────────────────────────────────────────────────────────────────────
# max_user_instances — max number of independent inotify file descriptor objects per user
# Each container that calls inotify_init() consumes one instance
# Default 128 — exhausted quickly with 20+ active containers
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Raises Linux inotify limits at array start to prevent exhaustion across the
# container stack. Run once at array start via ARRAY_START_SCRIPTS. Settings
# are lost on reboot — this script reapplies them on every array start.
#
# max_user_watches — SHARED budget across ALL users and containers on the system
# Each watched file or directory costs one watch from this pool
# Default 8192 — VSCode alone can need 50K-200K for large workspaces
# Must run FIRST in ARRAY_START_SCRIPTS before any containers start — containers
# inherit inotify limits at launch, not dynamically. Running this after Code-Server
# starts requires a docker restart to pick up the new values.
#
# max_queued_events — max events buffered before kernel starts dropping them
# Low value = events silently lost during high-activity periods
# Default 16384 — sufficient for most setups
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# ── WHY VSCODE THROWS "UNABLE TO WATCH FOR FILE CHANGES" ─────────────────────────────────────
# VSCode (and Code-Server in Docker) opens one inotify watch per file in the workspace.
# A typical project with node_modules can easily have 100K-200K files.
# All containers on the host share max_user_watches — the combined usage of:
# Sonarr, Radarr, Lidarr, Emby, Nextcloud, Code-Server, AdGuard, all other arrs
# easily exceeds 524288 (512K) watches on a busy server.
# Raising to 1048576 (1M) gives sufficient headroom — safe on 128GB RAM (~128MB kernel use).
# Three inotify Limits
# max_user_instances — max independent inotify fd objects per user; each container
# calling inotify_init() consumes one. Default 128 — exhausted
# quickly with 20+ active containers.
#
# ── STARTUP ORDER MATTERS ─────────────────────────────────────────────────────────────────────
# inotify_tuning.sh must run BEFORE containers that watch files start.
# In ARRAY_START_SCRIPTS order: inotify_tuning.sh first, then container-starting scripts.
# If Code-Server starts before limits are raised it inherits the old (low) limits.
# Code-Server restart fixes this: limits are kernel-wide, not process-bound at start.
# So if Code-Server is already running: docker restart Code-Server after this script runs.
# max_user_watches — SHARED budget across ALL users and containers on the system.
# Each watched file or directory costs one watch. Default 8192 —
# VSCode alone needs 50K200K for large workspaces. Combined
# usage of Sonarr, Radarr, Lidarr, Emby, Nextcloud, Code-Server
# easily exceeds 512K on a busy server.
#
# ── CONSUMERS ON THIS STACK ───────────────────────────────────────────────────────────────────
# Emby — watches all media library paths (1 watch per folder)
# Sonarr — watches TV_Shows folder tree
# Radarr — watches Movies folder tree
# Lidarr — watches Music folder tree
# Nextcloud — watches data directory for changes
# Code-Server — watches entire workspace (can be 50K-200K with node_modules)
# AdGuard Home — watches config directory
# + all other containers using inotify internally
# max_queued_events — max events buffered before the kernel drops them. Low value =
# events silently lost during high-activity bursts. Default 16384.
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# acquire_lock — prevents duplicate runs at array start
# Root check — sysctl writes require root
# validate_unraid — notify validated before use
# Silent on success — runs every boot, no noise when already correct
# Only warns on changes or failures
# Why 1M Watches
# Raising max_user_watches to 1048576 (1M) gives sufficient headroom for all
# containers combined — safe on 128GB RAM (~128MB kernel use for the pool).
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# INOTIFY_MAX_INSTANCES — default 1024
# INOTIFY_MAX_WATCHES — default 1048576 (1M)
# INOTIFY_MAX_QUEUED_EVENTS — default 32768
# Idempotent Per-Setting
# Each sysctl value is read before writing. Only changed if different from target —
# no-op on boots where limits are already correct.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
# sysctl writes require root.
#
# Single Instance Lock
# acquire_lock prevents duplicate runs at array start.
#
# Silent on Success
# Runs every boot — no noise when already correct.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
# INOTIFY_MAX_INSTANCES
# Max inotify fd objects per user. (default: 1024)
#
# INOTIFY_MAX_WATCHES
# Max watched files/dirs shared across all users and containers. (default: 1048576)
#
# INOTIFY_MAX_QUEUED_EVENTS
# Max events buffered before kernel drops them. (default: 32768)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# inotify_tuning.sh
# Apply inotify limits. No-op per setting if already at target.
#
# inotify_tuning.sh --dry-run
# Show current vs target for each limit. No sysctl writes.
#
# inotify_tuning.sh --status
# Show current vs target, active instance count, and top consumers by PID.
#
# inotify_tuning.sh --log
# Verbose output — show each sysctl check and result.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# inotify_tuning.sh — normal run (apply settings)
# inotify_tuning.sh --dry-run — show what would change
# inotify_tuning.sh --status — show current vs target values and top consumers
# inotify_tuning.sh --log — verbose output
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+60 -30
View File
@@ -2,44 +2,74 @@
# ==============================================================================================
# ================================= Mover Stop =================================================
# ==============================================================================================
# Safely stops the unRAID mover process with a warning before halting.
# Warns all logged-in users via wall message, waits the configured timeout, then stops.
#
# ── WHEN TO USE ───────────────────────────────────────────────────────────────────────────────
# - Before a planned reboot when mover is running mid-cycle
# - Before disk replacement or array operations that need mover stopped
# - Before rsync — mover and rsync simultaneously moving the same files causes corruption
# - Called automatically by maintenance scripts that need the mover stopped first
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Safely stops the unRAID mover with a wall warning, configurable timeout, and
# SIGTERM → SIGKILL sequence. Use before planned reboots, disk operations, or
# any operation where mover and rsync running simultaneously could corrupt files.
# Exits cleanly if mover is not running.
#
# ── STOP SEQUENCE ─────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Stop Sequence
# 1. Check if mover is running — exit cleanly if not
# 2. Broadcast wall warning to all logged-in users
# 3. Wait MOVER_STOP_TIMEOUT seconds (default 30) — gives active sessions a chance to note it
# 4. Send SIGTERM — mover can complete its current file operation before exiting
# 5. Wait 5 seconds for graceful exit
# 6. Verify stopped — if still running send SIGKILL (force)
# 2. Wall message to all logged-in terminal users
# 3. Wait MOVER_STOP_TIMEOUT seconds
# 4. SIGTERM — allows mover to finish its current file before stopping
# (no partial files — the mover completes what it is working on)
# 5. Wait 5 seconds → verify stopped
# 6. SIGKILL if still running — forced stop, partial files possible
# 7. Final verify — error if still running after SIGKILL
#
# ── SIGTERM vs SIGKILL ────────────────────────────────────────────────────────────────────────
# SIGTERM first — allows mover to finish the file it is currently moving (no partial files).
# SIGKILL only as fallback — forces immediate stop (may leave partial files on cache or array).
# SIGTERM first because the mover has an opportunity to finish the file it is
# currently moving, leaving no partial copies on cache or array. SIGKILL is only
# used as a last resort and may leave a file split across cache and array.
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# acquire_lock — prevents concurrent stop attempts racing each other
# Root check — pkill on emhttp processes requires root
# validate_unraid — notify validated before use
# SIGTERM → verify → SIGKILL sequence — graceful then forced
# Final verify — confirms mover actually stopped
# Silent on clean — mover not running = log() only, no output ✅
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# MOVER_STOP_TIMEOUT — seconds to warn users before stopping (default 30)
# Single Instance Lock
# acquire_lock prevents concurrent stop attempts racing each other.
#
# Root Required
# pkill on emhttp processes requires root.
#
# Final Verify
# Confirms mover is actually stopped after the kill sequence — errors if it
# is still running after SIGKILL.
#
# Silent When Clean
# Mover not running = log() only, no visible output.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
# MOVER_STOP_TIMEOUT
# Seconds between wall warning and SIGTERM. (default: 30)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# mover_stop.sh
# Check if mover is running. If so, warn users and stop it.
#
# mover_stop.sh --dry-run
# Show mover state and what would happen. No signals sent.
#
# mover_stop.sh --status
# Show mover state (running, PID, start time). Then exit.
#
# mover_stop.sh --log
# Verbose output showing each step of the stop sequence.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# mover_stop.sh — stop mover with configured timeout
# mover_stop.sh --dry-run — show what would happen
# mover_stop.sh --status — show mover state
# mover_stop.sh --log — verbose output
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+73 -35
View File
@@ -2,53 +2,91 @@
# ==============================================================================================
# ============================= PHP-FPM Max Children ===========================================
# ==============================================================================================
# Persistently sets PHP-FPM pm.max_children on unRAID to prevent WebGUI slowdowns.
# Run once at array start via ARRAY_START_SCRIPTS in master.conf.
# Idempotent — completely silent when value is already correct.
#
# ── WHY THIS EXISTS ───────────────────────────────────────────────────────────────────────────
# unRAID's WebGUI runs through PHP-FPM. The default pm.max_children is very low (4-8).
# Under load — multiple users, Docker operations, heavy dashboard usage — all PHP workers
# saturate and new requests queue. The WebGUI becomes slow or unresponsive.
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Raises PHP-FPM pm.max_children to prevent WebGUI slowdowns under load. Run
# once at array start via ARRAY_START_SCRIPTS. Idempotent — silent when the
# value is already correct, no restart on clean boot.
#
# pm.max_children controls how many PHP worker processes can run simultaneously.
# Raising it allows the WebGUI to handle more concurrent requests without queuing.
# Too high: wastes RAM. Too low: WebGUI slowdowns.
# PHP_MAX_CHILDREN=250 is appropriate for 128GB — ~2MB per worker = ~500MB total.
# unRAID's WebGUI runs through PHP-FPM. The default pm.max_children is very
# low (48). Under load — multiple users, Docker operations, heavy dashboard
# usage — all PHP workers saturate and new requests queue. The WebGUI becomes
# slow or unresponsive.
#
# ── WHY IDEMPOTENT ────────────────────────────────────────────────────────────────────────────
# This runs at every array start. If the value is already correct there is nothing to do —
# no config write, no PHP-FPM restart. Restarting PHP-FPM unnecessarily disrupts active
# WebGUI sessions and is annoying on every boot.
# PHP_MAX_CHILDREN=250 is appropriate for 128GB RAM: ~2MB per worker = ~500MB
# total. Too high wastes RAM; too low causes slowdowns.
#
# ── APPLY SEQUENCE ────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Idempotent Content Check
# Reads the current pm.max_children value before writing. If already at
# target → silent exit, no PHP-FPM restart. Restarting PHP-FPM unnecessarily
# disrupts active WebGUI sessions on every boot.
#
# Pattern Match Before Write
# Verifies the sed pattern finds pm.max_children in the config before
# applying any change. Prevents silent failures where sed succeeds but
# writes nothing because the key was missing or commented out.
#
# Apply Sequence
# 1. Read current pm.max_children from PHP_CONF
# 2. If already at target → exit silently (idempotent)
# 2. If already at target → exit silently
# 3. Verify sed pattern matches before writing
# 4. Apply sed replacement
# 5. Restart PHP-FPM via rc.php-fpm
# 6. Verify PHP-FPM process running after restart
# 7. Verify config file reflects target value
# 7. Read back config to confirm value applied
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# Root check — writing to system config requires root
# acquire_lock — prevents concurrent runs at array start
# Idempotent check — only restarts PHP-FPM when value actually changes
# Pattern match check — verifies sed found pm.max_children before writing
# Process verify — confirms PHP-FPM running after restart
# Config verify — reads back config to confirm value applied
# validate_unraid — notify validated before use
# Silent on correct — runs every boot, no noise when already set ✅
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# PHP_MAX_CHILDREN — target pm.max_children value (default 250)
# PHP_CONF — path to PHP-FPM www.conf (default /etc/php83/php-fpm.d/www.conf)
# Root Required
# Writing to /etc/php83/ requires root.
#
# Single Instance Lock
# acquire_lock prevents concurrent runs at array start.
#
# Process Verify
# Confirms PHP-FPM running after restart — errors if it failed to start.
#
# Config Verify
# Reads back config after restart to confirm the value was actually applied.
#
# Silent on Success
# Runs every boot — no noise when already correct.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
# PHP_MAX_CHILDREN
# Target pm.max_children value. (default: 250)
#
# PHP_CONF
# Path to PHP-FPM www.conf. (default: /etc/php83/php-fpm.d/www.conf)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# php_fpm_max_children.sh
# Read current value. Update and restart PHP-FPM only if changed. Silent if correct.
#
# php_fpm_max_children.sh --dry-run
# Show current vs target value. No config write or restart.
#
# php_fpm_max_children.sh --status
# Show current pm.max_children, target, and PHP-FPM process state.
#
# php_fpm_max_children.sh --log
# Verbose output showing idempotent check, config write, and restart result.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# php_fpm_max_children.sh — normal run (idempotent)
# php_fpm_max_children.sh --dry-run — show what would change
# php_fpm_max_children.sh --status — show current vs target and process state
# php_fpm_max_children.sh --log — verbose output
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+75 -35
View File
@@ -2,65 +2,105 @@
# ==============================================================================================
# ================================= Resource Manager ===========================================
# ==============================================================================================
# Pressure reduction layer — detects rising system load and reduces it intelligently.
# Called by watchdog_orchestrator.sh every minute — single-pass, not a continuous loop.
#
# ── RESPONSIBILITY ────────────────────────────────────────────────────────────────────────────
# Reduce system pressure before things break. NOT fixing broken containers (docker_watchdog)
# and NOT rebooting (system_watchdog). The middle layer that keeps the system comfortable.
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Pressure reduction layer — detects rising system load and reduces it before
# things break. Called by watchdog_orchestrator.sh every minute as a single-
# pass run. The middle layer between docker_watchdog.sh (fixes broken
# containers) and system_watchdog.sh (reboots). Does neither of those things.
#
# "Pressure is rising — reduce load intelligently."
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# ── THREE-LEVEL PRESSURE RESPONSE ─────────────────────────────────────────────────────────────
# Three-Level Pressure Response
#
# Level 1 — SOFT (RAM < RW_RAM_SOFT_GB OR load > RW_LOAD_SOFT_MULTIPLIER × cores):
# Throttle SABnzbd download speed to RW_SABNZBD_SPEED_SOFT
# Throttle qBittorrent download to RW_QBIT_DL_SOFT KB/s
# Throttle SABnzbd download speed to RW_SABNZBD_SPEED_SOFT.
# Throttle qBittorrent download to RW_QBIT_DL_SOFT KB/s.
#
# Level 2 — MEDIUM (RAM < RW_RAM_MEDIUM_GB OR load > RW_LOAD_MEDIUM_MULTIPLIER × cores):
# Further throttle SABnzbd + qBittorrent to medium limits
# docker pause RW_PAUSE_CONTAINERS — suspend without losing state, instant reversible
# Further throttle SABnzbd + qBittorrent to medium limits.
# docker pause RW_PAUSE_CONTAINERS — suspend without losing state, instantly reversible.
#
# Level 3 — HARD (RAM < RW_RAM_HARD_GB):
# docker stop RW_STOP_CONTAINERS — optional/heavy services (games, LocalAI, etc.)
# Write mem_shutdown_active=true signals docker_watchdog to defer container restarts
# docker stop RW_STOP_CONTAINERS — optional/heavy services (games, LocalAI, etc.).
# Write mem_shutdown_active=true signals docker_watchdog to defer container restarts.
#
# ── RECOVERY ──────────────────────────────────────────────────────────────────────────────────
# Pressure must stay below current action threshold for RW_RECOVER_CYCLES consecutive runs
# before restoring. De-escalates one level at a time to avoid re-triggering immediately.
# Level 3 de-escalation additionally requires RAM >= RW_RAM_RECOVER_GB before un-stopping.
# Recovery
# Pressure must stay below current threshold for RW_RECOVER_CYCLES consecutive
# runs before restoring. De-escalates one level at a time — prevents re-triggering
# immediately after recovery. Level 3 additionally requires RAM >= RW_RAM_RECOVER_GB
# before containers are un-stopped.
#
# ── COORDINATION WITH DOCKER WATCHDOG ─────────────────────────────────────────────────────────
# At level 3: writes mem_shutdown_active=true to RW_STATE_FILE.
# Coordination with docker_watchdog.sh
# At level 3, writes mem_shutdown_active=true to RW_STATE_FILE.
# docker_watchdog.sh reads this and defers all container restart logic.
# Cleared when pressure fully resolves and containers are restarted.
# This prevents docker_watchdog from restarting containers that RM just stopped to free RAM.
# Without this, docker_watchdog would immediately restart containers that were
# just stopped to free RAM — defeating the purpose of level 3.
# Cleared when pressure resolves and containers are restarted.
#
# ── NOT RESPONSIBLE FOR ───────────────────────────────────────────────────────────────────────
# Restarting broken containers — docker_watchdog.sh
# Rebooting the system — system_watchdog.sh
# Reacting to single data points — RW_RECOVER_CYCLES prevents flip-flopping
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# Root Required
# docker pause/stop require root.
#
# Single Instance Lock
# acquire_lock prevents concurrent runs from racing on state file writes.
#
# RW_CRITICAL_CONTAINERS
# Containers listed here are never paused or stopped regardless of pressure level.
#
# RW_ENABLED Flag
# Set RW_ENABLED=false to disable the entire script without removing it from
# the orchestrator schedule.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
# RW_ENABLED, RW_STATE_FILE
# RW_RAM_SOFT_GB, RW_RAM_MEDIUM_GB, RW_RAM_HARD_GB, RW_RAM_RECOVER_GB
# RW_LOAD_SOFT_MULTIPLIER, RW_LOAD_MEDIUM_MULTIPLIER
# RW_RECOVER_CYCLES
# RW_SABNZBD_ENABLED, RW_SABNZBD_SPEED_SOFT, RW_SABNZBD_SPEED_MEDIUM
# RW_QBIT_ENABLED, RW_QBIT_DL_SOFT, RW_QBIT_DL_MEDIUM
# RW_CRITICAL_CONTAINERS — never paused or stopped regardless of pressure
# RW_CRITICAL_CONTAINERS — never paused or stopped regardless of pressure
#
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
# HOST*_RW_PAUSE_CONTAINERS — docker pause at medium pressure (aliased by detect_hosts)
# HOST*_RW_STOP_CONTAINERS — docker stop at hard pressure (aliased by detect_hosts)
# master_host*.conf (aliased by detect_hosts())
# HOST*_RW_PAUSE_CONTAINERS — docker pause at medium pressure
# HOST*_RW_STOP_CONTAINERS — docker stop at hard pressure
# HOST*_SABNZBD_URL, HOST*_SABNZBD_API_KEY
# HOST*_QBIT_URL, HOST*_QBIT_USERNAME, HOST*_QBIT_PASSWORD
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# resource_watchdog.sh — normal run (via watchdog_orchestrator.sh)
# resource_watchdog.sh --dry-run — show what would happen without acting
# resource_watchdog.sh --status — current pressure level and active actions
# resource_watchdog.sh --log — verbose per-check output
# ==============================================================================================
# STATE FILES
# ==============================================================================================
#
# RW_STATE_FILE
# Pressure level, recovery cycle count, stopped container list, and the
# mem_shutdown_active coordination flag read by docker_watchdog.sh.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# resource_watchdog.sh
# Single-pass pressure check. Apply actions if threshold crossed. Silent if below.
#
# resource_watchdog.sh --dry-run
# Show current pressure level and what would be throttled/paused/stopped. No changes.
#
# resource_watchdog.sh --status
# Show current pressure level, active actions, recovery cycle count, stopped containers.
#
# resource_watchdog.sh --log
# Verbose per-check output — show RAM, load, each threshold comparison, each action.
#
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+73 -35
View File
@@ -2,49 +2,87 @@
# ==============================================================================================
# ================================= Rsync Stop =================================================
# ==============================================================================================
# Stops rsync intelligently on both local and remote servers.
# Auto-detects orchestrators and chooses the safest stop strategy automatically.
#
# ── TWO MODES ─────────────────────────────────────────────────────────────────────────────────
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Stops rsync intelligently on both local and remote servers. Auto-detects
# running orchestrators and chooses the safest stop strategy. If an
# orchestrator is running, kills only the rsync subprocess so the orchestrator
# exits cleanly after finishing the current share. Use --full-stop to kill
# everything immediately.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Two Stop Modes
# Default (smart):
# Detects if an orchestrator (daily/weekly/critical sync) is running
# If orchestrator found → kills rsync subprocess only
# Orchestrator sees rsync died → moves to next share or exits cleanly
# If no orchestrator → kills rsync directly (standalone rsync.sh run)
# Cleans stale lock files after kill
# Recovers containers left stopped by interrupted rsync (local only)
# Detects if an orchestrator (daily/weekly/critical sync) is running.
# If orchestrator found → kills rsync subprocess only. Orchestrator sees
# rsync exit → moves to next share or exits cleanly on its own.
# If no orchestrator → kills rsync directly (standalone rsync.sh run).
# Cleans stale lock files after kill.
# Recovers containers left stopped by interrupted rsync (local only).
#
# --full-stop (nuclear):
# Kills orchestrator first → then kills rsync
# Orchestrator will NOT continue to next share
# Use when: you need everything dead immediately
# Kills orchestrator first → then kills rsync.
# Orchestrator will NOT continue to next share.
# Use when everything needs to stop immediately.
#
# ── REMOTE HANDLING ───────────────────────────────────────────────────────────────────────────
# Both local and remote handled in one run via SSH.
# Remote containers left as-is — docker_watchdog.sh handles remote container recovery.
# If remote unreachable → skips remote cleanly, logs warning.
# Orchestrator Detection
# detect_rsync_parent() scans all lock files to find which running process
# has rsync as a descendant. No hardcoded list — works for any orchestrator.
# Returns "script_name:parent_pid" if found, empty if standalone.
#
# ── ORCHESTRATOR DETECTION ────────────────────────────────────────────────────────────────────
# detect_rsync_parent() scans all lock files to find which running process
# has rsync as a descendant. No hardcoded listworks for any orchestrator.
# Returns: "script_name:parent_pid" if found, empty if rsync running standalone.
# Remote Handling
# Both local and remote handled in one run via SSH.
# Remote containers left as-is — docker_watchdog.sh handles remote recovery.
# If remote unreachable → skips remote cleanly, logs warning.
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# Root check — pkill and docker require root
# acquire_lock — prevents concurrent stop attempts racing
# DOCKER_TIMEOUT — all docker calls protected against hung daemon
# SSH_TIMEOUT — all remote SSH calls timeout-protected
# SIGTERM → SIGKILL — graceful then forced for orchestrators
# Container recovery — restarts local containers left stopped by killed rsync
# validate_unraid_cmd — notify validated before use
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
# pkill and docker require root.
#
# Single Instance Lock
# acquire_lock prevents concurrent stop attempts racing each other.
#
# Timeout Protection
# DOCKER_TIMEOUT (15s) on all docker calls — hung daemon doesn't block.
# SSH_TIMEOUT (15s) on all remote SSH calls.
#
# SIGTERM → SIGKILL Sequence
# Orchestrators receive SIGTERM first, SIGKILL only if still running after 2s.
#
# Container Recovery
# Restarts local containers left stopped by the killed rsync session.
# Remote containers deferred to docker_watchdog.sh.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# rsync_stop.sh
# Auto-detect orchestrator. Kill rsync-only or full-stop accordingly.
#
# rsync_stop.sh --full-stop
# Kill orchestrator first, then kill rsync. Nothing continues after this.
#
# rsync_stop.sh --rsync-only
# Skip container recovery. Used when called by other scripts that handle
# recovery themselves.
#
# rsync_stop.sh --dry-run
# Show what would be killed without killing anything.
#
# rsync_stop.sh --status
# Show local and remote rsync PIDs, running orchestrators, and lock files.
#
# rsync_stop.sh --full-stop --dry-run
# Preview full-stop sequence without making any changes.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# rsync_stop.sh — smart stop (auto-detect)
# rsync_stop.sh --full-stop — kill orchestrator + rsync
# rsync_stop.sh --rsync-only — skip container recovery (called by other scripts)
# rsync_stop.sh --dry-run — preview without changes
# rsync_stop.sh --status — show what's currently running
# rsync_stop.sh --full-stop --dry-run — preview full stop
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+72 -39
View File
@@ -2,55 +2,88 @@
# ==============================================================================================
# ================================= Server Reboot ==============================================
# ==============================================================================================
# Gracefully reboots the unRAID server with full pre-flight checks and clean shutdown sequence.
# Warns all users, checks for active processes, stops services, syncs disks, then reboots.
#
# ── SHUTDOWN SEQUENCE ─────────────────────────────────────────────────────────────────────────
# 1. Pre-flight warnings — rsync, mover, active Emby sessions (warn not block)
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Gracefully reboots the unRAID server with pre-flight checks, user warnings,
# clean service shutdown, and disk sync. Use instead of raw /sbin/reboot —
# gives users warning time and ensures services stop cleanly before the kernel
# drops.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Shutdown Sequence
# 1. Pre-flight warnings — rsync, mover, active Emby sessions (warn, not block)
# 2. Wall message to all logged-in terminal users
# 3. unRAID notification to dashboard
# 4. Wait REBOOT_SLEEP seconds (default 30) — gives users time to save work
# 5. Gracefully shutdown VMs (virsh shutdown each, then wait)
# 3. unRAID dashboard notification
# 4. Wait REBOOT_SLEEP seconds users time to save work
# 5. Graceful VM shutdown via virsh — ACPI signal, then wait REBOOT_VM_WAIT
# 6. Stop libvirt (VM Manager)
# 7. Stop Docker service
# 8. Sync filesystem buffers to disk
# 9. Reboot
# 8. sync filesystem buffers flushed to disk
# 9. /sbin/reboot
#
# ── PRE-FLIGHT WARNINGS ───────────────────────────────────────────────────────────────────────
# The following are warnings only — they do not block the reboot. You called this script,
# so you know what you're doing. The warnings give you context before the countdown starts.
# - rsync running → partial files possible if mid-transfer
# - mover running → files may be left on cache or array mid-move
# - Emby sessions → active streams/transcodes will be interrupted
# Pre-flight Warnings (informational — do not block)
# rsync running → partial files possible if mid-transfer
# mover running → files may be left mid-move on cache or array
# Emby sessions → active streams/transcodes interrupted
# Warnings do not block the reboot — you called this script, you know.
#
# ── VM GRACEFUL SHUTDOWN ──────────────────────────────────────────────────────────────────────
# virsh shutdown sends ACPI power button signal to each VM — same as pressing power button.
# VM gets a chance to flush its own buffers and shutdown cleanly.
# Waits REBOOT_VM_WAIT seconds (default 30) for VMs to shut down before stopping libvirt.
# If VMs don't shut down in time libvirt stops anyway — system reboot takes priority.
# VM Graceful Shutdown
# virsh shutdown sends the ACPI power button signal — same as pressing the
# physical power button. VM gets a chance to flush buffers and shut down.
# After REBOOT_VM_WAIT seconds, libvirt stops anyway — reboot takes priority.
#
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
# detect_hosts() sets MY_ID — used in wall message, notification, and summary.
# Critical on a two-server setup — wall and notifications show WHICH server is rebooting.
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# Root check — reboot requires root
# acquire_lock — prevents concurrent reboot calls
# detect_hosts() — MY_ID in all user-facing messages
# validate_unraid_cmd — notify validated before use
# Graceful VM shutdown — VMs get clean ACPI signal before libvirt stops
# sync before reboot — filesystem buffers flushed to disk
# Root Required
# /sbin/reboot requires root.
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# REBOOT_SLEEP — seconds to warn users before starting shutdown sequence (default 30)
# REBOOT_VM_WAIT — seconds to wait for VMs to shut down gracefully (default 30)
# Single Instance Lock
# acquire_lock prevents concurrent reboot calls.
#
# Host Identity in All Messages
# detect_hosts() sets MY_ID — wall and notifications show which server is
# rebooting. Critical on a two-server setup.
#
# sync Before Reboot
# filesystem buffers flushed to disk before reboot command.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
# REBOOT_SLEEP
# Seconds between warning and shutdown sequence start. (default: 30)
#
# REBOOT_VM_WAIT
# Seconds to wait for VMs to shut down gracefully. (default: 30)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# server_reboot.sh
# Run pre-flight, warn users, stop services cleanly, then reboot.
#
# server_reboot.sh --dry-run
# Walk through the entire shutdown sequence without stopping anything or rebooting.
#
# server_reboot.sh --status
# Show running processes that would be affected: rsync, mover, VMs, containers.
#
# server_reboot.sh --reason="maintenance"
# Include reason in wall message and notification. Defaults to "manual".
#
# server_reboot.sh --log
# Verbose output — show each step of the shutdown sequence.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# server_reboot.sh — reboot with 30s warning
# server_reboot.sh --dry-run — walk through sequence without rebooting
# server_reboot.sh --status — show running processes that would be affected
# server_reboot.sh --reason="maintenance" — log reason for reboot
# server_reboot.sh --log — verbose output
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+84 -48
View File
@@ -2,71 +2,107 @@
# ==============================================================================================
# ================================= System Watchdog ============================================
# ==============================================================================================
# Last line of defense — reboots the system cleanly if it is about to become unstable.
# Runs continuously as a background process — started by array_started.sh at array start.
# Works alongside docker_watchdog.sh which handles container-level healing first.
#
# ── THREE-TIER RESPONSE SYSTEM ────────────────────────────────────────────────────────────────
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Last line of defense — reboots the system cleanly if it is about to become
# unstable. Runs continuously as a background process started by
# array_started.sh at array start. Works alongside docker_watchdog.sh which
# handles container-level healing first. Only escalates to reboot when
# docker_watchdog.sh cannot resolve the condition.
#
# TIER 1 — CRITICAL (bypass ALL strikes, reboot immediately)
# Docker daemon unresponsive — nothing can be healed, letting it run makes it worse
# rootfs at 99%+ — writes failing, SSH may stop, no recovery options
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Three-Tier Response System
#
# Tier 1 — CRITICAL (bypass all strikes, reboot immediately)
# Docker daemon unresponsive — nothing can be healed; running it longer makes it worse
# rootfs at 99%+ — writes failing; SSH may stop; no recovery options
# Kernel oops/BUG in dmesg — kernel running with corrupted state
# File descriptor exhaustion — new connections and processes failing silently
# /boot read-only unexpectedly — state files and config writes silently failing
#
# TIER 2 — URGENT (bypass strikes when OOM confirms active crisis)
# RAM < MEM_GB AND OOM kills >= OOM_LIMIT in this cycle
# Rationale: OOM kills at this rate means system is dying faster than watchdogs heal
# Without OOM confirmation → standard strike system applies
# Tier 2 — URGENT (bypass strikes when OOM confirms active crisis)
# RAM < MEM_GB AND OOM kills >= OOM_LIMIT in this cycle.
# OOM kills at this rate means the system is dying faster than watchdogs can heal.
# Without OOM confirmation → standard strike system applies.
#
# TIER 3 — STANDARD (N consecutive failures → reboot)
# RAM tiers, load, CPU temp, zombies, /var/log, /tmp, containers, NIC, mdstat
# Tier 3 — STANDARD (N consecutive failures → reboot)
# RAM tiers, load, CPU temp, zombies, /var/log, /tmp, containers, NIC, mdstat.
#
# ── RAM TIERS ─────────────────────────────────────────────────────────────────────────────────
# RAM Tiers
# MEM_WARN_GB (10GB) — warn + notify only
# MEM_SHUTDOWN_GB (6GB) — stop non-essential containers, wait for recovery
# MEM_GB (4GB) — strike system → reboot (bypass if OOM confirms)
# MEM_RECOVER_GB (30GB) — RAM must reach this before containers restart
# MEM_GB (4GB) — strike system → reboot (bypass with OOM confirmation)
# MEM_RECOVER_GB (30GB) — RAM must reach this before stopped containers restart
#
# ── CONTAINER SHUTDOWN LOGIC ──────────────────────────────────────────────────────────────────
# At MEM_SHUTDOWN_GB: stop all containers NOT in SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED
# Excluded: NginxProxyManager, Authelia, Mariadb, Redis, Emby, Dispatcharr
# Stopped containers tracked in shutdown list — won't restart until RAM recovers
# Strike system prevents flip-flopping — shutdown only happens once per degradation event
# Container Shutdown Logic (at MEM_SHUTDOWN_GB)
# Stops all containers not in SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED.
# Stopped containers tracked in shutdown list — won't restart until RAM recovers.
# Strike system prevents flip-flopping — shutdown only once per degradation event.
#
# ── OOM TRACKING ──────────────────────────────────────────────────────────────────────────────
# /proc/vmstat oom_kill counter — read each cycle, delta = kills this cycle
# Included in reboot message with process names from dmesg (diagnostic context)
# Bypass trigger: RAM critical AND kills this cycle >= SYS_WATCHDOG_OOM_LIMIT
# Abort Conditions (prevent reboot during sensitive operations)
# ZFS pool unhealthy, parity running, mover running — each toggleable.
# CRITICAL tier bypasses all abort conditions — imminent crash overrides data safety.
#
# ── NEW CHECKS THIS VERSION ───────────────────────────────────────────────────────────────────
# OOM rate tracking — delta from /proc/vmstat each cycle
# /boot read-only — write test on /boot each cycle
# Kernel oops detection — dmesg BUG/Oops count delta each cycle
# File descriptor exhaustion — /proc/sys/fs/file-nr utilisation
# /tmp usage — tmpfs fill detection with auto-clear attempt
# Array disk errors — mdstat error delta each cycle
# Runaway process — single process >N% CPU sustained (disabled by default)
# NIC state check — primary interface operstate
# sshd check — restart attempt before escalating
# Checks Run Every Cycle
# rootfs usage, /var/log, /tmp, free RAM, ZFS ARC, CPU temp, load avg,
# zombie processes, Docker daemon, OOM rate, /boot read-only, kernel oops,
# file descriptor exhaustion, array disk errors, NIC state, required containers.
#
# ── EXISTING CHECKS ───────────────────────────────────────────────────────────────────────────
# rootfs usage, /var/log, free RAM, ZFS ARC, CPU temp, load avg,
# zombie processes, Docker daemon, required containers from skip list
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# ── ABORT CONDITIONS ──────────────────────────────────────────────────────────────────────────
# ZFS pool unhealthy, parity running, mover running — toggleable
# CRITICAL tier bypasses abort conditions — imminent crash overrides data safety
# Root Required
# Reboot and container stop require root.
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# Full config under System Watchdog section — see master.conf for all vars
# Single Instance Lock
# acquire_lock prevents a second watchdog instance from starting.
#
# State File Verification
# All state files verified writable at startup — errors if any cannot be created.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf — System Watchdog section
# Full variable listing in master.conf. Key variables:
#
# SYS_WATCHDOG_REBOOT_WINDOW_HRS — reboot rate limit window (default: 2)
# SYS_WATCHDOG_MAX_REBOOTS — max reboots in window before giving up (default: 3)
# SYS_WATCHDOG_STRIKES — consecutive failures before reboot (default: 3)
# SYS_WATCHDOG_OOM_LIMIT — OOM kills/cycle to trigger URGENT bypass (default: 3)
# SYS_WATCHDOG_MEM_SHUTDOWN_EXCLUDED — containers exempt from memory shutdown
#
# ==============================================================================================
# STATE FILES
# ==============================================================================================
#
# SYS_WATCHDOG_STATE_FILE — strike counters and cycle state
# SYS_WATCHDOG_REBOOT_LOG — reboot history for rate limiting
# SYS_WATCHDOG_FAILED_FILE — containers confirmed down for skip list integration
# SYS_WATCHDOG_OOM_FILE — OOM kill counter from previous cycle
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# system_watchdog.sh
# Start continuous monitoring loop. Runs until stopped or system reboots.
#
# system_watchdog.sh --dry-run
# Run detection logic without rebooting or stopping containers.
#
# system_watchdog.sh --status
# Show config, thresholds, current system state, and strike counts.
#
# system_watchdog.sh --log
# Verbose per-cycle output — show every check result and threshold comparison.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# system_watchdog.sh — normal start (continuous loop)
# system_watchdog.sh --dry-run — trigger detection without rebooting
# system_watchdog.sh --status — show config and thresholds
# system_watchdog.sh --log — verbose per-cycle output
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+57 -32
View File
@@ -2,45 +2,70 @@
# ==============================================================================================
# ============================= User Scripts Stop ==============================================
# ==============================================================================================
# Stops all running User Script processes spawned by the unRAID User Scripts plugin.
# Identifies processes by their /tmp/user.scripts path signature.
# Shows script names not just PIDs — you know what's being stopped.
#
# ── WHEN TO USE ───────────────────────────────────────────────────────────────────────────────
# - Before a planned reboot when scripts are running mid-cycle
# - When a script is stuck and won't respond to the Abort button in the UI
# - Called automatically by server_reboot.sh as part of shutdown sequence
# - Emergency stop of all background ecosystem scripts
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Stops all running User Script processes spawned by the unRAID User Scripts
# plugin. Shows script names not just PIDs so you know what's being stopped.
# Called automatically by server_reboot.sh as part of the shutdown sequence,
# and useful directly when a script is stuck and won't respond to the UI.
#
# ── HOW IT IDENTIFIES PROCESSES ───────────────────────────────────────────────────────────────
# Scans /proc/*/cmdline for processes whose command line contains "/tmp/user.scripts".
# The unRAID User Scripts plugin stages all scripts in /tmp/user.scripts/ before execution.
# This is more reliable than process name matching which can vary.
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# ── STOP SEQUENCE PER PROCESS ─────────────────────────────────────────────────────────────────
# 1. Send SIGTERM — allows script to trap and clean up gracefully
# Process Identification
# Scans /proc/*/cmdline for processes whose command line contains
# "/tmp/user.scripts". The User Scripts plugin stages all scripts in
# /tmp/user.scripts/ before execution — more reliable than process name
# matching which can vary.
#
# Stop Sequence Per Process
# 1. Send SIGTERM — allows the script to trap and clean up gracefully
# 2. Wait 5 seconds
# 3. Check if still running → SIGKILL (force) if SIGTERM ignored
# 4. Verify dead after SIGKILL
# 3. If still running → SIGKILL (force)
# 4. Verify dead after SIGKILL — error if still running
#
# ── SELF-EXCLUSION ────────────────────────────────────────────────────────────────────────────
# If this script itself is run via the User Scripts plugin it would find its own PID.
# Self-exclusion prevents this script from killing itself mid-execution.
# Self-Exclusion
# If this script is run via the User Scripts plugin it would find its own
# PID in the scan. Self-exclusion by PID prevents killing its own process
# tree mid-execution.
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# Root check — kill requires root for other users' processes
# acquire_lock — prevents concurrent stop attempts
# Self-exclusion — never kills its own process tree
# SIGTERM → SIGKILL — graceful then forced
# Verify after kill — confirms processes are actually dead
# validate_unraid_cmd — notify validated before use
# Silent when clean — no processes running = log() only ✅
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
# kill requires root for other users' processes.
#
# Single Instance Lock
# acquire_lock prevents concurrent stop attempts.
#
# SIGTERM → SIGKILL Sequence
# Graceful first. Forced only if SIGTERM ignored after 5 seconds.
#
# Post-Kill Verify
# Confirms each process is actually dead. Errors and notifies if unkillable.
#
# Silent When Clean
# No processes running = log() only, no visible output.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# user_scripts_stop.sh
# Find and stop all User Script processes. Silent if none running.
#
# user_scripts_stop.sh --dry-run
# Show which processes would be stopped, with names and runtimes. No kills.
#
# user_scripts_stop.sh --status
# Show currently running User Script processes with names and elapsed time.
#
# user_scripts_stop.sh --log
# Verbose output — show each process found, each signal sent, each result.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# user_scripts_stop.sh — stop all user scripts
# user_scripts_stop.sh --dry-run — show what would be stopped
# user_scripts_stop.sh --status — show currently running user scripts
# user_scripts_stop.sh --log — verbose output
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+76 -41
View File
@@ -2,57 +2,92 @@
# ==============================================================================================
# ================================= WebGUI Watchdog ============================================
# ==============================================================================================
# Monitors the unRAID WebGUI and restarts services if unresponsive.
# Uses a three-step escalating strategy — lightest fix first, heaviest last.
# Run every 5-10 minutes via User Scripts plugin.
# Silent when healthy — only produces output when something needs fixing.
#
# ── ESCALATION PATH ───────────────────────────────────────────────────────────────────────────
# Check WebGUI → responding → log() + exit 0 (completely silent ✅)
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Monitors the unRAID WebGUI and restarts services if unresponsive. Uses a
# three-step escalating strategy — lightest fix first, heaviest last. Run
# every 510 minutes via the User Scripts plugin. Silent when healthy.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Escalation Path
# WebGUI responding → log() + exit 0 (completely silent ✅)
#
# Not responding:
# Step 1 — Restart nginx
# Lightest fix — handles most transient WebGUI failures
# nginx crash, worker stuck, connection timeout
# Wait WEBGUI_NGINX_WAIT seconds → recheck
# Step 1 — nginx restart
# Lightest fix — handles most transient WebGUI failures:
# nginx crash, worker stuck, connection timeout.
# Wait WEBGUI_NGINX_WAIT seconds → recheck.
#
# Step 2 — Restart php-fpm
# WebGUI runs through PHP-FPM — worker exhaustion causes silent failure
# php-fpm workers saturated → new requests queue WebGUI appears frozen
# system_tuning_monitor.sh tracks usage — this recovers it
# Wait WEBGUI_PHP_WAIT seconds → recheck
# Step 2 — php-fpm restart
# WebGUI runs through PHP-FPM. Worker exhaustion causes silent
# failure — requests queue and the WebGUI appears frozen.
# Wait WEBGUI_PHP_WAIT seconds → recheck.
#
# Step 3 — Restart emhttp
# Heaviest fix emhttp is the unRAID management daemon
# Array, Docker, shares stay running — only WebGUI management restarts
# Takes longer to recover — WEBGUI_EMHTTP_WAIT gives it time
# Wait WEBGUI_EMHTTP_WAIT seconds → recheck
# Step 3 — emhttp restart
# Heaviest fix. emhttp is the unRAID management daemon.
# Array, Docker, and shares stay running — only WebGUI
# management restarts. Takes longer — WEBGUI_EMHTTP_WAIT.
# Wait WEBGUI_EMHTTP_WAIT seconds → recheck.
#
# All three failed → notify warning, manual intervention needed → exit 1
# All three failed → notify, manual intervention needed → exit 1.
#
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
# detect_hosts() sets MY_ID — used in all notifications and summary.
# Critical on two-server setup — which server's WebGUI failed?
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# acquire_lock — prevents concurrent runs double-restarting services
# detect_hosts() — MY_ID in all notifications
# Process verify — pgrep check after each service restart
# Silent healthy — completely silent on healthy cycle ✅
# validate_unraid_cmd — notify validated before use
# Root Required
# Service restart commands require root.
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# WEBGUI_URL — URL to check (default http://localhost)
# WEBGUI_TIMEOUT — curl timeout in seconds (default 5)
# WEBGUI_NGINX_WAIT — seconds after nginx restart before rechecking (default 15)
# WEBGUI_PHP_WAIT — seconds after php-fpm restart before rechecking (default 10)
# WEBGUI_EMHTTP_WAIT — seconds after emhttp restart before rechecking (default 30)
# Single Instance Lock
# acquire_lock prevents concurrent runs double-restarting services.
#
# Process Verify After Each Restart
# pgrep check after each rc.* command — errors if process not running.
#
# Silent When Healthy
# Completely silent on healthy cycles. Only produces output when recovering.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
# WEBGUI_URL
# URL to check for WebGUI response. (default: http://localhost)
#
# WEBGUI_TIMEOUT
# curl timeout in seconds. (default: 5)
#
# WEBGUI_NGINX_WAIT
# Seconds after nginx restart before rechecking. (default: 15)
#
# WEBGUI_PHP_WAIT
# Seconds after php-fpm restart before rechecking. (default: 10)
#
# WEBGUI_EMHTTP_WAIT
# Seconds after emhttp restart before rechecking. (default: 30)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# webgui_restart.sh
# Check WebGUI. Escalate through nginx → php-fpm → emhttp if unresponsive.
#
# webgui_restart.sh --dry-run
# Show which services would be restarted. No restarts, no waits.
#
# webgui_restart.sh --status
# Show current WebGUI response state and nginx/php-fpm/emhttp process states.
#
# webgui_restart.sh --log
# Verbose output — show each check, each restart attempt, each wait.
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# webgui_restart.sh — check and recover if needed
# webgui_restart.sh --dry-run — show what would be restarted
# webgui_restart.sh --status — show current WebGUI and service states
# webgui_restart.sh --log — verbose output
# ==============================================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"