#!/bin/bash # ============================================================================================== # ============================= Docker Container Stop ========================================== # ============================================================================================== # Stops all running Docker containers one at a time, verifying each is stopped before # moving to the next. Called by array_stop.sh as part of a planned shutdown sequence. # # ── STOP SEQUENCE PER CONTAINER ────────────────────────────────────────────────────────────── # 1. docker stop -t 30 (SIGTERM + 30s grace period — docker sends SIGKILL if needed) # 2. Verify stopped — if still running, retry up to RETRY_COUNT times # 3. docker kill (SIGKILL) if all retries exhausted # 4. Final verify — error if still running after force-kill # Never moves to the next container until the current one is confirmed stopped. # # ── WHY SEQUENTIAL ──────────────────────────────────────────────────────────────────────────── # Containers may have dependencies — stopping one at a time avoids abruptly severing a # service while its dependents are still running and trying to use it. # # ── SAFEGUARDS ──────────────────────────────────────────────────────────────────────────────── # Root check — docker requires root # Per-container verify — confirmed stopped before proceeding to next # Retry loop — RETRY_COUNT attempts before escalating to force-kill # SIGTERM → SIGKILL — graceful then forced, never skips graceful # DOCKER_TIMEOUT — all docker calls protected against hung daemon # notify on failures — alert if any container cannot be stopped # # ── CONFIGURATION (master.conf) ─────────────────────────────────────────────────────────────── # RETRY_COUNT — retry attempts before force-kill (default 3) # SLEEP — seconds between retries # # ── USAGE ───────────────────────────────────────────────────────────────────────────────────── # docker_container_stop.sh — stop all running containers # docker_container_stop.sh --dry-run — show which containers would be stopped # docker_container_stop.sh --status — show running containers and exit # docker_container_stop.sh --log — verbose 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 if ! command -v docker &>/dev/null; then error "Docker command not found" exit 1 fi detect_hosts [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no containers will be stopped" DOCKER_TIMEOUT=30 DOCKER_STOP_TIMEOUT=30 # grace period for SIGTERM before docker sends SIGKILL internally _RETRY_COUNT="${RETRY_COUNT:-3}" # ============================================================================================== # ━━━ Status ━━━ # ============================================================================================== if [[ "$SHOW_STATUS" == true ]]; then mapfile -t RUNNING < <(docker ps --format '{{.Names}}' 2>/dev/null | sort) echo "" echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_CONTAINERS Running: ${#RUNNING[@]} container(s)" echo "$ICON_RETRY Retries: $_RETRY_COUNT" for c in "${RUNNING[@]}"; do echo " $ICON_RUNNING $c"; done echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi # ============================================================================================== # ━━━ Stop Containers ━━━ # ============================================================================================== mapfile -t RUNNING < <(docker ps --format '{{.Names}}' 2>/dev/null | sort) echo "" echo "━━━ $ICON_CONTAINERS Docker Container Stop — $(date '+%Y-%m-%d %H:%M:%S') ━━━" if [[ ${#RUNNING[@]} -eq 0 ]]; then log "No running containers — nothing to do" exit 0 fi echo "$ICON_CONTAINERS Containers: ${#RUNNING[@]} running" echo "" START=$(date +%s) STOPPED=() FAILED=() for container in "${RUNNING[@]}"; do [[ -z "$container" ]] && continue echo "━━━ $ICON_CONTAINERS $container ━━━" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would stop $container" STOPPED+=("$container") echo "" continue fi attempt=1 success=false while [[ "$attempt" -le "$_RETRY_COUNT" ]]; do log "$ICON_RETRY Attempt $attempt of $_RETRY_COUNT — stopping $container..." timeout "$DOCKER_TIMEOUT" docker stop -t "$DOCKER_STOP_TIMEOUT" "$container" \ >/dev/null 2>&1 STATE=$(timeout "$DOCKER_TIMEOUT" docker inspect \ -f '{{.State.Running}}' "$container" 2>/dev/null || echo "unknown") if [[ "$STATE" != "true" ]]; then echo "$ICON_DONE $container stopped ✅" STOPPED+=("$container") success=true break fi warn "Attempt $attempt failed — $container still running" (( attempt++ )) [[ "$attempt" -le "$_RETRY_COUNT" ]] && sleep "${SLEEP:-5}" done if [[ "$success" == false ]]; then warn "$container — retries exhausted, force-killing with SIGKILL..." timeout "$DOCKER_TIMEOUT" docker kill "$container" >/dev/null 2>&1 || true STATE=$(timeout "$DOCKER_TIMEOUT" docker inspect \ -f '{{.State.Running}}' "$container" 2>/dev/null || echo "unknown") if [[ "$STATE" != "true" ]]; then warn "$container force-killed ✅" STOPPED+=("$container") else error "$container still running after SIGKILL — manual intervention needed" notify "$container could not be stopped on $(hostname) ($MY_ID)" \ "Docker Container Stop" "warning" FAILED+=("$container") fi fi echo "" done END=$(date +%s) # ============================================================================================== # ━━━ Summary ━━━ # ============================================================================================== echo "━━━━━ $ICON_SUMMARY DOCKER CONTAINER STOP SUMMARY ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_TIME Duration: $(format_duration $(( END - START )))" echo "$ICON_CONTAINERS Scope: ${#RUNNING[@]} running → ${#STOPPED[@]} stopped" [[ ${#STOPPED[@]} -gt 0 ]] && echo "$ICON_DONE Stopped: ${STOPPED[*]}" [[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — no containers stopped" elif [[ ${#FAILED[@]} -eq 0 ]]; then log "$ICON_DONE Status: done ✅ — ${#STOPPED[@]} container(s) stopped" else warn "Status: ${#FAILED[@]} container(s) could not be stopped" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" [[ ${#FAILED[@]} -gt 0 ]] && exit 1 exit 0