#!/bin/bash # ============================================================================================== # ================================= 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 is already mounted reports status and exits cleanly without remounting. # # ── 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 # # ── 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/../load_config.sh" parse_args "$@" # ============================================================================================== # ━━━ Setup ━━━ # ============================================================================================== if [[ "$EUID" -ne 0 ]]; then error "Must be run as root — mount and symlink require root" exit 1 fi validate_unraid_cmd \ "/usr/local/emhttp/plugins/dynamix/scripts/notify" \ "" "" \ "unRAID notify script" || warn "unRAID notify script not found — native notifications disabled" 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_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 "" 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 if [[ -L "$TRANSCODE_LINK" ]]; then TARGET=$(readlink "$TRANSCODE_LINK") echo " $ICON_LINK Symlink: $TRANSCODE_LINK → $TARGET" else echo " $ICON_LINK Symlink: not set" fi echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi # ============================================================================================== # ━━━ Ramdisk ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_RAM Ramdisk — $MY_ID ━━━" echo "$ICON_RAM Path: $RAMDISK_PATH" echo "$ICON_RAM Size: $RAMDISK_SIZE" echo "" START=$(date +%s) SETUP_SUCCESS=true 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}') log "Ramdisk already mounted — $USAGE used / $AVAIL available" log "Skipping mount — verifying symlink and permissions" else if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would create $RAMDISK_PATH" warn "DRY RUN — would mount tmpfs ${RAMDISK_SIZE} at $RAMDISK_PATH" else log "Creating ramdisk mount point: $RAMDISK_PATH" mkdir -p "$RAMDISK_PATH" || { error "Failed to create $RAMDISK_PATH" notify "Ramdisk setup failed on $(hostname) ($MY_ID) — could not create mount point" \ "Ramdisk Setup" "warning" exit 1 } log "Mounting tmpfs ${RAMDISK_SIZE} at $RAMDISK_PATH..." if mount -t tmpfs -o size="$RAMDISK_SIZE" tmpfs "$RAMDISK_PATH"; then warn "Ramdisk mounted — ${RAMDISK_SIZE} at $RAMDISK_PATH ✅" else error "Failed to mount ramdisk at $RAMDISK_PATH" notify "Ramdisk setup failed on $(hostname) ($MY_ID) — mount failed" \ "Ramdisk Setup" "warning" exit 1 fi fi fi # ============================================================================================== # ━━━ SSD Fallback ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_DISK SSD Fallback ━━━" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would create SSD fallback: $TRANSCODE_SSD" else if [[ -d "$TRANSCODE_SSD" ]]; then log "SSD fallback already exists: $TRANSCODE_SSD" else log "Creating SSD fallback directory: $TRANSCODE_SSD" if mkdir -p "$TRANSCODE_SSD"; then log "SSD fallback created: $TRANSCODE_SSD ✅" else error "Failed to create SSD fallback: $TRANSCODE_SSD" SETUP_SUCCESS=false fi fi fi # ============================================================================================== # ━━━ Symlink ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_LINK Symlink ━━━" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would set $TRANSCODE_LINK → $RAMDISK_PATH" else if [[ -L "$TRANSCODE_LINK" ]]; then CURRENT_TARGET=$(readlink "$TRANSCODE_LINK") if [[ "$CURRENT_TARGET" == "$RAMDISK_PATH" ]]; then log "Symlink already points to ramdisk — no change needed ✅" else log "Updating symlink: $CURRENT_TARGET → $RAMDISK_PATH" ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || { error "Failed to update symlink" SETUP_SUCCESS=false } fi elif [[ -e "$TRANSCODE_LINK" ]]; then warn "$TRANSCODE_LINK exists but is not a symlink — removing and replacing" rm -rf "$TRANSCODE_LINK" ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || { error "Failed to create symlink" SETUP_SUCCESS=false } else log "Creating symlink: $TRANSCODE_LINK → $RAMDISK_PATH" mkdir -p "$(dirname "$TRANSCODE_LINK")" ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || { error "Failed to create symlink" SETUP_SUCCESS=false } fi [[ "$SETUP_SUCCESS" == true ]] && log "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅" fi # ============================================================================================== # ━━━ 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 ━━━" TRANSCODE_TEMP_DIR="${RAMDISK_PATH}/transcoding-temp" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would create $TRANSCODE_TEMP_DIR" else if [[ -d "$TRANSCODE_TEMP_DIR" ]]; then log "transcoding-temp already exists on ramdisk" else 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 if [[ -d "$TRANSCODE_TEMP_DIR" ]]; then chmod "$TRANSCODE_CHMOD" "$TRANSCODE_TEMP_DIR" chown "$TRANSCODE_OWNER" "$TRANSCODE_TEMP_DIR" log "Permissions set on transcoding-temp ($TRANSCODE_CHMOD $TRANSCODE_OWNER)" fi fi # ============================================================================================== # ━━━ Permissions ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_GEAR Permissions ━━━" if [[ "$DRY_RUN" == true ]]; then 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_CHMOD" "$path" chown "$TRANSCODE_OWNER" "$path" log "Permissions set: $path ($TRANSCODE_CHMOD $TRANSCODE_OWNER)" fi done fi # ============================================================================================== # ━━━ Initialise State File ━━━ # ============================================================================================== if [[ "$DRY_RUN" == false ]]; then STATE_FILE="/tmp/transcode_state.db" NOW=$(date +%s) cat > "$STATE_FILE" <