diff --git a/Arrs_Stack/arr_corruption_scan.sh b/Arrs_Stack/arr_corruption_scan.sh index ffcf0f3..373ea08 100755 --- a/Arrs_Stack/arr_corruption_scan.sh +++ b/Arrs_Stack/arr_corruption_scan.sh @@ -64,7 +64,13 @@ # container almost certainly mounts shares differently) # # master.conf -# CORRUPTION_SCAN_STATE_FILE — path to the clean-file skip-cache (default in DATA_DIR) +# CORRUPTION_SCAN_STATE_FILE — path to the clean-file skip-cache (default in DATA_DIR) +# CORRUPTION_SCAN_STRIKES_FILE — path to the consecutive-corrupt-detection counter (default +# in DATA_DIR), keyed by host path +# CORRUPTION_SCAN_STRIKE_LIMIT — consecutive corrupt detections required before --remediate +# acts on a file (default 2) — guards against a one-off +# ffprobe hiccup (mid-write file, NFS blip) triggering an +# unnecessary delete+re-search. Resets on a clean re-probe. # SONARR_VERSION_MAJOR — reused from sonarr_cleanup.sh for the API version check # # ============================================================================================== @@ -165,6 +171,36 @@ CORRUPTION_SCAN_STATE_FILE="${CORRUPTION_SCAN_STATE_FILE:-$DATA_DIR/corruption_s mkdir -p "$(dirname "$CORRUPTION_SCAN_STATE_FILE")" touch "$CORRUPTION_SCAN_STATE_FILE" +CORRUPTION_SCAN_STRIKES_FILE="${CORRUPTION_SCAN_STRIKES_FILE:-$DATA_DIR/corruption_scan_strikes.tsv}" +CORRUPTION_SCAN_STRIKE_LIMIT="${CORRUPTION_SCAN_STRIKE_LIMIT:-2}" +mkdir -p "$(dirname "$CORRUPTION_SCAN_STRIKES_FILE")" +touch "$CORRUPTION_SCAN_STRIKES_FILE" + +# Thin wrappers around common.sh's wd_state_get/wd_state_set — same shape as +# stability_watchdog.sh's get_strikes/set_strikes/increment_strikes/reset_strikes, keyed here +# by host path instead of a watchdog check name. Requires repeat corrupt detections across +# separate scan runs before --remediate acts, so a one-off ffprobe hiccup (mid-write file, +# NFS blip) can't trigger an unnecessary delete+re-search on its own. +get_scan_strikes() { + wd_state_get "$1" "$CORRUPTION_SCAN_STRIKES_FILE" +} +set_scan_strikes() { + wd_state_set "$1" "$2" "$CORRUPTION_SCAN_STRIKES_FILE" +} +increment_scan_strikes() { + local current + current=$(get_scan_strikes "$1") + [[ -z "$current" ]] && current=0 + (( current++ )) + set_scan_strikes "$1" "$current" + echo "$current" +} +reset_scan_strikes() { + local current + current=$(get_scan_strikes "$1") + [[ -n "$current" && "$current" != "0" ]] && set_scan_strikes "$1" 0 +} + if [[ "$SHOW_STATUS" == true ]]; then echo "" echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━" @@ -174,6 +210,7 @@ if [[ "$SHOW_STATUS" == true ]]; then echo "$ICON_GEAR FFprobe binary: $FFPROBE_BIN" echo "$ICON_GEAR FFprobe path map: ${#FFPROBE_PATH_MAP[@]} entries" echo "$ICON_GEAR State file: $CORRUPTION_SCAN_STATE_FILE" + echo "$ICON_GEAR Strike limit: $CORRUPTION_SCAN_STRIKE_LIMIT" echo "$ICON_GEAR Remediate: $REMEDIATE" echo "$ICON_GEAR Scan limit: ${SCAN_LIMIT:-unlimited}" echo "$ICON_GEAR Path filter: ${PATH_FILTER:-none}" @@ -278,6 +315,7 @@ SCANNED=0 SKIPPED_CACHED=0 SKIPPED_UNMAPPED=0 CORRUPT_COUNT=0 +STRIKE_HELD=0 REMEDIATED=0 REMEDIATE_FAILED=0 @@ -318,6 +356,7 @@ while IFS= read -r item; do fi if [[ "$result" == "clean" ]]; then + reset_scan_strikes "$host_path" echo -e "${host_path}\t${stamp}" >> "$FRESH_CLEAN_TMP" [[ "$ENABLE_LOGGING" == true ]] && echo " $ICON_SUCCESS $host_path" continue @@ -326,13 +365,21 @@ while IFS= read -r item; do # corrupt: reason="${result#corrupt:}" (( CORRUPT_COUNT++ )) - echo " $ICON_ERROR CORRUPT: $host_path" + strikes=$(increment_scan_strikes "$host_path") + echo " $ICON_ERROR CORRUPT: $host_path (strike $strikes/$CORRUPTION_SCAN_STRIKE_LIMIT)" [[ "$ENABLE_LOGGING" == true ]] && echo " $reason" if [[ "$REMEDIATE" != true ]]; then continue fi + if (( strikes < CORRUPTION_SCAN_STRIKE_LIMIT )); then + warn " $host_path — strike $strikes/$CORRUPTION_SCAN_STRIKE_LIMIT, not yet remediating (needs repeat confirmation)" + (( STRIKE_HELD++ )) + continue + fi + reset_scan_strikes "$host_path" + episode_id=$(echo "$item" | jq -r '.episodeId // empty') title=$(echo "$item" | jq -r '.sceneName // .relativePath // .path') @@ -389,6 +436,7 @@ echo "$ICON_SUCCESS Skipped (cached): $SKIPPED_CACHED" echo "$ICON_WARN Skipped (unmapped): $SKIPPED_UNMAPPED" echo "$ICON_ERROR Corrupt found: $CORRUPT_COUNT" if [[ "$REMEDIATE" == true ]]; then + echo "$ICON_WARN Held (strikes): $STRIKE_HELD" echo "$ICON_SUCCESS Remediated: $REMEDIATED" echo "$ICON_ERROR Remediation failed: $REMEDIATE_FAILED" fi diff --git a/Deployment/master.conf.template b/Deployment/master.conf.template index 2c31d5c..e419737 100644 --- a/Deployment/master.conf.template +++ b/Deployment/master.conf.template @@ -1185,6 +1185,12 @@ # queued behind other moves already in progress, not just its # own copy time CORRUPTION_SCAN_STATE_FILE="${DATA_DIR}/corruption_scan_state.tsv" # clean-file skip-cache + CORRUPTION_SCAN_STRIKES_FILE="${DATA_DIR}/corruption_scan_strikes.tsv" # consecutive corrupt-detection counts, keyed by host path + CORRUPTION_SCAN_STRIKE_LIMIT=2 # consecutive corrupt detections (across separate scan runs) + # required before --remediate deletes+re-searches — guards + # against a one-off ffprobe hiccup (mid-write file, NFS blip) + # triggering an unnecessary delete. Resets to 0 the moment a + # file probes clean again. SONARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "ts" "wmv" "mov") SONARR_PROTECTED_PATTERNS=( # Subtitles