Move all watchdog scripts to a dedicated Watchdogs/ folder: Docker_Essentials/docker_watchdog.sh → Watchdogs/ unRAID_Essentials/system_watchdog.sh → Watchdogs/ unRAID_Essentials/resource_watchdog.sh → Watchdogs/ Orchestrators/watchdog_orchestrator.sh → Watchdogs/ Tools/watchdog_skip_list_manager.sh → Watchdogs/ Rename host config files: master_host1.conf → host1.conf master_host2.conf → host2.conf Update all references across the ecosystem: master.conf: WATCHDOG_ORCHESTRATOR_SCRIPTS paths → Watchdogs/ load_config.sh: host*.conf glob + all comments git_pull_execute.sh: sparse checkout glob + all comments Partnership/ssh_setup.sh: HOST_CONF path construction user_script_plug-in.sh: all script paths + per-host conf path common.sh, README.md, README-User_Script_Plug-in.md: comment refs All Partnership, Fallback, Monitors, Transcodes, Tools scripts: comment refs
479 lines
17 KiB
Markdown
479 lines
17 KiB
Markdown
# ━━━━━ TRANSCODES — Manual ━━━━━
|
||
|
||
Configuration reference, Docker mount setup, threshold sizing, and troubleshooting
|
||
for the ramdisk transcode system. Read the Docker mount section before anything else.
|
||
|
||
---
|
||
|
||
## ━━━ CONTENTS ━━━
|
||
|
||
- [Docker Mount — Required Configuration](#docker-mount--required-configuration)
|
||
- [The Symlink Architecture](#the-symlink-architecture)
|
||
- [ramdisk_setup.sh](#ramdisk_setupsh)
|
||
- [transcode_manager.sh](#transcode_managersh)
|
||
- [transcode_cleanup.sh](#transcode_cleanupsh)
|
||
- [Full Configuration Reference](#full-configuration-reference)
|
||
- [Schedule](#schedule)
|
||
- [Troubleshooting](#troubleshooting)
|
||
|
||
---
|
||
|
||
## Output Tiers
|
||
|
||
All three scripts follow a two-tier output model: `echo` lines are always visible;
|
||
`log` lines only appear when `--log` is passed.
|
||
|
||
**ramdisk_setup.sh** — one-shot at array start. Without `--log`, section headers
|
||
and the final summary are visible. Per-step creation detail suppressed.
|
||
|
||
**transcode_manager.sh** — runs every 3 minutes. Without `--log`, only state
|
||
transitions (flips, warnings, errors) and active session display are shown. When
|
||
nothing changes, a single one-line confirmation is printed. Per-check detail suppressed.
|
||
|
||
**transcode_cleanup.sh** — runs every 3 minutes. Without `--log`, the cleanup
|
||
summary (files removed, space freed) is always visible. Per-file deletion detail
|
||
suppressed.
|
||
|
||
---
|
||
|
||
## Docker Mount — Required Configuration
|
||
|
||
> **This is the most important configuration requirement in this folder.**
|
||
> Get this wrong and symlink flips silently stop working after the first flip.
|
||
> The system appears to work initially and fails subtly.
|
||
|
||
### Required Mount
|
||
|
||
In Emby's **Extra Parameters** in the unRAID Docker template:
|
||
|
||
```
|
||
--mount type=bind,source=/mnt/ram-transcode,target=/ext-ram-transcode,bind-propagation=shared
|
||
```
|
||
|
||
This **replaces** the transcode path in the standard template path mapping UI. Do NOT
|
||
add this via the path mapping UI — that UI does not support propagation. Extra Parameters only.
|
||
|
||
In Emby's transcoding settings, set the transcode path to `/ext-ram-transcode`.
|
||
|
||
### Why `shared` Is Required
|
||
|
||
```
|
||
rprivate (Docker's default):
|
||
Docker resolves the symlink target at first mount and locks that inode.
|
||
Flip: ramdisk → SSD → works (new target locked in)
|
||
Flip: SSD → ramdisk → Docker ignores it. Container still sees SSD binding.
|
||
All sessions continue to land on SSD forever until Emby restarts.
|
||
Symptom: symlink on host is correct, Emby still uses SSD. Confusing.
|
||
|
||
shared:
|
||
Host mount changes propagate into the container in real time.
|
||
Every symlink flip is immediately visible inside the container. ✅
|
||
```
|
||
|
||
### Verify the Mount
|
||
|
||
```bash
|
||
# Check propagation — must show "shared":
|
||
docker inspect Emby | grep -A4 "ext-ram"
|
||
# Expected: "Propagation": "shared"
|
||
|
||
# Check Emby's transcode path setting:
|
||
docker exec Emby cat /config/config/encoding.xml | grep TranscodingTempPath
|
||
# Expected: /ext-ram-transcode
|
||
```
|
||
|
||
### What NOT to Do
|
||
|
||
Do NOT add a static SSD transcode path as a second path mapping in the template:
|
||
```
|
||
/mnt/cache/Temp_Storage/Emby/Transcodes → /ssd-transcode ← do not do this
|
||
```
|
||
|
||
If the SSD path is mounted inside the container, Emby can see it as an accessible
|
||
transcode location. It will route sessions there independently of the symlink —
|
||
bypassing the management system entirely. Sessions land on SSD regardless of symlink
|
||
state. The whole system stops working.
|
||
|
||
---
|
||
|
||
## The Symlink Architecture
|
||
|
||
```
|
||
Emby is configured to write transcodes to TRANSCODE_LINK (/mnt/ram-transcode).
|
||
TRANSCODE_LINK is a symlink — its target is managed at runtime.
|
||
|
||
Normal operation (ramdisk has headroom):
|
||
/mnt/ram-transcode → /mnt/ramdisk_transcodes/ (ramdisk — fast, no wear)
|
||
|
||
Heavy load (ramdisk filling up):
|
||
/mnt/ram-transcode → /mnt/cache/Temp_Storage/Emby/Transcodes/ (SSD)
|
||
|
||
Emby doesn't know this symlink exists. It writes to /mnt/ram-transcode.
|
||
ffmpeg resolves the symlink ONCE when a session starts.
|
||
After that it holds a direct reference to the actual directory.
|
||
|
||
Flipping the symlink has ZERO effect on sessions already in progress.
|
||
Only NEW sessions care about where the symlink currently points.
|
||
```
|
||
|
||
### What Lives Where
|
||
|
||
```
|
||
/mnt/ramdisk_transcodes/ ← tmpfs (HOST*_RAMDISK_SIZE ceiling)
|
||
transcoding-temp/ ← pre-created by ramdisk_setup.sh — always here
|
||
E0D8DC/ ← Emby session (Live TV HLS segments)
|
||
F1A9BB/ ← another session
|
||
|
||
/mnt/ram-transcode ← symlink — managed by transcode_manager.sh
|
||
currently points at: /mnt/ramdisk_transcodes/
|
||
|
||
/mnt/cache/Temp_Storage/Emby/Transcodes/ ← SSD fallback
|
||
transcoding-temp/ ← also pre-created — Emby finds ramdisk version first
|
||
xyz789/ ← sessions that started when ramdisk was full
|
||
```
|
||
|
||
---
|
||
|
||
## ramdisk_setup.sh
|
||
|
||
Creates the ramdisk, SSD fallback directory, symlink, and `transcoding-temp` on the
|
||
ramdisk. Run once at array start. Idempotent — already-mounted ramdisk exits cleanly.
|
||
|
||
### What It Creates
|
||
|
||
```
|
||
1. RAMDISK_PATH (/mnt/ramdisk_transcodes)
|
||
mount -t tmpfs -o size=HOST*_RAMDISK_SIZE tmpfs /mnt/ramdisk_transcodes
|
||
tmpfs uses only as much RAM as actually needed — RAMDISK_SIZE is a ceiling.
|
||
An empty ramdisk uses essentially zero RAM.
|
||
|
||
2. transcoding-temp/ inside the ramdisk
|
||
Pre-created so Emby always finds it here first.
|
||
Without this: Emby creates transcoding-temp at its first writable location,
|
||
which may be the SSD fallback even when the symlink points at the ramdisk.
|
||
chown nobody:users — correct ownership for Emby (PUID=99)
|
||
|
||
3. TRANSCODE_SSD (/mnt/cache/Temp_Storage/Emby/Transcodes/)
|
||
mkdir -p — created if missing, silent if exists
|
||
Also pre-creates transcoding-temp/ inside SSD fallback for consistency
|
||
|
||
4. TRANSCODE_LINK (/mnt/ram-transcode)
|
||
ln -sfn /mnt/ramdisk_transcodes /mnt/ram-transcode
|
||
Always reset to ramdisk at array start — clean state every boot
|
||
```
|
||
|
||
### Verify After Setup
|
||
|
||
```bash
|
||
# All three must pass:
|
||
mountpoint /mnt/ramdisk_transcodes
|
||
# Expected: /mnt/ramdisk_transcodes is a mountpoint
|
||
|
||
readlink /mnt/ram-transcode
|
||
# Expected: /mnt/ramdisk_transcodes
|
||
|
||
ls /mnt/ramdisk_transcodes/
|
||
# Expected: transcoding-temp/
|
||
```
|
||
|
||
### Usage
|
||
|
||
```bash
|
||
ramdisk_setup.sh # normal run (called by array_start.sh)
|
||
ramdisk_setup.sh --dry-run # show what would be created without creating
|
||
ramdisk_setup.sh --status # show current ramdisk, symlink, and SSD state
|
||
ramdisk_setup.sh --log # verbose — show each creation step
|
||
```
|
||
|
||
---
|
||
|
||
## transcode_manager.sh
|
||
|
||
Monitors ramdisk usage and manages the symlink direction. Called second in every 3-minute
|
||
cycle by `transcode_management.sh`.
|
||
|
||
### Three Modes
|
||
|
||
```bash
|
||
# master.conf
|
||
TRANSCODE_MANAGER_MODE="smart" # smart | ramdisk | ssd
|
||
|
||
# smart (default — use in production):
|
||
# Ramdisk above RAMDISK_WARN_GB → flip symlink to SSD
|
||
# Ramdisk below RAMDISK_LOW_GB → flip symlink back to ramdisk
|
||
# Hysteresis gap prevents flip-flopping under moderate load
|
||
|
||
# ramdisk:
|
||
# Always uses ramdisk. Warns if usage exceeds threshold. Never flips.
|
||
# Use for: light load server, guaranteed RAM performance, testing ramdisk behaviour.
|
||
|
||
# ssd:
|
||
# Always uses SSD. Never uses ramdisk.
|
||
# Use for: post-flip drain (waiting for ramdisk sessions to end naturally),
|
||
# maintenance windows, ramdisk capacity testing.
|
||
```
|
||
|
||
### Threshold Sizing
|
||
|
||
```bash
|
||
# host*.conf
|
||
HOST1_RAMDISK_SIZE="10G"
|
||
HOST1_RAMDISK_WARN_GB=8.8 # flip to SSD above this
|
||
HOST1_RAMDISK_LOW_GB=6.5 # flip back below this
|
||
```
|
||
|
||
Keep ~1.5–2.5GB hysteresis gap between WARN and LOW. Without the gap, usage hovering
|
||
near WARN causes constant flip-flopping. The gap requires multiple sessions to end
|
||
completely before flipping back — a genuine recovery, not a brief fluctuation.
|
||
|
||
Production data from a 7-household Live TV system:
|
||
```
|
||
Normal (2–3 streams) → ~1.5–2.0 GB
|
||
Busy evening (5–6 streams) → ~3.5–4.5 GB
|
||
Peak (8 streams, Live TV) → ~5.2 GB
|
||
Current setup: 10G ramdisk, 8.8 GB threshold → 1.2 GB safety headroom
|
||
```
|
||
|
||
Recommended thresholds by ramdisk size:
|
||
|
||
| RAMDISK_SIZE | RAMDISK_WARN_GB | RAMDISK_LOW_GB |
|
||
|-------------|----------------|----------------|
|
||
| 6G | 4.8 | 3.5 |
|
||
| 8G | 6.8 | 5.5 |
|
||
| 10G | 8.8 | 6.5 |
|
||
| 12G | 10.5 | 8.5 |
|
||
|
||
### Multi-Server Configuration
|
||
|
||
```bash
|
||
# master.conf
|
||
TRANSCODE_SERVERS=(
|
||
"Emby|http://localhost:8096|your-api-key|emby"
|
||
|
||
# Temporarily cover HOST2's Emby during maintenance:
|
||
# "Emby-Jayred365|http://100.x.x.x:8096|HOST2-api-key|emby"
|
||
|
||
# Jellyfin instance (separate port):
|
||
# "Jellyfin|http://localhost:8097|jellyfin-api-key|jellyfin"
|
||
)
|
||
```
|
||
|
||
Type field: `emby` | `jellyfin` | `plex` — controls which API endpoint format is used.
|
||
Entries with placeholder API keys are skipped automatically.
|
||
Comment out unused entries rather than deleting — placeholders show what's available.
|
||
|
||
**Tdarr does NOT belong here.** Tdarr encodes full video files — large working files
|
||
fill the ramdisk rapidly and cause constant flips. Tdarr belongs on SSD permanently.
|
||
|
||
### Session Display
|
||
|
||
```
|
||
━━━ Active Emby Sessions ━━━
|
||
Total: 7 | Live TV: 5 | Transcoding: 5 | Direct: 2
|
||
Storage: ramdisk
|
||
|
||
Sunny — ABC (WTAE) — Live TV — Transcode
|
||
Mama Bear — Cinemax — Live TV — Transcode
|
||
Gmer4Lfe — WAN Show — TV Show — Transcode
|
||
```
|
||
|
||
Split state during a flip (ramdisk sessions draining, new sessions on SSD):
|
||
```
|
||
⚠️ Split state — 4 folder(s) on ramdisk / 2 on SSD
|
||
Storage: ramdisk (4) + SSD (2)
|
||
```
|
||
|
||
### Usage
|
||
|
||
```bash
|
||
transcode_manager.sh # normal run
|
||
transcode_manager.sh --dry-run # preview flip decision without flipping
|
||
transcode_manager.sh --status # show current state, usage, sessions, flip history
|
||
transcode_manager.sh --log # verbose output per safety check
|
||
transcode_manager.sh --no-log # suppress daily log write
|
||
```
|
||
|
||
---
|
||
|
||
## transcode_cleanup.sh
|
||
|
||
Removes stale transcode files from ramdisk and SSD fallback. Called first in every
|
||
3-minute cycle — cleanup before usage measurement is non-negotiable.
|
||
|
||
### Deletion Rules
|
||
|
||
A file is eligible for deletion only when ALL conditions are true:
|
||
1. **Older than TRANSCODE_MAX_AGE minutes** (mtime — last write time). Active segments
|
||
are written every few seconds. Not touched in 20 minutes = session ended.
|
||
2. **Not currently open by any process** (lsof pre-built map, O(1) lookup per file).
|
||
If ffmpeg has a file open, it is not deleted regardless of age.
|
||
|
||
`transcoding-temp/` is **never deleted**, even when empty. Protected by name exclusion
|
||
in the find command — deleting it causes Emby to route all sessions to the SSD fallback.
|
||
|
||
### Usage
|
||
|
||
```bash
|
||
transcode_cleanup.sh # normal cleanup run
|
||
transcode_cleanup.sh --dry-run # show what would be deleted
|
||
transcode_cleanup.sh --status # show file counts, ages, open-file status per location
|
||
transcode_cleanup.sh --log # verbose per-file output
|
||
```
|
||
|
||
---
|
||
|
||
## Full Configuration Reference
|
||
|
||
```bash
|
||
# master.conf
|
||
|
||
# ── Paths ──────────────────────────────────────────────────────────────────────
|
||
TRANSCODE_LINK="/mnt/ram-transcode" # symlink Emby points at
|
||
TRANSCODE_SSD="/mnt/cache/Temp_Storage/Emby/Transcodes/" # SSD fallback
|
||
|
||
# ── Per-Host (host*.conf) ───────────────────────────────────────────────
|
||
HOST1_RAMDISK_PATH="/mnt/ramdisk_transcodes" # ramdisk mount point
|
||
HOST1_RAMDISK_SIZE="10G" # tmpfs ceiling (not a reservation)
|
||
HOST1_RAMDISK_WARN_GB=8.8 # flip to SSD above this
|
||
HOST1_RAMDISK_LOW_GB=6.5 # flip back below this
|
||
|
||
# ── Thresholds ─────────────────────────────────────────────────────────────────
|
||
RAMDISK_SSD_MIN_GB=20 # minimum SSD free space before allowing SSD flip
|
||
# prevents accidentally filling the SSD cache pool
|
||
TRANSCODE_FLIP_WARN=3 # notify if symlink flips this many times in one hour
|
||
# high flip count = ramdisk undersized for the load
|
||
|
||
# ── Cleanup ────────────────────────────────────────────────────────────────────
|
||
TRANSCODE_MAX_AGE=20 # minutes — files older than this are stale
|
||
TRANSCODE_ORPHAN_AGE=30 # minutes — orphaned session folders removed after this
|
||
|
||
# ── Manager Mode ───────────────────────────────────────────────────────────────
|
||
TRANSCODE_MANAGER_MODE="smart" # smart | ramdisk | ssd
|
||
TRANSCODE_CHECK_EMBY=true # skip threshold checks when Emby not running
|
||
# prevents unnecessary flips overnight
|
||
|
||
# ── Permissions ────────────────────────────────────────────────────────────────
|
||
TRANSCODE_OWNER="nobody:users" # matches PUID=99 PGID=100 container env
|
||
TRANSCODE_CHMOD="755"
|
||
|
||
# ── Daily Log ──────────────────────────────────────────────────────────────────
|
||
TRANSCODE_DAILY_LOG="$DATA_DIR/transcode_daily.db"
|
||
TRANSCODE_LOG_RETENTION=90 # days — trimmed on every write
|
||
TRANSCODE_STATE_FILE="/tmp/transcode_state.db" # /tmp — resets on reboot correctly
|
||
|
||
# ── Multi-Server ───────────────────────────────────────────────────────────────
|
||
TRANSCODE_SERVERS=(
|
||
"ContainerName|http://host:port|api-key|emby" # type: emby | jellyfin | plex
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## Schedule
|
||
|
||
```
|
||
At Startup of Array (via array_start.sh in unRAID_Essentials/):
|
||
ramdisk_setup.sh — creates ramdisk, symlink, transcoding-temp
|
||
Must run BEFORE Emby starts
|
||
|
||
Every 3 minutes (via transcode_management.sh in Orchestrators/):
|
||
1. transcode_cleanup.sh — remove stale files, check flip-back
|
||
2. transcode_manager.sh — check usage, flip if needed, display sessions
|
||
|
||
Do NOT schedule transcode_manager.sh or transcode_cleanup.sh directly.
|
||
```
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### Sessions Landing on SSD Despite Symlink Pointing at Ramdisk
|
||
|
||
```bash
|
||
# Check 1 — Docker mount propagation (most common cause):
|
||
docker inspect Emby | grep Propagation
|
||
# Expected: "Propagation": "shared"
|
||
# Wrong: "Propagation": "rprivate"
|
||
#
|
||
# Fix: Update Emby Extra Parameters, restart Emby:
|
||
# --mount type=bind,source=/mnt/ram-transcode,target=/ext-ram-transcode,bind-propagation=shared
|
||
# Remove any standard path mapping for the transcode directory.
|
||
|
||
# Check 2 — transcoding-temp exists on ramdisk:
|
||
ls /mnt/ramdisk_transcodes/
|
||
# Expected: transcoding-temp/
|
||
#
|
||
# Fix if missing:
|
||
mkdir -p /mnt/ramdisk_transcodes/transcoding-temp
|
||
chown nobody:users /mnt/ramdisk_transcodes/transcoding-temp
|
||
|
||
# Check 3 — no duplicate SSD mount in Emby template:
|
||
docker inspect Emby | grep -A3 "Mounts"
|
||
# Should show only /mnt/ram-transcode → /ext-ram-transcode
|
||
# Should NOT show /mnt/cache/Temp_Storage/... as a second mount
|
||
```
|
||
|
||
### Flip Count High — 3+ Per Hour
|
||
|
||
```bash
|
||
# Ramdisk filling up regularly — load exceeds the current ceiling.
|
||
# Check peak usage from the weekly health digest: Transcodes → "Week peak: X.XGB"
|
||
#
|
||
# If peak is close to RAMDISK_WARN_GB → increase ramdisk size:
|
||
# host1.conf
|
||
HOST1_RAMDISK_SIZE="12G" # increase by 2G
|
||
HOST1_RAMDISK_WARN_GB=10.5 # adjust thresholds accordingly
|
||
HOST1_RAMDISK_LOW_GB=8.5
|
||
|
||
# Remount at new size — run ramdisk_setup.sh manually:
|
||
ramdisk_setup.sh --log
|
||
# Ramdisk must be unmounted first if already mounted:
|
||
# umount /mnt/ramdisk_transcodes && ramdisk_setup.sh --log
|
||
```
|
||
|
||
### Ramdisk Not Mounting at Array Start
|
||
|
||
```bash
|
||
# Check if tmpfs is mounted:
|
||
mountpoint /mnt/ramdisk_transcodes
|
||
# "not a mountpoint" → setup failed or not run yet
|
||
|
||
# Run manually to see the error:
|
||
ramdisk_setup.sh --log
|
||
|
||
# Common causes:
|
||
# /mnt/ramdisk_transcodes missing → mkdir -p /mnt/ramdisk_transcodes
|
||
# Insufficient RAM → check free RAM: free -h
|
||
# RAMDISK_SIZE too large → reduce HOST*_RAMDISK_SIZE
|
||
```
|
||
|
||
### Emergency Manual Flip
|
||
|
||
```bash
|
||
# Flip to SSD immediately — all new sessions go to SSD:
|
||
ln -sfn /mnt/cache/Temp_Storage/Emby/Transcodes /mnt/ram-transcode
|
||
|
||
# Flip back to ramdisk — all new sessions go to ramdisk:
|
||
ln -sfn /mnt/ramdisk_transcodes /mnt/ram-transcode
|
||
|
||
# Check current symlink target:
|
||
readlink /mnt/ram-transcode
|
||
|
||
# Existing sessions in progress are NEVER affected — only new sessions follow the flip.
|
||
```
|
||
|
||
### Increasing Ramdisk Size After Initial Setup
|
||
|
||
```bash
|
||
# 1. Set new size and thresholds in host*.conf
|
||
# 2. Unmount the existing ramdisk (no sessions should be active):
|
||
umount /mnt/ramdisk_transcodes
|
||
|
||
# 3. Re-run setup to mount at new size:
|
||
ramdisk_setup.sh --log
|
||
|
||
# 4. Verify:
|
||
df -h /mnt/ramdisk_transcodes
|
||
# Should show new size as total
|
||
```
|