audit echo vs log across all scripts — outcomes always visible, verbose for per-item loops
This commit is contained in:
+662
@@ -0,0 +1,662 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================================
|
||||
# ================================= Transcode Manager ==========================================
|
||||
# ==============================================================================================
|
||||
#
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Monitors ramdisk usage and manages the transcode symlink direction. Called by
|
||||
# transcode_management.sh (Orchestrators/) every 7 minutes — always after
|
||||
# transcode_cleanup.sh runs first. Must be fast, non-blocking, and silent when
|
||||
# nothing has changed.
|
||||
#
|
||||
# Emby's transcode path points at TRANSCODE_LINK (a symlink). ffmpeg resolves
|
||||
# the symlink once at session start and holds a direct reference — existing
|
||||
# sessions are completely unaffected by symlink flips. Only new sessions care
|
||||
# where the symlink currently points.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Three Modes (TRANSCODE_MANAGER_MODE)
|
||||
# smart — auto-flips between ramdisk and SSD based on usage thresholds (default)
|
||||
# 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 (use during drain or maintenance)
|
||||
#
|
||||
# Safety Checks — Every Run Regardless of Mode
|
||||
# Symlink missing/broken → recreate pointing at ramdisk, notify
|
||||
# Ramdisk disappeared → flip to SSD immediately, notify warning
|
||||
# SSD path missing → disable SSD fallback (error if mode=ssd)
|
||||
# transcoding-temp missing → recreate on ramdisk silently
|
||||
# Permissions drift → fix silently every run
|
||||
# Emby not running → skip threshold checks, verify symlink only
|
||||
#
|
||||
# Session Display
|
||||
# Shows active streams from all configured TRANSCODE_SERVERS with user, title,
|
||||
# type (Live TV / TV Show / Movie), and play method (Transcode / Direct).
|
||||
# Split state shown when sessions exist on both ramdisk and SSD — normal during
|
||||
# a flip while ramdisk sessions drain.
|
||||
#
|
||||
# Daily Log
|
||||
# Appends one entry per run to TRANSCODE_DAILY_LOG, read by weekly_health_digest.sh.
|
||||
# Format: DATE|RAMDISK_USED_GB|FLIP_COUNT|RAM_SESSION_COUNT|SSD_SESSION_COUNT|FILES_CLEANED
|
||||
#
|
||||
# ==============================================================================================
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# Wait Lock
|
||||
# acquire_lock "wait" — waits if the previous run is still active. The 7-minute
|
||||
# interval can overlap on a system under heavy load.
|
||||
#
|
||||
# Docker Timeout
|
||||
# DOCKER_TIMEOUT caps all docker calls against a hung daemon.
|
||||
#
|
||||
# Notification Validated
|
||||
# platform_require_cmd confirms the notify script is present before use.
|
||||
#
|
||||
# Silent by Default
|
||||
# Runs every 7 minutes — only speaks when something changes or needs attention.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# host*.conf
|
||||
#
|
||||
# HOST*_RAMDISK_PATH / HOST*_TRANSCODE_SSD
|
||||
# Ramdisk mount point and SSD fallback path.
|
||||
# Aliased by detect_hosts().
|
||||
#
|
||||
# HOST*_RAMDISK_WARN_GB / HOST*_RAMDISK_LOW_GB / HOST*_RAMDISK_SIZE
|
||||
# Thresholds and ceiling. Change all three together.
|
||||
# Aliased by detect_hosts().
|
||||
#
|
||||
# HOST*_TRANSCODE_SERVERS
|
||||
# Array of media server definitions: "ContainerName|URL|APIKey|Type"
|
||||
# Type: emby | jellyfin | plex
|
||||
# Aliased by detect_hosts() → TRANSCODE_SERVERS.
|
||||
#
|
||||
# master.conf
|
||||
#
|
||||
# TRANSCODE_MANAGER_MODE
|
||||
# smart | ramdisk | ssd. (default: smart)
|
||||
#
|
||||
# TRANSCODE_CHECK_EMBY
|
||||
# Skip threshold checks when Emby not running — prevents unnecessary flips
|
||||
# overnight when no sessions are active. (default: true)
|
||||
#
|
||||
# TRANSCODE_FLIP_WARN
|
||||
# Notify if symlink flips this many times in one hour — indicates ramdisk
|
||||
# is undersized for the load. (default: 3)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# STATE FILES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# /tmp/transcode_state.db — current symlink target, flip count, last flip time
|
||||
# Lives in /tmp (ephemeral — resets correctly on reboot)
|
||||
# TRANSCODE_DAILY_LOG — per-run append, read by weekly_health_digest.sh
|
||||
# Trimmed to TRANSCODE_LOG_RETENTION days on each write
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# transcode_manager.sh
|
||||
# Check usage, flip if needed, run safety checks, display active sessions.
|
||||
#
|
||||
# transcode_manager.sh --dry-run
|
||||
# Show current usage and what flip decision would be made. No changes.
|
||||
#
|
||||
# transcode_manager.sh --status
|
||||
# Show current symlink target, ramdisk usage, session counts, and flip history.
|
||||
#
|
||||
# transcode_manager.sh --log
|
||||
# Verbose output including per-check results and session detail.
|
||||
#
|
||||
# transcode_manager.sh --no-log
|
||||
# Suppress daily log write. Used internally when called by transcode_cleanup.sh.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
# 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
|
||||
|
||||
parse_args "${FILTERED_ARGS[@]}"
|
||||
|
||||
DOCKER_TIMEOUT=15
|
||||
STATE_FILE="${TRANSCODE_STATE_FILE:-${STATE_DIR:-/tmp}/transcode_state.db}"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Setup ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
acquire_lock "wait"
|
||||
|
||||
if ! command -v docker &>/dev/null; then
|
||||
error "Docker command not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 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) log "Mode: $TRANSCODE_MANAGER_MODE" ;;
|
||||
*)
|
||||
warn "Unknown TRANSCODE_MANAGER_MODE: $TRANSCODE_MANAGER_MODE — defaulting to smart"
|
||||
TRANSCODE_MANAGER_MODE="smart"
|
||||
;;
|
||||
esac
|
||||
|
||||
log "$ICON_GEAR Config: ramdisk=$RAMDISK_PATH size=$RAMDISK_SIZE warn-at=${RAMDISK_WARN_GB}GB flip-back-at=${RAMDISK_LOW_GB}GB ssd-min-free=${RAMDISK_SSD_MIN_GB}GB flip-warn=${TRANSCODE_FLIP_WARN}/hr"
|
||||
log "$ICON_DISK SSD: $TRANSCODE_SSD"
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ Status ━━━
|
||||
# ==============================================================================================
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo ""
|
||||
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"
|
||||
echo "$ICON_DISK SSD: $TRANSCODE_SSD"
|
||||
echo "$ICON_RAM Warn at: ${RAMDISK_WARN_GB}GB"
|
||||
echo "$ICON_RAM Low at: ${RAMDISK_LOW_GB}GB"
|
||||
echo "$ICON_DISK SSD min free: ${RAMDISK_SSD_MIN_GB}GB"
|
||||
echo "$ICON_RAM Flip warn: $TRANSCODE_FLIP_WARN per hour"
|
||||
echo "$ICON_GEAR Check Emby: $TRANSCODE_CHECK_EMBY ($TRANSCODE_EMBY_CONTAINER)"
|
||||
echo "$ICON_GEAR Dry Run: $DRY_RUN"
|
||||
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_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"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ==============================================================================================
|
||||
# ── HELPER FUNCTIONS ──────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
|
||||
flip_symlink() {
|
||||
local target="$1" reason="$2"
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would flip symlink to: $target ($reason)"
|
||||
return 0
|
||||
fi
|
||||
ln -sfn "$target" "$TRANSCODE_LINK"
|
||||
warn "$ICON_LINK Symlink flipped to: $target ($reason)"
|
||||
}
|
||||
|
||||
fix_permissions() {
|
||||
local path="$1"
|
||||
[[ ! -d "$path" ]] && return
|
||||
chown -R "$TRANSCODE_OWNER" "$path" 2>/dev/null
|
||||
chmod -R "$TRANSCODE_CHMOD" "$path" 2>/dev/null
|
||||
log "Permissions fixed on $path"
|
||||
}
|
||||
|
||||
get_ramdisk_used_gb() {
|
||||
df "$RAMDISK_PATH" --output=used 2>/dev/null | \
|
||||
tail -1 | tr -d ' ' | awk '{printf "%.2f", $1/1048576}'
|
||||
}
|
||||
|
||||
get_ramdisk_avail_gb() {
|
||||
df "$RAMDISK_PATH" --output=avail 2>/dev/null | \
|
||||
tail -1 | tr -d ' ' | awk '{printf "%.2f", $1/1048576}'
|
||||
}
|
||||
|
||||
get_ssd_free_gb() {
|
||||
df "$TRANSCODE_SSD" --output=avail 2>/dev/null | \
|
||||
tail -1 | tr -d ' ' | awk '{printf "%.2f", $1/1048576}'
|
||||
}
|
||||
|
||||
get_flip_count() {
|
||||
local state_file="${STATE_DIR:-/tmp}/transcode_flip_state.db"
|
||||
local current_hour
|
||||
current_hour=$(date '+%Y-%m-%d-%H')
|
||||
[[ ! -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_count=$(awk -F'|' 'NR==1{print $2}' "$state_file" 2>/dev/null)
|
||||
[[ "$stored_hour" == "$current_hour" ]] && echo "${stored_count:-0}" || echo "0"
|
||||
}
|
||||
|
||||
increment_flip_count() {
|
||||
local state_file="${STATE_DIR:-/tmp}/transcode_flip_state.db"
|
||||
local current_hour
|
||||
current_hour=$(date '+%Y-%m-%d-%H')
|
||||
local current_count
|
||||
current_count=$(get_flip_count)
|
||||
current_count=$(( current_count + 1 ))
|
||||
echo "${current_hour}|${current_count}" > "$state_file"
|
||||
echo "$current_count"
|
||||
}
|
||||
|
||||
get_media_type_label() {
|
||||
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() {
|
||||
case "$1" in
|
||||
Transcode) echo "Transcode" ;;
|
||||
DirectStream) echo "Direct Stream" ;;
|
||||
DirectPlay) echo "Direct Play" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ 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 ───────────────────────────────────────────────────────────────────
|
||||
if [[ "$TRANSCODE_CHECK_EMBY" == true ]]; then
|
||||
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
|
||||
log "$TRANSCODE_EMBY_CONTAINER is running ✅"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Check 2 — Symlink integrity ───────────────────────────────────────────────────────────────
|
||||
log "Checking symlink integrity..."
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null)
|
||||
|
||||
if [[ -z "$CURRENT_TARGET" ]]; then
|
||||
warn "$ICON_LINK Symlink missing — recreating pointing to ramdisk"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK"
|
||||
CURRENT_TARGET="$RAMDISK_PATH"
|
||||
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) ($MY_ID) — reset to ramdisk" \
|
||||
"Transcode Manager" "warning"
|
||||
fi
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
log "Symlink valid: $TRANSCODE_LINK → $CURRENT_TARGET ✅"
|
||||
fi
|
||||
|
||||
# ── Check 3 — Ramdisk health ──────────────────────────────────────────────────────────────────
|
||||
log "Checking ramdisk..."
|
||||
|
||||
if ! mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
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) ($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 2>/dev/null | tail -1 | tr -d ' ')
|
||||
log "Ramdisk mounted — size: $RAMDISK_SIZE_ACTUAL ✅"
|
||||
fix_permissions "$RAMDISK_PATH"
|
||||
|
||||
# Guarantee transcoding-temp exists on ramdisk
|
||||
TRANSCODE_TEMP_RAM="${RAMDISK_PATH}/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"
|
||||
warn "transcoding-temp created on ramdisk — new sessions will use ramdisk ✅"
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
log "transcoding-temp exists on ramdisk ✅"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Check 4 — SSD health ─────────────────────────────────────────────────────────────────────
|
||||
log "Checking SSD fallback..."
|
||||
|
||||
if [[ ! -d "$TRANSCODE_SSD" ]]; then
|
||||
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) ($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)
|
||||
log "SSD available — ${SSD_FREE_GB}GB free ✅"
|
||||
fix_permissions "$TRANSCODE_SSD"
|
||||
fi
|
||||
|
||||
# ── 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)
|
||||
fi
|
||||
|
||||
SSD_FILES=$(find "$TRANSCODE_SSD" -type f 2>/dev/null | wc -l)
|
||||
FLIP_COUNT=$(get_flip_count)
|
||||
|
||||
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
|
||||
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
|
||||
log "Skipping $SRV_CONTAINER — placeholder API key"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 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
|
||||
|
||||
if ! check_api "$SRV_URL" "$SRV_CONTAINER" 5; then
|
||||
warn "$SRV_CONTAINER — API unreachable, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
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) ;;
|
||||
plex)
|
||||
SESSION_DATA=$(curl -sf --max-time 10 \
|
||||
-H "X-Plex-Token: $SRV_KEY" \
|
||||
"${SRV_URL}/status/sessions" 2>/dev/null) ;;
|
||||
*)
|
||||
warn "Unknown server type '$SRV_TYPE' for $SRV_CONTAINER — skipping"
|
||||
continue ;;
|
||||
esac
|
||||
|
||||
[[ -z "$SESSION_DATA" ]] && continue
|
||||
! command -v jq >/dev/null 2>&1 && continue
|
||||
|
||||
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' \
|
||||
2>/dev/null || echo 0)
|
||||
SRV_TRANSCODE=$(echo "$SESSION_DATA" | \
|
||||
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' \
|
||||
2>/dev/null || echo 0)
|
||||
|
||||
TOTAL_SESSIONS=$(( TOTAL_SESSIONS + SRV_TOTAL ))
|
||||
LIVE_TV=$(( LIVE_TV + SRV_LIVE ))
|
||||
TRANSCODING=$(( TRANSCODING + SRV_TRANSCODE ))
|
||||
DIRECT=$(( DIRECT + SRV_DIRECT ))
|
||||
|
||||
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)
|
||||
TITLE=$(echo "$session" | jq -r '.NowPlayingItem.Name // "Unknown"' 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
|
||||
|
||||
# Storage state
|
||||
# Sessions may use subdirectories (legacy transcode) or flat files (Live TV / Direct Stream HLS).
|
||||
# Count both: subdirs per-session, and unique hex session ID prefixes for flat files.
|
||||
_count_sessions() {
|
||||
local base="$1/transcoding-temp"
|
||||
local _dirs _flat
|
||||
_dirs=$(find "$base" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||
_flat=$(find "$base" -maxdepth 1 -type f -printf '%f\n' 2>/dev/null | \
|
||||
grep -oE '^[0-9a-f]{16,}' | sort -u | wc -l)
|
||||
echo $(( _dirs + _flat ))
|
||||
}
|
||||
RAM_SESSION_COUNT=$(_count_sessions "$RAMDISK_PATH")
|
||||
SSD_SESSION_COUNT=$(_count_sessions "$TRANSCODE_SSD")
|
||||
unset -f _count_sessions
|
||||
|
||||
if [[ "$TOTAL_SESSIONS" -gt 0 ]]; then
|
||||
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 "Split state — $RAM_SESSION_COUNT session(s) ramdisk / $SSD_SESSION_COUNT SSD"
|
||||
warn "Older sessions remain on original location until they end naturally"
|
||||
elif [[ "$RAM_SESSION_COUNT" -gt 0 ]]; then
|
||||
log "Storage: ramdisk ($RAM_SESSION_COUNT session(s))"
|
||||
elif [[ "$SSD_SESSION_COUNT" -gt 0 ]]; then
|
||||
log "Storage: SSD ($SSD_SESSION_COUNT session(s))"
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ "$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)
|
||||
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) ($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
|
||||
FLIP_COUNT=$(get_flip_count)
|
||||
SOMETHING_HAPPENED=true
|
||||
fi
|
||||
if (( $(awk "BEGIN {print ($RAMDISK_USED_GB >= $RAMDISK_WARN_GB) ? 1 : 0}") )); then
|
||||
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)
|
||||
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
|
||||
FLIP_COUNT=$(get_flip_count)
|
||||
SOMETHING_HAPPENED=true
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
smart)
|
||||
log "Mode: SMART — auto threshold management"
|
||||
|
||||
if [[ "$EMBY_RUNNING" == false ]]; then
|
||||
log "No media servers running — skipping threshold checks"
|
||||
|
||||
elif [[ "$RAMDISK_HEALTHY" == false ]]; then
|
||||
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) ($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 "SSD only ${SSD_FREE_GB}GB free — below ${RAMDISK_SSD_MIN_GB}GB minimum, not flipping"
|
||||
SOMETHING_HAPPENED=true
|
||||
else
|
||||
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) ($MY_ID) — ${NEW_COUNT} flips this hour — consider increasing HOST*_RAMDISK_SIZE" \
|
||||
"Transcode Manager" "warning"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
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
|
||||
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
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
END=$(date +%s)
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null || echo "unknown")
|
||||
|
||||
# ==============================================================================================
|
||||
# ━━━ 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
|
||||
echo "Transcode manager — all healthy — $MY_ID ($(format_duration $(( END - START ))))"
|
||||
fi
|
||||
Reference in New Issue
Block a user