massive update. Master conf split, now modular with a load sceriprt to drive all configs to scripts. with unraid scpecific safeguard tests , and improved standardized ux. including dynamic host detect, who am i who else it there. EVERY SINGLE SCRIPT UPDATED. DEBATING THAT THIS IS ACUALLY V2
This commit is contained in:
+653
-285
File diff suppressed because it is too large
Load Diff
+168
-114
@@ -1,79 +1,136 @@
|
||||
#!/bin/bash
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# --------------------------------- Ramdisk Setup Script ---------------------------------------
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ================================= Ramdisk Setup ==============================================
|
||||
# ==============================================================================================
|
||||
# Creates a tmpfs ramdisk for Emby transcodes and points the transcode symlink at it.
|
||||
# Run once at array start via User Scripts plugin — scheduled as "At Startup of Array".
|
||||
# If ramdisk already mounted reports status and exits cleanly without remounting.
|
||||
# If ramdisk is already mounted reports status and exits cleanly without remounting.
|
||||
#
|
||||
# Creates:
|
||||
# RAMDISK_PATH — tmpfs mount point (fast, in-memory transcode location)
|
||||
# TRANSCODE_SSD — SSD fallback directory (created if missing)
|
||||
# TRANSCODE_LINK — symlink pointing at ramdisk by default
|
||||
# ── WHAT IT CREATES ───────────────────────────────────────────────────────────────────────────
|
||||
# RAMDISK_PATH — tmpfs mount point (in-memory transcode location)
|
||||
# Size: HOST*_RAMDISK_SIZE (e.g. 8G) — must fit in available RAM
|
||||
# TRANSCODE_SSD — SSD fallback directory (created if missing)
|
||||
# transcode_manager.sh flips symlink here if ramdisk fills up
|
||||
# TRANSCODE_LINK — symlink pointing at RAMDISK_PATH by default
|
||||
# transcoding-temp/ — pre-created inside ramdisk so Emby always finds it there
|
||||
# Without this Emby creates it at its own first-writable location
|
||||
# which may be SSD even when symlink points at ramdisk
|
||||
#
|
||||
# All configuration in Master.conf under Transcode Manager section.
|
||||
# Supports --dry-run to preview what would be created without making changes.
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ── TRANSCODE_LINK SYMLINK ────────────────────────────────────────────────────────────────────
|
||||
# Emby's transcode path is set to TRANSCODE_LINK in Emby config.
|
||||
# transcode_manager.sh flips the symlink between RAMDISK_PATH and TRANSCODE_SSD at runtime
|
||||
# based on ramdisk usage — Emby sessions automatically follow without restart.
|
||||
# This script always sets the link to RAMDISK_PATH at array start (clean state).
|
||||
#
|
||||
# ── STATE FILE ────────────────────────────────────────────────────────────────────────────────
|
||||
# Initialises /tmp/transcode_state.db with current target and flip tracking counters.
|
||||
# /tmp resets on reboot — correct, transcode state is ephemeral.
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# detect_hosts() sets MY_ID and aliases HOST*_RAMDISK_SIZE → RAMDISK_SIZE.
|
||||
# HOST*_RAMDISK_SIZE, HOST*_RAMDISK_WARN_GB, HOST*_RAMDISK_LOW_GB must all be
|
||||
# configured per host — different servers have different amounts of RAM available.
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# Root check — mount and symlink require root
|
||||
# acquire_lock — prevents duplicate runs at array start
|
||||
# detect_hosts() — correct RAMDISK_SIZE per host
|
||||
# Already mounted — exits cleanly without remounting (idempotent)
|
||||
# validate_unraid_cmd — notify validated before use
|
||||
# Silent on success — startup script runs every boot — no noise when healthy
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# HOST*_RAMDISK_SIZE — tmpfs size (e.g. 8G) — must change together with WARN_GB/LOW_GB
|
||||
# HOST*_RAMDISK_WARN_GB — warn threshold in GB
|
||||
# HOST*_RAMDISK_LOW_GB — flip to SSD threshold in GB
|
||||
# HOST*_TRANSCODE_SSD — SSD fallback path
|
||||
# HOST*_TRANSCODE_SERVERS — which servers run transcoding
|
||||
# Aliased by detect_hosts()
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# TRANSCODE_LINK — symlink path Emby uses as transcode directory
|
||||
# TRANSCODE_CHMOD — permissions applied to ramdisk and fallback
|
||||
# TRANSCODE_OWNER — owner applied (default nobody:users)
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# ramdisk_setup.sh — normal setup (runs at array start)
|
||||
# ramdisk_setup.sh --dry-run — preview without making changes
|
||||
# ramdisk_setup.sh --status — show current ramdisk and symlink state
|
||||
# ramdisk_setup.sh --log — verbose output
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/../Master.conf"
|
||||
source "$SCRIPT_DIR/../common.sh"
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_GEAR Setup ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Setup ━━━"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Setup ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root"
|
||||
error "Must be run as root — mount and symlink require root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "Running as root"
|
||||
validate_unraid_cmd \
|
||||
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
|
||||
"" "" \
|
||||
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_SUMMARY Status ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
acquire_lock
|
||||
|
||||
# detect_hosts() sets MY_ID and aliases RAMDISK_SIZE, TRANSCODE_SSD etc.
|
||||
detect_hosts
|
||||
|
||||
log "Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
log "Ramdisk: $RAMDISK_PATH ($RAMDISK_SIZE)"
|
||||
log "Fallback: $TRANSCODE_SSD"
|
||||
log "Symlink: $TRANSCODE_LINK"
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Status ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
|
||||
echo "$ICON_RAM Ramdisk path: $RAMDISK_PATH"
|
||||
echo "$ICON_RAM Ramdisk size: $RAMDISK_SIZE"
|
||||
echo "$ICON_LINK Transcode link: $TRANSCODE_LINK"
|
||||
echo "$ICON_DISK SSD fallback: $TRANSCODE_SSD"
|
||||
echo "$ICON_GEAR Owner: $TRANSCODE_OWNER"
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_MODE"
|
||||
echo "$ICON_GEAR Dry Run: $DRY_RUN"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_RAM Ramdisk path: $RAMDISK_PATH"
|
||||
echo "$ICON_RAM Ramdisk size: $RAMDISK_SIZE"
|
||||
echo "$ICON_RAM Warn at: ${RAMDISK_WARN_GB}GB"
|
||||
echo "$ICON_RAM Flip at: ${RAMDISK_LOW_GB}GB"
|
||||
echo "$ICON_DISK SSD fallback: $TRANSCODE_SSD"
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK"
|
||||
echo "$ICON_GEAR Owner: $TRANSCODE_OWNER"
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_CHMOD"
|
||||
echo ""
|
||||
|
||||
# Show current mount state
|
||||
if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
CURRENT_USAGE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $3}')
|
||||
CURRENT_AVAIL=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $4}')
|
||||
echo "$ICON_RAM Current usage: $CURRENT_USAGE used / $CURRENT_AVAIL available"
|
||||
USAGE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $3}')
|
||||
AVAIL=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $4}')
|
||||
echo " $ICON_RAM Ramdisk: mounted — $USAGE used / $AVAIL available ✅"
|
||||
else
|
||||
echo "$ICON_RAM Ramdisk: not mounted"
|
||||
echo " $ICON_RAM Ramdisk: NOT mounted"
|
||||
fi
|
||||
|
||||
if [[ -L "$TRANSCODE_LINK" ]]; then
|
||||
echo "$ICON_LINK Current target: $(readlink "$TRANSCODE_LINK")"
|
||||
TARGET=$(readlink "$TRANSCODE_LINK")
|
||||
echo " $ICON_LINK Symlink: $TRANSCODE_LINK → $TARGET"
|
||||
else
|
||||
echo "$ICON_LINK Symlink: not set"
|
||||
echo " $ICON_LINK Symlink: not set"
|
||||
fi
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_RAM Ramdisk Setup ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ Ramdisk ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━ $ICON_RAM Ramdisk Setup ━━━"
|
||||
echo "━━━ $ICON_RAM Ramdisk — $MY_ID ━━━"
|
||||
echo "$ICON_RAM Path: $RAMDISK_PATH"
|
||||
echo "$ICON_RAM Size: $RAMDISK_SIZE"
|
||||
echo ""
|
||||
@@ -81,39 +138,39 @@ echo ""
|
||||
START=$(date +%s)
|
||||
SETUP_SUCCESS=true
|
||||
|
||||
# Check if ramdisk already mounted
|
||||
if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
CURRENT_USAGE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $3}')
|
||||
CURRENT_AVAIL=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $4}')
|
||||
info "$ICON_RAM Ramdisk already mounted — $CURRENT_USAGE used / $CURRENT_AVAIL available"
|
||||
info "Skipping mount — verifying symlink and permissions"
|
||||
USAGE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $3}')
|
||||
AVAIL=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $4}')
|
||||
log "Ramdisk already mounted — $USAGE used / $AVAIL available"
|
||||
log "Skipping mount — verifying symlink and permissions"
|
||||
else
|
||||
# Create mount point
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would create $RAMDISK_PATH"
|
||||
warn "DRY RUN — would mount tmpfs ${RAMDISK_SIZE} at $RAMDISK_PATH"
|
||||
else
|
||||
info "Creating ramdisk mount point: $RAMDISK_PATH"
|
||||
log "Creating ramdisk mount point: $RAMDISK_PATH"
|
||||
mkdir -p "$RAMDISK_PATH" || {
|
||||
error "Failed to create $RAMDISK_PATH"
|
||||
notify "Ramdisk setup failed on $(hostname) — could not create mount point" "Ramdisk Setup" "warning"
|
||||
notify "Ramdisk setup failed on $(hostname) ($MY_ID) — could not create mount point" \
|
||||
"Ramdisk Setup" "warning"
|
||||
exit 1
|
||||
}
|
||||
|
||||
info "$ICON_RAM Mounting tmpfs ${RAMDISK_SIZE} at $RAMDISK_PATH..."
|
||||
log "Mounting tmpfs ${RAMDISK_SIZE} at $RAMDISK_PATH..."
|
||||
if mount -t tmpfs -o size="$RAMDISK_SIZE" tmpfs "$RAMDISK_PATH"; then
|
||||
success "Ramdisk mounted — ${RAMDISK_SIZE} at $RAMDISK_PATH"
|
||||
warn "Ramdisk mounted — ${RAMDISK_SIZE} at $RAMDISK_PATH ✅"
|
||||
else
|
||||
error "Failed to mount ramdisk at $RAMDISK_PATH"
|
||||
notify "Ramdisk setup failed on $(hostname) — mount failed" "Ramdisk Setup" "warning"
|
||||
notify "Ramdisk setup failed on $(hostname) ($MY_ID) — mount failed" \
|
||||
"Ramdisk Setup" "warning"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_DISK SSD Fallback ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ SSD Fallback ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━ $ICON_DISK SSD Fallback ━━━"
|
||||
|
||||
@@ -121,11 +178,11 @@ if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would create SSD fallback: $TRANSCODE_SSD"
|
||||
else
|
||||
if [[ -d "$TRANSCODE_SSD" ]]; then
|
||||
info "$ICON_DISK SSD fallback already exists: $TRANSCODE_SSD"
|
||||
log "SSD fallback already exists: $TRANSCODE_SSD"
|
||||
else
|
||||
info "Creating SSD fallback directory: $TRANSCODE_SSD"
|
||||
log "Creating SSD fallback directory: $TRANSCODE_SSD"
|
||||
if mkdir -p "$TRANSCODE_SSD"; then
|
||||
success "SSD fallback created: $TRANSCODE_SSD"
|
||||
log "SSD fallback created: $TRANSCODE_SSD ✅"
|
||||
else
|
||||
error "Failed to create SSD fallback: $TRANSCODE_SSD"
|
||||
SETUP_SUCCESS=false
|
||||
@@ -133,22 +190,21 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_LINK Symlink ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ Symlink ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━ $ICON_LINK Symlink ━━━"
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would set $TRANSCODE_LINK → $RAMDISK_PATH"
|
||||
else
|
||||
# Remove existing symlink or directory at link path
|
||||
if [[ -L "$TRANSCODE_LINK" ]]; then
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK")
|
||||
if [[ "$CURRENT_TARGET" == "$RAMDISK_PATH" ]]; then
|
||||
info "$ICON_LINK Symlink already points to ramdisk — no change needed"
|
||||
log "Symlink already points to ramdisk — no change needed ✅"
|
||||
else
|
||||
info "$ICON_LINK Updating symlink: $CURRENT_TARGET → $RAMDISK_PATH"
|
||||
log "Updating symlink: $CURRENT_TARGET → $RAMDISK_PATH"
|
||||
ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
|
||||
error "Failed to update symlink"
|
||||
SETUP_SUCCESS=false
|
||||
@@ -162,8 +218,7 @@ else
|
||||
SETUP_SUCCESS=false
|
||||
}
|
||||
else
|
||||
info "Creating symlink: $TRANSCODE_LINK → $RAMDISK_PATH"
|
||||
# Ensure parent directory exists
|
||||
log "Creating symlink: $TRANSCODE_LINK → $RAMDISK_PATH"
|
||||
mkdir -p "$(dirname "$TRANSCODE_LINK")"
|
||||
ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
|
||||
error "Failed to create symlink"
|
||||
@@ -171,19 +226,15 @@ else
|
||||
}
|
||||
fi
|
||||
|
||||
if [[ "$SETUP_SUCCESS" == true ]]; then
|
||||
success "$ICON_LINK $TRANSCODE_LINK → $RAMDISK_PATH"
|
||||
fi
|
||||
[[ "$SETUP_SUCCESS" == true ]] && log "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_GEAR Transcoding-temp Directory ━━━
|
||||
# Creates the transcoding-temp folder on the ramdisk proactively.
|
||||
# If this folder doesn't exist on the ramdisk Emby creates it wherever it finds
|
||||
# a writable path first — which may be the SSD fallback — locking all sessions
|
||||
# onto SSD even when the symlink points at the ramdisk.
|
||||
# Creating it here guarantees Emby always finds it on the ramdisk at session start.
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ Transcoding-temp Directory ━━━
|
||||
# ==============================================================================================
|
||||
# Pre-created inside ramdisk so Emby always finds it there at session start.
|
||||
# Without this Emby creates it at its own first-writable path — which may be
|
||||
# SSD even when the symlink points at the ramdisk — locking all sessions onto SSD.
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Transcoding Temp Directory ━━━"
|
||||
|
||||
@@ -193,42 +244,44 @@ if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would create $TRANSCODE_TEMP_DIR"
|
||||
else
|
||||
if [[ -d "$TRANSCODE_TEMP_DIR" ]]; then
|
||||
info "transcoding-temp already exists on ramdisk"
|
||||
log "transcoding-temp already exists on ramdisk"
|
||||
else
|
||||
mkdir -p "$TRANSCODE_TEMP_DIR" && \
|
||||
success "Created transcoding-temp on ramdisk: $TRANSCODE_TEMP_DIR" || \
|
||||
{ error "Failed to create transcoding-temp on ramdisk"; SETUP_SUCCESS=false; }
|
||||
if mkdir -p "$TRANSCODE_TEMP_DIR"; then
|
||||
log "Created transcoding-temp on ramdisk ✅"
|
||||
else
|
||||
error "Failed to create transcoding-temp on ramdisk"
|
||||
SETUP_SUCCESS=false
|
||||
fi
|
||||
fi
|
||||
|
||||
# Apply correct permissions so Emby (abc/nobody:users) can write to it
|
||||
if [[ -d "$TRANSCODE_TEMP_DIR" ]]; then
|
||||
chmod "$TRANSCODE_CHMOD" "$TRANSCODE_TEMP_DIR"
|
||||
chown "$TRANSCODE_OWNER" "$TRANSCODE_TEMP_DIR"
|
||||
success "Permissions set on transcoding-temp"
|
||||
log "Permissions set on transcoding-temp ($TRANSCODE_CHMOD $TRANSCODE_OWNER)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_GEAR Permissions ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ Permissions ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Permissions ━━━"
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would apply $TRANSCODE_MODE $TRANSCODE_OWNER to $RAMDISK_PATH and $TRANSCODE_SSD"
|
||||
warn "DRY RUN — would apply $TRANSCODE_CHMOD $TRANSCODE_OWNER to $RAMDISK_PATH and $TRANSCODE_SSD"
|
||||
else
|
||||
for path in "$RAMDISK_PATH" "$TRANSCODE_SSD"; do
|
||||
if [[ -d "$path" ]]; then
|
||||
chmod "$TRANSCODE_MODE" "$path"
|
||||
chmod "$TRANSCODE_CHMOD" "$path"
|
||||
chown "$TRANSCODE_OWNER" "$path"
|
||||
success "Permissions set: $path"
|
||||
log "Permissions set: $path ($TRANSCODE_CHMOD $TRANSCODE_OWNER)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# Initialise state file
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ Initialise State File ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
STATE_FILE="/tmp/transcode_state.db"
|
||||
NOW=$(date +%s)
|
||||
@@ -243,25 +296,26 @@ fi
|
||||
|
||||
END=$(date +%s)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_SUMMARY Summary ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ Summary ━━━
|
||||
# ==============================================================================================
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY RAMDISK SETUP SUMMARY ━━━━━"
|
||||
echo "$ICON_RAM Ramdisk: $RAMDISK_PATH ($RAMDISK_SIZE)"
|
||||
echo "$ICON_DISK Fallback: $TRANSCODE_SSD"
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK"
|
||||
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
echo "$ICON_WARN Status: DRY RUN — no changes made"
|
||||
elif [[ "$SETUP_SUCCESS" == true ]]; then
|
||||
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
|
||||
notify "Ramdisk setup complete on $(hostname) — ${RAMDISK_SIZE} mounted at $RAMDISK_PATH" "Ramdisk Setup" "normal"
|
||||
else
|
||||
echo "$ICON_ERROR Status: $ICON_ERROR SETUP HAD ERRORS"
|
||||
notify "Ramdisk setup completed with errors on $(hostname)" "Ramdisk Setup" "warning"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_RAM Ramdisk: $RAMDISK_PATH ($RAMDISK_SIZE)"
|
||||
echo "$ICON_DISK Fallback: $TRANSCODE_SSD"
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK"
|
||||
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
|
||||
echo ""
|
||||
|
||||
[[ "$SETUP_SUCCESS" == false ]] && exit 1
|
||||
exit 0
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — no changes made"
|
||||
elif [[ "$SETUP_SUCCESS" == true ]]; then
|
||||
log "$ICON_DONE Status: done ✅"
|
||||
else
|
||||
echo "$ICON_ERROR Status: SETUP HAD ERRORS"
|
||||
notify "Ramdisk setup errors on $(hostname) ($MY_ID) — check output" \
|
||||
"Ramdisk Setup" "warning"
|
||||
exit 1
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
+163
-119
@@ -1,115 +1,178 @@
|
||||
#!/bin/bash
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# --------------------------------- Transcode Cleanup ------------------------------------------
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ================================= Transcode Cleanup ==========================================
|
||||
# ==============================================================================================
|
||||
# Removes old inactive transcode files from both ramdisk and SSD fallback locations.
|
||||
# Called every 5 minutes by transcode_manager.sh — must be fast and non-blocking.
|
||||
# Never deletes files that are currently open by any process.
|
||||
# After cleanup checks if ramdisk usage dropped enough to flip symlink back to ramdisk.
|
||||
#
|
||||
# Safety rules — a file is eligible for deletion only if ALL conditions are true:
|
||||
# 1. Older than TRANSCODE_MAX_AGE minutes (mtime)
|
||||
# 2. Not currently open by any process (single lsof call per location — not per file)
|
||||
# ── SAFETY RULES ──────────────────────────────────────────────────────────────────────────────
|
||||
# A file is eligible for deletion only if ALL conditions are true:
|
||||
# 1. Older than TRANSCODE_MAX_AGE minutes (mtime — last modified time)
|
||||
# 2. Not currently open by any process (checked via lsof pre-built map)
|
||||
#
|
||||
# Performance note:
|
||||
# lsof is called ONCE per location to build an open file list — not once per file.
|
||||
# This is critical for locations with hundreds or thousands of segment files.
|
||||
# A per-file lsof approach stalls on busy systems with live TV buffering.
|
||||
# ── WHY NOT SESSION-AWARE CLEANUP ─────────────────────────────────────────────────────────────
|
||||
# ffmpeg generates folder names independently of the media server API session IDs.
|
||||
# There is no reliable correlation between API session IDs and transcoding-temp subfolder
|
||||
# names — matching them would falsely treat active sessions as ended.
|
||||
# lsof is the correct and reliable active file check — if ffmpeg has a file open,
|
||||
# lsof sees it regardless of folder naming or session state.
|
||||
#
|
||||
# Run every 5 minutes via cron/User Scripts plugin.
|
||||
# All configuration in Master.conf under Transcode Manager section.
|
||||
# Supports --dry-run to preview what would be deleted without making changes.
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ── TRANSCODING-TEMP PROTECTION ───────────────────────────────────────────────────────────────
|
||||
# The transcoding-temp directory is excluded from deletion even when empty.
|
||||
# If cleanup removes the empty transcoding-temp folder from the ramdisk, Emby finds
|
||||
# the SSD version instead and all new sessions land on SSD until Emby restarts.
|
||||
# ! -name "transcoding-temp" exclusion in find prevents this permanently.
|
||||
#
|
||||
# ── PERFORMANCE ───────────────────────────────────────────────────────────────────────────────
|
||||
# lsof is called ONCE per location — never once per file.
|
||||
# Per-file lsof stalls on busy systems with live TV buffering hundreds of segments.
|
||||
#
|
||||
# Open file check uses in-memory associative array (OPEN_FILES_MAP):
|
||||
# Was: echo "$OPEN_FILES" | grep -qF "$file" — O(n) per file → O(n²) total
|
||||
# Now: [[ -n "${OPEN_FILES_MAP[$file]:-}" ]] — O(1) per file → O(n) total
|
||||
# Same lesson as TRACKED_MAP in arr cleanup scripts.
|
||||
#
|
||||
# ── POST-CLEANUP SYMLINK FLIP ─────────────────────────────────────────────────────────────────
|
||||
# After cleanup, if ramdisk has recovered below RAMDISK_LOW_GB and symlink currently
|
||||
# points at SSD → triggers transcode_manager.sh to flip back to ramdisk.
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# detect_hosts() sets MY_ID and aliases RAMDISK_PATH, TRANSCODE_SSD, RAMDISK_LOW_GB.
|
||||
# Each server cleans its own transcode locations at the correct thresholds.
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock "wait" — wait if previous cleanup still running
|
||||
# detect_hosts() — correct paths and thresholds per host
|
||||
# lsof timeout — lsof call capped at 15 seconds per location
|
||||
# OPEN_FILES_MAP — in-memory O(1) active file lookup
|
||||
# transcoding-temp guard — never deletes this directory
|
||||
# Silent by default — runs every 5 minutes, must not produce noise when healthy
|
||||
#
|
||||
# ── CONFIGURATION (master_host*.conf) ─────────────────────────────────────────────────────────
|
||||
# HOST*_RAMDISK_PATH / HOST*_TRANSCODE_SSD / HOST*_RAMDISK_LOW_GB
|
||||
# Aliased by detect_hosts()
|
||||
#
|
||||
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
||||
# TRANSCODE_MAX_AGE — minutes before an inactive transcode file is eligible
|
||||
# TRANSCODE_ORPHAN_AGE — minutes for orphan detection (informational — future use)
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# transcode_cleanup.sh — normal cleanup run
|
||||
# transcode_cleanup.sh --dry-run — show what would be deleted
|
||||
# transcode_cleanup.sh --status — show current state
|
||||
# transcode_cleanup.sh --log — verbose per-file output
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/../Master.conf"
|
||||
source "$SCRIPT_DIR/../common.sh"
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
STATE_FILE="/tmp/transcode_state.db"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_GEAR Setup ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Setup ━━━"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Setup ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "Running as root"
|
||||
validate_unraid_cmd \
|
||||
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
|
||||
"" "" \
|
||||
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
|
||||
|
||||
acquire_lock "wait"
|
||||
|
||||
if ! command -v lsof >/dev/null 2>&1; then
|
||||
warn "lsof not available — active file check will be skipped, all aged files will be eligible"
|
||||
LSOF_AVAILABLE=false
|
||||
else
|
||||
# detect_hosts() sets MY_ID and aliases RAMDISK_PATH, TRANSCODE_SSD, RAMDISK_LOW_GB
|
||||
detect_hosts
|
||||
|
||||
# lsof availability check
|
||||
LSOF_AVAILABLE=false
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
LSOF_AVAILABLE=true
|
||||
log "lsof available — active file check enabled"
|
||||
else
|
||||
warn "lsof not available — active file check skipped, all aged files eligible for deletion"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_SUMMARY Status ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
log "Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
log "Max age: ${TRANSCODE_MAX_AGE} minutes"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Status ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
|
||||
echo "$ICON_RAM Ramdisk: $RAMDISK_PATH"
|
||||
echo "$ICON_DISK SSD fallback: $TRANSCODE_SSD"
|
||||
echo "$ICON_TRASH Max age: ${TRANSCODE_MAX_AGE} minutes"
|
||||
echo "$ICON_TRASH Orphan age: ${TRANSCODE_ORPHAN_AGE} minutes"
|
||||
echo "$ICON_GEAR lsof check: $LSOF_AVAILABLE"
|
||||
echo "$ICON_GEAR Dry Run: $DRY_RUN"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_RAM Ramdisk: $RAMDISK_PATH"
|
||||
echo "$ICON_DISK SSD fallback: $TRANSCODE_SSD"
|
||||
echo "$ICON_TRASH Max age: ${TRANSCODE_MAX_AGE} minutes"
|
||||
echo "$ICON_TRASH Orphan age: ${TRANSCODE_ORPHAN_AGE} minutes"
|
||||
echo "$ICON_RAM Flip at: ${RAMDISK_LOW_GB}GB"
|
||||
echo "$ICON_GEAR lsof check: $LSOF_AVAILABLE"
|
||||
echo "$ICON_GEAR Dry Run: $DRY_RUN"
|
||||
echo ""
|
||||
|
||||
if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
USAGE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $3}')
|
||||
AVAIL=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $4}')
|
||||
echo " $ICON_RAM Ramdisk: mounted — $USAGE used / $AVAIL available ✅"
|
||||
else
|
||||
echo " $ICON_RAM Ramdisk: not mounted"
|
||||
fi
|
||||
|
||||
CURRENT_TARGET=$(grep "^current_target=" "$STATE_FILE" 2>/dev/null | cut -d= -f2)
|
||||
echo " $ICON_LINK Current target: ${CURRENT_TARGET:-unknown}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no files will be deleted"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# CLEANUP FUNCTION
|
||||
# ==============================================================================================
|
||||
# ── CLEANUP FUNCTION ──────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# Scans a location and removes eligible files.
|
||||
# Calls lsof ONCE per location to build open file list — never per file.
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# Calls lsof ONCE per location — builds in-memory OPEN_FILES_MAP for O(1) lookup.
|
||||
# Returns via LOCATION_REMOVED, LOCATION_FREED, LOCATION_SKIPPED, LOCATION_ACTIVE
|
||||
|
||||
cleanup_location() {
|
||||
local location="$1" label="$2" max_age="$3"
|
||||
local files_removed=0
|
||||
local bytes_freed=0
|
||||
local files_skipped=0
|
||||
local files_active=0
|
||||
local files_removed=0 bytes_freed=0 files_skipped=0 files_active=0
|
||||
|
||||
if [[ ! -d "$location" ]]; then
|
||||
warn "$label does not exist — skipping"
|
||||
LOCATION_REMOVED=0
|
||||
LOCATION_FREED="0B"
|
||||
LOCATION_SKIPPED=0
|
||||
log "$label does not exist — skipping"
|
||||
LOCATION_REMOVED=0 LOCATION_FREED="0B" LOCATION_SKIPPED=0 LOCATION_ACTIVE=0
|
||||
return
|
||||
fi
|
||||
|
||||
local file_count
|
||||
file_count=$(find "$location" -type f 2>/dev/null | wc -l)
|
||||
info "$ICON_TRASH $label: $file_count files to scan (age threshold: ${max_age}min)"
|
||||
log "$label: $file_count files to scan (age threshold: ${max_age}min)"
|
||||
|
||||
# Build open file list with a single lsof call — timeout prevents stalling
|
||||
local OPEN_FILES=""
|
||||
# Build in-memory open file map — O(1) lookup per file
|
||||
# One lsof call per location — never per file
|
||||
declare -A OPEN_FILES_MAP
|
||||
if [[ "$LSOF_AVAILABLE" == true ]]; then
|
||||
info "Building open file list for $label..."
|
||||
OPEN_FILES=$(timeout 15 lsof +D "$location" 2>/dev/null | awk 'NR>1 {print $9}' | sort -u)
|
||||
local open_count
|
||||
open_count=$(echo "$OPEN_FILES" | grep -c "." 2>/dev/null || echo 0)
|
||||
info "$open_count files currently open in $label"
|
||||
log "Building open file map for $label..."
|
||||
while IFS= read -r open_file; do
|
||||
[[ -n "$open_file" ]] && OPEN_FILES_MAP["$open_file"]=1
|
||||
done < <(timeout 15 lsof +D "$location" 2>/dev/null | awk 'NR>1 {print $9}' | sort -u)
|
||||
log "${#OPEN_FILES_MAP[@]} files currently open in $label"
|
||||
fi
|
||||
|
||||
# Find files older than max_age and process them
|
||||
# Process aged files
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
|
||||
# Check if file is currently open — fast string match against pre-built list
|
||||
if [[ "$LSOF_AVAILABLE" == true ]] && echo "$OPEN_FILES" | grep -qF "$file"; then
|
||||
((files_active++))
|
||||
# O(1) open file check — in-memory map
|
||||
if [[ -n "${OPEN_FILES_MAP[$file]:-}" ]]; then
|
||||
(( files_active++ ))
|
||||
log "Skipping open file: $file"
|
||||
continue
|
||||
fi
|
||||
@@ -118,30 +181,29 @@ cleanup_location() {
|
||||
file_size=$(stat -c%s "$file" 2>/dev/null || echo 0)
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would delete: $(basename "$file")"
|
||||
((files_skipped++))
|
||||
log "DRY RUN — would delete: $(basename "$file")"
|
||||
(( files_skipped++ ))
|
||||
else
|
||||
if rm -f "$file" 2>/dev/null; then
|
||||
((files_removed++))
|
||||
bytes_freed=$((bytes_freed + file_size))
|
||||
(( files_removed++ ))
|
||||
bytes_freed=$(( bytes_freed + file_size ))
|
||||
log "Deleted: $file"
|
||||
else
|
||||
warn "Could not delete: $file"
|
||||
((files_skipped++))
|
||||
(( files_skipped++ ))
|
||||
fi
|
||||
fi
|
||||
|
||||
done < <(find "$location" -type f -mmin +"$max_age" 2>/dev/null)
|
||||
|
||||
# Remove empty directories left behind — but NEVER remove transcoding-temp itself
|
||||
# transcoding-temp must always exist on the ramdisk so Emby finds it there first
|
||||
# If deleted Emby falls back to the SSD version and all new sessions land on SSD
|
||||
# Remove empty directories — but NEVER remove transcoding-temp
|
||||
# transcoding-temp must always exist on ramdisk so Emby finds it there first
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
find "$location" -mindepth 1 -type d -empty \
|
||||
! -name "transcoding-temp" -delete 2>/dev/null
|
||||
fi
|
||||
|
||||
# Format bytes freed for display
|
||||
# Format bytes freed
|
||||
local freed_human
|
||||
if (( bytes_freed > 1073741824 )); then
|
||||
freed_human=$(awk "BEGIN {printf \"%.1fGB\", $bytes_freed / 1073741824}")
|
||||
@@ -153,11 +215,7 @@ cleanup_location() {
|
||||
freed_human="0B"
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
info "$label — dry run complete ($file_count files scanned, $files_active active)"
|
||||
else
|
||||
success "$label — removed $files_removed files ($freed_human freed), $files_active active, $files_skipped skipped"
|
||||
fi
|
||||
log "$label — removed $files_removed files ($freed_human freed) | active: $files_active | skipped: $files_skipped"
|
||||
|
||||
LOCATION_REMOVED=$files_removed
|
||||
LOCATION_FREED=$freed_human
|
||||
@@ -165,79 +223,65 @@ cleanup_location() {
|
||||
LOCATION_ACTIVE=$files_active
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_TRASH Transcode Cleanup ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━ $ICON_TRASH Transcode Cleanup — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
||||
echo ""
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Transcode Cleanup ━━━
|
||||
# ==============================================================================================
|
||||
START=$(date +%s)
|
||||
TOTAL_REMOVED=0
|
||||
TOTAL_SKIPPED=0
|
||||
TOTAL_ACTIVE=0
|
||||
RAMDISK_FREED="0B"
|
||||
SSD_FREED="0B"
|
||||
TOTAL_REMOVED=0 TOTAL_SKIPPED=0 TOTAL_ACTIVE=0
|
||||
RAMDISK_FREED="0B" SSD_FREED="0B"
|
||||
|
||||
# Cleanup ramdisk
|
||||
if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
echo "━━━ $ICON_RAM Ramdisk ━━━"
|
||||
cleanup_location "$RAMDISK_PATH" "Ramdisk" "$TRANSCODE_MAX_AGE"
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + LOCATION_REMOVED))
|
||||
TOTAL_SKIPPED=$((TOTAL_SKIPPED + LOCATION_SKIPPED))
|
||||
TOTAL_ACTIVE=$((TOTAL_ACTIVE + LOCATION_ACTIVE))
|
||||
TOTAL_REMOVED=$(( TOTAL_REMOVED + LOCATION_REMOVED ))
|
||||
TOTAL_SKIPPED=$(( TOTAL_SKIPPED + LOCATION_SKIPPED ))
|
||||
TOTAL_ACTIVE=$(( TOTAL_ACTIVE + LOCATION_ACTIVE ))
|
||||
RAMDISK_FREED=$LOCATION_FREED
|
||||
echo ""
|
||||
else
|
||||
warn "$ICON_RAM Ramdisk not mounted — skipping ramdisk cleanup"
|
||||
log "Ramdisk not mounted — skipping ramdisk cleanup"
|
||||
fi
|
||||
|
||||
# Cleanup SSD fallback
|
||||
if [[ -d "$TRANSCODE_SSD" ]]; then
|
||||
echo "━━━ $ICON_DISK SSD Fallback ━━━"
|
||||
cleanup_location "$TRANSCODE_SSD" "SSD fallback" "$TRANSCODE_MAX_AGE"
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + LOCATION_REMOVED))
|
||||
TOTAL_SKIPPED=$((TOTAL_SKIPPED + LOCATION_SKIPPED))
|
||||
TOTAL_ACTIVE=$((TOTAL_ACTIVE + LOCATION_ACTIVE))
|
||||
TOTAL_REMOVED=$(( TOTAL_REMOVED + LOCATION_REMOVED ))
|
||||
TOTAL_SKIPPED=$(( TOTAL_SKIPPED + LOCATION_SKIPPED ))
|
||||
TOTAL_ACTIVE=$(( TOTAL_ACTIVE + LOCATION_ACTIVE ))
|
||||
SSD_FREED=$LOCATION_FREED
|
||||
echo ""
|
||||
else
|
||||
info "$ICON_DISK SSD fallback not found — skipping"
|
||||
log "SSD fallback not found — skipping SSD cleanup"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# Post-cleanup — check if ramdisk recovered enough to flip symlink back
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# Post-cleanup — check if ramdisk recovered enough to flip symlink back to ramdisk
|
||||
if [[ "$DRY_RUN" == false ]] && mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
RAMDISK_USED_KB=$(df "$RAMDISK_PATH" --output=used | tail -1 | tr -d ' ')
|
||||
RAMDISK_USED_GB=$(awk "BEGIN {printf \"%.2f\", $RAMDISK_USED_KB / 1048576}")
|
||||
RAMDISK_USED_KB=$(df "$RAMDISK_PATH" --output=used 2>/dev/null | tail -1 | tr -d ' ')
|
||||
RAMDISK_USED_GB=$(awk "BEGIN {printf \"%.2f\", ${RAMDISK_USED_KB:-0} / 1048576}")
|
||||
LOW_RECOVERED=$(awk "BEGIN {print ($RAMDISK_USED_GB < $RAMDISK_LOW_GB) ? 1 : 0}")
|
||||
CURRENT_TARGET=$(grep "^current_target=" "$STATE_FILE" 2>/dev/null | cut -d'=' -f2)
|
||||
CURRENT_TARGET=$(grep "^current_target=" "$STATE_FILE" 2>/dev/null | cut -d= -f2)
|
||||
|
||||
if [[ "$LOW_RECOVERED" == "1" && "$CURRENT_TARGET" == "$TRANSCODE_SSD" ]]; then
|
||||
info "$ICON_RAM Ramdisk has space after cleanup — triggering manager to flip back"
|
||||
log "Ramdisk has space after cleanup (${RAMDISK_USED_GB}GB < ${RAMDISK_LOW_GB}GB) — triggering manager to flip back"
|
||||
bash "$SCRIPT_DIR/transcode_manager.sh" --no-log
|
||||
fi
|
||||
fi
|
||||
|
||||
END=$(date +%s)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_SUMMARY Summary ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ Summary ━━━
|
||||
# ==============================================================================================
|
||||
echo "━━━━━ $ICON_SUMMARY TRANSCODE CLEANUP SUMMARY ━━━━━"
|
||||
echo "$ICON_RAM Ramdisk freed: $RAMDISK_FREED"
|
||||
echo "$ICON_DISK SSD freed: $SSD_FREED"
|
||||
echo "$ICON_TRASH Removed: $TOTAL_REMOVED files"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_RAM Ramdisk freed: $RAMDISK_FREED"
|
||||
echo "$ICON_DISK SSD freed: $SSD_FREED"
|
||||
echo "$ICON_TRASH Removed: $TOTAL_REMOVED files"
|
||||
echo "$ICON_RUNNING Active: $TOTAL_ACTIVE files (open — protected)"
|
||||
echo "$ICON_TRASH Skipped: $TOTAL_SKIPPED files"
|
||||
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
|
||||
echo "$ICON_TRASH Skipped: $TOTAL_SKIPPED files"
|
||||
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
echo "$ICON_WARN Status: DRY RUN — no files deleted"
|
||||
warn "DRY RUN — no files deleted"
|
||||
else
|
||||
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
|
||||
if [[ "$TOTAL_REMOVED" -gt 0 ]]; then
|
||||
notify "Transcode cleanup on $(hostname) — removed $TOTAL_REMOVED files (RAM: $RAMDISK_FREED SSD: $SSD_FREED)" "Transcode Cleanup" "normal"
|
||||
fi
|
||||
log "$ICON_DONE Status: done ✅"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
+253
-235
@@ -1,63 +1,105 @@
|
||||
#!/bin/bash
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# --------------------------------- Transcode Manager ------------------------------------------
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ================================= Transcode Manager ==========================================
|
||||
# ==============================================================================================
|
||||
# Manages Emby transcode storage using filesystem symlink indirection.
|
||||
# ffmpeg resolves the symlink ONCE at session start — existing sessions are never affected.
|
||||
# Only new sessions care about where the symlink currently points.
|
||||
# Called every 5 minutes by User Scripts — must be fast, non-blocking, and silent when healthy.
|
||||
#
|
||||
# Three operating modes — set TRANSCODE_MANAGER_MODE in Master.conf:
|
||||
# ── HOW IT WORKS ──────────────────────────────────────────────────────────────────────────────
|
||||
# Emby's transcode path is set to TRANSCODE_LINK (a symlink).
|
||||
# ffmpeg resolves the symlink ONCE at session start — existing sessions are never affected.
|
||||
# Only NEW sessions care about where the symlink currently points.
|
||||
# Flipping the symlink mid-stream is safe — in-progress transcodes continue uninterrupted.
|
||||
#
|
||||
# ── THREE MODES ───────────────────────────────────────────────────────────────────────────────
|
||||
# smart — auto-flips between ramdisk and SSD based on usage thresholds (default)
|
||||
# ramdisk — always uses ramdisk, never flips to SSD regardless of usage
|
||||
# ramdisk above RAMDISK_WARN_GB → flip to SSD
|
||||
# ramdisk below RAMDISK_LOW_GB → flip back to ramdisk
|
||||
# ramdisk — always uses ramdisk, warns if above threshold, never flips
|
||||
# ssd — always uses SSD, never uses ramdisk
|
||||
#
|
||||
# Safety checks on every run regardless of mode:
|
||||
# Ramdisk disappeared → auto-flip to SSD, notify warning
|
||||
# SSD path missing → disable SSD fallback, notify warning
|
||||
# Symlink broken → auto-recreate, notify
|
||||
# Symlink missing → auto-recreate, notify
|
||||
# Permissions drift → fix silently
|
||||
# Emby not running → skip threshold checks, verify symlink only
|
||||
# ── SAFETY CHECKS — EVERY RUN ─────────────────────────────────────────────────────────────────
|
||||
# Symlink missing/broken → auto-recreate pointing at ramdisk, notify
|
||||
# Ramdisk disappeared → auto-flip to SSD, notify warning
|
||||
# SSD path missing → disable SSD fallback / error if mode=ssd
|
||||
# transcoding-temp missing → recreate on ramdisk immediately
|
||||
# Permissions drift → fix silently
|
||||
# Emby not running → skip threshold checks, verify symlink only
|
||||
#
|
||||
# Session display shows active Emby streams with storage state.
|
||||
# Split state detected when sessions exist on both ramdisk and SSD simultaneously —
|
||||
# ── SESSION DISPLAY ───────────────────────────────────────────────────────────────────────────
|
||||
# Shows active Emby/Jellyfin/Plex streams with user, title, type, and play method.
|
||||
# Split state shown when sessions exist on both ramdisk and SSD simultaneously —
|
||||
# this happens naturally when symlink flips mid-session.
|
||||
#
|
||||
# All configuration in Master.conf under Transcode Manager section.
|
||||
# Supports --dry-run and --status.
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ── DAILY LOG ─────────────────────────────────────────────────────────────────────────────────
|
||||
# Appends to TRANSCODE_DAILY_LOG after each run — read by weekly_health_digest.sh.
|
||||
# Format: DATE|RAMDISK_USED_GB|FLIP_COUNT|RAM_SESSION_COUNT|SSD_SESSION_COUNT|FILES_CLEANED
|
||||
#
|
||||
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
|
||||
# detect_hosts() sets MY_ID and aliases HOST*_TRANSCODE_SERVERS, HOST*_RAMDISK_PATH,
|
||||
# HOST*_TRANSCODE_SSD, HOST*_RAMDISK_WARN_GB, HOST*_RAMDISK_LOW_GB, HOST*_RAMDISK_SIZE.
|
||||
#
|
||||
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
||||
# acquire_lock "wait" — wait if previous run still active
|
||||
# detect_hosts() — correct paths and thresholds per host
|
||||
# DOCKER_TIMEOUT — all docker calls protected against daemon hangs
|
||||
# validate_unraid_cmd — notify validated before use
|
||||
# Silent by default — runs every 5 minutes, only speaks when something changes
|
||||
#
|
||||
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
||||
# transcode_manager.sh — normal run
|
||||
# transcode_manager.sh --dry-run — preview without making changes
|
||||
# transcode_manager.sh --status — show current state and exit
|
||||
# transcode_manager.sh --log — verbose output
|
||||
# transcode_manager.sh --no-log — suppress daily log write (called by cleanup)
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/../Master.conf"
|
||||
source "$SCRIPT_DIR/../common.sh"
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
parse_args "$@"
|
||||
# Handle --no-log flag before parse_args
|
||||
NO_LOG=false
|
||||
FILTERED_ARGS=()
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--no-log) NO_LOG=true ;;
|
||||
*) FILTERED_ARGS+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_GEAR Setup ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Setup ━━━"
|
||||
parse_args "${FILTERED_ARGS[@]}"
|
||||
|
||||
DOCKER_TIMEOUT=15
|
||||
STATE_FILE="/tmp/transcode_state.db"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Setup ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "Running as root"
|
||||
validate_unraid_cmd \
|
||||
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
|
||||
"" "" \
|
||||
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
|
||||
|
||||
acquire_lock "wait"
|
||||
|
||||
# Primary server for container running check — first entry in TRANSCODE_SERVERS
|
||||
# Full session display loops over all entries in the array
|
||||
# detect_hosts() sets MY_ID and aliases all HOST*_TRANSCODE_* vars
|
||||
detect_hosts
|
||||
|
||||
# Primary container — first entry in TRANSCODE_SERVERS
|
||||
TRANSCODE_EMBY_CONTAINER="Emby"
|
||||
if [[ "${#TRANSCODE_SERVERS[@]}" -gt 0 ]]; then
|
||||
IFS='|' read -r PRIMARY_CONTAINER _ _ _ <<< "${TRANSCODE_SERVERS[0]}"
|
||||
TRANSCODE_EMBY_CONTAINER="${PRIMARY_CONTAINER:-Emby}"
|
||||
fi
|
||||
|
||||
case "$TRANSCODE_MANAGER_MODE" in
|
||||
smart|ramdisk|ssd) info "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE" ;;
|
||||
smart|ramdisk|ssd) log "Mode: $TRANSCODE_MANAGER_MODE" ;;
|
||||
*)
|
||||
warn "Unknown TRANSCODE_MANAGER_MODE: $TRANSCODE_MANAGER_MODE — defaulting to smart"
|
||||
TRANSCODE_MANAGER_MODE="smart"
|
||||
@@ -66,12 +108,13 @@ esac
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_SUMMARY Status ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ━━━ Status ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
|
||||
echo "━━━━━ $ICON_SUMMARY TRANSCODE MANAGER STATUS ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE"
|
||||
echo "$ICON_RAM Ramdisk: $RAMDISK_PATH ($RAMDISK_SIZE)"
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK"
|
||||
@@ -85,19 +128,19 @@ if [[ "$SHOW_STATUS" == true ]]; then
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null || echo "missing")
|
||||
echo "$ICON_LINK Symlink now: $TRANSCODE_LINK → $CURRENT_TARGET"
|
||||
if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
USED_KB=$(df "$RAMDISK_PATH" --output=used | tail -1 | tr -d ' ')
|
||||
USED_GB=$(awk "BEGIN {printf \"%.2f\", $USED_KB / 1048576}")
|
||||
USED_GB=$(df "$RAMDISK_PATH" --output=used 2>/dev/null | \
|
||||
tail -1 | tr -d ' ' | awk '{printf "%.2f", $1/1048576}')
|
||||
echo "$ICON_RAM Ramdisk now: ${USED_GB}GB used"
|
||||
else
|
||||
echo "$ICON_RAM Ramdisk: NOT MOUNTED"
|
||||
echo "$ICON_RAM Ramdisk: NOT MOUNTED"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# HELPERS
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ==============================================================================================
|
||||
# ── HELPER FUNCTIONS ──────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
|
||||
flip_symlink() {
|
||||
local target="$1" reason="$2"
|
||||
@@ -106,7 +149,7 @@ flip_symlink() {
|
||||
return 0
|
||||
fi
|
||||
ln -sfn "$target" "$TRANSCODE_LINK"
|
||||
success "$ICON_LINK Symlink flipped to: $target ($reason)"
|
||||
warn "$ICON_LINK Symlink flipped to: $target ($reason)"
|
||||
}
|
||||
|
||||
fix_permissions() {
|
||||
@@ -118,30 +161,27 @@ fix_permissions() {
|
||||
}
|
||||
|
||||
get_ramdisk_used_gb() {
|
||||
local used_kb
|
||||
used_kb=$(df "$RAMDISK_PATH" --output=used 2>/dev/null | tail -1 | tr -d ' ')
|
||||
awk "BEGIN {printf \"%.2f\", ${used_kb:-0} / 1048576}"
|
||||
df "$RAMDISK_PATH" --output=used 2>/dev/null | \
|
||||
tail -1 | tr -d ' ' | awk '{printf "%.2f", $1/1048576}'
|
||||
}
|
||||
|
||||
get_ramdisk_avail_gb() {
|
||||
local avail_kb
|
||||
avail_kb=$(df "$RAMDISK_PATH" --output=avail 2>/dev/null | tail -1 | tr -d ' ')
|
||||
awk "BEGIN {printf \"%.2f\", ${avail_kb:-0} / 1048576}"
|
||||
df "$RAMDISK_PATH" --output=avail 2>/dev/null | \
|
||||
tail -1 | tr -d ' ' | awk '{printf "%.2f", $1/1048576}'
|
||||
}
|
||||
|
||||
get_ssd_free_gb() {
|
||||
local free_kb
|
||||
free_kb=$(df "$TRANSCODE_SSD" --output=avail 2>/dev/null | tail -1 | tr -d ' ')
|
||||
awk "BEGIN {printf \"%.2f\", ${free_kb:-0} / 1048576}"
|
||||
df "$TRANSCODE_SSD" --output=avail 2>/dev/null | \
|
||||
tail -1 | tr -d ' ' | awk '{printf "%.2f", $1/1048576}'
|
||||
}
|
||||
|
||||
get_flip_count() {
|
||||
local state_file="/tmp/transcode_flip_state.db"
|
||||
local current_hour
|
||||
current_hour=$(date '+%Y-%m-%d-%H')
|
||||
if [[ ! -f "$state_file" ]]; then echo "0"; return; fi
|
||||
[[ ! -f "$state_file" ]] && echo "0" && return
|
||||
local stored_hour stored_count
|
||||
stored_hour=$(awk -F'|' 'NR==1{print $1}' "$state_file" 2>/dev/null)
|
||||
stored_hour=$(awk -F'|' 'NR==1{print $1}' "$state_file" 2>/dev/null)
|
||||
stored_count=$(awk -F'|' 'NR==1{print $2}' "$state_file" 2>/dev/null)
|
||||
[[ "$stored_hour" == "$current_hour" ]] && echo "${stored_count:-0}" || echo "0"
|
||||
}
|
||||
@@ -152,76 +192,55 @@ increment_flip_count() {
|
||||
current_hour=$(date '+%Y-%m-%d-%H')
|
||||
local current_count
|
||||
current_count=$(get_flip_count)
|
||||
current_count=$((current_count + 1))
|
||||
current_count=$(( current_count + 1 ))
|
||||
echo "${current_hour}|${current_count}" > "$state_file"
|
||||
echo "$current_count"
|
||||
}
|
||||
|
||||
emby_api() {
|
||||
local endpoint="$1"
|
||||
[[ -z "$EMBY_API_KEY" ]] && return 1
|
||||
curl -sf \
|
||||
--max-time 10 \
|
||||
-H "X-Emby-Token: $EMBY_API_KEY" \
|
||||
"${EMBY_URL}/${endpoint}" 2>/dev/null
|
||||
}
|
||||
|
||||
get_media_type_label() {
|
||||
local type="$1"
|
||||
case "$type" in
|
||||
LiveTv) echo "Live TV" ;;
|
||||
TvChannel) echo "Live TV" ;;
|
||||
Episode) echo "TV Show" ;;
|
||||
Movie) echo "Movie" ;;
|
||||
Audio) echo "Music" ;;
|
||||
MusicVideo) echo "Music Video" ;;
|
||||
*) echo "$type" ;;
|
||||
case "$1" in
|
||||
LiveTv|TvChannel) echo "Live TV" ;;
|
||||
Episode) echo "TV Show" ;;
|
||||
Movie) echo "Movie" ;;
|
||||
Audio) echo "Music" ;;
|
||||
MusicVideo) echo "Music Video" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_play_method_label() {
|
||||
local method="$1"
|
||||
case "$method" in
|
||||
Transcode) echo "Transcode" ;;
|
||||
DirectStream) echo "Direct Stream" ;;
|
||||
DirectPlay) echo "Direct Play" ;;
|
||||
*) echo "$method" ;;
|
||||
case "$1" in
|
||||
Transcode) echo "Transcode" ;;
|
||||
DirectStream) echo "Direct Stream" ;;
|
||||
DirectPlay) echo "Direct Play" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_RAM Transcode Manager ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━ $ICON_RAM Transcode Manager — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE"
|
||||
echo ""
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Transcode Manager ━━━
|
||||
# ==============================================================================================
|
||||
START=$(date +%s)
|
||||
RAMDISK_HEALTHY=true
|
||||
SSD_HEALTHY=true
|
||||
EMBY_RUNNING=true
|
||||
SOMETHING_HAPPENED=false # controls whether summary is printed
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# CHECK 1 — Emby running
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ── Check 1 — Emby running ───────────────────────────────────────────────────────────────────
|
||||
if [[ "$TRANSCODE_CHECK_EMBY" == true ]]; then
|
||||
info "$ICON_CONTAINERS Checking $TRANSCODE_EMBY_CONTAINER..."
|
||||
if ! docker inspect "$TRANSCODE_EMBY_CONTAINER" \
|
||||
log "Checking $TRANSCODE_EMBY_CONTAINER..."
|
||||
if ! timeout "$DOCKER_TIMEOUT" docker inspect "$TRANSCODE_EMBY_CONTAINER" \
|
||||
--format '{{.State.Running}}' 2>/dev/null | grep -q "true"; then
|
||||
warn "$TRANSCODE_EMBY_CONTAINER is not running — skipping threshold checks"
|
||||
EMBY_RUNNING=false
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
success "$TRANSCODE_EMBY_CONTAINER is running"
|
||||
log "$TRANSCODE_EMBY_CONTAINER is running ✅"
|
||||
fi
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# CHECK 2 — Symlink integrity
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
info "$ICON_LINK Checking symlink integrity..."
|
||||
|
||||
# ── Check 2 — Symlink integrity ───────────────────────────────────────────────────────────────
|
||||
log "Checking symlink integrity..."
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null)
|
||||
|
||||
if [[ -z "$CURRENT_TARGET" ]]; then
|
||||
@@ -229,175 +248,162 @@ if [[ -z "$CURRENT_TARGET" ]]; then
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK"
|
||||
CURRENT_TARGET="$RAMDISK_PATH"
|
||||
notify "Transcode symlink was missing on $(hostname) — recreated" "Transcode Manager" "warning"
|
||||
notify "Transcode symlink was missing on $(hostname) ($MY_ID) — recreated" \
|
||||
"Transcode Manager" "warning"
|
||||
fi
|
||||
SOMETHING_HAPPENED=true
|
||||
elif [[ ! -e "$CURRENT_TARGET" ]]; then
|
||||
warn "$ICON_LINK Symlink target missing: $CURRENT_TARGET — resetting to ramdisk"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK"
|
||||
CURRENT_TARGET="$RAMDISK_PATH"
|
||||
notify "Transcode symlink target was missing on $(hostname) — reset to ramdisk" "Transcode Manager" "warning"
|
||||
notify "Transcode symlink target was missing on $(hostname) ($MY_ID) — reset to ramdisk" \
|
||||
"Transcode Manager" "warning"
|
||||
fi
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
success "$ICON_LINK Symlink valid: $TRANSCODE_LINK → $CURRENT_TARGET"
|
||||
log "Symlink valid: $TRANSCODE_LINK → $CURRENT_TARGET ✅"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# CHECK 3 — Ramdisk health
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
info "$ICON_RAM Checking ramdisk..."
|
||||
# ── Check 3 — Ramdisk health ──────────────────────────────────────────────────────────────────
|
||||
log "Checking ramdisk..."
|
||||
|
||||
if ! mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
error "$ICON_RAM Ramdisk not mounted at $RAMDISK_PATH"
|
||||
error "Ramdisk not mounted at $RAMDISK_PATH"
|
||||
RAMDISK_HEALTHY=false
|
||||
SOMETHING_HAPPENED=true
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
warn "Flipping symlink to SSD — ramdisk unavailable"
|
||||
flip_symlink "$TRANSCODE_SSD" "ramdisk disappeared"
|
||||
CURRENT_TARGET="$TRANSCODE_SSD"
|
||||
notify "Ramdisk disappeared on $(hostname) — transcodes falling back to SSD. Run ramdisk_setup.sh to restore." "Transcode Manager" "warning"
|
||||
notify "Ramdisk disappeared on $(hostname) ($MY_ID) — transcodes falling back to SSD. Run ramdisk_setup.sh to restore." \
|
||||
"Transcode Manager" "warning"
|
||||
fi
|
||||
else
|
||||
RAMDISK_SIZE_ACTUAL=$(df "$RAMDISK_PATH" --output=size -h | tail -1 | tr -d ' ')
|
||||
success "$ICON_RAM Ramdisk mounted — size: $RAMDISK_SIZE_ACTUAL"
|
||||
RAMDISK_SIZE_ACTUAL=$(df "$RAMDISK_PATH" --output=size -h 2>/dev/null | tail -1 | tr -d ' ')
|
||||
log "Ramdisk mounted — size: $RAMDISK_SIZE_ACTUAL ✅"
|
||||
fix_permissions "$RAMDISK_PATH"
|
||||
|
||||
# Guarantee transcoding-temp exists on ramdisk
|
||||
# If missing Emby creates it wherever it finds a writable path first —
|
||||
# which may be the SSD fallback — locking all new sessions onto SSD
|
||||
# even when the symlink correctly points at the ramdisk.
|
||||
TRANSCODE_TEMP_RAM="${RAMDISK_PATH}/transcoding-temp"
|
||||
TRANSCODE_TEMP_SSD="${TRANSCODE_SSD}/transcoding-temp"
|
||||
|
||||
if [[ ! -d "$TRANSCODE_TEMP_RAM" ]]; then
|
||||
warn "transcoding-temp missing from ramdisk — creating now"
|
||||
mkdir -p "$TRANSCODE_TEMP_RAM"
|
||||
chmod "$TRANSCODE_CHMOD" "$TRANSCODE_TEMP_RAM"
|
||||
chown "$TRANSCODE_OWNER" "$TRANSCODE_TEMP_RAM"
|
||||
success "transcoding-temp created on ramdisk — new sessions will use ramdisk"
|
||||
warn "transcoding-temp created on ramdisk — new sessions will use ramdisk ✅"
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
log "transcoding-temp exists on ramdisk — ok"
|
||||
log "transcoding-temp exists on ramdisk ✅"
|
||||
fi
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# CHECK 4 — SSD health
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
info "$ICON_DISK Checking SSD fallback..."
|
||||
# ── Check 4 — SSD health ─────────────────────────────────────────────────────────────────────
|
||||
log "Checking SSD fallback..."
|
||||
|
||||
if [[ ! -d "$TRANSCODE_SSD" ]]; then
|
||||
warn "$ICON_DISK SSD fallback path missing: $TRANSCODE_SSD"
|
||||
warn "SSD fallback path missing: $TRANSCODE_SSD"
|
||||
SSD_HEALTHY=false
|
||||
SOMETHING_HAPPENED=true
|
||||
if [[ "$TRANSCODE_MANAGER_MODE" == "ssd" ]]; then
|
||||
error "Mode is 'ssd' but SSD path is missing — cannot continue"
|
||||
notify "Transcode SSD path missing on $(hostname) — mode is 'ssd', manual intervention needed" "Transcode Manager" "warning"
|
||||
notify "Transcode SSD path missing on $(hostname) ($MY_ID) — mode is 'ssd', manual intervention needed" \
|
||||
"Transcode Manager" "warning"
|
||||
exit 1
|
||||
else
|
||||
warn "SSD fallback disabled — will stay on ramdisk"
|
||||
fi
|
||||
else
|
||||
SSD_FREE_GB=$(get_ssd_free_gb)
|
||||
success "$ICON_DISK SSD available — ${SSD_FREE_GB}GB free"
|
||||
log "SSD available — ${SSD_FREE_GB}GB free ✅"
|
||||
fix_permissions "$TRANSCODE_SSD"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# USAGE STATS
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
# ── Usage stats ───────────────────────────────────────────────────────────────────────────────
|
||||
RAMDISK_USED_GB="0.00"
|
||||
RAMDISK_AVAIL_GB="0.00"
|
||||
RAMDISK_FILES=0
|
||||
|
||||
if [[ "$RAMDISK_HEALTHY" == true ]]; then
|
||||
RAMDISK_USED_GB=$(get_ramdisk_used_gb)
|
||||
RAMDISK_AVAIL_GB=$(get_ramdisk_avail_gb)
|
||||
RAMDISK_FILES=$(find "$RAMDISK_PATH" -type f 2>/dev/null | wc -l)
|
||||
RAMDISK_SIZE_H=$(du -sh "$RAMDISK_PATH" 2>/dev/null | cut -f1)
|
||||
fi
|
||||
|
||||
SSD_FILES=$(find "$TRANSCODE_SSD" -type f 2>/dev/null | wc -l)
|
||||
SSD_SIZE_H=$(du -sh "$TRANSCODE_SSD" 2>/dev/null | cut -f1)
|
||||
|
||||
info "$ICON_RAM Ramdisk usage: ${RAMDISK_USED_GB}GB used / ${RAMDISK_AVAIL_GB}GB available"
|
||||
info "$ICON_RAM Ramdisk files: ${RAMDISK_FILES:-0} ($RAMDISK_SIZE_H)"
|
||||
info "$ICON_DISK SSD files: $SSD_FILES ($SSD_SIZE_H)"
|
||||
|
||||
FLIP_COUNT=$(get_flip_count)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_EMBY Active Transcode Sessions ━━━
|
||||
# Queries all configured servers in TRANSCODE_SERVERS array
|
||||
# Aggregates sessions, counts, and storage state across all servers
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━ $ICON_EMBY Active Transcode Sessions ━━━"
|
||||
log "Ramdisk: ${RAMDISK_USED_GB}GB used / ${RAMDISK_AVAIL_GB}GB available ($RAMDISK_FILES files)"
|
||||
log "SSD: $SSD_FILES files"
|
||||
log "Flips: $FLIP_COUNT this hour"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Active Transcode Sessions ━━━
|
||||
# ==============================================================================================
|
||||
TOTAL_SESSIONS=0
|
||||
LIVE_TV=0
|
||||
TRANSCODING=0
|
||||
DIRECT=0
|
||||
ANY_SERVER_RUNNING=false
|
||||
RAM_SESSION_COUNT=0
|
||||
SSD_SESSION_COUNT=0
|
||||
|
||||
for server_entry in "${TRANSCODE_SERVERS[@]}"; do
|
||||
# Parse entry — ContainerName|URL|APIKey|Type
|
||||
IFS='|' read -r SRV_CONTAINER SRV_URL SRV_KEY SRV_TYPE <<< "$server_entry"
|
||||
|
||||
# Skip placeholder entries
|
||||
if [[ "$SRV_KEY" == *"api-key"* ]] || [[ "$SRV_KEY" == *"token"* && ${#SRV_KEY} -lt 20 ]]; then
|
||||
if [[ "$SRV_KEY" == *"api-key"* ]] || \
|
||||
[[ "$SRV_KEY" == *"token"* && ${#SRV_KEY} -lt 20 ]]; then
|
||||
log "Skipping $SRV_CONTAINER — placeholder API key"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if container is running
|
||||
if ! docker inspect "$SRV_CONTAINER" --format '{{.State.Running}}' 2>/dev/null | grep -q "true"; then
|
||||
info "$ICON_CONTAINERS $SRV_CONTAINER — not running, skipping"
|
||||
# Container running check with timeout
|
||||
if ! timeout "$DOCKER_TIMEOUT" docker inspect "$SRV_CONTAINER" \
|
||||
--format '{{.State.Running}}' 2>/dev/null | grep -q "true"; then
|
||||
log "$SRV_CONTAINER — not running, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
ANY_SERVER_RUNNING=true
|
||||
|
||||
# Check API reachability
|
||||
if ! check_api "$SRV_URL" "$SRV_CONTAINER" 5; then
|
||||
warn "$SRV_CONTAINER — API unreachable, skipping (container up but API not responding)"
|
||||
warn "$SRV_CONTAINER — API unreachable, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Query sessions based on server type
|
||||
case "$SRV_TYPE" in
|
||||
emby|jellyfin)
|
||||
SESSION_DATA=$(curl -sf --max-time 10 \
|
||||
-H "X-Emby-Token: $SRV_KEY" \
|
||||
"${SRV_URL}/Sessions" 2>/dev/null)
|
||||
;;
|
||||
"${SRV_URL}/Sessions" 2>/dev/null) ;;
|
||||
plex)
|
||||
SESSION_DATA=$(curl -sf --max-time 10 \
|
||||
-H "X-Plex-Token: $SRV_KEY" \
|
||||
"${SRV_URL}/status/sessions" 2>/dev/null)
|
||||
;;
|
||||
"${SRV_URL}/status/sessions" 2>/dev/null) ;;
|
||||
*)
|
||||
warn "Unknown server type '$SRV_TYPE' for $SRV_CONTAINER — skipping"
|
||||
continue
|
||||
;;
|
||||
continue ;;
|
||||
esac
|
||||
|
||||
if [[ -z "$SESSION_DATA" ]] || ! command -v jq >/dev/null 2>&1; then
|
||||
warn "Could not retrieve session data from $SRV_CONTAINER"
|
||||
continue
|
||||
fi
|
||||
[[ -z "$SESSION_DATA" ]] && continue
|
||||
! command -v jq >/dev/null 2>&1 && continue
|
||||
|
||||
# Count sessions (Emby/Jellyfin format)
|
||||
if [[ "$SRV_TYPE" == "emby" || "$SRV_TYPE" == "jellyfin" ]]; then
|
||||
SRV_TOTAL=$(echo "$SESSION_DATA" | \
|
||||
jq '[.[] | select(.NowPlayingItem != null)] | length' 2>/dev/null || echo 0)
|
||||
SRV_LIVE=$(echo "$SESSION_DATA" | \
|
||||
jq '[.[] | select(.NowPlayingItem != null) | select(.NowPlayingItem.Type == "LiveTv" or .NowPlayingItem.Type == "TvChannel")] | length' \
|
||||
jq '[.[] | select(.NowPlayingItem != null) |
|
||||
select(.NowPlayingItem.Type == "LiveTv" or
|
||||
.NowPlayingItem.Type == "TvChannel")] | length' \
|
||||
2>/dev/null || echo 0)
|
||||
SRV_TRANSCODE=$(echo "$SESSION_DATA" | \
|
||||
jq '[.[] | select(.NowPlayingItem != null) | select(.PlayState.PlayMethod == "Transcode")] | length' \
|
||||
jq '[.[] | select(.NowPlayingItem != null) |
|
||||
select(.PlayState.PlayMethod == "Transcode")] | length' \
|
||||
2>/dev/null || echo 0)
|
||||
SRV_DIRECT=$(echo "$SESSION_DATA" | \
|
||||
jq '[.[] | select(.NowPlayingItem != null) | select(.PlayState.PlayMethod != "Transcode")] | length' \
|
||||
jq '[.[] | select(.NowPlayingItem != null) |
|
||||
select(.PlayState.PlayMethod != "Transcode")] | length' \
|
||||
2>/dev/null || echo 0)
|
||||
|
||||
TOTAL_SESSIONS=$(( TOTAL_SESSIONS + SRV_TOTAL ))
|
||||
@@ -405,138 +411,134 @@ for server_entry in "${TRANSCODE_SERVERS[@]}"; do
|
||||
TRANSCODING=$(( TRANSCODING + SRV_TRANSCODE ))
|
||||
DIRECT=$(( DIRECT + SRV_DIRECT ))
|
||||
|
||||
# Display server header if more than one server configured and running
|
||||
if [[ "${#TRANSCODE_SERVERS[@]}" -gt 1 ]]; then
|
||||
echo " $ICON_EMBY $SRV_CONTAINER — $SRV_TOTAL streams"
|
||||
fi
|
||||
|
||||
# List each active session
|
||||
if [[ "$SRV_TOTAL" -gt 0 ]]; then
|
||||
SOMETHING_HAPPENED=true
|
||||
[[ "${#TRANSCODE_SERVERS[@]}" -gt 1 ]] && \
|
||||
echo " $ICON_EMBY $SRV_CONTAINER — $SRV_TOTAL streams"
|
||||
|
||||
while IFS= read -r session; do
|
||||
USER=$(echo "$session" | jq -r '.UserName // "Unknown"' 2>/dev/null)
|
||||
USER=$(echo "$session" | jq -r '.UserName // "Unknown"' 2>/dev/null)
|
||||
TITLE=$(echo "$session" | jq -r '.NowPlayingItem.Name // "Unknown"' 2>/dev/null)
|
||||
MEDIA_TYPE=$(echo "$session" | jq -r '.NowPlayingItem.Type // "Unknown"' 2>/dev/null)
|
||||
PLAY_METHOD=$(echo "$session" | jq -r '.PlayState.PlayMethod // "Unknown"' 2>/dev/null)
|
||||
|
||||
MEDIA_LABEL=$(get_media_type_label "$MEDIA_TYPE")
|
||||
METHOD_LABEL=$(get_play_method_label "$PLAY_METHOD")
|
||||
|
||||
echo " $ICON_EMBY $(printf '%-12s' "$USER") — $(printf '%-30s' "$TITLE") — $(printf '%-10s' "$MEDIA_LABEL") — $METHOD_LABEL"
|
||||
done < <(echo "$SESSION_DATA" | \
|
||||
jq -c '.[] | select(.NowPlayingItem != null)' 2>/dev/null)
|
||||
MTYPE=$(echo "$session" | jq -r '.NowPlayingItem.Type // "Unknown"' 2>/dev/null)
|
||||
METH=$(echo "$session" | jq -r '.PlayState.PlayMethod // "Unknown"' 2>/dev/null)
|
||||
echo " $ICON_EMBY $(printf '%-12s' "$USER") — $(printf '%-30s' "$TITLE") — $(get_media_type_label "$MTYPE") — $(get_play_method_label "$METH")"
|
||||
done < <(echo "$SESSION_DATA" | jq -c '.[] | select(.NowPlayingItem != null)' 2>/dev/null)
|
||||
fi
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
# Totals header — shown after all servers
|
||||
echo ""
|
||||
echo " $ICON_EMBY Total: $TOTAL_SESSIONS | $ICON_RAM Live TV: $LIVE_TV | $ICON_SYNC Transcoding: $TRANSCODING | $ICON_DONE Direct: $DIRECT"
|
||||
# Storage state
|
||||
RAM_SESSION_COUNT=$(find "$RAMDISK_PATH/transcoding-temp" \
|
||||
-mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||
SSD_SESSION_COUNT=$(find "$TRANSCODE_SSD/transcoding-temp" \
|
||||
-mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||
|
||||
# Storage state — based on total ramdisk/SSD session folders
|
||||
if [[ "$TOTAL_SESSIONS" -gt 0 ]]; then
|
||||
TRANSCODE_TEMP="transcoding-temp"
|
||||
RAM_SESSION_COUNT=$(find "$RAMDISK_PATH/$TRANSCODE_TEMP" \
|
||||
-mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||
SSD_SESSION_COUNT=$(find "$TRANSCODE_SSD/$TRANSCODE_TEMP" \
|
||||
-mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||
echo ""
|
||||
echo " $ICON_EMBY Total: $TOTAL_SESSIONS | Live TV: $LIVE_TV | Transcoding: $TRANSCODING | Direct: $DIRECT"
|
||||
|
||||
if [[ "$RAM_SESSION_COUNT" -gt 0 && "$SSD_SESSION_COUNT" -gt 0 ]]; then
|
||||
warn "$ICON_WARN Split state — $RAM_SESSION_COUNT folder(s) on ramdisk / $SSD_SESSION_COUNT on SSD"
|
||||
warn "Split state — $RAM_SESSION_COUNT folder(s) ramdisk / $SSD_SESSION_COUNT SSD"
|
||||
warn "Older sessions remain on original location until they end naturally"
|
||||
echo " $ICON_LINK Storage: $ICON_RAM ramdisk ($RAM_SESSION_COUNT) + $ICON_DISK SSD ($SSD_SESSION_COUNT)"
|
||||
elif [[ "$RAM_SESSION_COUNT" -gt 0 ]]; then
|
||||
echo " $ICON_LINK Storage: $ICON_RAM ramdisk"
|
||||
log "Storage: ramdisk ($RAM_SESSION_COUNT sessions)"
|
||||
elif [[ "$SSD_SESSION_COUNT" -gt 0 ]]; then
|
||||
echo " $ICON_LINK Storage: $ICON_DISK SSD"
|
||||
log "Storage: SSD ($SSD_SESSION_COUNT sessions)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$ANY_SERVER_RUNNING" == false && "$TRANSCODE_CHECK_EMBY" == true ]]; then
|
||||
info "No configured media servers are running — skipping threshold checks"
|
||||
EMBY_RUNNING=false
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# MODE LOGIC
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
[[ "$ANY_SERVER_RUNNING" == false && "$TRANSCODE_CHECK_EMBY" == true ]] && \
|
||||
{ log "No configured media servers running — skipping threshold checks"; EMBY_RUNNING=false; }
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Mode Logic ━━━
|
||||
# ==============================================================================================
|
||||
case "$TRANSCODE_MANAGER_MODE" in
|
||||
|
||||
ramdisk)
|
||||
info "$ICON_RAM Mode: RAMDISK — forcing symlink to ramdisk"
|
||||
log "Mode: RAMDISK — forcing symlink to ramdisk"
|
||||
if [[ "$RAMDISK_HEALTHY" == false ]]; then
|
||||
error "Ramdisk mode selected but ramdisk is not available"
|
||||
notify "Transcode ramdisk mode failed on $(hostname) — ramdisk not mounted" "Transcode Manager" "warning"
|
||||
notify "Transcode ramdisk mode failed on $(hostname) ($MY_ID) — ramdisk not mounted" \
|
||||
"Transcode Manager" "warning"
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
if [[ "$CURRENT_TARGET" != "$RAMDISK_PATH" ]]; then
|
||||
flip_symlink "$RAMDISK_PATH" "ramdisk mode"
|
||||
increment_flip_count > /dev/null
|
||||
else
|
||||
log "Symlink already points to ramdisk — no change"
|
||||
FLIP_COUNT=$(get_flip_count)
|
||||
SOMETHING_HAPPENED=true
|
||||
fi
|
||||
if (( $(awk "BEGIN {print ($RAMDISK_USED_GB >= $RAMDISK_WARN_GB) ? 1 : 0}") )); then
|
||||
warn "$ICON_RAM Ramdisk usage ${RAMDISK_USED_GB}GB above threshold ${RAMDISK_WARN_GB}GB — consider switching to smart mode"
|
||||
notify "Ramdisk usage high on $(hostname) — ${RAMDISK_USED_GB}GB used in ramdisk-only mode" "Transcode Manager" "warning"
|
||||
warn "Ramdisk usage ${RAMDISK_USED_GB}GB above threshold ${RAMDISK_WARN_GB}GB — consider switching to smart mode"
|
||||
notify "Ramdisk high on $(hostname) ($MY_ID) — ${RAMDISK_USED_GB}GB in ramdisk-only mode" \
|
||||
"Transcode Manager" "warning"
|
||||
SOMETHING_HAPPENED=true
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
ssd)
|
||||
info "$ICON_DISK Mode: SSD — forcing symlink to SSD"
|
||||
log "Mode: SSD — forcing symlink to SSD"
|
||||
if [[ "$SSD_HEALTHY" == false ]]; then
|
||||
error "SSD mode selected but SSD path is not available"
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
if [[ "$CURRENT_TARGET" != "$TRANSCODE_SSD" ]]; then
|
||||
flip_symlink "$TRANSCODE_SSD" "ssd mode"
|
||||
increment_flip_count > /dev/null
|
||||
else
|
||||
log "Symlink already points to SSD — no change"
|
||||
FLIP_COUNT=$(get_flip_count)
|
||||
SOMETHING_HAPPENED=true
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
smart)
|
||||
info "$ICON_GEAR Mode: SMART — auto threshold management"
|
||||
log "Mode: SMART — auto threshold management"
|
||||
|
||||
if [[ "$EMBY_RUNNING" == false ]]; then
|
||||
info "No media servers running — skipping threshold checks"
|
||||
log "No media servers running — skipping threshold checks"
|
||||
|
||||
elif [[ "$RAMDISK_HEALTHY" == false ]]; then
|
||||
info "Ramdisk unavailable — staying on SSD until ramdisk recovers"
|
||||
log "Ramdisk unavailable — staying on SSD until ramdisk recovers"
|
||||
|
||||
elif [[ "$CURRENT_TARGET" == "$RAMDISK_PATH" ]]; then
|
||||
if (( $(awk "BEGIN {print ($RAMDISK_USED_GB >= $RAMDISK_WARN_GB) ? 1 : 0}") )); then
|
||||
if [[ "$SSD_HEALTHY" == false ]]; then
|
||||
error "Ramdisk above threshold but SSD unavailable — cannot flip"
|
||||
notify "Transcode ramdisk full on $(hostname) and SSD unavailable — intervention needed" "Transcode Manager" "warning"
|
||||
notify "Transcode ramdisk full on $(hostname) ($MY_ID) and SSD unavailable" \
|
||||
"Transcode Manager" "warning"
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
SSD_FREE_GB=$(get_ssd_free_gb)
|
||||
if (( $(awk "BEGIN {print ($SSD_FREE_GB < $RAMDISK_SSD_MIN_GB) ? 1 : 0}") )); then
|
||||
warn "$ICON_DISK SSD only ${SSD_FREE_GB}GB free — below ${RAMDISK_SSD_MIN_GB}GB minimum, not flipping"
|
||||
warn "SSD only ${SSD_FREE_GB}GB free — below ${RAMDISK_SSD_MIN_GB}GB minimum, not flipping"
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
warn "$ICON_RAM Ramdisk ${RAMDISK_USED_GB}GB — above ${RAMDISK_WARN_GB}GB, flipping to SSD"
|
||||
warn "Ramdisk ${RAMDISK_USED_GB}GB — above ${RAMDISK_WARN_GB}GB, flipping to SSD"
|
||||
flip_symlink "$TRANSCODE_SSD" "threshold exceeded"
|
||||
CURRENT_TARGET="$TRANSCODE_SSD"
|
||||
NEW_COUNT=$(increment_flip_count)
|
||||
FLIP_COUNT=$NEW_COUNT
|
||||
SOMETHING_HAPPENED=true
|
||||
if [[ "$NEW_COUNT" -ge "$TRANSCODE_FLIP_WARN" ]]; then
|
||||
notify "Transcode flipped to SSD on $(hostname) — ${NEW_COUNT} flips this hour. Consider increasing RAMDISK_SIZE." "Transcode Manager" "warning"
|
||||
notify "Transcode flipped to SSD on $(hostname) ($MY_ID) — ${NEW_COUNT} flips this hour — consider increasing HOST*_RAMDISK_SIZE" \
|
||||
"Transcode Manager" "warning"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "Symlink already points to ramdisk — no change"
|
||||
log "Ramdisk ${RAMDISK_USED_GB}GB — below threshold — no action needed"
|
||||
fi
|
||||
|
||||
elif [[ "$CURRENT_TARGET" == "$TRANSCODE_SSD" ]]; then
|
||||
if (( $(awk "BEGIN {print ($RAMDISK_USED_GB <= $RAMDISK_LOW_GB) ? 1 : 0}") )); then
|
||||
info "$ICON_RAM Ramdisk ${RAMDISK_USED_GB}GB — below ${RAMDISK_LOW_GB}GB, flipping back to ramdisk"
|
||||
warn "Ramdisk ${RAMDISK_USED_GB}GB — below ${RAMDISK_LOW_GB}GB, flipping back to ramdisk"
|
||||
flip_symlink "$RAMDISK_PATH" "usage recovered"
|
||||
CURRENT_TARGET="$RAMDISK_PATH"
|
||||
NEW_COUNT=$(increment_flip_count)
|
||||
FLIP_COUNT=$NEW_COUNT
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
log "On SSD — ramdisk ${RAMDISK_USED_GB}GB still above low threshold ${RAMDISK_LOW_GB}GB"
|
||||
fi
|
||||
@@ -547,16 +549,32 @@ esac
|
||||
END=$(date +%s)
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null || echo "unknown")
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_SUMMARY Summary ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY TRANSCODE MANAGER SUMMARY ━━━━━"
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE"
|
||||
echo "$ICON_RAM Ramdisk: ${RAMDISK_USED_GB}GB used / ${RAMDISK_AVAIL_GB}GB available"
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK → $CURRENT_TARGET"
|
||||
echo "$ICON_LINK Flips: $FLIP_COUNT this hour (warn at $TRANSCODE_FLIP_WARN)"
|
||||
echo "$ICON_EMBY Streams: ${TOTAL_SESSIONS} total | Live TV: ${LIVE_TV} | Transcoding: ${TRANSCODING} | Direct: ${DIRECT}"
|
||||
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
|
||||
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
# ==============================================================================================
|
||||
# ━━━ Daily Log Write ━━━
|
||||
# ==============================================================================================
|
||||
# Read by weekly_health_digest.sh — format: DATE|USED_GB|FLIPS|RAM_SESSIONS|SSD_SESSIONS
|
||||
if [[ "$DRY_RUN" == false && "$NO_LOG" == false && -n "${TRANSCODE_DAILY_LOG:-}" ]]; then
|
||||
TODAY=$(date '+%Y-%m-%d')
|
||||
mkdir -p "$(dirname "$TRANSCODE_DAILY_LOG")"
|
||||
echo "${TODAY}|${RAMDISK_USED_GB}|${FLIP_COUNT}|${RAM_SESSION_COUNT}|${SSD_SESSION_COUNT}" \
|
||||
>> "$TRANSCODE_DAILY_LOG" 2>/dev/null || true
|
||||
log "Daily log written: $TRANSCODE_DAILY_LOG"
|
||||
fi
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Summary — only shown when something happened ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$SOMETHING_HAPPENED" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY TRANSCODE MANAGER SUMMARY ━━━━━"
|
||||
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE"
|
||||
echo "$ICON_RAM Ramdisk: ${RAMDISK_USED_GB}GB used / ${RAMDISK_AVAIL_GB}GB available"
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK → $CURRENT_TARGET"
|
||||
echo "$ICON_LINK Flips: $FLIP_COUNT this hour (warn at $TRANSCODE_FLIP_WARN)"
|
||||
echo "$ICON_EMBY Streams: $TOTAL_SESSIONS total | Live TV: $LIVE_TV | Transcoding: $TRANSCODING | Direct: $DIRECT"
|
||||
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
else
|
||||
log "Transcode manager — all healthy — $MY_ID ($(format_duration $(( END - START ))))"
|
||||
fi
|
||||
Reference in New Issue
Block a user