renamed docker ess

This commit is contained in:
2026-04-11 21:57:36 -04:00
parent ed19e8f450
commit 8bd2426337
4 changed files with 0 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
#!/bin/bash
# -----------------------------------------------------------------------------------------------
# --------------------------------- Docker Daily Restart ---------------------------------------
# -----------------------------------------------------------------------------------------------
# Restarts or starts specified Docker containers with retry logic.
# Containers are configured in Master.conf under DAILY_RESTART_CONTAINERS.
# Uses global RETRY_COUNT and SLEEP from Master.conf for retry behaviour.
# Sends notifications on completion or failure via common.sh notify().
# Supports --dry-run to preview what would be restarted without taking action.
# -----------------------------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../Master.conf"
source "$SCRIPT_DIR/../common.sh"
parse_args "$@"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_GEAR Setup ━━━"
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
success "Running as root"
if ! command -v docker &>/dev/null; then
error "Docker command not found — check PATH or Docker installation"
notify "Docker daily restart failed — Docker not found on $(hostname)" "Docker Daily Restart" "warning"
exit 1
fi
success "Docker found"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Status ━━━
# -----------------------------------------------------------------------------------------------
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_CONTAINERS Containers: ${DAILY_RESTART_CONTAINERS[*]}"
echo "$ICON_RETRY Retries: $RETRY_COUNT"
echo "$ICON_TIME Sleep: ${SLEEP}s between retries"
echo "$ICON_NOTIFY Notifications: unRAID=${NOTIFY_UNRAID:-false} Discord=$([[ -n "${DISCORD_WEBHOOK:-}" ]] && echo enabled || echo disabled)"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no containers will be restarted"
# -----------------------------------------------------------------------------------------------
# FUNCTIONS
# -----------------------------------------------------------------------------------------------
retry_docker() {
local attempt=1
while [[ "$attempt" -le "$RETRY_COUNT" ]]; do
info "$ICON_RETRY Attempt $attempt of $RETRY_COUNT: $*"
if "$@"; then
success "Succeeded on attempt $attempt"
return 0
else
warn "Attempt $attempt failed"
(( attempt++ ))
[[ "$attempt" -le "$RETRY_COUNT" ]] && sleep "$SLEEP"
fi
done
error "Command failed after $RETRY_COUNT attempts: $*"
return 1
}
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_CONTAINERS Daily Restart ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_CONTAINERS Daily Restart — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
echo "$ICON_CONTAINERS Containers: ${DAILY_RESTART_CONTAINERS[*]}"
echo "$ICON_RETRY Retries: $RETRY_COUNT"
echo ""
START=$(date +%s)
FAILED=()
RESTARTED=()
STARTED=()
for container in "${DAILY_RESTART_CONTAINERS[@]}"; do
echo "━━━ $ICON_CONTAINERS $container ━━━"
if ! docker inspect "$container" &>/dev/null; then
error "$container does not exist — skipping"
FAILED+=("$container")
echo ""
continue
fi
STATUS=$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)
case "$STATUS" in
true)
echo "$ICON_RUNNING $container is running — restarting..."
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would restart $container"
else
if retry_docker docker restart "$container"; then
echo "$ICON_STARTED $container restarted"
RESTARTED+=("$container")
else
error "Failed to restart $container after $RETRY_COUNT attempts"
notify "$container failed to restart on $(hostname)" "Docker Daily Restart" "warning"
FAILED+=("$container")
fi
fi
;;
false)
echo "$ICON_NOT_RUNNING $container is stopped — starting..."
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would start $container"
else
if retry_docker docker start "$container"; then
echo "$ICON_STARTED $container started"
STARTED+=("$container")
else
error "Failed to start $container after $RETRY_COUNT attempts"
notify "$container failed to start on $(hostname)" "Docker Daily Restart" "warning"
FAILED+=("$container")
fi
fi
;;
*)
error "Unknown status for $container: $STATUS"
FAILED+=("$container")
;;
esac
echo ""
done
END=$(date +%s)
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Summary ━━━
# -----------------------------------------------------------------------------------------------
echo "━━━━━ $ICON_SUMMARY DAILY RESTART SUMMARY ━━━━━"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
[[ ${#RESTARTED[@]} -gt 0 ]] && echo "$ICON_STARTED Restarted: ${RESTARTED[*]}"
[[ ${#STARTED[@]} -gt 0 ]] && echo "$ICON_STARTED Started: ${STARTED[*]}"
[[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}"
if [[ "$DRY_RUN" == true ]]; then
echo "$ICON_WARN Status: DRY RUN — no changes made"
elif [[ ${#FAILED[@]} -eq 0 ]]; then
echo "$ICON_DONE Status: $ICON_SUCCESS ALL DONE"
notify "Daily restart complete — ${#RESTARTED[@]} restarted, ${#STARTED[@]} started on $(hostname)" "Docker Daily Restart" "normal"
else
echo "$ICON_ERROR Status: $ICON_ERROR ${#FAILED[@]} container(s) failed"
notify "Daily restart completed with errors on $(hostname) — failed: ${FAILED[*]}" "Docker Daily Restart" "warning"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
[[ ${#FAILED[@]} -gt 0 ]] && exit 1
exit 0
+159
View File
@@ -0,0 +1,159 @@
#!/bin/bash
# -----------------------------------------------------------------------------------------------
# --------------------------------- Docker Network Connect -------------------------------------
# -----------------------------------------------------------------------------------------------
# Connects specified Docker containers to extra networks on array start.
# Useful when containers need to communicate across networks they were not
# originally configured with — e.g. memcached needing access to nextcloud-aio network.
#
# Every container in NETWORK_CONNECT_CONTAINERS is connected to every network
# in NETWORK_CONNECT_NETWORKS. Already-connected containers are skipped cleanly.
#
# Run once at array start via User Scripts plugin.
# All configuration in Master.conf under Docker Network Connect section.
# Supports --dry-run to preview connections without making them.
# -----------------------------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../Master.conf"
source "$SCRIPT_DIR/../common.sh"
parse_args "$@"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_GEAR Setup ━━━"
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
success "Running as root"
if ! command -v docker &>/dev/null; then
error "Docker not found"
exit 1
fi
success "Docker found"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Status ━━━
# -----------------------------------------------------------------------------------------------
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_CONTAINERS Containers: ${NETWORK_CONNECT_CONTAINERS[*]}"
echo "$ICON_DOCKER_NET Networks: ${NETWORK_CONNECT_NETWORKS[*]}"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo ""
echo "━━━ Current Connections ━━━"
for container in "${NETWORK_CONNECT_CONTAINERS[@]}"; do
[[ -z "$container" ]] && continue
echo "$ICON_CONTAINERS $container:"
docker inspect "$container" \
--format '{{range $k, $v := .NetworkSettings.Networks}} {{$k}}{{"\n"}}{{end}}' \
2>/dev/null || echo " not found"
done
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no network connections will be made"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_DOCKER_NET Network Connect ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_DOCKER_NET Network Connect — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
echo "$ICON_CONTAINERS Containers: ${NETWORK_CONNECT_CONTAINERS[*]}"
echo "$ICON_DOCKER_NET Networks: ${NETWORK_CONNECT_NETWORKS[*]}"
echo ""
START=$(date +%s)
CONNECTED=()
SKIPPED=()
FAILED=()
for container in "${NETWORK_CONNECT_CONTAINERS[@]}"; do
[[ -z "$container" ]] && continue
echo "━━━ $ICON_CONTAINERS $container ━━━"
# Verify container exists
if ! docker inspect "$container" &>/dev/null; then
warn "$container not found — skipping"
FAILED+=("$container")
echo ""
continue
fi
for network in "${NETWORK_CONNECT_NETWORKS[@]}"; do
[[ -z "$network" ]] && continue
# Verify network exists
if ! docker network inspect "$network" &>/dev/null; then
warn "Network $network not found — skipping"
FAILED+=("$container:$network")
continue
fi
# Check if already connected
if docker network inspect "$network" \
--format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null \
| grep -qw "$container"; then
info "$ICON_DOCKER_NET $container already connected to $network — skipping"
SKIPPED+=("$container$network")
continue
fi
# Connect
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would connect $container to $network"
continue
fi
info "$ICON_DOCKER_NET Connecting $container to $network..."
if docker network connect "$network" "$container" 2>/dev/null; then
success "$ICON_DOCKER_NET $container connected to $network"
CONNECTED+=("$container$network")
else
error "Failed to connect $container to $network"
FAILED+=("$container:$network")
fi
done
echo ""
done
END=$(date +%s)
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Summary ━━━
# -----------------------------------------------------------------------------------------------
echo "━━━━━ $ICON_SUMMARY NETWORK CONNECT SUMMARY ━━━━━"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
[[ ${#CONNECTED[@]} -gt 0 ]] && echo "$ICON_DOCKER_NET Connected: ${CONNECTED[*]}"
[[ ${#SKIPPED[@]} -gt 0 ]] && echo "$ICON_RUNNING Skipped: ${SKIPPED[*]}"
[[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}"
echo ""
if [[ "$DRY_RUN" == true ]]; then
echo "$ICON_WARN Status: DRY RUN — no changes made"
elif [[ ${#FAILED[@]} -gt 0 ]]; then
echo "$ICON_ERROR Status: $ICON_ERROR SOME CONNECTIONS FAILED"
notify "Docker network connect failed on $(hostname)${FAILED[*]}" "Network Connect" "warning"
else
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
if [[ ${#CONNECTED[@]} -gt 0 ]]; then
notify "Docker networks connected on $(hostname)${CONNECTED[*]}" "Network Connect" "normal"
fi
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
[[ ${#FAILED[@]} -gt 0 ]] && exit 1
exit 0
+446
View File
@@ -0,0 +1,446 @@
#!/bin/bash
# -----------------------------------------------------------------------------------------------
# --------------------------------- Docker Watchdog --------------------------------------------
# -----------------------------------------------------------------------------------------------
# First line of defense — monitors Docker containers for memory, CPU, HTTP responsiveness,
# and unexpected stops. Restarts containers that exceed thresholds or go offline.
#
# Works alongside system_watchdog.sh:
# docker_watchdog.sh — container level, minimal disruption, tries to self-heal
# system_watchdog.sh — system level, last resort, reboots when healing fails
#
# Behaviour:
# Memory — immediate restart if hard limit exceeded
# CPU — strike system, restarts after CPU_FAIL_LIMIT consecutive hits
# HTTP — strike system, restarts after RESP_FAIL_LIMIT consecutive failures
# Required — strike system, restarts stopped containers, persistent skip list
# prevents reboot loops, auto-clears when container recovers
# Daemon — immediate notify if Docker daemon is unresponsive
#
# Strike cadence depends on cron schedule:
# Every 15min + 2 strikes = 30min sustained before restart
# Every 10min + 2 strikes = 20min sustained before restart
# Every 5min + 2 strikes = 10min sustained before restart
#
# All configuration in Master.conf under Docker Watchdog section.
# Supports --dry-run to show what would happen without acting.
# -----------------------------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../Master.conf"
source "$SCRIPT_DIR/../common.sh"
parse_args "$@"
TOTAL_CORES=$(nproc)
# Persistent skip list — shared with system_watchdog.sh
# Containers in this list are skipped until they recover
SKIP_LIST_FILE="$SYS_WATCHDOG_FAILED_FILE"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_GEAR Setup ━━━"
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
success "Running as root"
info "$ICON_WATCHDOG Watchdog initialising — $TOTAL_CORES cores detected"
touch "$WATCHDOG_STATE_FILE" 2>/dev/null || {
error "Cannot create state file: $WATCHDOG_STATE_FILE"
exit 1
}
touch "$SKIP_LIST_FILE" 2>/dev/null || {
error "Cannot create skip list: $SKIP_LIST_FILE"
exit 1
}
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Status ━━━
# -----------------------------------------------------------------------------------------------
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_WATCHDOG Monitored: ${!WATCHDOG_CONTAINERS[*]}"
echo "$ICON_CONTAINERS Required: ${WATCHDOG_REQUIRED_CONTAINERS[*]}"
echo "$ICON_MEM Soft mem: ${SOFT_MEM_THRESHOLD}% of limit"
echo "$ICON_ZFS CPU soft: ${SOFT_CPU_THRESHOLD}%"
echo "$ICON_ZFS CPU hard: ${HARD_CPU_THRESHOLD}%"
echo "$ICON_RETRY CPU strikes: ${CPU_FAIL_LIMIT}"
echo "$ICON_RETRY Resp strikes: ${RESP_FAIL_LIMIT}"
echo "$ICON_TIME Curl timeout: ${CURL_TIMEOUT}s"
echo "$ICON_NOTIFY Notify: unRAID=${NOTIFY_UNRAID:-false} Discord=$([[ -n "${DISCORD_WEBHOOK:-}" ]] && echo enabled || echo disabled)"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no containers will be restarted"
# -----------------------------------------------------------------------------------------------
# STATE HELPERS
# -----------------------------------------------------------------------------------------------
get_strikes() {
local container="$1" metric="$2"
grep -E "^${container}:${metric}:" "$WATCHDOG_STATE_FILE" 2>/dev/null | cut -d':' -f3
}
set_strikes() {
local container="$1" metric="$2" count="$3"
grep -vE "^${container}:${metric}:" "$WATCHDOG_STATE_FILE" 2>/dev/null > "${WATCHDOG_STATE_FILE}.tmp"
echo "${container}:${metric}:${count}" >> "${WATCHDOG_STATE_FILE}.tmp"
mv "${WATCHDOG_STATE_FILE}.tmp" "$WATCHDOG_STATE_FILE"
}
# -----------------------------------------------------------------------------------------------
# SKIP LIST HELPERS
# Container skip list — persistent across reboots via /boot/
# Auto-clears entries when container is found running again.
# -----------------------------------------------------------------------------------------------
is_in_skip_list() {
local container="$1"
grep -qE "^${container}$" "$SKIP_LIST_FILE" 2>/dev/null
}
add_to_skip_list() {
local container="$1"
if ! is_in_skip_list "$container"; then
echo "$container" >> "$SKIP_LIST_FILE"
warn "$ICON_WATCHDOG $container added to persistent skip list"
notify "$container added to watchdog skip list on $(hostname) — manual check recommended" "Docker Watchdog" "warning"
fi
}
remove_from_skip_list() {
local container="$1"
grep -vE "^${container}$" "$SKIP_LIST_FILE" 2>/dev/null > "${SKIP_LIST_FILE}.tmp"
mv "${SKIP_LIST_FILE}.tmp" "$SKIP_LIST_FILE"
success "$ICON_WATCHDOG $container recovered — removed from skip list"
notify "$container recovered and removed from watchdog skip list on $(hostname)" "Docker Watchdog" "normal"
}
# -----------------------------------------------------------------------------------------------
# SKIP LIST AUTO-HEAL CHECK
# On every run check if any skipped containers are now running.
# If running remove from skip list — could have recovered after reboot or manual fix.
# -----------------------------------------------------------------------------------------------
check_skip_list_recovery() {
[[ ! -s "$SKIP_LIST_FILE" ]] && return
info "$ICON_WATCHDOG Checking skip list for recovered containers..."
while IFS= read -r container; do
[[ -z "$container" ]] && continue
STATUS=$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null || echo "unknown")
if [[ "$STATUS" == "true" ]]; then
remove_from_skip_list "$container"
else
log "$container still not running — remains on skip list"
fi
done < "$SKIP_LIST_FILE"
}
# -----------------------------------------------------------------------------------------------
# DOCKER DAEMON HEALTH CHECK
# Verifies Docker daemon is responding before attempting any container operations.
# A hung daemon means all checks will fail — notify immediately and exit.
# -----------------------------------------------------------------------------------------------
check_docker_daemon() {
info "$ICON_CONTAINERS Checking Docker daemon..."
if ! timeout 10 docker ps >/dev/null 2>&1; then
error "Docker daemon is not responding"
notify "Docker daemon unresponsive on $(hostname) — immediate attention required" "Docker Watchdog" "warning"
exit 1
fi
success "Docker daemon is healthy"
}
# -----------------------------------------------------------------------------------------------
# HELPERS
# -----------------------------------------------------------------------------------------------
parse_stats() {
local container="$1"
docker stats --no-stream --format "{{.MemUsage}}|{{.CPUPerc}}" "$container"
}
convert_to_mb() {
local value="$1" unit="$2"
case "$unit" in
KiB) awk "BEGIN {print $value / 1024}" ;;
MiB) echo "$value" ;;
GiB) awk "BEGIN {print $value * 1024}" ;;
*) echo "UNKNOWN" ;;
esac
}
# Restarts a container and sends notification.
# Failed restarts also notify — system_watchdog.sh is the next line of defense.
restart_container() {
local container="$1" reason="$2"
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would restart $container ($reason)"
return 0
fi
info "Restarting $container ($reason)..."
if docker restart "$container" >/dev/null 2>&1; then
echo "$ICON_STARTED $container restarted"
notify "$container restarted on $(hostname)$reason" "Docker Watchdog" "warning"
return 0
else
error "Failed to restart $container"
notify "Failed to restart $container on $(hostname)$reason" "Docker Watchdog" "warning"
return 1
fi
}
# -----------------------------------------------------------------------------------------------
# MEMORY CHECK
# Restarts immediately if container exceeds hard memory limit.
# Warns if approaching soft threshold.
# Usage: check_memory "Emby" 16384
# -----------------------------------------------------------------------------------------------
check_memory() {
local container="$1" limit_mb="$2"
local stats mem_raw mem_val mem_unit mem_mb usage_pct
stats=$(parse_stats "$container")
mem_raw=$(echo "$stats" | awk -F'|' '{print $1}' | awk '{print $1}')
mem_val=$(echo "$mem_raw" | sed -E 's/([0-9.]+).*/\1/')
mem_unit=$(echo "$mem_raw" | sed -E 's/[0-9.]+([a-zA-Z]+).*/\1/')
mem_mb=$(convert_to_mb "$mem_val" "$mem_unit")
local mem_int
mem_int=$(printf "%.0f" "$mem_mb")
usage_pct=$(( (mem_int * 100) / limit_mb ))
if (( usage_pct >= 100 )); then
error "$ICON_MEM $container memory ${mem_int}MB (${usage_pct}%) — exceeded ${limit_mb}MB hard limit"
restart_container "$container" "memory hard limit"
set_strikes "$container" "CPU" 0
set_strikes "$container" "RESP" 0
elif (( usage_pct >= SOFT_MEM_THRESHOLD )); then
warn "$ICON_MEM $container memory ${mem_int}MB (${usage_pct}% of ${limit_mb}MB limit)"
else
success "$ICON_MEM $container memory ${mem_int}MB (${usage_pct}% of ${limit_mb}MB limit)"
fi
}
# -----------------------------------------------------------------------------------------------
# CPU CHECK
# Strike system — restarts after CPU_FAIL_LIMIT consecutive over-threshold checks.
# Resets strikes on recovery or restart.
# Usage: check_cpu "Emby"
# -----------------------------------------------------------------------------------------------
check_cpu() {
local container="$1"
local stats cpu_raw cpu_norm cpu_int violations
stats=$(parse_stats "$container")
cpu_raw=$(echo "$stats" | awk -F'|' '{print $2}' | tr -d '%')
cpu_norm=$(awk "BEGIN {print $cpu_raw / $TOTAL_CORES}")
cpu_int=$(printf "%.0f" "$cpu_norm")
violations=$(get_strikes "$container" "CPU")
[[ -z "$violations" ]] && violations=0
if (( cpu_int >= HARD_CPU_THRESHOLD )); then
((violations++))
error "$ICON_ZFS $container CPU ${cpu_int}% — hard threshold ($violations/$CPU_FAIL_LIMIT strikes)"
set_strikes "$container" "CPU" "$violations"
elif (( cpu_int >= SOFT_CPU_THRESHOLD )); then
((violations++))
warn "$ICON_ZFS $container CPU ${cpu_int}% — soft threshold ($violations/$CPU_FAIL_LIMIT strikes)"
set_strikes "$container" "CPU" "$violations"
else
[[ $violations -gt 0 ]] && info "$ICON_ZFS $container CPU ${cpu_int}% — recovered, resetting strikes"
[[ $violations -eq 0 ]] && success "$ICON_ZFS $container CPU ${cpu_int}%"
set_strikes "$container" "CPU" 0
violations=0
fi
if (( violations >= CPU_FAIL_LIMIT )); then
error "$ICON_ZFS $container CPU limit hit for $CPU_FAIL_LIMIT consecutive checks"
restart_container "$container" "sustained CPU abuse"
set_strikes "$container" "CPU" 0
fi
}
# -----------------------------------------------------------------------------------------------
# RESPONSIVENESS CHECK
# Strike system — restarts after RESP_FAIL_LIMIT consecutive failed HTTP checks.
# Skips containers with no URL defined in WATCHDOG_CONTAINER_URLS.
# Usage: check_responsiveness "Emby"
# -----------------------------------------------------------------------------------------------
check_responsiveness() {
local container="$1"
local url="${WATCHDOG_CONTAINER_URLS[$container]:-}"
[[ -z "$url" ]] && return
local fails
fails=$(get_strikes "$container" "RESP")
[[ -z "$fails" ]] && fails=0
if ! curl -s --max-time "$CURL_TIMEOUT" "$url" >/dev/null 2>&1; then
((fails++))
warn "$ICON_PING $container unresponsive at $url ($fails/$RESP_FAIL_LIMIT strikes)"
set_strikes "$container" "RESP" "$fails"
else
[[ $fails -gt 0 ]] && info "$ICON_PING $container responsive again — resetting strikes"
[[ $fails -eq 0 ]] && success "$ICON_PING $container responsive at $url"
set_strikes "$container" "RESP" 0
fails=0
fi
if (( fails >= RESP_FAIL_LIMIT )); then
error "$ICON_PING $container unresponsive for $RESP_FAIL_LIMIT consecutive checks"
restart_container "$container" "HTTP unresponsive"
set_strikes "$container" "RESP" 0
fi
}
# -----------------------------------------------------------------------------------------------
# REQUIRED CONTAINER CHECK
# Monitors WATCHDOG_REQUIRED_CONTAINERS for unexpected stops.
# Strike system — attempts restart on each strike.
# After strike limit hit — adds to persistent skip list and notifies system_watchdog handoff.
# Skip list auto-clears at start of each run if container has recovered.
# Usage: check_required_containers
# -----------------------------------------------------------------------------------------------
check_required_containers() {
[[ ${#WATCHDOG_REQUIRED_CONTAINERS[@]} -eq 0 ]] && return
info "$ICON_CONTAINERS Checking required containers..."
for container in "${WATCHDOG_REQUIRED_CONTAINERS[@]}"; do
[[ -z "$container" ]] && continue
# Skip if on persistent skip list
if is_in_skip_list "$container"; then
warn "$ICON_NOT_RUNNING $container is on skip list — skipping until recovered"
continue
fi
STATUS=$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null || echo "unknown")
if [[ "$STATUS" == "true" ]]; then
success "$ICON_RUNNING $container is running"
set_strikes "$container" "STOP" 0
continue
fi
if [[ "$STATUS" == "unknown" ]]; then
warn "$container not found on this host — skipping"
continue
fi
# Container is stopped — apply strike
local strikes
strikes=$(get_strikes "$container" "STOP")
[[ -z "$strikes" ]] && strikes=0
((strikes++))
warn "$ICON_NOT_RUNNING $container is stopped ($strikes/$SYS_WATCHDOG_STRIKE_LIMIT strikes)"
set_strikes "$container" "STOP" "$strikes"
# Attempt restart on each strike
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would attempt restart of $container"
else
if restart_container "$container" "unexpected stop"; then
set_strikes "$container" "STOP" 0
else
# Restart failed
if (( strikes >= SYS_WATCHDOG_STRIKE_LIMIT )); then
error "$container failed to restart after $SYS_WATCHDOG_STRIKE_LIMIT attempts"
add_to_skip_list "$container"
set_strikes "$container" "STOP" 0
notify "$container handed off to system_watchdog on $(hostname) — added to skip list" "Docker Watchdog" "warning"
fi
fi
fi
done
}
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_WATCHDOG Watchdog Run ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_WATCHDOG Watchdog Run — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
echo ""
START=$(date +%s)
SKIPPED=()
# Daemon check first — if daemon is down nothing else works
check_docker_daemon
# Auto-heal skip list before processing
check_skip_list_recovery
# -----------------------------------------------------------------------------------------------
# Resource monitoring — WATCHDOG_CONTAINERS
# -----------------------------------------------------------------------------------------------
if [[ ${#WATCHDOG_CONTAINERS[@]} -gt 0 ]]; then
echo ""
echo "━━━ $ICON_MEM Resource Monitoring ━━━"
for container in "${!WATCHDOG_CONTAINERS[@]}"; do
echo ""
info "$ICON_CONTAINERS $container"
if ! docker inspect "$container" &>/dev/null; then
warn "$container not found — skipping"
SKIPPED+=("$container")
continue
fi
if ! docker ps --filter "name=^/${container}$" --format "{{.Names}}" | grep -qw "$container"; then
warn "$ICON_NOT_RUNNING $container is not running — skipping resource checks"
SKIPPED+=("$container")
continue
fi
check_memory "$container" "${WATCHDOG_CONTAINERS[$container]}"
check_cpu "$container"
check_responsiveness "$container"
done
fi
# -----------------------------------------------------------------------------------------------
# Required container monitoring — WATCHDOG_REQUIRED_CONTAINERS
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_CONTAINERS Required Container Check ━━━"
check_required_containers
END=$(date +%s)
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Summary ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━━━ $ICON_SUMMARY WATCHDOG SUMMARY ━━━━━"
echo "$ICON_TIME $(date '+%Y-%m-%d %H:%M:%S')"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
echo "$ICON_WATCHDOG Monitored: ${#WATCHDOG_CONTAINERS[@]} containers"
echo "$ICON_CONTAINERS Required: ${#WATCHDOG_REQUIRED_CONTAINERS[@]} containers"
[[ ${#SKIPPED[@]} -gt 0 ]] && echo "$ICON_NOT_RUNNING Skipped: ${SKIPPED[*]}"
[[ "$DRY_RUN" == true ]] && echo "$ICON_WARN Dry Run: no actions taken"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+174
View File
@@ -0,0 +1,174 @@
#!/bin/bash
# -----------------------------------------------------------------------------------------------
# --------------------------------- Docker Weekly Restart --------------------------------------
# -----------------------------------------------------------------------------------------------
# Restarts or starts specified Docker containers with retry logic.
# Containers are configured in Master.conf under WEEKLY_RESTART_CONTAINERS.
# Uses global RETRY_COUNT and SLEEP from Master.conf for retry behaviour.
# Sends notifications on completion or failure via common.sh notify().
# Supports --dry-run to preview what would be restarted without taking action.
# -----------------------------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../Master.conf"
source "$SCRIPT_DIR/../common.sh"
parse_args "$@"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_GEAR Setup ━━━"
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
success "Running as root"
if ! command -v docker &>/dev/null; then
error "Docker command not found — check PATH or Docker installation"
notify "Docker weekly restart failed — Docker not found on $(hostname)" "Docker Weekly Restart" "warning"
exit 1
fi
success "Docker found"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Status ━━━
# -----------------------------------------------------------------------------------------------
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_CONTAINERS Containers: ${WEEKLY_RESTART_CONTAINERS[*]}"
echo "$ICON_RETRY Retries: $RETRY_COUNT"
echo "$ICON_TIME Sleep: ${SLEEP}s between retries"
echo "$ICON_NOTIFY Notifications: unRAID=${NOTIFY_UNRAID:-false} Discord=$([[ -n "${DISCORD_WEBHOOK:-}" ]] && echo enabled || echo disabled)"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no containers will be restarted"
# -----------------------------------------------------------------------------------------------
# FUNCTIONS
# -----------------------------------------------------------------------------------------------
# Attempts a docker command up to RETRY_COUNT times with SLEEP seconds between attempts.
# Returns 0 on success, 1 if all attempts fail.
retry_docker() {
local attempt=1
while [[ "$attempt" -le "$RETRY_COUNT" ]]; do
info "$ICON_RETRY Attempt $attempt of $RETRY_COUNT: $*"
if "$@"; then
success "Succeeded on attempt $attempt"
return 0
else
warn "Attempt $attempt failed"
(( attempt++ ))
[[ "$attempt" -le "$RETRY_COUNT" ]] && sleep "$SLEEP"
fi
done
error "Command failed after $RETRY_COUNT attempts: $*"
return 1
}
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_CONTAINERS Weekly Restart ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_CONTAINERS Weekly Restart — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
echo "$ICON_CONTAINERS Containers: ${WEEKLY_RESTART_CONTAINERS[*]}"
echo "$ICON_RETRY Retries: $RETRY_COUNT"
echo ""
START=$(date +%s)
FAILED=()
RESTARTED=()
STARTED=()
for container in "${WEEKLY_RESTART_CONTAINERS[@]}"; do
echo "━━━ $ICON_CONTAINERS $container ━━━"
if ! docker inspect "$container" &>/dev/null; then
error "$container does not exist — skipping"
FAILED+=("$container")
echo ""
continue
fi
STATUS=$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)
case "$STATUS" in
true)
echo "$ICON_RUNNING $container is running — restarting..."
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would restart $container"
else
if retry_docker docker restart "$container"; then
echo "$ICON_STARTED $container restarted"
RESTARTED+=("$container")
else
error "Failed to restart $container after $RETRY_COUNT attempts"
notify "$container failed to restart on $(hostname)" "Docker Weekly Restart" "warning"
FAILED+=("$container")
fi
fi
;;
false)
echo "$ICON_NOT_RUNNING $container is stopped — starting..."
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would start $container"
else
if retry_docker docker start "$container"; then
echo "$ICON_STARTED $container started"
STARTED+=("$container")
else
error "Failed to start $container after $RETRY_COUNT attempts"
notify "$container failed to start on $(hostname)" "Docker Weekly Restart" "warning"
FAILED+=("$container")
fi
fi
;;
*)
error "Unknown status for $container: $STATUS"
FAILED+=("$container")
;;
esac
echo ""
done
END=$(date +%s)
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Summary ━━━
# -----------------------------------------------------------------------------------------------
echo "━━━━━ $ICON_SUMMARY WEEKLY RESTART SUMMARY ━━━━━"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
[[ ${#RESTARTED[@]} -gt 0 ]] && echo "$ICON_STARTED Restarted: ${RESTARTED[*]}"
[[ ${#STARTED[@]} -gt 0 ]] && echo "$ICON_STARTED Started: ${STARTED[*]}"
[[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}"
if [[ "$DRY_RUN" == true ]]; then
echo "$ICON_WARN Status: DRY RUN — no changes made"
elif [[ ${#FAILED[@]} -eq 0 ]]; then
echo "$ICON_DONE Status: $ICON_SUCCESS ALL DONE"
notify "Weekly restart complete — ${#RESTARTED[@]} restarted, ${#STARTED[@]} started on $(hostname)" "Docker Weekly Restart" "normal"
else
echo "$ICON_ERROR Status: $ICON_ERROR ${#FAILED[@]} container(s) failed"
notify "Weekly restart completed with errors on $(hostname) — failed: ${FAILED[*]}" "Docker Weekly Restart" "warning"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
[[ ${#FAILED[@]} -gt 0 ]] && exit 1
exit 0