#!/bin/bash # ============================================================================================== # ================================= Mover Stop ================================================= # ============================================================================================== # # 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. # # ============================================================================================== # OPERATIONAL MODEL # ============================================================================================== # # Stop Sequence # 1. Check if mover is running — exit cleanly if not # 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 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. # # ============================================================================================== # OPERATIONAL SAFEGUARDS # ============================================================================================== # # 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. # # ============================================================================================== 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 — pkill on emhttp processes requires 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" validate_int MOVER_STOP_TIMEOUT "$MOVER_STOP_TIMEOUT" acquire_lock detect_hosts [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made" # ============================================================================================== # ━━━ Status ━━━ # ============================================================================================== if [[ "$SHOW_STATUS" == true ]]; then echo "" echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_MOVER Timeout: ${MOVER_STOP_TIMEOUT}s" echo "$ICON_GEAR Dry Run: $DRY_RUN" echo "" if pgrep -f "emhttp.*Mover" >/dev/null 2>&1; then MOVER_PID=$(pgrep -f "emhttp.*Mover" | head -1) MOVER_START=$(ps -o lstart= -p "$MOVER_PID" 2>/dev/null | xargs) echo " $ICON_MOVER Mover: RUNNING (PID $MOVER_PID)" [[ -n "$MOVER_START" ]] && echo " $ICON_TIME Started: $MOVER_START" else echo " $ICON_MOVER Mover: not running" fi echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi # ============================================================================================== # ━━━ Mover Stop ━━━ # ============================================================================================== START=$(date +%s) if ! pgrep -f "emhttp.*Mover" >/dev/null 2>&1; then echo "Mover is not running — nothing to do" exit 0 fi MOVER_PID=$(pgrep -f "emhttp.*Mover" | head -1) warn "Mover is running (PID $MOVER_PID) — stopping in ${MOVER_STOP_TIMEOUT}s" # ── Warn users via wall ─────────────────────────────────────────────────────────────────────── if [[ "$DRY_RUN" == false ]]; then wall "$ICON_WARN $MY_ID ($LOCAL_SERVER_NAME) — unRAID Mover stopping in ${MOVER_STOP_TIMEOUT}s" echo "Wall message sent — waiting ${MOVER_STOP_TIMEOUT}s..." sleep "$MOVER_STOP_TIMEOUT" else warn "DRY RUN — would send wall warning and wait ${MOVER_STOP_TIMEOUT}s" fi # ── SIGTERM — graceful stop ─────────────────────────────────────────────────────────────────── if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would send SIGTERM to mover (PID $MOVER_PID)" else log "Sending SIGTERM to mover (PID $MOVER_PID)..." pkill -TERM -f "emhttp.*Mover" 2>/dev/null || true sleep 5 # Verify stopped after SIGTERM if ! pgrep -f "emhttp.*Mover" >/dev/null 2>&1; then warn "Mover stopped cleanly (SIGTERM) ✅" else # ── SIGKILL — forced stop ───────────────────────────────────────────────────────────── warn "Mover still running after SIGTERM — sending SIGKILL (may leave partial files)" pkill -KILL -f "emhttp.*Mover" 2>/dev/null || true sleep 2 # Final verify if pgrep -f "emhttp.*Mover" >/dev/null 2>&1; then error "Mover still running after SIGKILL — manual intervention needed" notify "Mover stop failed on $(hostname) ($MY_ID) — process unkillable" \ "Mover Stop" "warning" exit 1 else warn "Mover force-stopped (SIGKILL) — check for partial files on cache" fi fi fi END=$(date +%s) # ============================================================================================== # ━━━ Summary ━━━ # ============================================================================================== echo "" echo "━━━━━ $ICON_SUMMARY MOVER STOP SUMMARY ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_MOVER Timeout: ${MOVER_STOP_TIMEOUT}s" echo "$ICON_TIME Duration: $(format_duration $(( END - START )))" echo "" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — no changes made" else echo "$ICON_DONE Status: done — mover stopped ✅" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"