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
+99 -6
View File
@@ -15,8 +15,11 @@
# FORWARD — a movie classified as anime/kids is sitting outside its dedicated root
# REVERSE — a movie sitting inside the kids/anime root doesn't match that classification
#
# Report-only. No files are moved and no Radarr API writes happen — this is a detection
# tool. Every rule below was validated against this library's real data before being
# Report-only by default — no files are moved and no Radarr API writes happen unless a
# mode flag is given. Pass --move to relocate forward misplacements, or --remove-junk to
# delete and import-exclude bad-metadata entries (see OPERATIONAL MODEL below); without
# those flags this is purely a detection tool. Every rule below was validated against
# this library's real data before being
# adopted (see master.conf comments above the curated lists) — this is not a generic
# genre-matcher, it's tuned specifically against the false-positive traps that showed up
# when testing looser rules (documented per-rule below).
@@ -52,13 +55,38 @@
# blocklist and likely nothing legitimate to redownload under that exact TMDb match.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Report pass (always):
# check_api → check_arr_version → arr_get_tracked_data (cache-first, one call)
# → classify every movie → report FORWARD, REVERSE and JUNK findings
#
# Remove-junk pass (--remove-junk, runs first when combined with --move):
# One entry at a time, halting on the first failure.
# DELETE with deleteFiles=false and addImportExclusion=true — the Radarr entry is
# removed and blocked from re-adding, files on disk are never touched. is_junk
# requires hasFile == false, so there is no file behind these entries anyway.
# Verified by re-fetching and requiring a 404 before counting as removed.
#
# Move pass (--move):
# One movie at a time, verified after each.
# hasFile == true → moveFiles=true, then poll the async MoveMovie command to
# "completed" (bounded by RADARR_MOVE_POLL_TIMEOUT) before the
# DB-field check — the DB flips instantly while the physical
# move is still queued.
# hasFile == false → correct rootFolderPath/path and trigger MoviesSearch instead;
# there is nothing to move.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Report, Don't Act
# This script never calls Radarr's write API and never touches a file. Every finding is
# a candidate for a human decision — moving media and re-pointing Radarr's tracking is a
# separate, deliberate follow-up action, not something this scan does automatically.
# Report by Default, Act Only on Request
# A bare run never calls Radarr's write API and never touches a file — every finding is
# just a candidate. Acting on them requires an explicit --move or --remove-junk flag, so
# the scan can be scheduled and re-run freely while the curated lists are being tuned
# without any risk of it rearranging the library on its own.
#
# Curated Lists, Not Bare Genre/Cert Matching
# Every signal used here failed at least once as a bare/standalone check during rule
@@ -72,6 +100,71 @@
# if the shared cache is warm) regardless of library size.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
# Required by the container interaction and state writes.
#
# Lock Acquisition
# acquire_lock, plus acquire_lock "wait" around the write passes with an EXIT trap
# releasing all locks, so an interrupted run never leaves a lock behind.
#
# Host Detection
# detect_hosts() aliases RADARR_URL / RADARR_API_KEY / the root literals.
#
# curl + jq Dependency Check
# Fails fast if either is missing — every classification signal is parsed with jq.
#
# Report-Only Default
# No write happens without --move or --remove-junk.
#
# Required Var Check
# require_var on RADARR_URL and RADARR_API_KEY before any request.
#
# API Reachability + Version Gate
# check_api then check_arr_version against RADARR_VERSION_MAJOR. A major version bump
# can move or rename the fields every rule depends on, so a mismatch aborts rather
# than classifying against an unknown schema.
#
# Empty Library Abort
# A response of 0 movies aborts — an empty list is indistinguishable from a clean
# library and would otherwise report success during an API fault.
#
# Unconfigured Root Skip
# A blank kids/anime root skips that category's checks rather than comparing paths
# against an empty string.
#
# Files Never Deleted
# Junk removal passes deleteFiles=false. Only the Radarr entry is removed, and
# addImportExclusion=true stops it being re-added. is_junk additionally requires
# hasFile == false, so these entries have nothing on disk in the first place.
#
# One At A Time, Stop On First Failure
# Both write passes process one entry at a time and halt on the first failure rather
# than continuing through the library.
#
# Post-Write Verification
# Removal is confirmed by re-fetching and requiring a 404. Moves are confirmed by
# re-fetching and checking rootFolderPath and hasFile. The API response alone is
# never treated as proof.
#
# Async Move Completion Polling
# moveFiles=true flips the DB instantly while the physical move is a separate async
# MoveMovie command. Each move polls its own command to "completed" (bounded by
# RADARR_MOVE_POLL_TIMEOUT) before the DB-field check, so a batch cannot report
# everything moved while files are still queued at the old path.
#
# Junk Vote Threshold
# RADARR_JUNK_MIN_VOTES gates junk detection alongside hasFile == false and a null
# imdbId. All three must hold — a thin-metadata entry that actually has a file, or
# has an IMDb ID, is never treated as junk.
#
# Post-Write Cache Refresh
# The tracked-data cache is refreshed after writes so no other arr script reads a
# stale rootFolderPath or a movie that no longer exists.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#