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
+83 -10
View File
@@ -14,6 +14,30 @@
# or unRAID environment resets after updates.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# For each share in MEDIA_PERMISSION_SHARES:
#
# 1. Path safety and existence
# → unsafe or missing paths are refused or skipped, never scanned
#
# 2. Count wrong ownership (diagnostic)
# → find ! -user / ! -group — the number reported as "corrected"
#
# 3. Ownership pass — only if the count is non-zero
# → chown PERMISSIONS_OWNER on non-matching entries only
#
# 4. Directory mode pass
# → chmod PERMISSIONS_DIR_MODE on directories not already at that mode
#
# 5. File mode pass
# → chmod PERMISSIONS_FILE_MODE on files not already at that mode
# → "No such file" errors ignored: volatile dirs (Emby transcodes) race
#
# Every pass is conditional by design — see Conditional Passes below.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
@@ -35,16 +59,53 @@
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# acquire_lock "wait" — wait if previous run still active (large share scans)
# detect_hosts() — correct share list per host via MY_ID aliases
# Empty array guard — warns and exits cleanly if no shares configured
# Folder existence — skips missing shares with warning, continues others
# Separate passes — directories and files chmod'd separately for correctness
# Conditional passes — only entries whose owner/mode is actually wrong are touched.
# chown/chmod restamp ctime even when the value doesn't change,
# and the arr cleanups gate orphan deletion on ctime
# platform_require_cmd — notify script validated before use
# Silent by default — only failures produce output, success is silent
# Root Enforcement
# chown to an arbitrary owner requires root.
#
# Lock Acquisition
# acquire_lock "wait" — waits rather than skipping. Share scans are long, and
# this runs first in the daily window; skipping it would let arr cleanup run
# against uncorrected ownership.
#
# Host Detection
# detect_hosts() aliases HOST*_MEDIA_PERMISSION_SHARES to this host's shares.
#
# Empty Array Guard
# Exits cleanly if no shares are configured for this host.
#
# Share Path Depth Guard
# Every share must be an absolute path at least three levels deep before it is
# scanned. A truncated entry like /mnt/user passes an existence check and would
# chown and chmod every share on the array — which, because chown/chmod restamp
# ctime, would erase the age signal the arr cleanups depend on across the whole
# library in a single run.
#
# Folder Existence
# Missing shares are skipped with a warning; remaining shares still process.
#
# Separate Passes
# Directories and files are chmod'd in separate passes — directories need the
# execute bit for traversal, media files must not have it.
#
# Conditional Passes
# Only entries whose owner or mode is actually wrong are touched. This is not
# an optimisation: chown/chmod rewrite an inode's ctime even when the value is
# unchanged, so a blanket pass would restamp every file nightly and destroy
# ctime as an age signal. The arr cleanups gate orphan deletion on ctime, and
# mtime cannot substitute — imports preserve the release's original timestamp.
# Making any pass unconditional silently stops orphan collection.
#
# Transcode Race Tolerance
# "No such file or directory" errors from the file pass are ignored. Volatile
# directories such as Emby transcodes delete files mid-scan; that is expected,
# not a permissions failure.
#
# Dry Run Support
# --dry-run counts the dirs, files and ownership entries that would change and
# modifies nothing.
#
# Silent by Default
# Only failures and diagnostics produce output; a clean run is quiet.
#
# Diagnostic — high corrected count on every run means a container has wrong PUID/PGID:
# Correct values on unRAID: PUID=99 (nobody) PGID=100 (users)
@@ -153,6 +214,18 @@ PERMISSIONS_GROUP="${PERMISSIONS_OWNER##*:}"
for SHARE in "${MEDIA_PERMISSION_SHARES[@]}"; do
SHARE_NAME=$(basename "$SHARE")
# A truncated entry such as /mnt/user passes the -d check below and would chown/chmod
# every share on the array. Because chown/chmod restamp ctime, that would erase the age
# signal the arr cleanups gate orphan deletion on — across the whole library, in one run.
_depth="${SHARE//[^\/]/}"
if [[ -z "$SHARE" || "$SHARE" != /* || "${#_depth}" -lt 3 ]]; then
error "Refusing to touch unsafe path: '${SHARE:-empty}' — expected an absolute path at least 3 levels deep"
notify "Media permissions refused unsafe path on $(hostname): '${SHARE:-empty}'" \
"Media Permissions" "warning"
FAILED+=("${SHARE_NAME:-empty}")
continue
fi
if [[ ! -d "$SHARE" ]]; then
warn "$SHARE_NAME not found — skipping"
SKIPPED+=("$SHARE_NAME")