common.sh:
- Add resolve_tailscale_ip() helper — tries `tailscale ip -4` first, falls back to
parsing `tailscale status` output; handles hosts where MagicDNS short-name resolution
is not active
- Add PARTNERSHIP_OWN_CONTAINERS alias in detect_hosts()
- Add aliasing for 4 Emby provisioning vars (PARTNERSHIP_PROVISION_EMBY_ADMIN,
PARTNERSHIP_EMBY_ADMIN_USER, PARTNERSHIP_EMBY_ADMIN_PASS, PARTNERSHIP_EMBY_PORT)
Partnership/partnership_manager.sh:
- Replace 9 bare `tailscale ip -4` calls with resolve_tailscale_ip()
- Add read_remote_conf_var() and read_remote_conf_array() — SSH to mirror, source its
own load_config.sh + detect_hosts(), return aliased variable; solves sparse-checkout
problem where HOST1 cannot read master_host2.conf directly
- Add derive_short_name() — strips unraid- prefix, capitalises first char
- Add cleanup_partner_containers() — removes partner containers via FolderView3 folder
if enabled, else falls back to FALLBACK_*_COVERS_*_TIER* arrays
- Add cleanup_owner_containers_on_mirror() — SSH to mirror, stops and removes containers
matching *-${OWNER_SHORT} naming convention
- Add start_own_stack() and start_mirror_own_stack() — restart own containers locally
or on mirror via SSH using PARTNERSHIP_OWN_CONTAINERS
- Add provision_emby_admin() — reads mirror credentials via read_remote_conf_var, checks
for username collision, creates user + sets password + grants admin policy via Emby API
- Add revoke_emby_admin() — looks up mirror username on local Emby, deletes via REST API
- Wire offboard paths (both mirror-initiated and owner-initiated) to call container
cleanup and stack restart; update --check finalisation paths accordingly
- Fix write_state_file in --onboard not gated on DRY_RUN (was writing ACTIVE state on
dry runs)
master_host1.conf:
- Add HOST1_PARTNERSHIP_OWN_CONTAINERS array
- Add partnership Emby provisioning config (toggle + port + per-host credentials)
master_host2.conf:
- Add HOST2_PARTNERSHIP_OWN_CONTAINERS array
- Add HOST2_PARTNERSHIP_EMBY_ADMIN_USER and HOST2_PARTNERSHIP_EMBY_ADMIN_PASS
Tailscale fix applied to:
- Initial_run/ssh_setup.sh (2 callsites)
- unRAID_Essentials/rsync_stop.sh (1 callsite)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
321 lines
15 KiB
Bash
Executable File
321 lines
15 KiB
Bash
Executable File
#!/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" <<EOF
|
|
current_target=$RAMDISK_PATH
|
|
last_flip_time=$NOW
|
|
flip_count_hour=0
|
|
flip_hour_start=$NOW
|
|
EOF
|
|
log "State file initialised: $STATE_FILE"
|
|
fi
|
|
|
|
END=$(date +%s)
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Summary ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY RAMDISK SETUP SUMMARY ━━━━━"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_RAM Ramdisk: $RAMDISK_PATH ($RAMDISK_SIZE)"
|
|
echo "$ICON_DISK Fallback: $TRANSCODE_SSD"
|
|
echo "$ICON_LINK Symlink: $TRANSCODE_LINK"
|
|
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
|
|
echo ""
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — no changes made"
|
|
elif [[ "$SETUP_SUCCESS" == true ]]; then
|
|
log "$ICON_DONE Status: done ✅"
|
|
else
|
|
echo "$ICON_ERROR Status: SETUP HAD ERRORS"
|
|
notify "Ramdisk setup errors on $(hostname) ($MY_ID) — check output" \
|
|
"Ramdisk Setup" "warning"
|
|
exit 1
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |