Bring script headers onto the template and close safeguard gaps

Headers claimed protections the code never had, and several destructive paths had no
guard against a collapsed config value.
This commit is contained in:
Gmer4Lfe
2026-08-01 20:37:59 -04:00
parent cdce877601
commit e8b114094a
78 changed files with 3301 additions and 277 deletions
+93
View File
@@ -62,6 +62,94 @@
# logged in Sonarr's history as "UserInvokedSearch").
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Detect Always, Act Only on Request
# A bare run probes and reports. Deleting a file the arr believes it has is a
# destructive act, so it requires --remediate explicitly. The scan can be scheduled
# weekly and read without any risk of it removing media on its own.
#
# One Probe Result Is Not Evidence
# ffprobe can fail for reasons that have nothing to do with the file — a mid-write
# import, an NFS blip, a container restart. Corruption must be observed
# CORRUPTION_SCAN_STRIKE_LIMIT times consecutively before remediation acts, and a
# single clean re-probe resets the counter.
#
# Skip-Cache Over Re-Probing
# At 90k+ tracked files a full re-probe every run is not viable. Files unchanged by
# mtime and size since they last verified clean are skipped, so each run spends its
# time on what actually changed rather than re-proving the library from scratch.
#
# Delete the Record, Let the Arr Re-Acquire
# Remediation removes the file record and explicitly triggers a search. The arr is
# left to obtain a good copy through its normal path — this script never tries to
# repair a file in place.
#
# Explicit Search, Not the Background Cycle
# The re-search is triggered directly rather than left to the arr's own missing-search
# cycle, because that cycle skips unmonitored items entirely and would silently leave
# an unmonitored corrupt file deleted and never replaced.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
# docker exec into the ffprobe container requires root.
#
# Lock Acquisition
# acquire_lock prevents overlapping runs. Two instances would both probe and could
# both count a strike against the same file, reaching the limit in half the intended
# number of observations.
#
# Host Detection
# detect_hosts() aliases the arr URLs, API keys and HOST*_FFPROBE_CONTAINER.
#
# jq Dependency Check
# Fails fast if jq is missing — the tracked-file lists and every hasFile verification
# are parsed with it.
#
# Report-Only Default
# Nothing is deleted without --remediate.
#
# ffprobe Configuration Check
# Exits cleanly if FFPROBE_CONTAINER / FFPROBE_BIN are unconfigured for this host.
#
# ffprobe Container Health Check
# check_container_health() verifies the container is running and healthy before any
# probing. Every probe is a docker exec into it — if it is stopped or unhealthy every
# exec fails, every file reads as corrupt, and two consecutive runs would clear the
# strike limit and hand --remediate the whole library to delete.
#
# API Reachability + Version Gate
# check_api then check_arr_version per arr. A version mismatch skips that arr rather
# than issuing deletes against an API whose file-record endpoints may have moved.
#
# Per-Arr Isolation
# Sonarr and Radarr run sequentially, and one failing, unconfigured or version-
# mismatched arr never blocks the other.
#
# Unmapped Path Skip
# Files whose arr-side path cannot be mapped into the ffprobe container's mount
# namespace are skipped and counted, never probed through a wrong path and never
# treated as corrupt because the probe could not see them.
#
# Strike Threshold
# CORRUPTION_SCAN_STRIKE_LIMIT consecutive corrupt detections are required before
# --remediate deletes anything. A transient ffprobe failure cannot trigger a delete,
# and a clean re-probe clears the counter.
#
# Post-Delete Verification
# The DELETE response is never trusted. hasFile is re-checked and must have flipped
# false before the re-search is issued, so a failed delete never leaves the arr
# searching for something it still believes it has.
#
# Targeted Deletion
# Only the specific episodefile/moviefile record for the corrupt file is removed —
# never the series, movie, or any sibling file.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
@@ -174,6 +262,11 @@ if [[ -z "$FFPROBE_CONTAINER" || -z "$FFPROBE_BIN" ]]; then
exit 0
fi
# Every probe is a docker exec into this container. If it is stopped or unhealthy, every
# exec fails, every file reads as corrupt, and two such runs would clear the strike limit
# and hand --remediate an entire library to delete. Abort before probing anything.
check_container_health "$FFPROBE_CONTAINER" "${DOCKER_TIMEOUT:-30}" "Arr Corruption Scan"
CORRUPTION_SCAN_STATE_FILE="${CORRUPTION_SCAN_STATE_FILE:-$DATA_DIR/corruption_scan_state.tsv}"
mkdir -p "$(dirname "$CORRUPTION_SCAN_STATE_FILE")"
touch "$CORRUPTION_SCAN_STATE_FILE"