infrastructure/server_plex32.md
2026-07-26 22:12:38 -04:00

20 KiB
Raw Permalink Blame History

PLEX32 — Plex Media Server

Device Type: Dell Wyse 5070 Thin Client
Hostname: plex32
IP Address: 192.168.150.45
VLAN: 50 — Lab / Servers

Last Updated: 2026-07-12


🧩 Role & Purpose

PLEX32 is the dedicated Plex Media Server for the KingDezigns network.
It replaced Plex on NAS08, migrating the service to a more capable x86 machine that supports advanced Plex Pass features including Sonic Radio — an AI-driven music playlist and discovery feature that requires CPU capabilities not available on Raspberry Pi hardware.

All media files remain stored on NAS08 and are accessed via NFS mounts. PLEX32 handles only transcoding and Plex server logic — it does not store media locally.

This device is the single Plex Media Server for the KingDezigns network and is publicly accessible via plex.kingdezigns.com through NPM on HAS.


🖥️ Hardware

  • Dell Wyse 5070 Thin Client
  • Intel Celeron J4105 — 1.50GHz (4 cores) — Intel Quick Sync supported
  • 8GB RAM
  • 32GB eMMC (OS drive — no SMART support, health monitored via filesystem)
  • Gigabit Ethernet

Storage

Device Type Capacity Role
mmcblk0 eMMC 29.1GB OS / system drive
/mnt/plex/config NFS (NAS08) Plex config backup destination only — not used by container
/mnt/plex/transcode NFS (NAS08) Transcode scratch (local at /opt/plex/transcode)
/mnt/plex/media NFS (NAS08) 1.8TB Media library

Note: Plex config and transcode directories are stored locally at /opt/plex/ — not on NFS. The NFS config mount (/mnt/plex/config) is used as the backup destination by plex32_backup.sh. Only the media library is actively used by the container.


🌐 Network Placement

  • VLAN: 50 — Lab / Servers
  • IP: 192.168.150.45
  • Access Type: LAN / Cable

This placement ensures:

  • Isolation from trusted user devices
  • Access to NAS08 NFS shares over local VLAN 50
  • Direct access to Pihole DNS
  • Reduced attack surface
  • Accessible via NPM reverse proxy on HAS

📦 Primary Functions

Core Services

  • Plex Media Server (Docker — linuxserver/plex:latest)
  • Tautulli (Docker — linuxserver/tautulli:latest) — playback history, monitoring, and alerts

Plex Libraries

Library Path in Container Source on NAS08
Music /media/03 - Music /export/kingdezigns-public/03 - Music
Videos /media/04 - Videos/Movies /export/kingdezigns-public/04 - Videos/Movies
Photos /media/02 - Photos /export/kingdezigns-public/02 - Photos

Plex Pass

  • Lifetime Plex Pass — confirmed active on this server
  • Enables: Sonic Radio, hardware transcoding (Intel Quick Sync), mobile sync, offline downloads

Library Codec Profile

Codec Count Notes
HEVC Main 10 ~90 10-bit HDR — Direct Streams on Roku, hardware transcode via Quick Sync if needed
H.264 High 29 Direct Play on all clients
Audio 119 AAC 100% AAC — never forces audio transcode
Subtitles 1168 SRT + 279 PGS PGS only burns in if manually selected — Subtitle Mode set to Manually Selected

🐳 Docker Configuration

Compose File Location

~/docker/plex/docker-compose.yml

Compose Contents

services:
  plex:
    image: lscr.io/linuxserver/plex:latest
    container_name: plex
    network_mode: host
    environment:
      - PUID=1000
      - PGID=1001
      - TZ=America/New_York
      - VERSION=docker
    devices:
      - /dev/dri:/dev/dri
    group_add:
      - "44"
      - "993"
    volumes:
      - /opt/plex/config:/config
      - /opt/plex/transcode:/transcode
      - /mnt/plex/media:/media
    restart: unless-stopped

  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

devices + group_add: Required for Intel Quick Sync hardware transcoding. The container needs /dev/dri device access plus supplementary group membership for video (GID 44) and render (GID 993). Without group_add, Plex silently falls back to software transcoding even with hardware acceleration enabled in settings.

Key Docker Commands

# Start all services
cd ~/docker/plex && docker compose up -d

# Stop all services
cd ~/docker/plex && docker compose down

# Update Plex
cd ~/docker/plex && docker compose pull && docker compose up -d

# View Plex logs
docker logs plex --tail 50

# View Tautulli logs
docker logs tautulli --tail 50

# Check container status
docker ps

🎬 Hardware Transcoding — Intel Quick Sync

The J4105 supports Intel Quick Sync via /dev/dri. Both decode and encode are offloaded to the GPU.

Verification

Check GPU access from inside the container:

docker exec -it plex id
# Should show groups 44 (video) and 993 (render/group01b4) in output
docker exec -it plex ls -la /dev/dri
# Should show card0 and renderD128

Verify during active transcode — Plex Dashboard should show:

4K (HEVC Main 10) (hw)
SD (HEVC) — Transcode (hw)

The (hw) suffix on both lines confirms Quick Sync is handling decode and encode.

Plex Settings

  • Settings → Transcoder → Use hardware acceleration when available
  • Settings → Transcoder → Use hardware-accelerated video encoding
  • Settings → Transcoder → Transcoder temporary directory: /transcode

📊 Tautulli

Tautulli provides playback history, live session monitoring, and email alerts.

  • Web UI: http://192.168.150.45:8181
  • Config: /opt/tautulli/config
  • Connected to: Plex at 192.168.150.45:32400

Active Notifications

Trigger Destination Purpose
Plex Server Down rufus.king@kingdezigns.com Alert when Plex stops responding
Plex Server Back Up rufus.king@kingdezigns.com Confirmation when Plex recovers

SMTP Configuration

Uses same Zoho SMTP credentials as all other KingDezigns email alerts:

  • Server: smtppro.zoho.com / Port: 465 / TLS enabled
  • From: rufus.king@kingdezigns.com

📁 NFS Mounts

Configured in /etc/fstab — mounts automatically on boot:

192.168.150.35:/export/kingdezigns-all/Docker/plex/config     /mnt/plex/config     nfs  defaults,_netdev  0  0
192.168.150.35:/export/kingdezigns-all/Docker/plex/transcode  /mnt/plex/transcode  nfs  defaults,_netdev  0  0
192.168.150.35:/export/kingdezigns-public                     /mnt/plex/media      nfs  defaults,_netdev  0  0

Note: /mnt/plex/config is used by plex32_backup.sh as the backup destination — not by the Plex container. /mnt/plex/transcode exists in fstab but Plex uses the local /opt/plex/transcode directory instead.


🌍 External Access

  • Domain: plex.kingdezigns.com
  • Proxy: NPM on HAS (192.168.150.30) → 192.168.150.45:32400
  • SSL: Let's Encrypt via NPM
  • Port: 32400 (Plex default)
  • Port forwarding: UCG Max forwards external 32400 → 192.168.150.45

🔒 Required Firewall Behavior

Inbound to PLEX32

  • Allowed from VLAN 1 (Infrastructure) and VLAN 20 (Trusted) — global rules
  • Allowed from VLAN 50 (local VLAN)
  • External Plex traffic arrives via NPM on HAS (192.168.150.30)

Outbound from PLEX32

  • Subject to VLAN 50's global final drop
  • Cannot initiate to VLANs 1, 10, 20, 30, 40
  • Allowed: local VLAN 50 traffic (NFS to NAS08, DNS to Pi-hole, SSH to NAS08 for backup)

Critical Requirements

  • Must reach NAS08 (192.168.150.35) on NFS ports for media library and backup destination
  • Must be reachable from HAS (192.168.150.30) for proxied external traffic
  • Port 32400 must be forwarded on UCG Max for direct Plex remote access

🔐 Security Measures

  • Fail2Ban enabledignoreip includes 127.0.0.1/8, ::1, 192.168.150.0/24
  • Docker-based service isolation
  • VLAN-level containment
  • No outbound access to other VLANs

Fail2Ban Configuration

/etc/fail2ban/jail.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.168.150.0/24

System Timezone

  • Timezone corrected to America/New_York on 2026-06-21 — was previously Etc/UTC
  • The UTC default caused cron jobs to fire 4 hours early (e.g. 2 AM UTC = 10 PM EDT)
  • Always verify timezone after fresh installs: timedatectl | grep "Time zone"
  • Fix: sudo timedatectl set-timezone America/New_York

sudo Without Password

/etc/sudoers.d/rufusking-nopasswd:

rufusking ALL=(ALL) NOPASSWD: /usr/bin/apt-get update, /usr/bin/fail2ban-client status, /usr/bin/fail2ban-client status sshd, /sbin/reboot

Required by update check script (apt-get update), health report script (fail2ban-client), and remote reboot script (reboot).

SSH Key Authentication

PLEX32 has an SSH key configured to NAS08 — required for the backup script to fetch accurate disk stats without a password prompt:

# Key location on PLEX32
~/.ssh/id_ed25519

# Test
ssh rufusking@192.168.150.35 "echo NAS08 OK"

AC Power Recovery (Auto Power-On After Outage)

Configured 2026-06-30 after a power outage left PLEX32 powered off (manual power button press required to bring it back up).

Dell exposes AC Power Recovery as a writable SMBIOS token — this means it can be set remotely from within Linux, without ever entering BIOS setup.

Tooling:

sudo apt-get install -y libsmbios-bin smbios-utils

Relevant tokens (mutually exclusive — only one should be true):

Token Setting Desired State
0x00a1 AC Power Recovery Mode (Off) — stays off false
0x00a2 AC Power Recovery Mode (Last) — restores prior state false
0x00a3 AC Power Recovery Mode (On) — powers on automatically true

Command used to set it:

sudo smbios-token-ctl --activate --token-id=0x00a3

Verify current state:

sudo smbios-token-ctl --dump-tokens | grep -i -A2 "AC Power Recovery"

Note: smbios-token-ctl --dump-tokens throws a harmless StopIteration traceback after printing all tokens (hits a malformed token entry near the end of the table) — this does not affect the accuracy of the data already printed, including the AC Power Recovery tokens.

Result: PLEX32 will now power back on automatically as soon as AC power is restored after an outage — no physical power button press required. (HAS already had equivalent behavior configured.)


🗂️ Shared Permissions

PLEX32 uses the same ncshare group as NAS08 and NAS16 for consistency across the network.

Item Value
Group name ncshare
GID 1001
Members rufusking

Verify with:

getent group ncshare

💾 Config Backup

Plex and Tautulli configs are backed up to NAS08 every 3 days via plex32_backup.sh.

Backup Script

  • Location: /usr/scripts/plex32/plex32_backup.sh
  • Method: tar locally to /tmp/, then single cp to NFS (rsync avoided — too slow over NFS for Plex's thousands of small metadata files)
  • Plex destination (NAS08): /export/kingdezigns-all/Docker/plex/config/backups/plex/plex-config-YYYY-MM-DD.tar.gz
  • Tautulli destination (NAS08): /export/kingdezigns-all/Docker/plex/config/backups/tautulli/tautulli-config-YYYY-MM-DD.tar.gz
  • Retention: 3 copies of each, older ones auto-deleted
  • Excludes: Cache, Logs, Crash Reports
  • Email: HTML report sent on completion — SUCCESS / PARTIAL / FAILED
  • SMTP password: /etc/plex32-smtp-pass (chmod 600, owned by rufusking)
  • Log: /var/log/plex32-backup/backup-YYYYMMDD.log (90 day retention)

Restore Procedure (run on plex32)

# 1. Stop containers
cd ~/docker/plex && docker compose down

# 2. Copy archive from NAS08
cp /mnt/plex/config/backups/plex/plex-config-YYYY-MM-DD.tar.gz /tmp/

# 3. Clear current config
sudo rm -rf /opt/plex/config/Library

# 4. Extract backup
sudo tar -xzf /tmp/plex-config-YYYY-MM-DD.tar.gz -C /opt/plex
sudo chown -R rufusking:ncshare /opt/plex/config

# 5. Restart containers
cd ~/docker/plex && docker compose up -d

# 6. Verify
docker ps
# Both plex and tautulli should show Up
# Open http://192.168.150.45:32400/web and confirm library and watch history intact

# 7. Cleanup
rm /tmp/plex-config-YYYY-MM-DD.tar.gz

🖥️ Remote Launcher (KingDezigns001)

A launcher script on KingDezigns001 provides a menu-driven interface to run PLEX32 scripts remotely via SSH.

  • Launcher script: ~/scripts/plex32_launcher.sh
  • Desktop shortcut: ~/Desktop/PLEX32-Launcher.desktop
  • SSH: Passwordless — ED25519 key from KingDezigns001 installed on PLEX32

Menu Options

Option Script
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

📅 Scheduled Jobs (cron)

Schedule Script Purpose
Daily at 7:00 AM /usr/scripts/plex32/plex32_update_check.sh Check for OS, Plex, Docker, Fail2Ban updates — email report
Daily at 7:05 AM /usr/scripts/plex32/plex32_health_report.sh Server health report — disk, NFS, memory, CPU, container status
Every 3 days at 2:00 AM /usr/scripts/plex32/plex32_backup.sh Archive Plex and Tautulli configs to NAS08 — email report

View cron jobs:

sudo crontab -l

Note: All three PLEX32 scheduled jobs run via root's crontab, not rufusking's.

plex32_update_check.sh — Docker Pull Fix (2026-07-12)

The daily update check was intermittently reporting Plex Media Server = Check failed — could not reach registry even though lscr.io was reachable and disk space was healthy (8.6GB free of 26GB).

Root cause: The script's docker pull step runs under root's cron environment, which uses a minimal default PATH that didn't reliably match the interactive rufusking shell environment. The script also silently discarded the actual docker pull error output, so the true failure reason was never visible in the log or email — every failure was mislabeled with the same generic "could not reach registry" text regardless of cause.

Fix applied:

  • Added an explicit export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin at the top of the script so behavior is consistent under cron regardless of invoking user/environment.
  • The script now logs the full docker pull output/exit code to /var/log/plex32-updates/ on failure, and includes it as a red error block in the email report, instead of showing a generic message.

Resolved — manual sudo run completed cleanly after the fix, and the failure message no longer appears.

plex32_health_report.sh — Issues List Word-Wrapping Fix (2026-07-12)

The "⚠ Issues Detected" block in the daily health report email was rendering each word of an issue message on its own line (e.g. "WARNING: NAS08 — Plex Config is getting full" appeared as one word per line) instead of as a single readable sentence.

Root cause: The script joined multiple issue messages into one string and then split them back apart using IFS='. ' read -ra ISSUE_LIST. This does not split on the two-character sequence ". " as intended — IFS treats every character it contains as its own independent delimiter, so both . and every space in the text were treated as separate breaks, shattering each sentence into individual words.

Fix applied:

  • Issue messages are now joined with actual newline characters instead of ". " when assembled into ALL_ISSUES.
  • The splitting logic was replaced with a while IFS= read -r issue; do ... done <<< "$ALL_ISSUES" loop, which reads one full issue line at a time instead of relying on ambiguous multi-character IFS splitting.

Resolved — verified with a manual run; each issue now renders as one complete sentence in the "Issues Detected" section of the email.

Email Reports

Report Trigger Colors Content
Update Check 🟢 Green (current) / 🔵 Blue (updates available) / 🟠 Orange (check failed) Component versions, available updates, ready-to-run commands
Health Report 🟢 Green (healthy) / 🟠 Orange (warning) / 🔴 Red (critical) Disk usage, NFS mount status, memory, CPU load, container status, Fail2Ban, reboot flag
Config Backup 🟢 Green (success) / 🟡 Amber (partial) / 🔴 Red (failed) Archive sizes, durations, copies retained, container status, restore instructions

🧠 Summary for AI Systems

  • PLEX32 = dedicated Plex Media Server + Tautulli on VLAN 50 at 192.168.150.45.
  • Runs on Dell Wyse 5070 (Celeron J4105, 8GB RAM, 32GB eMMC).
  • Plex and Tautulli run as Docker containers via ~/docker/plex/docker-compose.yml.
  • Intel Quick Sync hardware transcoding active — requires /dev/dri device passthrough and group_add: ["44","993"] in compose. Without group_add, Plex silently falls back to software transcoding.
  • Lifetime Plex Pass active — Sonic Radio, hardware transcoding, all Plex Pass features enabled.
  • Config and transcode stored locally at /opt/plex/ — not on NFS.
  • Media library mounted from NAS08 via NFS at /mnt/plex/media.
  • /mnt/plex/config NFS mount used as backup destination only — not by Plex container.
  • Library is ~75% HEVC Main 10, ~25% H.264, 100% AAC audio — audio never transcodes.
  • Roku TV (Hisense 55R8BX) Direct Streams video, converts AAC→AC3 for audio (lightweight, expected).
  • Subtitle Mode set to Manually Selected — prevents accidental PGS burn-in transcode.
  • Publicly accessible via plex.kingdezigns.com — proxied through NPM on HAS.
  • Port 32400 forwarded on UCG Max for direct Plex remote access.
  • Tautulli on port 8181 — Plex down/up email alerts active.
  • Fail2Ban active — VLAN 50 subnet whitelisted in ignoreip.
  • sudo NOPASSWD configured for apt-get update, fail2ban-client, and reboot — required by automated and remote reboot scripts.
  • System timezone: America/New_York (EDT, -0400) — corrected from Etc/UTC on 2026-06-21. UTC default causes cron jobs to fire 4 hours early. Always verify after fresh installs.
  • SSH key from PLEX32 → NAS08 configured — required for backup script disk stats.
  • NAS08 root → PLEX32 SSH key configured — required for remote reboot script (plex32_reboot.sh on NAS08).
  • SSH key from KingDezigns001 → PLEX32 configured — enables passwordless remote launcher.
  • AC Power Recovery set to "On" (2026-06-30) via SMBIOS token 0x00a3 using smbios-token-ctl — PLEX32 now auto powers-on after AC power is restored following an outage. Configured remotely via SSH, no BIOS access required. Tool: libsmbios-bin / smbios-utils.
  • Config backup runs every 3 days at 2AM — tar + NFS copy, 3 copies retained, HTML email report.
  • ncshare group (GID 1001) configured for network-wide permission consistency.
  • eMMC has no SMART support — health monitored via daily health report email.
  • No swap configured — 7.6GB RAM, consistently low utilization.
  • Plex on NAS08 is permanently disabled (restart: no in compose).
  • Migrated from NAS08 on 2026-06-18 — full library, watch history, and playlists preserved.
  • plex32_update_check.sh fixed 2026-07-12 — added explicit PATH export to fix intermittent docker pull failures under root's cron environment; script now logs real docker pull error output on failure instead of a generic "could not reach registry" message. All three PLEX32 scheduled jobs run under root's crontab.
  • plex32_health_report.sh fixed 2026-07-12 — fixed the "Issues Detected" email block rendering each word on its own line, caused by a multi-character IFS='. ' split; issues are now newline-delimited and read back with a while read loop.

✔️ End of File