#!/bin/bash # ============================================================================================== # ================================= Ramdisk Stop =============================================== # ============================================================================================== # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── # Safely stops the transcode ramdisk: redirects the transcode symlink to the # SSD fallback before unmounting so Emby continues writing without interruption, # then unmounts the tmpfs and updates the state file. # # Primary use case: stopping the current ramdisk before re-running # ramdisk_setup.sh with new size or threshold values (setup is idempotent — # if the ramdisk is mounted, it skips the mount and reports status, so you # must stop it first to change the size). # # ============================================================================================== # OPERATIONAL MODEL # ============================================================================================== # # Executes in safe order: # 1. Validate — ramdisk mounted, SSD fallback exists # 2. Redirect symlink → SSD (Emby immediately writes to SSD instead) # 3. Warn if active transcode files still on ramdisk (informational — not a blocker) # 4. Unmount ramdisk tmpfs # 5. Update /tmp/transcode_state.db → current_target=TRANSCODE_SSD # # The symlink redirect happens before unmount so there is no window where Emby # has nowhere to write. Existing in-progress transcode files on the ramdisk are # lost on unmount — warn the user but proceed (this is expected for maintenance). # # ============================================================================================== # OPERATIONAL SAFEGUARDS # ============================================================================================== # # Root Required # umount requires root. # # Single Instance Lock # acquire_lock prevents concurrent stop attempts. # # Mounted Check # Exits cleanly if ramdisk is not mounted — nothing to do. # # Symlink-First Order # Symlink is redirected before unmount — Emby never sees a broken path. # # transcode_manager Warning # Warns if transcode_manager.sh is running — it may flip the symlink back # to ramdisk on its next cycle. Stop transcode_manager before running this # if you need the SSD redirect to hold. # # ============================================================================================== # CONFIGURATION # ============================================================================================== # # host*.conf # HOST*_TRANSCODE_SSD SSD fallback directory — redirect target during stop. # Aliased by detect_hosts() → TRANSCODE_SSD. # # master.conf # TRANSCODE_LINK Symlink Emby uses. Must match Emby's transcode path setting. # RAMDISK_PATH tmpfs mount point. # # ============================================================================================== # STATE FILES # ============================================================================================== # # /tmp/transcode_state.db — updated to current_target=TRANSCODE_SSD after stop. # transcode_manager.sh reads this on its next cycle. # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # ramdisk_stop.sh # Stop the ramdisk: redirect symlink → SSD, unmount, update state. # # ramdisk_stop.sh --dry-run # Show what would happen without making any changes. # # ramdisk_stop.sh --status # Show current mount state, symlink target, active files on ramdisk. Exit. # # ramdisk_stop.sh --log # Verbose output for each step. # # ============================================================================================== 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 — umount requires 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 STATE_FILE="${TRANSCODE_STATE_FILE:-${STATE_DIR:-/tmp}/transcode_state.db}" log "Identity: $MY_ID ($LOCAL_SERVER_NAME)" log "Ramdisk: $RAMDISK_PATH" 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_DISK SSD fallback: $TRANSCODE_SSD" echo "$ICON_LINK Symlink: $TRANSCODE_LINK" 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 ✅" FILE_COUNT=$(find "$RAMDISK_PATH" -type f 2>/dev/null | wc -l) echo " $ICON_RAM Active files on ramdisk: $FILE_COUNT" 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 if [[ -f "$STATE_FILE" ]]; then echo "" echo " State file ($STATE_FILE):" while IFS='=' read -r key value; do [[ -z "$key" ]] && continue echo " $key = $value" done < "$STATE_FILE" else echo " $ICON_INFO State file: not found (ramdisk never started this boot)" fi if pgrep -f "transcode_manager.sh" >/dev/null 2>&1; then echo "" warn "transcode_manager.sh is currently RUNNING" fi echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi # ============================================================================================== # ━━━ Preflight ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_GEAR Preflight ━━━" # Bail if not mounted — nothing to do if ! mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then warn "Ramdisk is not mounted at $RAMDISK_PATH — nothing to stop" exit 0 fi log "Ramdisk is mounted ✅" # Warn if transcode_manager is running — it may flip symlink back on next cycle if pgrep -f "transcode_manager.sh" >/dev/null 2>&1; then warn "transcode_manager.sh is currently RUNNING" warn "It may flip the symlink back to ramdisk on its next cycle" warn "Stop transcode_manager.sh first if you need the SSD redirect to hold" echo "" fi # Confirm SSD fallback exists if [[ ! -d "$TRANSCODE_SSD" ]]; then if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — SSD fallback does not exist: $TRANSCODE_SSD" warn "DRY RUN — would create it before redirecting symlink" else warn "SSD fallback does not exist — creating: $TRANSCODE_SSD" mkdir -p "$TRANSCODE_SSD" || { error "Failed to create SSD fallback: $TRANSCODE_SSD" error "Cannot safely redirect symlink — aborting" exit 1 } log "SSD fallback created ✅" fi else log "SSD fallback exists: $TRANSCODE_SSD ✅" fi START=$(date +%s) STOP_SUCCESS=true # ============================================================================================== # ━━━ Redirect Symlink → SSD ━━━ # ============================================================================================== # Redirect BEFORE unmount — Emby continues writing to SSD with no broken path window. echo "" echo "━━━ $ICON_LINK Redirect Symlink → SSD ━━━" if [[ -L "$TRANSCODE_LINK" ]]; then CURRENT_TARGET=$(readlink "$TRANSCODE_LINK") if [[ "$CURRENT_TARGET" == "$TRANSCODE_SSD" ]]; then echo "Symlink already points to SSD — no change needed ✅" else if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would redirect: $TRANSCODE_LINK → $TRANSCODE_SSD" else ln -sfn "$TRANSCODE_SSD" "$TRANSCODE_LINK" && \ warn "Symlink redirected: $TRANSCODE_LINK → $TRANSCODE_SSD ✅" || { error "Failed to redirect symlink" STOP_SUCCESS=false } fi fi elif [[ -e "$TRANSCODE_LINK" ]]; then warn "$TRANSCODE_LINK exists but is not a symlink — leaving as-is" else warn "Symlink $TRANSCODE_LINK does not exist — nothing to redirect" fi # ============================================================================================== # ━━━ Active Files Warning ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_RAM Active Files Check ━━━" FILE_COUNT=$(find "$RAMDISK_PATH" -type f 2>/dev/null | wc -l) if [[ "$FILE_COUNT" -gt 0 ]]; then warn "⚠️ $FILE_COUNT file(s) still on ramdisk — will be lost on unmount" warn "Active transcode sessions should be stopped before unmounting" warn "Proceeding regardless (this is expected for maintenance)" if [[ "$LOG" == true ]]; then find "$RAMDISK_PATH" -type f 2>/dev/null | while read -r f; do log " $f" done fi else log "No active files on ramdisk ✅" fi # ============================================================================================== # ━━━ Unmount ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_RAM Unmount Ramdisk ━━━" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would unmount: $RAMDISK_PATH" else if umount "$RAMDISK_PATH" 2>/dev/null; then warn "Ramdisk unmounted: $RAMDISK_PATH ✅" else # Regular unmount failed — check if only directory handles are open (no active writes) OPEN_FILES=$(lsof +D "$RAMDISK_PATH" 2>/dev/null | awk 'NR>1 && $5 != "DIR"' | wc -l) if [[ "$OPEN_FILES" -eq 0 ]]; then warn "Busy — only directory handles open, no active writes — trying lazy unmount" if umount -l "$RAMDISK_PATH"; then warn "Ramdisk lazy-unmounted: $RAMDISK_PATH ✅" warn "Handles will release when owning processes next check the directory" else error "Lazy unmount also failed — $RAMDISK_PATH" STOP_SUCCESS=false fi else error "Failed to unmount $RAMDISK_PATH — $OPEN_FILES file(s) still open for writing" error "Stop active transcode sessions and retry" STOP_SUCCESS=false fi fi fi # ============================================================================================== # ━━━ Update State File ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_GEAR State File ━━━" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would update $STATE_FILE: current_target=$TRANSCODE_SSD" elif [[ "$STOP_SUCCESS" == true ]]; then NOW=$(date +%s) cat > "$STATE_FILE" </dev/null || echo "not set")" echo "$ICON_TIME Duration: $(format_duration $(( END - START )))" echo "" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — no changes made" elif [[ "$STOP_SUCCESS" == true ]]; then echo "$ICON_DONE Status: done ✅" echo "Run ramdisk_setup.sh to remount with new configuration" else echo "$ICON_ERROR Status: STOP HAD ERRORS" notify "Ramdisk stop errors on $(hostname) ($MY_ID) — check output" \ "Ramdisk Stop" "warning" exit 1 fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"