259 lines
12 KiB
Bash
259 lines
12 KiB
Bash
#!/bin/bash
|
|
# ==============================================================================================
|
|
# =========================== Watchdog Skip List Manager =======================================
|
|
# ==============================================================================================
|
|
# View and manage the persistent container skip list used by docker_watchdog.sh.
|
|
#
|
|
# ── WHAT THE SKIP LIST IS ─────────────────────────────────────────────────────────────────────
|
|
# docker_watchdog.sh adds a container to the skip list when it exceeds the restart loop
|
|
# limit (WATCHDOG_CONTAINER_RESTART_LIMIT in WATCHDOG_CONTAINER_RESTART_WINDOW hours).
|
|
# Once on the skip list the watchdog stops restarting it — prevents infinite restart loops.
|
|
#
|
|
# Skip list persists on /boot/config — survives reboots.
|
|
# Auto-clears when docker_watchdog.sh sees the container running on a cycle.
|
|
# This script clears it manually when you have fixed the underlying problem.
|
|
#
|
|
# ── ACTIONS ───────────────────────────────────────────────────────────────────────────────────
|
|
# --status — show skip list, container states, restart history
|
|
# --clear ContainerName — clear a specific container from skip list + history
|
|
# --clear-all — clear all skip lists and restart history
|
|
#
|
|
# ── AFTER CLEARING ────────────────────────────────────────────────────────────────────────────
|
|
# 1. Fix whatever was causing the container to fail
|
|
# 2. Start it manually: docker start ContainerName
|
|
# 3. docker_watchdog.sh monitors it normally on the next cycle
|
|
# 4. If it crashes again → watchdog adds it back and notifies
|
|
#
|
|
# ── SKIP LIST AUTO-CLEAR ──────────────────────────────────────────────────────────────────────
|
|
# docker_watchdog.sh auto-clears a container from the skip list when it sees it running.
|
|
# So if a container recovers on its own (Docker restart policy eventually works),
|
|
# the watchdog will see it running, remove it from the skip list, and resume monitoring.
|
|
# Manual clear only needed when container is stuck stopped and needs intervention.
|
|
#
|
|
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
|
# acquire_lock — prevents concurrent access with docker_watchdog.sh writing files
|
|
# docker_watchdog check — warns if watchdog is running during clear (could re-add instantly)
|
|
# DOCKER_TIMEOUT — docker inspect calls protected against daemon hangs
|
|
# Confirmation required — interactive: YES | non-interactive: --force flag
|
|
# validate_unraid_cmd — notify validated before use
|
|
#
|
|
# ── FILES MANAGED ─────────────────────────────────────────────────────────────────────────────
|
|
# SYS_WATCHDOG_FAILED_FILE — persistent container skip list
|
|
# WATCHDOG_CONTAINER_RESTART_LOG — restart history for loop detection
|
|
#
|
|
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
|
# watchdog_skip_list_manager.sh — show status
|
|
# watchdog_skip_list_manager.sh --status — show status explicitly
|
|
# watchdog_skip_list_manager.sh --clear ContainerName — clear specific container
|
|
# watchdog_skip_list_manager.sh --clear-all — clear everything
|
|
# Any action supports --dry-run and --force
|
|
# ==============================================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
source "$SCRIPT_DIR/../load_config.sh"
|
|
|
|
DOCKER_TIMEOUT=15
|
|
|
|
# ── Parse action flags before parse_args ──────────────────────────────────────────────────────
|
|
ACTION="status"
|
|
TARGET_CONTAINER=""
|
|
FORCE=false
|
|
FILTERED_ARGS=()
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--clear-all) ACTION="clear-all" ;;
|
|
--clear) ACTION="clear" ;;
|
|
--status) ACTION="status" ;;
|
|
--force) FORCE=true ;;
|
|
*)
|
|
if [[ "$ACTION" == "clear" && -z "$TARGET_CONTAINER" ]]; then
|
|
TARGET_CONTAINER="$arg"
|
|
else
|
|
FILTERED_ARGS+=("$arg")
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
parse_args "${FILTERED_ARGS[@]}"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Setup ━━━
|
|
# ==============================================================================================
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
error "Must be run as root"
|
|
exit 1
|
|
fi
|
|
|
|
validate_unraid_cmd \
|
|
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
|
|
"" "" \
|
|
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
|
|
|
|
acquire_lock
|
|
|
|
# detect_hosts() sets MY_ID — used in output
|
|
detect_hosts
|
|
|
|
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
|
[[ "$FORCE" == true ]] && warn "FORCE mode — confirmation prompt skipped"
|
|
|
|
# Ensure state files exist
|
|
touch "$SYS_WATCHDOG_FAILED_FILE" "$WATCHDOG_CONTAINER_RESTART_LOG" 2>/dev/null
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Status — always shown regardless of action ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━ $ICON_WATCHDOG Skip List Status — $MY_ID ━━━"
|
|
|
|
SKIP_COUNT=$(grep -c "." "$SYS_WATCHDOG_FAILED_FILE" 2>/dev/null || echo 0)
|
|
SKIP_COUNT="${SKIP_COUNT//[^0-9]/}"; SKIP_COUNT="${SKIP_COUNT:-0}"
|
|
RESTART_COUNT=$(wc -l < "$WATCHDOG_CONTAINER_RESTART_LOG" 2>/dev/null || echo 0)
|
|
RESTART_COUNT="${RESTART_COUNT//[^0-9]/}"; RESTART_COUNT="${RESTART_COUNT:-0}"
|
|
|
|
# docker_watchdog.sh running check
|
|
WATCHDOG_RUNNING=false
|
|
if pgrep -f "docker_watchdog.sh" >/dev/null 2>&1; then
|
|
WATCHDOG_RUNNING=true
|
|
warn "docker_watchdog.sh is currently RUNNING"
|
|
[[ "$ACTION" != "status" ]] && \
|
|
warn "Clearing during an active cycle — watchdog may re-add container on next iteration"
|
|
fi
|
|
|
|
echo ""
|
|
if [[ "$SKIP_COUNT" -eq 0 ]]; then
|
|
log "Skip list: empty — all containers monitored normally ✅"
|
|
else
|
|
warn "$SKIP_COUNT container(s) on skip list — manual intervention needed:"
|
|
echo ""
|
|
while IFS= read -r container; do
|
|
[[ -z "$container" ]] && continue
|
|
STATUS=$(timeout "$DOCKER_TIMEOUT" docker inspect -f \
|
|
'{{.State.Running}}' "$container" 2>/dev/null || echo "unknown")
|
|
case "$STATUS" in
|
|
true)
|
|
echo " $ICON_RUNNING $container — RUNNING (watchdog will auto-clear next cycle)"
|
|
;;
|
|
false)
|
|
echo " $ICON_NOT_RUNNING $container — STOPPED — fix and start manually"
|
|
;;
|
|
*)
|
|
echo " $ICON_WARN $container — not found on this server"
|
|
;;
|
|
esac
|
|
done < "$SYS_WATCHDOG_FAILED_FILE"
|
|
fi
|
|
|
|
echo ""
|
|
echo "━━━ $ICON_WATCHDOG Restart History ━━━"
|
|
if [[ "$RESTART_COUNT" -eq 0 ]]; then
|
|
log "No restart history"
|
|
else
|
|
log "$RESTART_COUNT restart entries (window: ${WATCHDOG_CONTAINER_RESTART_WINDOW}h)"
|
|
echo ""
|
|
awk -F'|' '{counts[$1]++} END {
|
|
for (c in counts)
|
|
printf " %-30s %d restart(s)\n", c, counts[c]
|
|
}' "$WATCHDOG_CONTAINER_RESTART_LOG" 2>/dev/null | sort
|
|
fi
|
|
|
|
[[ "$ACTION" == "status" ]] && exit 0
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Clear All ━━━
|
|
# ==============================================================================================
|
|
if [[ "$ACTION" == "clear-all" ]]; then
|
|
echo ""
|
|
echo "━━━ $ICON_TRASH Clear All Skip Lists ━━━"
|
|
warn "This will clear the skip list and restart history for ALL containers"
|
|
echo ""
|
|
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
if [[ "$FORCE" == true ]]; then
|
|
log "FORCE flag set — skipping confirmation"
|
|
elif [[ -t 0 ]]; then
|
|
read -r -p "Type YES to confirm: " CONFIRM
|
|
if [[ "$CONFIRM" != "YES" ]]; then
|
|
warn "Cancelled"
|
|
exit 0
|
|
fi
|
|
else
|
|
error "Non-interactive mode — use --force flag to skip confirmation"
|
|
exit 1
|
|
fi
|
|
|
|
> "$SYS_WATCHDOG_FAILED_FILE"
|
|
> "$WATCHDOG_CONTAINER_RESTART_LOG"
|
|
warn "Skip list cleared ✅"
|
|
warn "Restart history cleared ✅"
|
|
[[ "$WATCHDOG_RUNNING" == true ]] && \
|
|
warn "Note: watchdog is running — containers will be monitored on next cycle"
|
|
notify "Watchdog skip list cleared on $(hostname) ($MY_ID) — all containers will be monitored normally" \
|
|
"Watchdog Manager" "warning"
|
|
else
|
|
warn "DRY RUN — would clear: $SYS_WATCHDOG_FAILED_FILE"
|
|
warn "DRY RUN — would clear: $WATCHDOG_CONTAINER_RESTART_LOG"
|
|
fi
|
|
fi
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Clear Specific Container ━━━
|
|
# ==============================================================================================
|
|
if [[ "$ACTION" == "clear" ]]; then
|
|
echo ""
|
|
echo "━━━ $ICON_TRASH Clear Container: $TARGET_CONTAINER ━━━"
|
|
|
|
if [[ -z "$TARGET_CONTAINER" ]]; then
|
|
error "No container specified"
|
|
error "Usage: watchdog_skip_list_manager.sh --clear ContainerName"
|
|
exit 1
|
|
fi
|
|
|
|
# Remove from skip list
|
|
if ! grep -q "^${TARGET_CONTAINER}$" "$SYS_WATCHDOG_FAILED_FILE" 2>/dev/null; then
|
|
warn "$TARGET_CONTAINER is not on the skip list"
|
|
else
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
sed -i "/^${TARGET_CONTAINER}$/d" "$SYS_WATCHDOG_FAILED_FILE"
|
|
warn "$TARGET_CONTAINER removed from skip list ✅"
|
|
else
|
|
warn "DRY RUN — would remove $TARGET_CONTAINER from skip list"
|
|
fi
|
|
fi
|
|
|
|
# Clear restart history for this container
|
|
HIST_COUNT=$(grep -c "^${TARGET_CONTAINER}|" \
|
|
"$WATCHDOG_CONTAINER_RESTART_LOG" 2>/dev/null || echo 0)
|
|
HIST_COUNT="${HIST_COUNT//[^0-9]/}"; HIST_COUNT="${HIST_COUNT:-0}"
|
|
|
|
if [[ "$HIST_COUNT" -gt 0 ]]; then
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
sed -i "/^${TARGET_CONTAINER}|/d" "$WATCHDOG_CONTAINER_RESTART_LOG"
|
|
warn "Cleared $HIST_COUNT restart history entries for $TARGET_CONTAINER ✅"
|
|
else
|
|
warn "DRY RUN — would clear $HIST_COUNT restart history entries"
|
|
fi
|
|
else
|
|
log "No restart history for $TARGET_CONTAINER"
|
|
fi
|
|
|
|
[[ "$WATCHDOG_RUNNING" == true ]] && \
|
|
warn "Note: watchdog is running — $TARGET_CONTAINER may be re-added if still failing"
|
|
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
echo ""
|
|
echo "━━━ $ICON_INFO Next Steps ━━━"
|
|
echo " 1. Fix whatever was causing $TARGET_CONTAINER to fail"
|
|
echo " 2. Start it manually: docker start $TARGET_CONTAINER"
|
|
echo " 3. docker_watchdog.sh monitors it on the next cycle"
|
|
echo " 4. If it crashes again → watchdog adds it back and notifies"
|
|
notify "$TARGET_CONTAINER cleared from watchdog skip list on $(hostname) ($MY_ID)" \
|
|
"Watchdog Manager" "warning"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY DONE — $MY_ID ━━━━━" |