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:
@@ -30,6 +30,37 @@
|
||||
# /tmp resets on reboot — correct, transcode state should not persist across boots.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# DESIGN PRINCIPLES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Size Is a Ceiling, Not a Reservation
|
||||
# tmpfs allocates on write. RAMDISK_SIZE caps how large the ramdisk may grow; it does
|
||||
# not take that RAM away from the system up front. Sizing it generously costs nothing
|
||||
# until transcodes actually fill it, which is why the ceiling can sit well above
|
||||
# normal usage without starving anything.
|
||||
#
|
||||
# Clean Symlink State Every Boot
|
||||
# TRANSCODE_LINK is reset to the ramdisk at every array start rather than left wherever
|
||||
# the last flip put it. transcode_manager.sh flips it to SSD under pressure, and that
|
||||
# flip is a runtime response to a full ramdisk — carrying it across a reboot would mean
|
||||
# starting on the fallback with an empty ramdisk sitting unused.
|
||||
#
|
||||
# Pre-Create Before Emby Starts
|
||||
# transcoding-temp/ is created on the ramdisk before any container launches. Emby
|
||||
# searches accessible paths for an existing transcoding-temp at startup and binds to
|
||||
# the first it finds — if only the SSD copy exists, every session lands there until
|
||||
# Emby is restarted. Ordering here is not cosmetic; it decides where transcodes go.
|
||||
#
|
||||
# Idempotent Re-Runs
|
||||
# An already-mounted ramdisk is left mounted and only the symlink and permissions are
|
||||
# verified. Re-running never tears down a mount that active sessions are writing into.
|
||||
#
|
||||
# State Belongs in /tmp
|
||||
# The transcode state DB lives in /tmp and resets on reboot. Flip counters and the
|
||||
# current target describe a running system; carrying them across a boot would make the
|
||||
# manager act on pressure that no longer exists.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
@@ -43,9 +74,6 @@
|
||||
# If RAMDISK_PATH is already a mountpoint, reports status and exits cleanly
|
||||
# without attempting to remount or changing anything.
|
||||
#
|
||||
# Notification Validated
|
||||
# platform_require_cmd confirms the notify script is present before use.
|
||||
#
|
||||
# Silent on Success
|
||||
# Startup script runs on every boot — no output when healthy.
|
||||
#
|
||||
@@ -127,6 +155,23 @@ acquire_lock
|
||||
# detect_hosts() sets MY_ID and aliases RAMDISK_SIZE, TRANSCODE_SSD etc.
|
||||
detect_hosts
|
||||
|
||||
# This script mounts a tmpfs over RAMDISK_PATH and, when TRANSCODE_LINK exists but is not a
|
||||
# symlink, rm -rf's it before replacing it. Neither of those checks catches a collapsed path:
|
||||
# / and /mnt both satisfy -e, and mounting a tmpfs over a system directory hides its contents
|
||||
# for the life of the mount. Require at least two path components before either is touched.
|
||||
for _tc_pair in "RAMDISK_PATH:$RAMDISK_PATH" "TRANSCODE_LINK:$TRANSCODE_LINK"; do
|
||||
_tc_name="${_tc_pair%%:*}"
|
||||
_tc_path="${_tc_pair#*:}"
|
||||
_tc_slashes="${_tc_path//[^\/]/}"
|
||||
if [[ -z "$_tc_path" || "$_tc_path" != /* || "${#_tc_slashes}" -lt 2 ]]; then
|
||||
error "$_tc_name is unset or unsafe ('${_tc_path:-unset}') — refusing to mount or relink"
|
||||
notify "Ramdisk setup aborted on $(hostname) ($MY_ID) — $_tc_name is '${_tc_path:-unset}'" \
|
||||
"Ramdisk Setup" "warning"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
unset _tc_pair _tc_name _tc_path _tc_slashes
|
||||
|
||||
log "Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
log "Ramdisk: $RAMDISK_PATH ($RAMDISK_SIZE)"
|
||||
log "Fallback: $TRANSCODE_SSD"
|
||||
|
||||
@@ -15,9 +15,41 @@
|
||||
# 2. Not currently open by any process (checked via lsof pre-built map)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Two locations cleaned in sequence, each through the same routine:
|
||||
#
|
||||
# Ramdisk — only if mountpoint -q confirms it is actually mounted. An unmounted
|
||||
# ramdisk means the underlying directory is the real filesystem, and
|
||||
# cleaning it would delete from disk rather than from tmpfs.
|
||||
# SSD fallback — only if the directory exists.
|
||||
#
|
||||
# Per location:
|
||||
# 1. Path sanity check — refuse anything shallower than two components
|
||||
# 2. Count total and age-eligible files (-mmin +TRANSCODE_MAX_AGE)
|
||||
# 3. One lsof +D call → in-memory open-file map for the whole location
|
||||
# 4. Per eligible file: open → skip and count as active; otherwise rm -f
|
||||
# 5. Remove empty directories older than TRANSCODE_ORPHAN_AGE, never transcoding-temp
|
||||
#
|
||||
# Afterwards, if the ramdisk recovered enough headroom, the symlink is flipped back to
|
||||
# it so new sessions return to RAM.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# DESIGN PRINCIPLES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Two Independent Conditions, Both Required
|
||||
# Age and open-file state are checked separately and a file must pass both. Age alone
|
||||
# would delete a long-running session's segments; lsof alone cannot see HLS segments,
|
||||
# which are written and closed atomically. Neither signal is sufficient on its own,
|
||||
# which is exactly why both are applied rather than picking the better one.
|
||||
#
|
||||
# Mounted-Only Ramdisk Cleaning
|
||||
# The ramdisk is only cleaned when it is genuinely mounted. If the tmpfs failed to
|
||||
# mount, that same path is an ordinary directory on the array — cleaning it then would
|
||||
# delete real files from disk while believing it was clearing RAM.
|
||||
#
|
||||
# lsof Called Once, Not Per File
|
||||
# On a busy Live TV system the ramdisk contains thousands of HLS segment files.
|
||||
# Calling lsof once per file creates thousands of subprocess calls every 7 minutes.
|
||||
@@ -55,6 +87,13 @@
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Location Path Guard
|
||||
# cleanup_location() refuses any path that is not absolute with at least two
|
||||
# components. Neither caller's own check catches a collapsed value — mountpoint -q
|
||||
# returns true for /, and -d is true for both / and /mnt — so the guard lives inside
|
||||
# the function that does the deleting, covering both call sites.
|
||||
#
|
||||
#
|
||||
# Wait Lock
|
||||
# acquire_lock "wait" — waits if a previous cleanup run is still active rather
|
||||
# than exiting. The caller's 7-minute interval can overlap on a slow system.
|
||||
@@ -188,6 +227,19 @@ cleanup_location() {
|
||||
return
|
||||
fi
|
||||
|
||||
# This function deletes every file under $location past the age gate. Neither caller's
|
||||
# own check catches a collapsed path: mountpoint -q returns true for /, and -d is true
|
||||
# for / and /mnt alike. Require at least two path components so a blank or truncated
|
||||
# RAMDISK_PATH / TRANSCODE_SSD can never point this at a system directory.
|
||||
local _loc_slashes="${location//[^\/]/}"
|
||||
if [[ "$location" != /* || "${#_loc_slashes}" -lt 2 ]]; then
|
||||
error "Refusing to clean unsafe location: '$location' ($label)"
|
||||
notify "Transcode cleanup refused unsafe path on $(hostname): '$location'" \
|
||||
"Transcode Cleanup" "warning"
|
||||
LOCATION_REMOVED=0 LOCATION_FREED="0B" LOCATION_SKIPPED=0 LOCATION_ACTIVE=0 LOCATION_STREAMING=0 LOCATION_TOO_YOUNG=0 LOCATION_FAILED=0
|
||||
return
|
||||
fi
|
||||
|
||||
local file_count eligible_count
|
||||
file_count=$(find "$location" -type f 2>/dev/null | wc -l)
|
||||
eligible_count=$(find "$location" -type f -mmin +"$max_age" 2>/dev/null | wc -l)
|
||||
|
||||
@@ -45,6 +45,36 @@
|
||||
# Format: DATE|RAMDISK_USED_GB|FLIP_COUNT|RAM_SESSION_COUNT|SSD_SESSION_COUNT|FILES_CLEANED
|
||||
#
|
||||
# ==============================================================================================
|
||||
# DESIGN PRINCIPLES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Flip the Destination, Never Move the Sessions
|
||||
# Switching targets only repoints the symlink so NEW sessions land elsewhere. Existing
|
||||
# transcodes keep writing to the path they opened and drain naturally. Moving files
|
||||
# mid-transcode would break every stream currently playing, which is the opposite of
|
||||
# what pressure relief is for.
|
||||
#
|
||||
# Asymmetric Thresholds
|
||||
# Flipping away happens at RAMDISK_WARN_GB, flipping back at RAMDISK_LOW_GB — two
|
||||
# separate values, not one. A single threshold would flip on every fluctuation around
|
||||
# it; the gap between them is what makes the decision stable under load.
|
||||
#
|
||||
# Safety Checks Are Unconditional
|
||||
# Symlink, ramdisk presence, SSD presence, transcoding-temp and permissions are all
|
||||
# verified on every run in every mode, including the fixed ramdisk/ssd modes. Mode
|
||||
# controls where transcodes go, not whether the plumbing gets checked.
|
||||
#
|
||||
# Degrade Toward the Fallback
|
||||
# Every failure path resolves toward SSD, never toward an unusable target. A vanished
|
||||
# ramdisk flips to SSD immediately rather than leaving sessions pointed at nothing —
|
||||
# transcoding slower is recoverable, transcoding nowhere is not.
|
||||
#
|
||||
# Observe Without Emby
|
||||
# With Emby down, threshold logic is skipped but the symlink is still verified. There
|
||||
# is no session pressure to react to, and acting on stale usage would flip the target
|
||||
# for sessions that no longer exist.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
@@ -55,9 +85,6 @@
|
||||
# Docker Timeout
|
||||
# DOCKER_TIMEOUT caps all docker calls against a hung daemon.
|
||||
#
|
||||
# Notification Validated
|
||||
# platform_require_cmd confirms the notify script is present before use.
|
||||
#
|
||||
# Silent by Default
|
||||
# Runs every 7 minutes — only speaks when something changes or needs attention.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user