Chunker splits on the header sections the audit standardised, then sub-splits named-paragraph safeguards — without that a specific question about one of rsync.sh's fourteen safeguards scored below unrelated chunks, because the other thirteen dominated the vector. Index is SQLite with raw float32 blobs and is incremental on mtime; a no-op re-index takes 66ms. The stale defaults were found by asking the system a question and checking its answer: it correctly reported what mover_stop.sh's header claimed, and the header was wrong.
217 lines
9.5 KiB
Bash
Executable File
217 lines
9.5 KiB
Bash
Executable File
#!/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.
|
|
#
|
|
# ==============================================================================================
|
|
# DESIGN PRINCIPLES
|
|
# ==============================================================================================
|
|
#
|
|
# Warn the Room First
|
|
# A wall message goes out before the mover is signalled. The mover moves other people's
|
|
# data between cache and array; anyone watching a transfer deserves to know why it stopped.
|
|
#
|
|
# Graceful First, Forced Last
|
|
# SIGTERM with a configurable window, then SIGKILL only if it is ignored. The mover is
|
|
# mid-file-move by definition — giving it the chance to finish the current file and exit
|
|
# cleanly is the difference between a stopped transfer and a half-moved file.
|
|
#
|
|
# Not Running Is Success
|
|
# An absent mover exits 0. Callers use this as a precondition ("ensure the mover is not
|
|
# running"), not as a command that must find something to kill — treating "already stopped"
|
|
# as failure would abort every reboot on a quiet system.
|
|
#
|
|
# Stop, Never Start
|
|
# This script has no counterpart that restarts the mover. Unraid's own schedule owns when
|
|
# the mover runs; this only ever removes it from the picture for a window.
|
|
#
|
|
# ==============================================================================================
|
|
# 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. (shipped default: 300)
|
|
#
|
|
# ==============================================================================================
|
|
# 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
|
|
|
|
platform_require_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 platform_is_mover_running; then
|
|
MOVER_PID=$(platform_get_mover_pid)
|
|
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 ! platform_is_mover_running; then
|
|
echo "Mover is not running — nothing to do"
|
|
exit 0
|
|
fi
|
|
|
|
MOVER_PID=$(platform_get_mover_pid)
|
|
MOVER_START=$(ps -o lstart= -p "$MOVER_PID" 2>/dev/null | xargs)
|
|
MOVER_ELAPSED=$(ps -o etimes= -p "$MOVER_PID" 2>/dev/null | tr -d ' ')
|
|
warn "Mover is running (PID $MOVER_PID) — stopping in ${MOVER_STOP_TIMEOUT}s"
|
|
echo "$ICON_TIME Mover started: ${MOVER_START:-unknown} — running for $(format_duration "${MOVER_ELAPSED:-0}")"
|
|
|
|
# ── 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)..."
|
|
kill -TERM "$MOVER_PID" 2>/dev/null || true
|
|
sleep 5
|
|
|
|
# Verify stopped after SIGTERM
|
|
if ! platform_is_mover_running; then
|
|
warn "Mover stopped cleanly (SIGTERM) ✅"
|
|
else
|
|
# ── SIGKILL — forced stop ─────────────────────────────────────────────────────────────
|
|
warn "Mover still running after SIGTERM — sending SIGKILL (may leave partial files)"
|
|
kill -KILL "$MOVER_PID" 2>/dev/null || true
|
|
sleep 2
|
|
|
|
# Final verify
|
|
if platform_is_mover_running; 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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |