609 lines
25 KiB
HTML
609 lines
25 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>KingDezigns — PLEX32 Optimization Summary</title>
|
|||
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|||
|
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500&display=swap" rel="stylesheet">
|
|||
|
|
<style>
|
|||
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|||
|
|
|
|||
|
|
:root {
|
|||
|
|
--bg: #f7f6f2;
|
|||
|
|
--surface: #ffffff;
|
|||
|
|
--surface-alt: #f1f0eb;
|
|||
|
|
--border: #e2e0d8;
|
|||
|
|
--border-strong: #c8c5ba;
|
|||
|
|
--text-primary: #1a1917;
|
|||
|
|
--text-secondary: #6b6960;
|
|||
|
|
--text-muted: #9b9890;
|
|||
|
|
--green-bg: #eaf3de;
|
|||
|
|
--green-border: #97c459;
|
|||
|
|
--green-text: #2d5a0e;
|
|||
|
|
--red-bg: #fcebeb;
|
|||
|
|
--red-border: #f09595;
|
|||
|
|
--red-text: #7a1f1f;
|
|||
|
|
--amber-bg: #faeeda;
|
|||
|
|
--amber-border: #fac775;
|
|||
|
|
--amber-text: #633806;
|
|||
|
|
--coral-bg: #faece7;
|
|||
|
|
--coral-border: #f0997b;
|
|||
|
|
--coral-text: #4a1b0c;
|
|||
|
|
--mono: 'IBM Plex Mono', monospace;
|
|||
|
|
--sans: 'IBM Plex Sans', sans-serif;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body {
|
|||
|
|
font-family: var(--sans);
|
|||
|
|
font-size: 14px;
|
|||
|
|
background: var(--bg);
|
|||
|
|
color: var(--text-primary);
|
|||
|
|
line-height: 1.7;
|
|||
|
|
padding: 2.5rem 1.5rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.page { max-width: 860px; margin: 0 auto; }
|
|||
|
|
|
|||
|
|
header {
|
|||
|
|
border-bottom: 1px solid var(--border-strong);
|
|||
|
|
padding-bottom: 1.25rem;
|
|||
|
|
margin-bottom: 2rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
header h1 {
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
letter-spacing: -0.02em;
|
|||
|
|
margin-bottom: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.meta {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--text-muted);
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
display: flex;
|
|||
|
|
gap: 16px;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section { margin-bottom: 2.25rem; }
|
|||
|
|
|
|||
|
|
.section-title {
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
letter-spacing: 0.08em;
|
|||
|
|
color: var(--text-muted);
|
|||
|
|
margin-bottom: 1rem;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title::before {
|
|||
|
|
content: attr(data-num);
|
|||
|
|
background: var(--text-primary);
|
|||
|
|
color: var(--bg);
|
|||
|
|
font-size: 10px;
|
|||
|
|
width: 18px;
|
|||
|
|
height: 18px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: inline-flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title::after {
|
|||
|
|
content: '';
|
|||
|
|
flex: 1;
|
|||
|
|
height: 1px;
|
|||
|
|
background: var(--border);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p { margin-bottom: 0.75rem; color: var(--text-primary); }
|
|||
|
|
p:last-child { margin-bottom: 0; }
|
|||
|
|
|
|||
|
|
.badge-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 1rem; }
|
|||
|
|
|
|||
|
|
.badge {
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
font-size: 11px;
|
|||
|
|
padding: 3px 10px;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
background: var(--surface);
|
|||
|
|
color: var(--text-secondary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.badge.red { background: var(--red-bg); color: var(--red-text); border-color: var(--red-border); }
|
|||
|
|
.badge.green { background: var(--green-bg); color: var(--green-text); border-color: var(--green-border); }
|
|||
|
|
.badge.amber { background: var(--amber-bg); color: var(--amber-text); border-color: var(--amber-border); }
|
|||
|
|
|
|||
|
|
.step {
|
|||
|
|
background: var(--surface);
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
border-radius: 6px;
|
|||
|
|
padding: 1rem 1.25rem;
|
|||
|
|
margin-bottom: 0.6rem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.step-header {
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: var(--text-secondary);
|
|||
|
|
margin-bottom: 0.6rem;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.step-num {
|
|||
|
|
background: var(--surface-alt);
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
width: 20px;
|
|||
|
|
height: 20px;
|
|||
|
|
display: inline-flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 10px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
color: var(--text-muted);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre {
|
|||
|
|
background: var(--surface-alt);
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
border-radius: 4px;
|
|||
|
|
padding: 10px 12px;
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
font-size: 12px;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
margin: 0.5rem 0;
|
|||
|
|
white-space: pre;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
code {
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
font-size: 12px;
|
|||
|
|
background: var(--surface-alt);
|
|||
|
|
padding: 1px 5px;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
border: 1px solid var(--border);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.why {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--text-muted);
|
|||
|
|
margin-top: 0.4rem;
|
|||
|
|
padding-left: 2px;
|
|||
|
|
}
|
|||
|
|
.why::before { content: '↳ why: '; font-family: var(--mono); }
|
|||
|
|
|
|||
|
|
.result { font-size: 12px; margin-top: 0.35rem; padding-left: 2px; }
|
|||
|
|
.result.pass { color: var(--green-text); }
|
|||
|
|
.result.pass::before { content: '✓ '; font-family: var(--mono); }
|
|||
|
|
.result.fail { color: var(--red-text); }
|
|||
|
|
.result.fail::before { content: '✗ '; font-family: var(--mono); }
|
|||
|
|
.result.neutral { color: var(--text-secondary); }
|
|||
|
|
.result.neutral::before { content: '→ '; font-family: var(--mono); }
|
|||
|
|
|
|||
|
|
.callout { border-radius: 6px; padding: 1rem 1.25rem; margin-bottom: 0.75rem; }
|
|||
|
|
.callout.coral { background: var(--coral-bg); border: 1px solid var(--coral-border); }
|
|||
|
|
.callout.coral p { color: var(--coral-text); }
|
|||
|
|
.callout.coral pre { background: #fdf0eb; border-color: var(--coral-border); }
|
|||
|
|
.callout.amber { background: var(--amber-bg); border: 1px solid var(--amber-border); }
|
|||
|
|
.callout.amber p { color: var(--amber-text); }
|
|||
|
|
.callout.amber pre { background: #fdf5e6; border-color: var(--amber-border); }
|
|||
|
|
.callout.amber code { background: #fdf5e6; border-color: var(--amber-border); color: var(--amber-text); }
|
|||
|
|
.callout.green { background: var(--green-bg); border: 1px solid var(--green-border); }
|
|||
|
|
.callout.green p { color: var(--green-text); }
|
|||
|
|
.callout.green pre { background: #d8efc0; border-color: var(--green-border); }
|
|||
|
|
|
|||
|
|
.clue-label {
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
font-size: 10px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
letter-spacing: 0.07em;
|
|||
|
|
color: var(--amber-text);
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.script-box { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
|
|||
|
|
.script-row { padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--border); }
|
|||
|
|
.script-row:last-child { border-bottom: none; }
|
|||
|
|
.script-label { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-muted); margin-bottom: 6px; }
|
|||
|
|
.script-row pre { margin: 0; }
|
|||
|
|
|
|||
|
|
table { width: 100%; border-collapse: collapse; font-size: 13px; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
|
|||
|
|
th { text-align: left; padding: 8px 14px; font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); background: var(--surface-alt); border-bottom: 1px solid var(--border); font-weight: 500; }
|
|||
|
|
td { padding: 9px 14px; border-bottom: 1px solid var(--border); vertical-align: top; line-height: 1.5; }
|
|||
|
|
tr:last-child td { border-bottom: none; }
|
|||
|
|
|
|||
|
|
.footer {
|
|||
|
|
font-family: var(--mono);
|
|||
|
|
font-size: 11px;
|
|||
|
|
color: var(--text-muted);
|
|||
|
|
margin-top: 2.5rem;
|
|||
|
|
padding-top: 1rem;
|
|||
|
|
border-top: 1px solid var(--border);
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="page">
|
|||
|
|
|
|||
|
|
<header>
|
|||
|
|
<h1>KingDezigns — PLEX32 Optimization & Setup Summary</h1>
|
|||
|
|
<div class="meta">
|
|||
|
|
<span>📅 June 19–20, 2026</span>
|
|||
|
|
<span>🖥 PLEX32 — Dell Wyse 5070</span>
|
|||
|
|
<span>⚙️ Ubuntu 24.04</span>
|
|||
|
|
<span>🐳 Docker Compose</span>
|
|||
|
|
<span>☁️ Plex Media Server + Tautulli</span>
|
|||
|
|
</div>
|
|||
|
|
</header>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 1 — OVERVIEW -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="1">Overview</div>
|
|||
|
|
|
|||
|
|
<p>PLEX32 was recently migrated from NAS08 to a dedicated Dell Wyse 5070 (Intel Celeron J4105, 8GB RAM) to support Plex Pass features including Sonic Radio that require x86 CPU capabilities. This session covered a full 7-step optimization of the new server — hardware transcoding, playback tuning, Tautulli setup, automated backup, scheduled maintenance, and subtitle handling.</p>
|
|||
|
|
|
|||
|
|
<div class="badge-row">
|
|||
|
|
<span class="badge green">Hardware Transcoding — Enabled</span>
|
|||
|
|
<span class="badge green">Tautulli — Installed</span>
|
|||
|
|
<span class="badge green">Backup — Automated</span>
|
|||
|
|
<span class="badge green">SSH Keys — Configured</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p>All 7 optimization steps completed successfully. PLEX32 is now fully configured and production-ready.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 2 — STEP 1: HARDWARE TRANSCODING -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="2">Step 1 — Hardware transcoding (Intel Quick Sync)</div>
|
|||
|
|
|
|||
|
|
<p>The J4105 has Intel Quick Sync but the Plex Docker container had no access to <code>/dev/dri</code> and was silently falling back to software transcoding on every stream.</p>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">1</span> Confirmed iGPU is exposed on the host</div>
|
|||
|
|
<pre>ls -la /dev/dri</pre>
|
|||
|
|
<p class="result pass">card0 (video, GID 44) and renderD128 (render, GID 993) both present</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">2</span> Updated docker-compose.yml — added device passthrough and group_add</div>
|
|||
|
|
<pre>devices:
|
|||
|
|
- /dev/dri:/dev/dri
|
|||
|
|
group_add:
|
|||
|
|
- "44"
|
|||
|
|
- "993"</pre>
|
|||
|
|
<p class="why">Container needs both the device node and supplementary group membership to access the GPU</p>
|
|||
|
|
<p class="result pass">docker exec confirmed groups 44 and 993 in container process — /dev/dri visible inside container</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">3</span> Enabled hardware transcoding in Plex UI</div>
|
|||
|
|
<pre>Settings → Transcoder → Use hardware acceleration when available ✅
|
|||
|
|
Settings → Transcoder → Use hardware-accelerated video encoding ✅</pre>
|
|||
|
|
<p class="why">Both were already checked — confirming the settings were attempting HW transcode but failing silently due to missing device access</p>
|
|||
|
|
<p class="result pass">Dashboard showed: 4K (HEVC Main 10) (hw) / SD (HEVC) — Transcode (hw) — both sides of pipeline on GPU</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">4</span> Set transcoder temp directory</div>
|
|||
|
|
<pre>Settings → Transcoder → Transcoder temporary directory: /transcode</pre>
|
|||
|
|
<p class="why">Was empty — Plex was writing transcode scratch files to uncontrolled location inside container filesystem</p>
|
|||
|
|
<p class="result pass">Now maps to /opt/plex/transcode on host via existing volume mount</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 3 — STEP 2: DIRECT PLAY -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="3">Step 2 — Direct Play / Direct Stream tuning</div>
|
|||
|
|
|
|||
|
|
<p>Library codec analysis revealed 90 HEVC Main 10 files (~75%) and 29 H.264 files (~25%), all with AAC audio. Roku TV (Hisense 55R8BX) was tested — video Direct Streams, audio converts AAC → AC3 for HDMI passthrough.</p>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">1</span> Codec analysis — video</div>
|
|||
|
|
<pre>find "/mnt/plex/media/04 - Videos/Movies" \( -iname "*.mkv" -o -iname "*.mp4" \) | while read -r f; do
|
|||
|
|
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,profile \
|
|||
|
|
-of csv=p=0 "$f" 2>/dev/null
|
|||
|
|
done | sort | uniq -c | sort -rn</pre>
|
|||
|
|
<p class="result neutral">82+8 hevc,Main 10 / 29 h264,High — library is 75% HEVC 10-bit</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">2</span> Codec analysis — audio</div>
|
|||
|
|
<pre>find ... | ffprobe ... -select_streams a:0 ... | sort | uniq -c</pre>
|
|||
|
|
<p class="result pass">119 aac — 100% AAC audio across entire library. No TrueHD, no DTS. Audio will never force a transcode due to format incompatibility.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="step">
|
|||
|
|
<div class="step-header"><span class="step-num">3</span> Live playback test on Roku</div>
|
|||
|
|
<pre>Plex Dashboard → active session while playing on Roku TV</pre>
|
|||
|
|
<p class="result pass">Video: 4K (HEVC Main 10) — Direct Stream ✅ / Audio: English (AAC 5.1) → AC3 Transcode (lightweight, expected for Roku HDMI passthrough)</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="callout green">
|
|||
|
|
<p><strong>Result:</strong> Video never transcodes. Audio does a lightweight AAC→AC3 conversion for Roku surround output. Optimal for this setup.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 4 — STEP 3 & 4: SONIC RADIO + TAUTULLI -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="4">Steps 3 & 4 — Sonic Radio + Tautulli</div>
|
|||
|
|
|
|||
|
|
<p><strong>Sonic Radio</strong> was confirmed working — the whole reason for migrating to PLEX32. Opening any artist in the Music library and clicking Live Radio plays that artist then branches into similar artists using AI-driven discovery. Requires x86 CPU — confirmed working on J4105.</p>
|
|||
|
|
|
|||
|
|
<p><strong>Tautulli</strong> was installed as a second service in the existing docker-compose.yml, running on port 8181. Connected to Plex, email notification configured for Plex server down/up alerts using existing Zoho SMTP credentials.</p>
|
|||
|
|
|
|||
|
|
<div class="script-box">
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Tautulli added to docker-compose.yml</div>
|
|||
|
|
<pre>tautulli:
|
|||
|
|
image: lscr.io/linuxserver/tautulli:latest
|
|||
|
|
container_name: tautulli
|
|||
|
|
environment:
|
|||
|
|
- PUID=1000
|
|||
|
|
- PGID=1001
|
|||
|
|
- TZ=America/New_York
|
|||
|
|
volumes:
|
|||
|
|
- /opt/tautulli/config:/config
|
|||
|
|
ports:
|
|||
|
|
- 8181:8181
|
|||
|
|
restart: unless-stopped</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Config directory</div>
|
|||
|
|
<pre>/opt/tautulli/config</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Web UI</div>
|
|||
|
|
<pre>http://192.168.150.45:8181</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Notification triggers set</div>
|
|||
|
|
<pre>Plex Server Down + Plex Server Back Up → email to rufus.king@kingdezigns.com</pre>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 5 — STEP 5: BACKUP -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="5">Step 5 — Automated config backup</div>
|
|||
|
|
|
|||
|
|
<p>A backup script was created to archive Plex and Tautulli configs to NAS08 via NFS every 3 days. rsync was attempted first but failed due to NFS directory scanning performance issues and permission constraints from NAS08's all_squash export. The final approach uses tar locally then a single cp to NFS.</p>
|
|||
|
|
|
|||
|
|
<div class="callout amber">
|
|||
|
|
<div class="clue-label">⚠ Key issue — rsync over NFS hung indefinitely</div>
|
|||
|
|
<p style="margin-bottom:6px;">rsync hung at "sending incremental file list" because scanning thousands of small Plex metadata files over NFS was extremely slow. Additionally NAS08's all_squash NFS export (anonuid=33/anongid=1001) prevented rufusking on PLEX32 from writing new files to the destination until directory permissions were corrected on NAS08.</p>
|
|||
|
|
<pre>sudo chown -R www-data:ncshare /export/kingdezigns-all/Docker/plex/config
|
|||
|
|
sudo chmod -R 2770 /export/kingdezigns-all/Docker/plex/config</pre>
|
|||
|
|
<p style="margin-top:8px;">Final solution: tar to /tmp locally (no NFS), then single cp to NFS. NFS write speed confirmed at 100 MB/s. Total backup runtime ~10 minutes.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="script-box">
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Backup script location</div>
|
|||
|
|
<pre>/usr/scripts/plex32/plex32_backup.sh</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Schedule</div>
|
|||
|
|
<pre>0 2 */3 * * /usr/scripts/plex32/plex32_backup.sh # Every 3 days at 2:00 AM</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Plex archive destination (NAS08)</div>
|
|||
|
|
<pre>/export/kingdezigns-all/Docker/plex/config/backups/plex/
|
|||
|
|
plex-config-YYYY-MM-DD.tar.gz (3 copies retained)</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Tautulli archive destination (NAS08)</div>
|
|||
|
|
<pre>/export/kingdezigns-all/Docker/plex/config/backups/tautulli/
|
|||
|
|
tautulli-config-YYYY-MM-DD.tar.gz (3 copies retained)</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Excludes from Plex archive</div>
|
|||
|
|
<pre>Cache / Logs / Crash Reports</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">SMTP password file</div>
|
|||
|
|
<pre>/etc/plex32-smtp-pass (chmod 600, owned by rufusking)</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Restore procedure (run on plex32)</div>
|
|||
|
|
<pre>cd ~/docker/plex && docker compose down
|
|||
|
|
cp /mnt/plex/config/backups/plex/plex-config-YYYY-MM-DD.tar.gz /tmp/
|
|||
|
|
sudo rm -rf /opt/plex/config/Library
|
|||
|
|
sudo tar -xzf /tmp/plex-config-YYYY-MM-DD.tar.gz -C /opt/plex
|
|||
|
|
sudo chown -R rufusking:ncshare /opt/plex/config
|
|||
|
|
cd ~/docker/plex && docker compose up -d
|
|||
|
|
docker ps # verify both plex and tautulli show Up
|
|||
|
|
# Open http://192.168.150.45:32400/web and confirm library intact
|
|||
|
|
rm /tmp/plex-config-YYYY-MM-DD.tar.gz</pre>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 6 — LAUNCHER + SSH KEYS -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="6">Remote launcher + SSH key authentication</div>
|
|||
|
|
|
|||
|
|
<p>A launcher script was created on KingDezigns001 to remotely execute PLEX32 scripts via SSH with a simple menu. SSH key authentication was configured from KingDezigns001 to all VLAN 50 servers, and from PLEX32 to NAS08, eliminating all password prompts.</p>
|
|||
|
|
|
|||
|
|
<div class="script-box">
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Launcher script (KingDezigns001)</div>
|
|||
|
|
<pre>~/scripts/plex32_launcher.sh</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Desktop shortcut (KingDezigns001)</div>
|
|||
|
|
<pre>~/Desktop/PLEX32-Launcher.desktop</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">Menu options</div>
|
|||
|
|
<pre>1) Update Check → /usr/scripts/plex32/plex32_update_check.sh
|
|||
|
|
2) Health Report → /usr/scripts/plex32/plex32_health_report.sh
|
|||
|
|
3) Plex32 Backup → /usr/scripts/plex32/plex32_backup.sh</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">SSH keys configured (no password)</div>
|
|||
|
|
<pre>KingDezigns001 → PLEX32 (192.168.150.45)
|
|||
|
|
KingDezigns001 → NAS08 (192.168.150.35)
|
|||
|
|
KingDezigns001 → NAS16 (192.168.150.40)
|
|||
|
|
PLEX32 → NAS08 (192.168.150.35) # required for backup disk stats</pre>
|
|||
|
|
</div>
|
|||
|
|
<div class="script-row">
|
|||
|
|
<div class="script-label">sudo without password (plex32) — /etc/sudoers.d/rufusking-nopasswd</div>
|
|||
|
|
<pre>rufusking ALL=(ALL) NOPASSWD: /usr/bin/apt-get update, /usr/bin/fail2ban-client status, /usr/bin/fail2ban-client status sshd</pre>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 7 — STEPS 6 & 7 -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="7">Steps 6 & 7 — Scheduled maintenance & subtitle handling</div>
|
|||
|
|
|
|||
|
|
<p><strong>Scheduled maintenance</strong> was already configured to run between 2:00 AM and 5:00 AM. The following library analysis tasks were confirmed enabled as scheduled tasks:</p>
|
|||
|
|
|
|||
|
|
<table>
|
|||
|
|
<thead>
|
|||
|
|
<tr><th>Task</th><th>Setting</th></tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr><td>Generate video preview thumbnails</td><td>As a scheduled task (changed from Never)</td></tr>
|
|||
|
|
<tr><td>Generate chapter image thumbnails</td><td>As a scheduled task</td></tr>
|
|||
|
|
<tr><td>Analyze audio tracks for loudness</td><td>As a scheduled task</td></tr>
|
|||
|
|
<tr><td>Generate intro video markers</td><td>As a scheduled task and when media is added</td></tr>
|
|||
|
|
<tr><td>Generate credits video markers</td><td>As a scheduled task and when media is added</td></tr>
|
|||
|
|
<tr><td>Analyze audio tracks for sonic features</td><td>As a scheduled task</td></tr>
|
|||
|
|
<tr><td>Generate voice activity data</td><td>As a scheduled task (changed from Never)</td></tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
|
|||
|
|
<p style="margin-top:1rem;"><strong>Subtitle handling:</strong> Library contains 1168 SRT (safe, no transcode) and 279 PGS image-based subtitles (burn-in forces transcode if selected). Since subtitles are used occasionally and Quick Sync handles the transcode efficiently, Option B (bulk PGS→SRT conversion) was not pursued. Subtitle Mode confirmed set to <code>Manually selected</code> — subtitles only appear when explicitly chosen, preventing accidental burn-in transcoding.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 8 — LESSONS LEARNED -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="8">Lessons learned</div>
|
|||
|
|
<table>
|
|||
|
|
<thead>
|
|||
|
|
<tr><th>Lesson</th><th>Detail</th></tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr>
|
|||
|
|
<td>GPU device passthrough requires group membership</td>
|
|||
|
|
<td>Adding /dev/dri to Docker devices is not enough — the container process also needs supplementary groups matching the video (44) and render (993) GIDs on the host. Use group_add in docker-compose.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>rsync over NFS is unreliable for large Plex configs</td>
|
|||
|
|
<td>Thousands of small metadata files cause rsync to hang indefinitely building the file list over NFS. Use tar locally then a single cp to NFS instead — much faster and more reliable.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>NAS08 all_squash NFS exports require correct directory ownership</td>
|
|||
|
|
<td>Even sudo on PLEX32 cannot create files on NFS mounts with all_squash unless the destination directory on NAS08 is owned by www-data:ncshare with 2770 permissions.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Scripts running as non-root need sudo for apt-get and fail2ban-client</td>
|
|||
|
|
<td>Both commands require root access. Grant specific NOPASSWD sudo rights via /etc/sudoers.d/ rather than running the whole script as root.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>SSH keys should be set up on all servers from the start</td>
|
|||
|
|
<td>Key authentication eliminates password prompts for both interactive SSH and script-to-script SSH calls (e.g. PLEX32 → NAS08 for disk stats). Generate once per machine, copy to all targets.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Hardware transcoding silently falls back without /dev/dri access</td>
|
|||
|
|
<td>Plex showed hardware transcoding as "enabled" in settings but was silently using software transcoding. Always verify with the Dashboard showing (hw) next to the codec during an active transcode.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>AAC audio across the entire library is ideal</td>
|
|||
|
|
<td>TrueHD and DTS-HD MA are the common culprits that force audio transcoding on most clients. A library with 100% AAC will never trigger an audio-forced transcode regardless of client.</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- SECTION 9 — DOCS TO UPDATE -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title" data-num="9">Network docs — items to update</div>
|
|||
|
|
<table>
|
|||
|
|
<thead>
|
|||
|
|
<tr><th>Item</th><th>Old value</th><th>New value</th></tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr>
|
|||
|
|
<td>server_plex32.md — docker-compose.yml</td>
|
|||
|
|
<td>No devices or group_add block</td>
|
|||
|
|
<td>devices: /dev/dri + group_add: ["44","993"] + Tautulli service added</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>server_plex32.md — Scheduled Jobs</td>
|
|||
|
|
<td>2 cron jobs (update check, health report)</td>
|
|||
|
|
<td>3 cron jobs — add: <code>0 2 */3 * * /usr/scripts/plex32/plex32_backup.sh</code></td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>server_plex32.md — new scripts section</td>
|
|||
|
|
<td>No backup script</td>
|
|||
|
|
<td>plex32_backup.sh — tar + NFS copy to NAS08, 3 copies retained, HTML email report</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>server_plex32.md — SSH keys</td>
|
|||
|
|
<td>Not documented</td>
|
|||
|
|
<td>PLEX32 has SSH key to NAS08 (192.168.150.35) — required for backup disk stats</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>server_plex32.md — sudoers</td>
|
|||
|
|
<td>Not documented</td>
|
|||
|
|
<td>/etc/sudoers.d/rufusking-nopasswd — apt-get update + fail2ban-client commands</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>server_plex32.md — Tautulli</td>
|
|||
|
|
<td>Not present</td>
|
|||
|
|
<td>Tautulli running on port 8181, config at /opt/tautulli/config, Plex down/up alerts active</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>network_index.md — KingDezigns001 tools</td>
|
|||
|
|
<td>Not documented</td>
|
|||
|
|
<td>plex32_launcher.sh at ~/scripts/ + PLEX32-Launcher.desktop on Desktop — SSH keys to all VLAN 50 servers</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>server_nas08.md — NFS export permissions</td>
|
|||
|
|
<td>Not documented</td>
|
|||
|
|
<td>/export/kingdezigns-all/Docker/plex/config — www-data:ncshare 2770, backups/plex and backups/tautulli subdirs created</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<div class="footer">
|
|||
|
|
<span>KingDezigns Home Network</span>
|
|||
|
|
<span>Generated June 20, 2026</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</body>
|
|||
|
|
</html>
|