578 lines
23 KiB
Bash
Executable File
578 lines
23 KiB
Bash
Executable File
#!/bin/bash
|
|
# ==============================================================================================
|
|
# ================================= Rsync Stop =================================================
|
|
# ==============================================================================================
|
|
#
|
|
# PURPOSE
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Stops rsync intelligently on both local and remote servers. Auto-detects
|
|
# running orchestrators and chooses the safest stop strategy. If an
|
|
# orchestrator is running, kills only the rsync subprocess so the orchestrator
|
|
# exits cleanly after finishing the current share. Use --full-stop to kill
|
|
# everything immediately.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL MODEL
|
|
# ==============================================================================================
|
|
#
|
|
# Two Stop Modes
|
|
# Default (smart):
|
|
# Detects if an orchestrator (daily/weekly/critical sync) is running.
|
|
# If orchestrator found → kills rsync subprocess only. Orchestrator sees
|
|
# rsync exit → moves to next share or exits cleanly on its own.
|
|
# If no orchestrator → kills rsync directly (standalone rsync.sh run).
|
|
# Cleans stale lock files after kill.
|
|
# Recovers containers left stopped by interrupted rsync (local only).
|
|
#
|
|
# --full-stop (nuclear):
|
|
# Kills orchestrator first → then kills rsync.
|
|
# Orchestrator will NOT continue to next share.
|
|
# Use when everything needs to stop immediately.
|
|
#
|
|
# Orchestrator Detection
|
|
# detect_rsync_parent() scans all lock files to find which running process
|
|
# has rsync as a descendant. No hardcoded list — works for any orchestrator.
|
|
# Returns "script_name:parent_pid" if found, empty if standalone.
|
|
#
|
|
# Remote Handling
|
|
# Both local and remote handled in one run via SSH.
|
|
# Remote containers left as-is — docker_watchdog.sh handles remote recovery.
|
|
# If remote unreachable → skips remote cleanly, logs warning.
|
|
#
|
|
# ==============================================================================================
|
|
# DESIGN PRINCIPLES
|
|
# ==============================================================================================
|
|
#
|
|
# Stop the Transfer, Not the Schedule
|
|
# The default mode kills only the rsync subprocess and lets the orchestrator notice
|
|
# the exit and wind down on its own. Killing the orchestrator too would abandon the
|
|
# remaining shares silently; letting it finish its own loop keeps the schedule honest
|
|
# about what ran and what did not.
|
|
#
|
|
# Exact-Name Process Matching
|
|
# Targets are found with pgrep -x rsync — exact process name, never a pattern match
|
|
# against a command line. A loose pattern on a box running arbitrary containers could
|
|
# match something that merely mentions rsync in its arguments.
|
|
#
|
|
# Liveness Checked Before Every Signal
|
|
# kill -0 confirms a PID is still alive immediately before signalling it. PIDs are
|
|
# reused, and a transfer that exited on its own between discovery and signalling must
|
|
# not have its number sent a kill.
|
|
#
|
|
# Interrupting Is Safe by Construction
|
|
# rsync runs with --partial, so a killed transfer resumes rather than restarting.
|
|
# That is what makes stopping mid-sync a routine operation rather than a costly one.
|
|
#
|
|
# Clean Up What the Interruption Left
|
|
# A killed rsync leaves its lock file behind and may leave profile containers stopped.
|
|
# Both are cleared afterwards, so the next scheduled run is not blocked by a lock
|
|
# whose owner no longer exists.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL SAFEGUARDS
|
|
# ==============================================================================================
|
|
#
|
|
# Root Required
|
|
# pkill and docker require root.
|
|
#
|
|
# Single Instance Lock
|
|
# acquire_lock prevents concurrent stop attempts racing each other.
|
|
#
|
|
# Timeout Protection
|
|
# DOCKER_TIMEOUT (15s) on all docker calls — hung daemon doesn't block.
|
|
# SSH_TIMEOUT (15s) on all remote SSH calls.
|
|
#
|
|
# SIGTERM → SIGKILL Sequence
|
|
# Orchestrators receive SIGTERM first, SIGKILL only if still running after 2s.
|
|
#
|
|
# Container Recovery
|
|
# Restarts local containers left stopped by the killed rsync session.
|
|
# Remote containers deferred to docker_watchdog.sh.
|
|
#
|
|
# ==============================================================================================
|
|
# CONFIGURATION
|
|
# ==============================================================================================
|
|
#
|
|
# master.conf
|
|
#
|
|
# LOCK_DIR
|
|
# Directory holding rsync and orchestrator lock files. Scanned after a kill to
|
|
# clear locks whose owning PID is gone.
|
|
#
|
|
# DOCKER_TIMEOUT
|
|
# Timeout applied to the docker calls used when recovering containers a killed
|
|
# rsync left stopped.
|
|
#
|
|
# PROFILE_CRITICAL_CONTAINER_NAMES
|
|
# Per-profile container lists — used to work out which containers an interrupted
|
|
# profile sync had stopped and therefore needs restarting.
|
|
#
|
|
# SSH_KEY / SSH timeouts
|
|
# Used to reach the partner when stopping its rsync as well.
|
|
#
|
|
# host*.conf
|
|
#
|
|
# HOST* — resolved via detect_hosts() for MY_ID and remote routing
|
|
#
|
|
# ==============================================================================================
|
|
# RUNTIME MODES
|
|
# ==============================================================================================
|
|
#
|
|
# rsync_stop.sh
|
|
# Auto-detect orchestrator. Kill rsync-only or full-stop accordingly.
|
|
#
|
|
# rsync_stop.sh --full-stop
|
|
# Kill orchestrator first, then kill rsync. Nothing continues after this.
|
|
#
|
|
# rsync_stop.sh --rsync-only
|
|
# Skip container recovery. Used when called by other scripts that handle
|
|
# recovery themselves.
|
|
#
|
|
# rsync_stop.sh --dry-run
|
|
# Show what would be killed without killing anything.
|
|
#
|
|
# rsync_stop.sh --status
|
|
# Show local and remote rsync PIDs, running orchestrators, and lock files.
|
|
#
|
|
# rsync_stop.sh --full-stop --dry-run
|
|
# Preview full-stop sequence without making any changes.
|
|
#
|
|
# ==============================================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
source "$SCRIPT_DIR/../load_config.sh"
|
|
|
|
DOCKER_TIMEOUT=15
|
|
SSH_TIMEOUT=15
|
|
|
|
# ── Parse special flags before parse_args ─────────────────────────────────────────────────────
|
|
FULL_STOP=false
|
|
RSYNC_ONLY_MODE=false
|
|
FILTERED_ARGS=()
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--full-stop) FULL_STOP=true ;;
|
|
--rsync-only) RSYNC_ONLY_MODE=true ;;
|
|
*) FILTERED_ARGS+=("$arg") ;;
|
|
esac
|
|
done
|
|
|
|
parse_args "${FILTERED_ARGS[@]}"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Setup ━━━
|
|
# ==============================================================================================
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
error "Must be run as root — pkill and docker require root"
|
|
exit 1
|
|
fi
|
|
|
|
|
|
acquire_lock
|
|
|
|
if ! command -v docker &>/dev/null; then
|
|
error "Docker command not found"
|
|
exit 1
|
|
fi
|
|
|
|
detect_hosts
|
|
|
|
# Soft IP resolution — rsync_stop continues local-only if remote unreachable
|
|
REMOTE_REACHABLE=false
|
|
REMOTE_SERVER=$(resolve_tailscale_ip "$REMOTE_SERVER_NAME")
|
|
if [[ -z "$REMOTE_SERVER" ]]; then
|
|
warn "$REMOTE_SERVER_NAME — cannot resolve Tailscale IP, remote operations will be skipped"
|
|
elif timeout "$SSH_TIMEOUT" ping -c1 -W3 "$REMOTE_SERVER" &>/dev/null; then
|
|
REMOTE_REACHABLE=true
|
|
echo "$REMOTE_SERVER_NAME reachable ✅"
|
|
else
|
|
warn "$REMOTE_SERVER_NAME unreachable — remote operations will be skipped"
|
|
fi
|
|
|
|
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
|
[[ "$FULL_STOP" == true ]] && warn "FULL STOP mode — orchestrator + rsync will be killed"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Status ━━━
|
|
# ==============================================================================================
|
|
if [[ "$SHOW_STATUS" == true ]]; then
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY RSYNC STOP STATUS ━━━━━"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_NET Remote: $REMOTE_ID ($REMOTE_SERVER_NAME)"
|
|
echo ""
|
|
|
|
LOCAL_PIDS=$(pgrep -x rsync 2>/dev/null | tr '\n' ' ')
|
|
echo " $ICON_SYNC Local rsync PIDs: ${LOCAL_PIDS:-none}"
|
|
|
|
for lockfile in "$LOCK_DIR"/*.lock; do
|
|
[[ -f "$lockfile" ]] || continue
|
|
content=$(cat "$lockfile" 2>/dev/null)
|
|
pid="${content%%:*}"
|
|
name="${content##*:}"
|
|
[[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null && \
|
|
echo " $ICON_RUNNING Lock: $name (PID $pid)"
|
|
done
|
|
|
|
if [[ "$REMOTE_REACHABLE" == true ]]; then
|
|
REMOTE_PIDS=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
|
-o ConnectTimeout="$SSH_TIMEOUT" \
|
|
root@"$REMOTE_SERVER" "pgrep -x rsync || true" 2>/dev/null | tr '\n' ' ')
|
|
echo " $ICON_SYNC Remote rsync PIDs: ${REMOTE_PIDS:-none}"
|
|
else
|
|
echo " $ICON_WARN Remote: unreachable"
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
|
exit 0
|
|
fi
|
|
|
|
# ==============================================================================================
|
|
# ── ORCHESTRATOR DETECTION ────────────────────────────────────────────────────────────────────
|
|
# ==============================================================================================
|
|
# Scans lock files to find which running process has rsync as a descendant.
|
|
# No hardcoded script names — detects any orchestrator automatically.
|
|
|
|
detect_rsync_parent() {
|
|
local rsync_pids
|
|
rsync_pids=$(pgrep -x rsync 2>/dev/null || true)
|
|
[[ -z "$rsync_pids" ]] && echo "" && return
|
|
|
|
for lockfile in "$LOCK_DIR"/*.lock; do
|
|
[[ -f "$lockfile" ]] || continue
|
|
local content pid locked_name
|
|
content=$(cat "$lockfile" 2>/dev/null)
|
|
pid="${content%%:*}"
|
|
locked_name="${content##*:}"
|
|
[[ -z "$pid" ]] && continue
|
|
! kill -0 "$pid" 2>/dev/null && continue
|
|
[[ "$locked_name" == rsync_* ]] && continue
|
|
|
|
local all_descendants
|
|
all_descendants=$(pgrep -P "$pid" 2>/dev/null || true)
|
|
|
|
while IFS= read -r rsync_pid; do
|
|
[[ -z "$rsync_pid" ]] && continue
|
|
local ppid
|
|
ppid=$(awk '/^PPid:/{print $2}' /proc/"$rsync_pid"/status 2>/dev/null || echo "")
|
|
if echo "$all_descendants" | grep -qw "$rsync_pid" 2>/dev/null || \
|
|
[[ "$ppid" == "$pid" ]]; then
|
|
echo "${locked_name}:${pid}"
|
|
return
|
|
fi
|
|
done <<< "$rsync_pids"
|
|
done
|
|
echo ""
|
|
}
|
|
|
|
detect_rsync_parent_remote() {
|
|
[[ "$REMOTE_REACHABLE" != true ]] && echo "" && return
|
|
|
|
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
|
-o ConnectTimeout="$SSH_TIMEOUT" \
|
|
root@"$REMOTE_SERVER" bash << 'REMOTE_SCRIPT' 2>/dev/null
|
|
LOCK_DIR="/tmp/unraid_locks"
|
|
rsync_pids=$(pgrep -x rsync 2>/dev/null || true)
|
|
[[ -z "$rsync_pids" ]] && exit 0
|
|
for lockfile in "$LOCK_DIR"/*.lock; do
|
|
[[ -f "$lockfile" ]] || continue
|
|
content=$(cat "$lockfile" 2>/dev/null)
|
|
pid="${content%%:*}"
|
|
locked_name="${content##*:}"
|
|
[[ -z "$pid" ]] && continue
|
|
! kill -0 "$pid" 2>/dev/null && continue
|
|
[[ "$locked_name" == rsync_* ]] && continue
|
|
all_descendants=$(pgrep -P "$pid" 2>/dev/null || true)
|
|
while IFS= read -r rsync_pid; do
|
|
[[ -z "$rsync_pid" ]] && continue
|
|
ppid=$(awk '/^PPid:/{print $2}' /proc/"$rsync_pid"/status 2>/dev/null || echo "")
|
|
if echo "$all_descendants" | grep -qw "$rsync_pid" 2>/dev/null || \
|
|
[[ "$ppid" == "$pid" ]]; then
|
|
echo "${locked_name}:${pid}"
|
|
exit 0
|
|
fi
|
|
done <<< "$rsync_pids"
|
|
done
|
|
REMOTE_SCRIPT
|
|
}
|
|
|
|
LOCAL_ORCH=$(detect_rsync_parent)
|
|
REMOTE_ORCH=""
|
|
[[ "$REMOTE_REACHABLE" == true ]] && REMOTE_ORCH=$(detect_rsync_parent_remote)
|
|
|
|
# Determine mode
|
|
if [[ "$FULL_STOP" == true ]]; then
|
|
MODE="full-stop"
|
|
elif [[ -n "$LOCAL_ORCH" ]] || [[ -n "$REMOTE_ORCH" ]]; then
|
|
MODE="rsync-only"
|
|
[[ -n "$LOCAL_ORCH" ]] && \
|
|
warn "Local orchestrator detected: ${LOCAL_ORCH%%:*} — rsync-only mode"
|
|
[[ -n "$REMOTE_ORCH" ]] && \
|
|
warn "Remote orchestrator detected: ${REMOTE_ORCH%%:*} — rsync-only mode"
|
|
warn "Use --full-stop to also kill the orchestrator"
|
|
else
|
|
MODE="rsync-only"
|
|
log "No orchestrator detected — killing rsync directly"
|
|
fi
|
|
|
|
# ==============================================================================================
|
|
# ── Kill Orchestrators (full-stop only) ───────────────────────────────────────────────────────
|
|
# ==============================================================================================
|
|
ORCHESTRATORS_KILLED=()
|
|
REMOTE_ORCHESTRATORS_KILLED=()
|
|
|
|
kill_orchestrator() {
|
|
local script_name="$1" pid="$2"
|
|
local lockfile="$LOCK_DIR/${script_name}.lock"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would kill $script_name (PID $pid)"
|
|
return 0
|
|
fi
|
|
|
|
kill -TERM "$pid" 2>/dev/null
|
|
sleep 2
|
|
kill -0 "$pid" 2>/dev/null && kill -KILL "$pid" 2>/dev/null
|
|
sleep 1
|
|
|
|
if ! kill -0 "$pid" 2>/dev/null; then
|
|
warn "$script_name stopped (PID $pid) ✅"
|
|
rm -f "$lockfile"
|
|
return 0
|
|
else
|
|
error "Failed to kill $script_name (PID $pid)"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
if [[ "$MODE" == "full-stop" ]]; then
|
|
echo ""
|
|
echo "━━━ $ICON_STOP Kill Orchestrators ━━━"
|
|
|
|
if [[ -n "$LOCAL_ORCH" ]]; then
|
|
local_name="${LOCAL_ORCH%%:*}"
|
|
local_pid="${LOCAL_ORCH##*:}"
|
|
warn "Killing local: $local_name (PID $local_pid)"
|
|
kill_orchestrator "$local_name" "$local_pid" && \
|
|
ORCHESTRATORS_KILLED+=("$local_name")
|
|
else
|
|
log "No local orchestrator running"
|
|
fi
|
|
|
|
if [[ "$REMOTE_REACHABLE" == true ]] && [[ -n "$REMOTE_ORCH" ]]; then
|
|
remote_name="${REMOTE_ORCH%%:*}"
|
|
remote_pid="${REMOTE_ORCH##*:}"
|
|
remote_lock="$LOCK_DIR/${remote_name}.lock"
|
|
warn "Killing remote: $remote_name (PID $remote_pid)"
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
|
-o ConnectTimeout="$SSH_TIMEOUT" \
|
|
root@"$REMOTE_SERVER" \
|
|
"kill -TERM '$remote_pid' 2>/dev/null; sleep 2; \
|
|
kill -0 '$remote_pid' 2>/dev/null && kill -KILL '$remote_pid' 2>/dev/null; \
|
|
rm -f '$remote_lock'" 2>/dev/null
|
|
warn "Remote $remote_name stopped ✅"
|
|
REMOTE_ORCHESTRATORS_KILLED+=("$remote_name")
|
|
else
|
|
warn "DRY RUN — would kill remote $remote_name (PID $remote_pid)"
|
|
fi
|
|
elif [[ "$REMOTE_REACHABLE" == true ]]; then
|
|
log "No remote orchestrator running"
|
|
fi
|
|
|
|
[[ ${#ORCHESTRATORS_KILLED[@]} -gt 0 || \
|
|
${#REMOTE_ORCHESTRATORS_KILLED[@]} -gt 0 ]] && sleep 3
|
|
fi
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Local Rsync ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━ $ICON_STOP Local Rsync ━━━"
|
|
|
|
LOCAL_KILLED=false
|
|
LOCAL_PIDS=$(pgrep -x rsync 2>/dev/null || true)
|
|
|
|
if [[ -z "$LOCAL_PIDS" ]]; then
|
|
log "No rsync processes running locally"
|
|
else
|
|
warn "Found local rsync PIDs: $(echo "$LOCAL_PIDS" | tr '\n' ' ')"
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would kill local rsync"
|
|
else
|
|
pkill -x rsync 2>/dev/null && LOCAL_KILLED=true || \
|
|
warn "pkill returned non-zero — rsync may have already exited"
|
|
[[ "$LOCAL_KILLED" == true ]] && warn "Local rsync killed ✅"
|
|
fi
|
|
fi
|
|
|
|
# Clean stale rsync lock files
|
|
for lockfile in "$LOCK_DIR"/rsync_*.lock; do
|
|
[[ -f "$lockfile" ]] || continue
|
|
content=$(cat "$lockfile" 2>/dev/null)
|
|
pid="${content%%:*}"
|
|
if [[ -n "$pid" ]] && ! kill -0 "$pid" 2>/dev/null; then
|
|
log "Cleaning stale lock: $(basename "$lockfile")"
|
|
[[ "$DRY_RUN" == false ]] && rm -f "$lockfile"
|
|
fi
|
|
done
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Remote Rsync ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━ $ICON_STOP Remote Rsync — $REMOTE_SERVER_NAME ━━━"
|
|
|
|
REMOTE_KILLED=false
|
|
|
|
if [[ "$REMOTE_REACHABLE" == false ]]; then
|
|
warn "Skipping — $REMOTE_SERVER_NAME unreachable"
|
|
else
|
|
REMOTE_PIDS=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
|
-o ConnectTimeout="$SSH_TIMEOUT" \
|
|
root@"$REMOTE_SERVER" "pgrep -x rsync || true" 2>/dev/null || true)
|
|
|
|
if [[ -z "$REMOTE_PIDS" ]]; then
|
|
log "No rsync running on $REMOTE_SERVER_NAME"
|
|
else
|
|
warn "Found remote rsync PIDs: $(echo "$REMOTE_PIDS" | tr '\n' ' ')"
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would kill remote rsync"
|
|
else
|
|
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
|
-o ConnectTimeout="$SSH_TIMEOUT" \
|
|
root@"$REMOTE_SERVER" "pkill -x rsync || true" 2>/dev/null && \
|
|
REMOTE_KILLED=true || \
|
|
warn "Remote pkill returned non-zero — rsync may have already exited"
|
|
[[ "$REMOTE_KILLED" == true ]] && warn "Remote rsync killed ✅"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Container Recovery ━━━
|
|
# ==============================================================================================
|
|
# Restart local containers left stopped by interrupted rsync.
|
|
# Remote containers left for docker_watchdog.sh to recover.
|
|
# Skipped with --rsync-only flag (called by other scripts that handle recovery themselves).
|
|
CONTAINERS_RESTARTED=()
|
|
CONTAINERS_FAILED=()
|
|
|
|
if [[ "$RSYNC_ONLY_MODE" == false ]] && \
|
|
{ [[ "$LOCAL_KILLED" == true ]] || [[ ${#ORCHESTRATORS_KILLED[@]} -gt 0 ]]; }; then
|
|
|
|
echo ""
|
|
echo "━━━ $ICON_START Container Recovery ━━━"
|
|
log "Checking profile containers for recovery..."
|
|
|
|
declare -A SEEN
|
|
ALL_CONTAINERS=()
|
|
|
|
for profile_containers in "${PROFILE_CRITICAL_CONTAINER_NAMES[@]:-}"; do
|
|
read -r -a container_list <<< "$profile_containers"
|
|
for c in "${container_list[@]:-}"; do
|
|
[[ -z "$c" ]] && continue
|
|
if [[ -z "${SEEN[$c]:-}" ]]; then
|
|
SEEN[$c]=1
|
|
ALL_CONTAINERS+=("$c")
|
|
fi
|
|
done
|
|
done
|
|
|
|
if [[ ${#ALL_CONTAINERS[@]} -eq 0 ]]; then
|
|
log "No profile containers defined — skipping recovery"
|
|
else
|
|
for c in "${ALL_CONTAINERS[@]}"; do
|
|
STATUS=$(timeout "$DOCKER_TIMEOUT" docker inspect -f \
|
|
'{{.State.Running}}' "$c" 2>/dev/null || echo "unknown")
|
|
case "$STATUS" in
|
|
true)
|
|
log "$c — running ✅"
|
|
;;
|
|
false)
|
|
warn "$c — stopped — restarting..."
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would restart $c"
|
|
else
|
|
if timeout "$DOCKER_TIMEOUT" docker start "$c" >/dev/null 2>&1; then
|
|
warn "$c restarted ✅"
|
|
CONTAINERS_RESTARTED+=("$c")
|
|
else
|
|
error "Failed to restart $c"
|
|
CONTAINERS_FAILED+=("$c")
|
|
fi
|
|
fi
|
|
;;
|
|
*)
|
|
log "$c not found locally — skipping"
|
|
;;
|
|
esac
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Summary ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY RSYNC STOP SUMMARY ━━━━━"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_GEAR Mode: $MODE"
|
|
echo ""
|
|
|
|
echo "$ICON_HOST Local ($MY_ID):"
|
|
[[ ${#ORCHESTRATORS_KILLED[@]} -gt 0 ]] && \
|
|
warn " Orchestrators killed: ${ORCHESTRATORS_KILLED[*]}"
|
|
if [[ "$LOCAL_KILLED" == true ]]; then
|
|
warn " Rsync killed ✅"
|
|
else
|
|
log " No rsync was running"
|
|
fi
|
|
|
|
echo "$ICON_NET Remote ($REMOTE_ID — $REMOTE_SERVER_NAME):"
|
|
if [[ "$REMOTE_REACHABLE" == false ]]; then
|
|
warn " Unreachable — skipped"
|
|
else
|
|
[[ ${#REMOTE_ORCHESTRATORS_KILLED[@]} -gt 0 ]] && \
|
|
warn " Orchestrators killed: ${REMOTE_ORCHESTRATORS_KILLED[*]}"
|
|
if [[ "$REMOTE_KILLED" == true ]]; then
|
|
warn " Rsync killed ✅"
|
|
else
|
|
log " No rsync was running"
|
|
fi
|
|
fi
|
|
|
|
[[ ${#CONTAINERS_RESTARTED[@]} -gt 0 ]] && \
|
|
warn "$ICON_CONTAINERS Containers recovered: ${CONTAINERS_RESTARTED[*]}"
|
|
[[ ${#CONTAINERS_FAILED[@]} -gt 0 ]] && \
|
|
echo "$ICON_ERROR Containers failed to restart: ${CONTAINERS_FAILED[*]}"
|
|
|
|
echo ""
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — no changes made"
|
|
else
|
|
echo "$ICON_DONE Status: done ✅"
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
# Notify if anything was actually killed or failed
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
if [[ ${#CONTAINERS_FAILED[@]} -gt 0 ]]; then
|
|
notify "Rsync stop on $(hostname) ($MY_ID) — containers failed to restart: ${CONTAINERS_FAILED[*]}" \
|
|
"Rsync Stop" "warning"
|
|
elif [[ "$LOCAL_KILLED" == true || "$REMOTE_KILLED" == true || \
|
|
${#ORCHESTRATORS_KILLED[@]} -gt 0 ]]; then
|
|
notify "Rsync stopped on $(hostname) ($MY_ID) — mode: $MODE${CONTAINERS_RESTARTED:+ — recovered: ${CONTAINERS_RESTARTED[*]}}" \
|
|
"Rsync Stop" "warning"
|
|
fi
|
|
fi
|
|
|
|
# Explicit, because callers branch on it. Without this the script's status is whatever the last
|
|
# thing to run happened to return — a notify, or a test that evaluated false — and
|
|
# partnership_offboard.sh does `rsync_stop.sh || STEP_STOP_OK=false`. That is how an offboard
|
|
# printed "Rsync stopped ✅" in the log and "Step 1 — Stop rsync: ❌" in its own summary.
|
|
#
|
|
# Only a container that failed to come back is a failure: killing rsync is the job, and finding
|
|
# nothing to kill is a success, not an error.
|
|
[[ ${#CONTAINERS_FAILED[@]} -gt 0 ]] && exit 1
|
|
exit 0 |