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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user