Files

364 lines
15 KiB
Bash
Executable File

#!/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).
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Redirect Before Unmounting
# The symlink is pointed at the SSD fallback first, and only then is the tmpfs
# unmounted. Reversing that order would leave in-flight transcodes writing into a
# path that is being pulled out from under them. New sessions land on SSD from the
# moment of the flip; existing ones drain.
#
# Unmount Is the Data Loss
# Everything on a tmpfs disappears when it is unmounted — there is nothing to migrate
# and no way to preserve it. That is acceptable only because the contents are
# regenerable transcode segments, which is exactly why this operation is safe for
# transcodes and would not be for any other kind of ramdisk.
#
# Not Mounted Is Success
# An already-unmounted ramdisk is reported and treated as done, not as an error. The
# goal state is "ramdisk not mounted", and the script is idempotent toward it.
#
# State File Reflects Reality
# The transcode state file is updated to record the SSD target, so transcode_manager.sh
# agrees with what actually happened rather than trying to flip back to a ramdisk that
# no longer exists.
#
# ==============================================================================================
# 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.
# TRANSCODE_STATE_FILE Override state file path (default: ${STATE_DIR}/transcode_state.db).
#
# ==============================================================================================
# 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
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
if read -r _ USED_MB AVAIL_MB <<< "$(disk_df "$RAMDISK_PATH")" && [[ -n "$AVAIL_MB" ]]; then
echo " $ICON_RAM Ramdisk: mounted — $(format_mb "$USED_MB") used / $(format_mb "$AVAIL_MB") available ✅"
else
echo " $ICON_RAM Ramdisk: mounted — size unreadable"
fi
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
echo "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
}
echo "SSD fallback created ✅"
fi
else
echo "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 [[ "$ENABLE_LOGGING" == true ]]; then
find "$RAMDISK_PATH" -type f 2>/dev/null | while read -r f; do
log " $f"
done
fi
else
echo "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" <<EOF
current_target=$TRANSCODE_SSD
last_flip_time=$NOW
flip_count_hour=0
flip_hour_start=$NOW
EOF
echo "State file updated: current_target=$TRANSCODE_SSD"
else
warn "Skipping state file update — stop had errors"
fi
END=$(date +%s)
# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY RAMDISK STOP SUMMARY ━━━━━"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_RAM Ramdisk: $RAMDISK_PATH"
echo "$ICON_DISK Fallback: $TRANSCODE_SSD"
echo "$ICON_LINK Symlink: $TRANSCODE_LINK$(readlink "$TRANSCODE_LINK" 2>/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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"