transcode manager now handles multiple media servers
This commit is contained in:
+52
-7
@@ -444,11 +444,11 @@ HOST2_DDNS_CONTAINERS=(
|
|||||||
# Own DDNS handled separately above — list additional containers here if needed
|
# Own DDNS handled separately above — list additional containers here if needed
|
||||||
# These stop when this server loses internet — regardless of remote state
|
# These stop when this server loses internet — regardless of remote state
|
||||||
FAILOVER_HOST1_STOP_ON_NO_NET=(
|
FAILOVER_HOST1_STOP_ON_NO_NET=(
|
||||||
"Gmer4Lfe.com" # add containers that should stop without internet
|
# "container-name" # add containers that should stop without internet
|
||||||
)
|
)
|
||||||
|
|
||||||
FAILOVER_HOST2_STOP_ON_NO_NET=(
|
FAILOVER_HOST2_STOP_ON_NO_NET=(
|
||||||
"Gmer4Lfe.us"
|
# "container-name"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ━━━ HOST1 runs these for HOST2 when HOST2 goes down ━━━
|
# ━━━ HOST1 runs these for HOST2 when HOST2 goes down ━━━
|
||||||
@@ -498,8 +498,7 @@ FAILOVER_HOST2_RUNS_FOR_HOST1_IMMEDIATE=(
|
|||||||
"Dispatcharr" # Live TV — people are watching, cannot wait
|
"Dispatcharr" # Live TV — people are watching, cannot wait
|
||||||
"Dispatcharr-Basic" # Live TV basic profile
|
"Dispatcharr-Basic" # Live TV basic profile
|
||||||
"Dispatcharr-Iptv-Users" # Live TV IPTV users
|
"Dispatcharr-Iptv-Users" # Live TV IPTV users
|
||||||
"ErsatzTV-Emby"
|
"ErsatzTV-Emby" # Live TV scheduling and channel management
|
||||||
# "container-placeholder" # Live TV scheduling and channel management
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tier 2 — starts after HOST1_TIER2_DELAY minutes
|
# Tier 2 — starts after HOST1_TIER2_DELAY minutes
|
||||||
@@ -1044,10 +1043,56 @@ RADARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.a
|
|||||||
# switch to this mode to drain ramdisk sessions gracefully
|
# switch to this mode to drain ramdisk sessions gracefully
|
||||||
TRANSCODE_MANAGER_MODE="smart" # smart | ramdisk | ssd
|
TRANSCODE_MANAGER_MODE="smart" # smart | ramdisk | ssd
|
||||||
|
|
||||||
# Emby container check — skips threshold checks when Emby is not running
|
# ━━━ Transcode Server Array ━━━
|
||||||
# Prevents unnecessary symlink flips when no transcoding is happening
|
# All media servers that share the ramdisk transcode space.
|
||||||
|
# transcode_manager.sh reads this array and queries each server's API for active sessions.
|
||||||
|
# Session display, storage detection, and threshold decisions cover ALL servers combined.
|
||||||
|
#
|
||||||
|
# One entry → works exactly as before — single server behavior
|
||||||
|
# Many entries → aggregates sessions from all servers, one threshold on total ramdisk usage
|
||||||
|
#
|
||||||
|
# Each server writes to its own subfolder inside transcoding-temp:
|
||||||
|
# /mnt/ram-transcode/transcoding-temp/ABC123/ ← Emby session
|
||||||
|
# /mnt/ram-transcode/transcoding-temp/XYZ789/ ← Jellyfin session
|
||||||
|
# They never touch each other's files — the ramdisk is shared scratch space.
|
||||||
|
#
|
||||||
|
# Docker Extra Parameters — each media server container that uses the ramdisk needs:
|
||||||
|
# --mount type=bind,source=/mnt/ram-transcode,target=/ext-ram-transcode,bind-propagation=shared
|
||||||
|
# The target path (/ext-ram-transcode) must match what's configured in each server's
|
||||||
|
# transcoding settings. Use a different target path per container if needed.
|
||||||
|
#
|
||||||
|
# Format: "ContainerName|URL|APIKey|Type"
|
||||||
|
# ContainerName — exact Docker container name (used for running check)
|
||||||
|
# URL — API base URL including port
|
||||||
|
# APIKey — server API key (Emby/Jellyfin token, Plex token etc.)
|
||||||
|
# Type — emby | jellyfin | plex (controls API endpoint format)
|
||||||
|
#
|
||||||
|
# The first entry is the primary server — used for container running check
|
||||||
|
# (TRANSCODE_CHECK_EMBY still applies to first entry).
|
||||||
|
# Additional entries are queried if their container is running.
|
||||||
|
# Entries with placeholder APIKey values are skipped automatically.
|
||||||
|
#
|
||||||
|
# ⚠️ Tdarr does NOT belong here — Tdarr encodes full files, not HLS segments.
|
||||||
|
# Large working files would fill the ramdisk rapidly and cause constant flips.
|
||||||
|
# Keep Tdarr on SSD. Use tdarr_cleanup.sh for Tdarr orphan management.
|
||||||
|
|
||||||
|
TRANSCODE_SERVERS=(
|
||||||
|
"Emby|http://localhost:8096|0c27448d93a7431f9ac63569f7655829|emby"
|
||||||
|
# "Emby-Jayred365|http://HOST2-TAILSCALE-IP:8096|his-api-key|emby" # his Emby temporarily
|
||||||
|
# "Jellyfin|http://localhost:8097|jellyfin-api-key|jellyfin" # test Jellyfin instance
|
||||||
|
# "Plex|http://localhost:32400|plex-token|plex" # Plex if needed
|
||||||
|
)
|
||||||
|
|
||||||
|
# Container running check — still applies — skips threshold checks when no servers active
|
||||||
|
# Checked against the first entry in TRANSCODE_SERVERS automatically
|
||||||
TRANSCODE_CHECK_EMBY=true
|
TRANSCODE_CHECK_EMBY=true
|
||||||
TRANSCODE_EMBY_CONTAINER="Emby" # exact Docker container name — case sensitive
|
|
||||||
|
# Per-host Emby container names for database repair
|
||||||
|
# HOST1 repairs its own personal Emby + the shared production Emby
|
||||||
|
# HOST2 repairs its own personal Emby
|
||||||
|
# detect_hosts() selects the correct container at runtime
|
||||||
|
HOST1_EMBY_REPAIR_CONTAINER="Emby" # shared production Emby lives on HOST1
|
||||||
|
HOST2_EMBY_REPAIR_CONTAINER="Emby-Jayred365" # HOST2's personal Emby instance
|
||||||
|
|
||||||
# Daily transcode statistics log — read by weekly_health_digest.sh
|
# Daily transcode statistics log — read by weekly_health_digest.sh
|
||||||
# Tracks peak usage, flip count, session ratio, files cleaned per day
|
# Tracks peak usage, flip count, session ratio, files cleaned per day
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
# authentication.db — API keys and sessions
|
# authentication.db — API keys and sessions
|
||||||
# activity.db — activity log
|
# activity.db — activity log
|
||||||
#
|
#
|
||||||
# All configuration in Master.conf — uses TRANSCODE_EMBY_CONTAINER and EMBY_URL.
|
# HOST1 repairs the shared production Emby (HOST1_EMBY_REPAIR_CONTAINER).
|
||||||
|
# HOST2 repairs its own personal Emby (HOST2_EMBY_REPAIR_CONTAINER).
|
||||||
|
# detect_hosts() selects the correct container at runtime.
|
||||||
|
# Config path is detected from Docker inspect — no hardcoded paths needed.
|
||||||
# Supports --dry-run to show what would be checked without stopping Emby.
|
# Supports --dry-run to show what would be checked without stopping Emby.
|
||||||
# -----------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -33,10 +36,6 @@ source "$SCRIPT_DIR/../common.sh"
|
|||||||
|
|
||||||
parse_args "$@"
|
parse_args "$@"
|
||||||
|
|
||||||
# Emby config path — inside the container it's /config, map to host path
|
|
||||||
# Detected from Docker inspect at runtime
|
|
||||||
EMBY_CONTAINER="$TRANSCODE_EMBY_CONTAINER"
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------
|
||||||
# ━━━ $ICON_GEAR Setup ━━━
|
# ━━━ $ICON_GEAR Setup ━━━
|
||||||
# -----------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------
|
||||||
@@ -52,6 +51,17 @@ success "Running as root"
|
|||||||
|
|
||||||
acquire_lock
|
acquire_lock
|
||||||
|
|
||||||
|
# Select correct Emby container based on which server is running this script
|
||||||
|
detect_hosts
|
||||||
|
|
||||||
|
if [[ "$LOCAL_SERVER_NAME" == "$HOST1" ]]; then
|
||||||
|
EMBY_CONTAINER="$HOST1_EMBY_REPAIR_CONTAINER"
|
||||||
|
else
|
||||||
|
EMBY_CONTAINER="$HOST2_EMBY_REPAIR_CONTAINER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Emby container: $LOCAL_SERVER_NAME → $EMBY_CONTAINER"
|
||||||
|
|
||||||
if ! command -v sqlite3 >/dev/null 2>&1; then
|
if ! command -v sqlite3 >/dev/null 2>&1; then
|
||||||
error "sqlite3 not found — install sqlite package"
|
error "sqlite3 not found — install sqlite package"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -223,13 +223,58 @@ TRANSCODE_CHMOD="755"
|
|||||||
|
|
||||||
TRANSCODE_MANAGER_MODE="smart" # smart | ramdisk | ssd
|
TRANSCODE_MANAGER_MODE="smart" # smart | ramdisk | ssd
|
||||||
|
|
||||||
TRANSCODE_CHECK_EMBY=true
|
TRANSCODE_CHECK_EMBY=true # skips threshold checks when no servers active
|
||||||
TRANSCODE_EMBY_CONTAINER="Emby"
|
|
||||||
|
|
||||||
TRANSCODE_DAILY_LOG="/boot/config/transcode_daily.db"
|
TRANSCODE_DAILY_LOG="/boot/config/transcode_daily.db"
|
||||||
TRANSCODE_LOG_RETENTION=90
|
TRANSCODE_LOG_RETENTION=90
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Multi-Server Configuration — `TRANSCODE_SERVERS`
|
||||||
|
|
||||||
|
The transcode manager supports any number of media servers sharing the same ramdisk scratch space. One server or ten — the same script handles it. The threshold decision is based on total ramdisk usage across all servers combined.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TRANSCODE_SERVERS=(
|
||||||
|
"Emby|http://localhost:8096|your-api-key|emby"
|
||||||
|
# "Emby-Jayred365|http://HOST2-IP:8096|his-api-key|emby" # temporarily cover his Emby
|
||||||
|
# "Jellyfin|http://localhost:8097|jellyfin-api-key|jellyfin" # test Jellyfin instance
|
||||||
|
# "Plex|http://localhost:32400|plex-token|plex" # Plex if needed
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format:** `"ContainerName|URL|APIKey|Type"`
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `ContainerName` | Exact Docker container name — used for running check |
|
||||||
|
| `URL` | API base URL including port |
|
||||||
|
| `APIKey` | Server API key or token |
|
||||||
|
| `Type` | `emby` \| `jellyfin` \| `plex` — controls API endpoint format |
|
||||||
|
|
||||||
|
**How it works with multiple servers:**
|
||||||
|
|
||||||
|
Each media server writes its transcode sessions to its own subfolder inside `transcoding-temp`:
|
||||||
|
|
||||||
|
```
|
||||||
|
/mnt/ram-transcode/transcoding-temp/
|
||||||
|
E0D8DC/ ← Emby session (Live TV segment files)
|
||||||
|
abc123/ ← Jellyfin session
|
||||||
|
xyz789/ ← his Emby session
|
||||||
|
```
|
||||||
|
|
||||||
|
They never touch each other's files. The ramdisk is shared scratch space — each server is unaware of the others. The manager aggregates session counts from all servers and makes one threshold decision based on total ramdisk usage.
|
||||||
|
|
||||||
|
**Each server container needs the shared propagation mount in Extra Parameters:**
|
||||||
|
```
|
||||||
|
--mount type=bind,source=/mnt/ram-transcode,target=/ext-ram-transcode,bind-propagation=shared
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the transcode path in each server's settings to `/ext-ram-transcode` (or whatever target path you use).
|
||||||
|
|
||||||
|
**Entries with placeholder API key values are skipped automatically** — comment out entries that aren't in use rather than removing them. The placeholders make it clear what's available to enable.
|
||||||
|
|
||||||
|
**⚠️ Tdarr does NOT belong in this array.** Tdarr encodes full files — large working files would fill the ramdisk rapidly and cause constant flips. Keep Tdarr on SSD. A dedicated `tdarr_cleanup.sh` handles Tdarr orphan management separately.
|
||||||
|
|
||||||
### Sizing the Ramdisk
|
### Sizing the Ramdisk
|
||||||
|
|
||||||
`tmpfs` only uses RAM actually needed — `RAMDISK_SIZE` is a ceiling, not a reservation.
|
`tmpfs` only uses RAM actually needed — `RAMDISK_SIZE` is a ceiling, not a reservation.
|
||||||
|
|||||||
+103
-51
@@ -49,15 +49,11 @@ success "Running as root"
|
|||||||
|
|
||||||
acquire_lock "wait"
|
acquire_lock "wait"
|
||||||
|
|
||||||
# Select correct Emby instance for session display
|
# Primary server for container running check — first entry in TRANSCODE_SERVERS
|
||||||
detect_hosts
|
# Full session display loops over all entries in the array
|
||||||
|
if [[ "${#TRANSCODE_SERVERS[@]}" -gt 0 ]]; then
|
||||||
if [[ "$LOCAL_SERVER_NAME" == "$HOST1" ]]; then
|
IFS='|' read -r PRIMARY_CONTAINER _ _ _ <<< "${TRANSCODE_SERVERS[0]}"
|
||||||
EMBY_URL="${HOST1_EMBY_URL}"
|
TRANSCODE_EMBY_CONTAINER="${PRIMARY_CONTAINER:-Emby}"
|
||||||
EMBY_API_KEY="${HOST1_EMBY_API_KEY}"
|
|
||||||
else
|
|
||||||
EMBY_URL="${HOST2_EMBY_URL}"
|
|
||||||
EMBY_API_KEY="${HOST2_EMBY_API_KEY}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$TRANSCODE_MANAGER_MODE" in
|
case "$TRANSCODE_MANAGER_MODE" in
|
||||||
@@ -335,42 +331,116 @@ FLIP_COUNT=$(get_flip_count)
|
|||||||
echo ""
|
echo ""
|
||||||
echo "━━━ $ICON_EMBY Active Emby Sessions ━━━"
|
echo "━━━ $ICON_EMBY Active Emby Sessions ━━━"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------
|
||||||
|
# ━━━ $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 ━━━"
|
||||||
|
|
||||||
TOTAL_SESSIONS=0
|
TOTAL_SESSIONS=0
|
||||||
LIVE_TV=0
|
LIVE_TV=0
|
||||||
TRANSCODING=0
|
TRANSCODING=0
|
||||||
DIRECT=0
|
DIRECT=0
|
||||||
|
ANY_SERVER_RUNNING=false
|
||||||
|
|
||||||
if [[ -z "$EMBY_API_KEY" ]]; then
|
for server_entry in "${TRANSCODE_SERVERS[@]}"; do
|
||||||
info "EMBY_API_KEY not set in Master.conf — skipping session display"
|
# Parse entry — ContainerName|URL|APIKey|Type
|
||||||
elif [[ "$EMBY_RUNNING" == false ]]; then
|
IFS='|' read -r SRV_CONTAINER SRV_URL SRV_KEY SRV_TYPE <<< "$server_entry"
|
||||||
info "Emby not running — no sessions"
|
|
||||||
else
|
# Skip placeholder entries
|
||||||
SESSION_DATA=$(emby_api "Sessions" 2>/dev/null)
|
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"
|
||||||
|
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)"
|
||||||
|
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)
|
||||||
|
;;
|
||||||
|
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
|
||||||
|
|
||||||
if [[ -z "$SESSION_DATA" ]] || ! command -v jq >/dev/null 2>&1; then
|
if [[ -z "$SESSION_DATA" ]] || ! command -v jq >/dev/null 2>&1; then
|
||||||
warn "Could not retrieve session data — check EMBY_URL and EMBY_API_KEY"
|
warn "Could not retrieve session data from $SRV_CONTAINER"
|
||||||
else
|
continue
|
||||||
# Count sessions
|
fi
|
||||||
TOTAL_SESSIONS=$(echo "$SESSION_DATA" | \
|
|
||||||
|
# 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)
|
jq '[.[] | select(.NowPlayingItem != null)] | length' 2>/dev/null || echo 0)
|
||||||
LIVE_TV=$(echo "$SESSION_DATA" | \
|
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)
|
2>/dev/null || echo 0)
|
||||||
TRANSCODING=$(echo "$SESSION_DATA" | \
|
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)
|
2>/dev/null || echo 0)
|
||||||
DIRECT=$(echo "$SESSION_DATA" | \
|
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)
|
2>/dev/null || echo 0)
|
||||||
|
|
||||||
echo " $ICON_EMBY Total: $TOTAL_SESSIONS | $ICON_RAM Live TV: $LIVE_TV | $ICON_SYNC Transcoding: $TRANSCODING | $ICON_DONE Direct: $DIRECT"
|
TOTAL_SESSIONS=$(( TOTAL_SESSIONS + SRV_TOTAL ))
|
||||||
echo ""
|
LIVE_TV=$(( LIVE_TV + SRV_LIVE ))
|
||||||
|
TRANSCODING=$(( TRANSCODING + SRV_TRANSCODE ))
|
||||||
|
DIRECT=$(( DIRECT + SRV_DIRECT ))
|
||||||
|
|
||||||
if [[ "$TOTAL_SESSIONS" -gt 0 ]]; then
|
# 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
|
||||||
|
|
||||||
# Storage state — count session folders on each location
|
# List each active session
|
||||||
# Emby folder names don't match API session IDs — per-session location not possible
|
if [[ "$SRV_TOTAL" -gt 0 ]]; then
|
||||||
# Split state shows when symlink has flipped mid-session
|
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)
|
||||||
|
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)
|
||||||
|
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 — based on total ramdisk/SSD session folders
|
||||||
|
if [[ "$TOTAL_SESSIONS" -gt 0 ]]; then
|
||||||
TRANSCODE_TEMP="transcoding-temp"
|
TRANSCODE_TEMP="transcoding-temp"
|
||||||
RAM_SESSION_COUNT=$(find "$RAMDISK_PATH/$TRANSCODE_TEMP" \
|
RAM_SESSION_COUNT=$(find "$RAMDISK_PATH/$TRANSCODE_TEMP" \
|
||||||
-mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
-mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||||
@@ -385,30 +455,12 @@ else
|
|||||||
echo " $ICON_LINK Storage: $ICON_RAM ramdisk"
|
echo " $ICON_LINK Storage: $ICON_RAM ramdisk"
|
||||||
elif [[ "$SSD_SESSION_COUNT" -gt 0 ]]; then
|
elif [[ "$SSD_SESSION_COUNT" -gt 0 ]]; then
|
||||||
echo " $ICON_LINK Storage: $ICON_DISK SSD"
|
echo " $ICON_LINK Storage: $ICON_DISK SSD"
|
||||||
else
|
|
||||||
echo " $ICON_LINK Storage: $ICON_INFO no transcode folders found"
|
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
if [[ "$ANY_SERVER_RUNNING" == false && "$TRANSCODE_CHECK_EMBY" == true ]]; then
|
||||||
|
info "No configured media servers are running — skipping threshold checks"
|
||||||
# List each active session
|
EMBY_RUNNING=false
|
||||||
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)
|
|
||||||
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)
|
|
||||||
else
|
|
||||||
info "No active streams"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------
|
||||||
@@ -455,7 +507,7 @@ case "$TRANSCODE_MANAGER_MODE" in
|
|||||||
info "$ICON_GEAR Mode: SMART — auto threshold management"
|
info "$ICON_GEAR Mode: SMART — auto threshold management"
|
||||||
|
|
||||||
if [[ "$EMBY_RUNNING" == false ]]; then
|
if [[ "$EMBY_RUNNING" == false ]]; then
|
||||||
info "Emby not running — skipping threshold checks"
|
info "No media servers running — skipping threshold checks"
|
||||||
|
|
||||||
elif [[ "$RAMDISK_HEALTHY" == false ]]; then
|
elif [[ "$RAMDISK_HEALTHY" == false ]]; then
|
||||||
info "Ramdisk unavailable — staying on SSD until ramdisk recovers"
|
info "Ramdisk unavailable — staying on SSD until ramdisk recovers"
|
||||||
|
|||||||
@@ -21,12 +21,45 @@
|
|||||||
# v1.5 — Full icon set expanded — each operation and state has its own distinct icon
|
# v1.5 — Full icon set expanded — each operation and state has its own distinct icon
|
||||||
# All function output updated to use correct icon per context
|
# All function output updated to use correct icon per context
|
||||||
# Icons grouped and commented by category for clarity
|
# Icons grouped and commented by category for clarity
|
||||||
|
# v1.6 — ICON_CONTAINERS added — 📦 anchors all container sections for visual consistency
|
||||||
|
# ICON_NOT_RUNNING changed to ⭕ — distinct from ICON_STOPPED 🔴
|
||||||
|
# Section dividers updated from --- to ━━━ for cleaner log readability
|
||||||
|
# Summary passed/failed lines use ICON_SUCCESS and ICON_ERROR consistently
|
||||||
|
# v1.7 — ICON_MOVER added for mover operations
|
||||||
|
# ICON_CONTAINERS replaces ICON_DOCKER for docker/container operations
|
||||||
|
# validate_int added — reusable integer validation for any script
|
||||||
|
# v1.8 — ICON_PHP added for PHP-FPM operations
|
||||||
|
# v1.9 — ICON_REBOOT added for server reboot operations
|
||||||
|
# v2.0 — ICON_PLUGIN added for User Scripts plugin operations
|
||||||
|
# v2.1 — ICON_ZFS and ICON_MEM added for ZFS and memory diagnostics
|
||||||
|
# Diagnostics icon group added to icon block
|
||||||
|
# v2.2 — ICON_WATCHDOG added for Docker watchdog monitoring operations
|
||||||
|
# v2.3 — ICON_NOTIFY added for notification operations
|
||||||
|
# notify() added — shared notification function supporting unRAID native and Discord
|
||||||
|
# NOTIFY_UNRAID and DISCORD_WEBHOOK configured in Master.conf
|
||||||
|
# v2.4 — ICON_CLEAN, ICON_TRASH added for media cleaner operations
|
||||||
|
# ICON_PERMS, ICON_UNLOCKED added for media permissions operations
|
||||||
|
# ICON_REBOOT_SMART added for smart conditional reboot
|
||||||
|
# v2.5 — ICON_RAM added for ramdisk operations
|
||||||
|
# ICON_LINK added for symlink state and management
|
||||||
|
# Transcode scripts group added to ecosystem
|
||||||
|
# v2.6 — ICON_FAILOVER added for failover operations
|
||||||
|
# check_local_array added — verifies local /mnt/user is mounted and healthy
|
||||||
|
# check_remote_array added — verifies remote /mnt/user is mounted and healthy
|
||||||
|
# check_remote_docker added — verifies remote Docker daemon is responding
|
||||||
|
# ping_remote added — non-fatal ping returning status for failover use
|
||||||
|
# ping_internet added — non-fatal external ping for failover use
|
||||||
|
# v2.7 — ICON_WEBGUI added for WebGUI watchdog operations
|
||||||
|
# ICON_DOCKER_NET added for Docker network connect operations
|
||||||
|
# v2.8 — ICON_CERT added for SSL certificate monitoring operations
|
||||||
|
# v2.9 — ICON_MONITOR added for monitoring section headers
|
||||||
# v3.0 — Script locking system added — prevents concurrent execution conflicts
|
# v3.0 — Script locking system added — prevents concurrent execution conflicts
|
||||||
# acquire_lock() — create PID lock file, register EXIT trap
|
# acquire_lock() — create PID lock file, register EXIT trap
|
||||||
# release_lock() — remove lock file on exit
|
# release_lock() — remove lock file on exit
|
||||||
# acquire_rsync_lock() — per-profile lock + global concurrent limit
|
# acquire_rsync_lock() — per-profile lock + global concurrent limit
|
||||||
# release_rsync_lock() — decrement global counter, remove profile lock
|
# release_rsync_lock() — decrement global counter, remove profile lock
|
||||||
# check_api() — pre-flight API reachability check
|
# check_api() — pre-flight API reachability check
|
||||||
|
# ICON_LOCK added — 🔏 script instance lock acquired/released
|
||||||
# ICON_SMART added for drive SMART health operations
|
# ICON_SMART added for drive SMART health operations
|
||||||
# ICON_BANDWIDTH added for bandwidth tracking operations
|
# ICON_BANDWIDTH added for bandwidth tracking operations
|
||||||
# ICON_DIGEST added for health digest operations
|
# ICON_DIGEST added for health digest operations
|
||||||
@@ -89,6 +122,7 @@ ICON_CLEAN="🧹" # media cleaner operations
|
|||||||
ICON_TRASH="🗑️" # files being deleted
|
ICON_TRASH="🗑️" # files being deleted
|
||||||
ICON_PERMS="🔐" # permissions operation / section header
|
ICON_PERMS="🔐" # permissions operation / section header
|
||||||
ICON_UNLOCKED="🔓" # permissions successfully applied to a share
|
ICON_UNLOCKED="🔓" # permissions successfully applied to a share
|
||||||
|
ICON_LOCK="🔏" # script instance lock — acquired/released
|
||||||
|
|
||||||
# Transcode Operations
|
# Transcode Operations
|
||||||
ICON_RAM="💨" # ramdisk operations — fast ephemeral storage
|
ICON_RAM="💨" # ramdisk operations — fast ephemeral storage
|
||||||
@@ -660,7 +694,7 @@ acquire_lock() {
|
|||||||
# Register EXIT trap to always release lock
|
# Register EXIT trap to always release lock
|
||||||
trap "_release_on_exit '$lockfile'" EXIT
|
trap "_release_on_exit '$lockfile'" EXIT
|
||||||
|
|
||||||
log "Lock acquired: $script_name (PID $$)"
|
log "$ICON_LOCK Lock acquired: $script_name (PID $$)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------
|
||||||
@@ -719,7 +753,7 @@ acquire_rsync_lock() {
|
|||||||
# Register EXIT trap
|
# Register EXIT trap
|
||||||
trap "_release_rsync_on_exit '$profile_lock'" EXIT
|
trap "_release_rsync_on_exit '$profile_lock'" EXIT
|
||||||
|
|
||||||
log "rsync lock acquired: profile '$profile' (PID $$, active: $(( current_count + 1 ))/$RSYNC_MAX_CONCURRENT)"
|
log "$ICON_LOCK rsync lock acquired: profile '$profile' (PID $$, active: $(( current_count + 1 ))/$RSYNC_MAX_CONCURRENT)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Internal — release rsync lock on exit
|
# Internal — release rsync lock on exit
|
||||||
|
|||||||
Reference in New Issue
Block a user