Files
Varaverk/Transcodes/transcode_manager.sh
T
2026-04-11 08:34:00 -04:00

298 lines
12 KiB
Bash

#!/bin/bash
# -----------------------------------------------------------------------------------------------
# --------------------------------- Transcode Manager ------------------------------------------
# -----------------------------------------------------------------------------------------------
# Monitors ramdisk usage and manages the transcode symlink direction.
# New transcode sessions land wherever the symlink points — existing sessions are unaffected.
# Run every 2-3 minutes via cron/User Scripts plugin.
#
# Logic:
# Ramdisk not found → ensure symlink points to SSD, notify
# Usage < RAMDISK_LOW_GB → ensure symlink points to ramdisk
# Usage >= RAMDISK_WARN_GB → flip symlink to SSD (after checking SSD has space)
# Only notifies on STATE CHANGE — silent when nothing changes
#
# Hysteresis gap between RAMDISK_WARN_GB and RAMDISK_LOW_GB prevents flip-flop
# when usage hovers near the threshold.
#
# State tracked in /tmp/transcode_state.db — resets on reboot (correct, ramdisk also resets)
# All configuration in Master.conf under Transcode Manager section.
# -----------------------------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../Master.conf"
source "$SCRIPT_DIR/../common.sh"
parse_args "$@"
STATE_FILE="/tmp/transcode_state.db"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_GEAR Setup ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_GEAR Setup ━━━"
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
success "Running as root"
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Status ━━━
# -----------------------------------------------------------------------------------------------
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_RAM Ramdisk path: $RAMDISK_PATH"
echo "$ICON_RAM Warn at: ${RAMDISK_WARN_GB}GB"
echo "$ICON_RAM Back to RAM at: ${RAMDISK_LOW_GB}GB"
echo "$ICON_DISK SSD fallback: $TRANSCODE_SSD"
echo "$ICON_DISK SSD min free: ${RAMDISK_SSD_MIN_GB}GB"
echo "$ICON_LINK Symlink: $TRANSCODE_LINK"
echo "$ICON_NOTIFY Flip warn: $TRANSCODE_FLIP_WARN per hour"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
if [[ -f "$STATE_FILE" ]]; then
echo ""
echo "━━━ Current State ━━━"
cat "$STATE_FILE"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no symlink changes will be made"
# -----------------------------------------------------------------------------------------------
# STATE HELPERS
# -----------------------------------------------------------------------------------------------
get_state() {
local key="$1"
grep -E "^${key}=" "$STATE_FILE" 2>/dev/null | cut -d'=' -f2
}
set_state() {
local key="$1" value="$2"
if [[ -f "$STATE_FILE" ]]; then
grep -vE "^${key}=" "$STATE_FILE" > "${STATE_FILE}.tmp"
echo "${key}=${value}" >> "${STATE_FILE}.tmp"
mv "${STATE_FILE}.tmp" "$STATE_FILE"
else
echo "${key}=${value}" > "$STATE_FILE"
fi
}
# Initialise state file if missing — ramdisk_setup.sh creates it but just in case
init_state() {
if [[ ! -f "$STATE_FILE" ]]; then
NOW=$(date +%s)
cat > "$STATE_FILE" <<EOF
current_target=$RAMDISK_PATH
last_flip_time=$NOW
flip_count_hour=0
flip_hour_start=$NOW
EOF
log "State file initialised"
fi
}
# -----------------------------------------------------------------------------------------------
# FLIP TRACKING
# Tracks how many times symlink has flipped in the current hour window.
# Resets counter when hour window rolls over.
# Notifies if flip count exceeds TRANSCODE_FLIP_WARN.
# -----------------------------------------------------------------------------------------------
track_flip() {
local NOW
NOW=$(date +%s)
local HOUR_START
HOUR_START=$(get_state "flip_hour_start")
local FLIP_COUNT
FLIP_COUNT=$(get_state "flip_count_hour")
[[ -z "$FLIP_COUNT" ]] && FLIP_COUNT=0
[[ -z "$HOUR_START" ]] && HOUR_START=$NOW
# Reset counter if hour window has passed
if (( NOW - HOUR_START >= 3600 )); then
FLIP_COUNT=0
set_state "flip_hour_start" "$NOW"
fi
((FLIP_COUNT++))
set_state "flip_count_hour" "$FLIP_COUNT"
set_state "last_flip_time" "$NOW"
log "Symlink flip count this hour: $FLIP_COUNT"
if (( FLIP_COUNT >= TRANSCODE_FLIP_WARN )); then
warn "$ICON_LINK Symlink has flipped $FLIP_COUNT times this hour — consider increasing ramdisk size"
notify "Transcode symlink flipping frequently on $(hostname)$FLIP_COUNT flips in 1hr — consider increasing RAMDISK_SIZE" "Transcode Manager" "warning"
fi
}
# -----------------------------------------------------------------------------------------------
# SYMLINK MANAGEMENT
# Changes the transcode symlink target.
# Only flips if current target differs from desired target.
# Tracks flip for frequency monitoring.
# -----------------------------------------------------------------------------------------------
set_symlink() {
local target="$1"
local current_target
current_target=$(get_state "current_target")
if [[ "$current_target" == "$target" ]]; then
log "Symlink already points to $target — no change"
return 0
fi
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would flip symlink: $TRANSCODE_LINK$target"
return 0
fi
info "$ICON_LINK Flipping symlink: $TRANSCODE_LINK$target"
if ln -sfn "$target" "$TRANSCODE_LINK"; then
success "$ICON_LINK Symlink updated → $target"
set_state "current_target" "$target"
track_flip
if [[ "$target" == "$RAMDISK_PATH" ]]; then
notify "Transcode symlink flipped to RAMDISK on $(hostname) — ramdisk has space" "Transcode Manager" "normal"
else
notify "Transcode symlink flipped to SSD on $(hostname) — ramdisk usage high" "Transcode Manager" "warning"
fi
else
error "Failed to update symlink"
notify "Transcode symlink flip failed on $(hostname)" "Transcode Manager" "warning"
return 1
fi
}
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_RAM Transcode Manager ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━ $ICON_RAM Transcode Manager — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
echo ""
init_state
START=$(date +%s)
# -----------------------------------------------------------------------------------------------
# Symlink integrity check
# -----------------------------------------------------------------------------------------------
info "$ICON_LINK Checking symlink integrity..."
if [[ ! -L "$TRANSCODE_LINK" ]]; then
error "$ICON_LINK Symlink missing: $TRANSCODE_LINK"
notify "Transcode symlink missing on $(hostname) — attempting recovery" "Transcode Manager" "warning"
# Attempt recovery — point at whichever location exists
if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" && success "Symlink recovered → ramdisk"
else
ln -sfn "$TRANSCODE_SSD" "$TRANSCODE_LINK" && success "Symlink recovered → SSD"
fi
fi
LINK_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null)
if [[ ! -d "$LINK_TARGET" ]]; then
error "$ICON_LINK Symlink target does not exist: $LINK_TARGET"
notify "Transcode symlink target missing on $(hostname): $LINK_TARGET" "Transcode Manager" "warning"
fi
info "$ICON_LINK Current target: $LINK_TARGET"
# -----------------------------------------------------------------------------------------------
# Ramdisk mount check
# -----------------------------------------------------------------------------------------------
info "$ICON_RAM Checking ramdisk..."
RAMDISK_MOUNTED=true
if ! mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
warn "$ICON_RAM Ramdisk not mounted at $RAMDISK_PATH — falling back to SSD"
RAMDISK_MOUNTED=false
set_symlink "$TRANSCODE_SSD"
notify "Ramdisk not found on $(hostname) — transcode symlink set to SSD fallback" "Transcode Manager" "warning"
fi
if [[ "$RAMDISK_MOUNTED" == true ]]; then
# Verify ramdisk size is correct
MOUNTED_SIZE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $2}' | tr -d 'G')
EXPECTED_SIZE=$(echo "$RAMDISK_SIZE" | tr -d 'G')
if [[ "$MOUNTED_SIZE" != "$EXPECTED_SIZE" ]]; then
warn "$ICON_RAM Ramdisk size mismatch — expected ${RAMDISK_SIZE} got ${MOUNTED_SIZE}G"
else
success "$ICON_RAM Ramdisk mounted and correct size (${RAMDISK_SIZE})"
fi
# Get current usage
RAMDISK_USED_KB=$(df "$RAMDISK_PATH" | awk 'NR==2 {print $3}')
RAMDISK_AVAIL_KB=$(df "$RAMDISK_PATH" | awk 'NR==2 {print $4}')
RAMDISK_USED_GB=$(awk "BEGIN {printf \"%.1f\", $RAMDISK_USED_KB / 1024 / 1024}")
RAMDISK_AVAIL_GB=$(awk "BEGIN {printf \"%.1f\", $RAMDISK_AVAIL_KB / 1024 / 1024}")
info "$ICON_RAM Ramdisk usage: ${RAMDISK_USED_GB}GB used / ${RAMDISK_AVAIL_GB}GB available"
# Count sessions in ramdisk and SSD
RAMDISK_FILES=$(find "$RAMDISK_PATH" -type f 2>/dev/null | wc -l)
SSD_FILES=$(find "$TRANSCODE_SSD" -type f 2>/dev/null | wc -l)
RAMDISK_SIZE_HUMAN=$(du -sh "$RAMDISK_PATH" 2>/dev/null | cut -f1)
SSD_SIZE_HUMAN=$(du -sh "$TRANSCODE_SSD" 2>/dev/null | cut -f1)
info "$ICON_RAM Ramdisk files: $RAMDISK_FILES ($RAMDISK_SIZE_HUMAN)"
info "$ICON_DISK SSD files: $SSD_FILES ($SSD_SIZE_HUMAN)"
# -----------------------------------------------------------------------------------------------
# Symlink direction decision
# -----------------------------------------------------------------------------------------------
WARN_THRESHOLD=$(awk "BEGIN {print $RAMDISK_WARN_GB * 1024 * 1024}")
LOW_THRESHOLD=$(awk "BEGIN {print $RAMDISK_LOW_GB * 1024 * 1024}")
if (( RAMDISK_USED_KB >= WARN_THRESHOLD )); then
# Ramdisk is getting full — check SSD has space before flipping
info "$ICON_DISK Checking SSD fallback space..."
SSD_AVAIL_KB=$(df "$TRANSCODE_SSD" | awk 'NR==2 {print $4}')
SSD_AVAIL_GB=$(awk "BEGIN {printf \"%.1f\", $SSD_AVAIL_KB / 1024 / 1024}")
SSD_MIN_KB=$(awk "BEGIN {print $RAMDISK_SSD_MIN_GB * 1024 * 1024}")
if (( SSD_AVAIL_KB < SSD_MIN_KB )); then
error "$ICON_DISK SSD fallback only has ${SSD_AVAIL_GB}GB free — minimum is ${RAMDISK_SSD_MIN_GB}GB"
notify "Transcode SSD fallback critically low on $(hostname)${SSD_AVAIL_GB}GB free" "Transcode Manager" "warning"
else
info "$ICON_DISK SSD has ${SSD_AVAIL_GB}GB free — flipping to SSD"
set_symlink "$TRANSCODE_SSD"
fi
elif (( RAMDISK_USED_KB < LOW_THRESHOLD )); then
# Ramdisk has recovered — flip back
set_symlink "$RAMDISK_PATH"
else
# In hysteresis gap — hold current state
CURRENT=$(get_state "current_target")
log "Usage ${RAMDISK_USED_GB}GB in hysteresis gap (${RAMDISK_LOW_GB}-${RAMDISK_WARN_GB}GB) — holding current target: $CURRENT"
info "$ICON_RAM Usage in hysteresis gap — holding current symlink target"
fi
fi
END=$(date +%s)
# -----------------------------------------------------------------------------------------------
# ━━━ $ICON_SUMMARY Summary ━━━
# -----------------------------------------------------------------------------------------------
echo ""
echo "━━━━━ $ICON_SUMMARY TRANSCODE MANAGER SUMMARY ━━━━━"
echo "$ICON_RAM Ramdisk: $([[ "$RAMDISK_MOUNTED" == true ]] && echo "${RAMDISK_USED_GB}GB used" || echo "NOT MOUNTED")"
echo "$ICON_LINK Symlink: $TRANSCODE_LINK$(readlink "$TRANSCODE_LINK" 2>/dev/null || echo "BROKEN")"
echo "$ICON_LINK Flip count: $(get_state "flip_count_hour") this hour (warn at $TRANSCODE_FLIP_WARN)"
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"