common.sh gains: - docker_cmd() + verify_running() + retry_docker() — removed from all 3 Docker_Essentials scripts where they were byte-for-byte duplicates - emby_api(endpoint, [timeout=30]) — removed from 6 Media/Tools scripts that each defined their own _emby_api() with the same curl/parse/error pattern; call sites renamed emby_api - format_duration() extended with days/hours branch (was capped at minutes+seconds) - notify() comment: scripts do not need to preflight the notify script via validate_unraid_cmd Tailscale deduplication: - arr_sync.sh: _resolve_node_ip() and inline block in _delete_remote_item() both replaced with resolve_tailscale_ip() from common.sh - git_pull_execute.sh: inline tailscale ip -4 replaced with resolve_tailscale_ip() (adds the tailscale status fallback that was missing)
296 lines
12 KiB
Bash
Executable File
296 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
# ==============================================================================================
|
|
# ================================= Docker Weekly Restart ======================================
|
|
# ==============================================================================================
|
|
#
|
|
# PURPOSE
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Restarts configured containers every Sunday at 2:30am as proactive maintenance.
|
|
#
|
|
# Called by weekly_sync_maintenance.sh via WEEKLY_MAINTENANCE_SCRIPTS. Runs after
|
|
# the sync window has completed and already restarted its own critical containers
|
|
# (Emby, auth stack). Targets a separate set of less-critical services that benefit
|
|
# from a weekly clean start but do not need to be stopped for the sync itself.
|
|
#
|
|
# Same behavioural rules as docker_daily_restart.sh: running → restart,
|
|
# stopped → leave, missing → skip. Container state is always respected.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL SAFEGUARDS
|
|
# ==============================================================================================
|
|
#
|
|
# Dependency Ordering
|
|
# Containers restart in dependency-safe order using HOST*_WATCHDOG_DEPENDENCIES.
|
|
# CONTAINER_DELAY seconds between dependency restart and dependent restart.
|
|
#
|
|
# Restart Verification
|
|
# Container state checked after a settle period. A container that crashes
|
|
# immediately after restart is marked failed with a notification sent.
|
|
#
|
|
# Timeout Protection
|
|
# All docker commands wrapped in a 30 second timeout. A hung Docker daemon
|
|
# cannot cause this script to hang indefinitely.
|
|
#
|
|
# Host Detection
|
|
# detect_hosts() identifies which server is running the script and aliases
|
|
# HOST*_WEEKLY_RESTART_CONTAINERS and HOST*_WATCHDOG_DEPENDENCIES to the
|
|
# correct host's values.
|
|
#
|
|
# Lock Acquisition
|
|
# acquire_lock() prevents concurrent execution.
|
|
#
|
|
# ==============================================================================================
|
|
# CONFIGURATION
|
|
# ==============================================================================================
|
|
#
|
|
# host*.conf
|
|
#
|
|
# HOST*_WEEKLY_RESTART_CONTAINERS
|
|
# Containers restarted weekly. Aliased by detect_hosts() →
|
|
# WEEKLY_RESTART_CONTAINERS
|
|
#
|
|
# HOST*_WATCHDOG_DEPENDENCIES
|
|
# Dependency ordering shared with docker_watchdog.sh. Aliased by
|
|
# detect_hosts() → WATCHDOG_DEPENDENCIES
|
|
#
|
|
# master.conf
|
|
#
|
|
# RETRY_COUNT
|
|
# Retry attempts before giving up on a container
|
|
#
|
|
# SLEEP
|
|
# Seconds between retry attempts
|
|
#
|
|
# CONTAINER_DELAY
|
|
# Seconds to wait after restarting a dependency before starting its dependents
|
|
#
|
|
# ==============================================================================================
|
|
# RUNTIME MODES
|
|
# ==============================================================================================
|
|
#
|
|
# docker_weekly_restart.sh
|
|
# Restart all containers in WEEKLY_RESTART_CONTAINERS
|
|
#
|
|
# docker_weekly_restart.sh --dry-run
|
|
# Preview which containers would be restarted and which would be skipped
|
|
#
|
|
# docker_weekly_restart.sh --status
|
|
# Show configured restart list, container states, and dependency ordering
|
|
#
|
|
# docker_weekly_restart.sh --log
|
|
# Verbose per-container execution output
|
|
#
|
|
# ==============================================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
source "$SCRIPT_DIR/../load_config.sh"
|
|
|
|
parse_args "$@"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Setup ━━━
|
|
# ==============================================================================================
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
error "Must be run as root"
|
|
exit 1
|
|
fi
|
|
acquire_lock
|
|
|
|
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
|
|
|
|
# detect_hosts() sets MY_ID and aliases HOST*_WEEKLY_RESTART_CONTAINERS → WEEKLY_RESTART_CONTAINERS
|
|
detect_hosts
|
|
|
|
if [[ ${#WEEKLY_RESTART_CONTAINERS[@]} -eq 0 ]]; then
|
|
warn "WEEKLY_RESTART_CONTAINERS is empty for $MY_ID — nothing to restart"
|
|
warn "Check HOST*_WEEKLY_RESTART_CONTAINERS in host*.conf"
|
|
exit 0
|
|
fi
|
|
|
|
log "$MY_ID ($LOCAL_SERVER_NAME) — ${#WEEKLY_RESTART_CONTAINERS[@]} containers configured"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Status ━━━
|
|
# ==============================================================================================
|
|
if [[ "$SHOW_STATUS" == true ]]; then
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_CONTAINERS Containers: ${WEEKLY_RESTART_CONTAINERS[*]}"
|
|
echo "$ICON_RETRY Retries: $RETRY_COUNT"
|
|
echo "$ICON_TIME Sleep: ${SLEEP}s between retries"
|
|
echo "$ICON_NOTIFY Notify: unRAID=${NOTIFY_UNRAID:-false} Discord=$([[ -n "${MY_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 ─────────────────────────────────────────────────────────────────────────────────
|
|
# ==============================================================================================
|
|
|
|
# docker_cmd, retry_docker, verify_running — defined in common.sh
|
|
|
|
# Builds a dependency-safe restart order from WEEKLY_RESTART_CONTAINERS.
|
|
# Containers that are dependencies of others restart first.
|
|
build_restart_order() {
|
|
ORDERED_RESTART=()
|
|
local remaining=("${WEEKLY_RESTART_CONTAINERS[@]}")
|
|
local placed=()
|
|
|
|
# First pass — add dependency containers that appear in our list
|
|
for container in "${remaining[@]}"; do
|
|
[[ -z "$container" ]] && continue
|
|
local is_dependency=false
|
|
for dependent in "${!WATCHDOG_DEPENDENCIES[@]}"; do
|
|
if [[ "${WATCHDOG_DEPENDENCIES[$dependent]}" == *"$container"* ]]; then
|
|
is_dependency=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$is_dependency" == true ]]; then
|
|
local already=false
|
|
for p in "${placed[@]}"; do [[ "$p" == "$container" ]] && already=true && break; done
|
|
if [[ "$already" == false ]]; then
|
|
ORDERED_RESTART+=("$container")
|
|
placed+=("$container")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Second pass — add remaining containers (dependents and independents)
|
|
for container in "${remaining[@]}"; do
|
|
[[ -z "$container" ]] && continue
|
|
local already=false
|
|
for p in "${placed[@]}"; do [[ "$p" == "$container" ]] && already=true && break; done
|
|
if [[ "$already" == false ]]; then
|
|
ORDERED_RESTART+=("$container")
|
|
placed+=("$container")
|
|
fi
|
|
done
|
|
|
|
log "Restart order: ${ORDERED_RESTART[*]}"
|
|
}
|
|
|
|
# Waits CONTAINER_DELAY if this container depends on the last restarted one.
|
|
check_dependency_delay() {
|
|
local container="$1"
|
|
local last="$2"
|
|
[[ -z "$last" ]] && return
|
|
local deps="${WATCHDOG_DEPENDENCIES[$container]:-}"
|
|
if [[ -n "$deps" ]] && [[ "$deps" == *"$last"* ]]; then
|
|
log "Waiting ${CONTAINER_DELAY}s — $container depends on $last..."
|
|
sleep "$CONTAINER_DELAY"
|
|
fi
|
|
}
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Weekly Restart ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━ $ICON_CONTAINERS Weekly Restart — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
|
log "$ICON_CONTAINERS Containers: ${WEEKLY_RESTART_CONTAINERS[*]}"
|
|
log "$ICON_RETRY Retries: $RETRY_COUNT"
|
|
log "$ICON_GEAR Config: sleep=${SLEEP}s delay=${CONTAINER_DELAY}s verify-wait=${RESTART_VERIFY_WAIT}s cmd-timeout=${DOCKER_TIMEOUT}s"
|
|
|
|
START=$(date +%s)
|
|
FAILED=()
|
|
RESTARTED=()
|
|
SKIPPED=()
|
|
|
|
# Build dependency-safe restart order
|
|
build_restart_order
|
|
|
|
LAST_RESTARTED=""
|
|
|
|
for container in "${ORDERED_RESTART[@]}"; do
|
|
[[ -z "$container" ]] && continue
|
|
local c_start
|
|
c_start=$(date +%s)
|
|
local c_image
|
|
c_image=$(docker inspect --format '{{.Config.Image}}' "$container" 2>/dev/null || echo "unknown")
|
|
log "━━━ $ICON_CONTAINERS $container ($c_image) ━━━"
|
|
|
|
if ! timeout "$DOCKER_TIMEOUT" docker inspect "$container" &>/dev/null; then
|
|
warn "$container does not exist — skipping"
|
|
continue
|
|
fi
|
|
|
|
STATUS=$(timeout "$DOCKER_TIMEOUT" docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)
|
|
|
|
case "$STATUS" in
|
|
true)
|
|
log "$ICON_RUNNING $container is running — restarting..."
|
|
|
|
# Wait if this container depends on the last one restarted
|
|
check_dependency_delay "$container" "$LAST_RESTARTED"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would restart $container"
|
|
RESTARTED+=("$container")
|
|
else
|
|
if retry_docker docker restart "$container"; then
|
|
if verify_running "$container"; then
|
|
log "$ICON_STARTED $container restarted and running in $(format_duration $(( $(date +%s) - c_start ))) ✅"
|
|
RESTARTED+=("$container")
|
|
LAST_RESTARTED="$container"
|
|
else
|
|
error "$container restarted but crashed immediately"
|
|
notify "$container crashed after restart on $(hostname)" "Docker Weekly Restart" "warning"
|
|
FAILED+=("$container")
|
|
fi
|
|
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)
|
|
# Container was stopped — leave it stopped
|
|
log "$ICON_NOT_RUNNING $container is stopped — skipping (respecting stopped state)"
|
|
SKIPPED+=("$container")
|
|
;;
|
|
*)
|
|
error "Unknown status for $container: $STATUS"
|
|
FAILED+=("$container")
|
|
;;
|
|
esac
|
|
|
|
done
|
|
|
|
END=$(date +%s)
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Summary ━━━
|
|
# ==============================================================================================
|
|
echo "━━━━━ $ICON_SUMMARY WEEKLY RESTART SUMMARY ━━━━━"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
|
|
if [[ ${#RESTARTED[@]} -gt 0 ]]; then
|
|
echo "$ICON_STARTED Restarted: ${#RESTARTED[@]}"
|
|
log " Names: ${RESTARTED[*]}"
|
|
fi
|
|
[[ ${#SKIPPED[@]} -gt 0 ]] && log "$ICON_NOT_RUNNING Skipped: ${SKIPPED[*]} (were stopped)"
|
|
[[ ${#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, ${#SKIPPED[@]} skipped (stopped) 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 |