diff --git a/scripts/50-plex.sh b/scripts/50-plex.sh index 5283ef1..2f4381d 100755 --- a/scripts/50-plex.sh +++ b/scripts/50-plex.sh @@ -1,135 +1,158 @@ #!/usr/bin/env bash # ============================================================================= -# 50-plex — deploy Plex. HUMAN-IN-THE-LOOP, run by hand, not from bootstrap. +# 50-plex — start Plex against the MIGRATED data directory. # -# Usage: -# # 1. open https://plex.tv/claim and copy the token -# # 2. within four minutes: -# sudo PLEX_CLAIM=claim-xxxxxxxxxxxx /srv/mediabox-bootstrap/scripts/50-plex.sh +# Usage: sudo /srv/mediabox-bootstrap/scripts/50-plex.sh # -# A claim token expires four minutes after it is issued. That is the entire -# reason this stage is not automated: there is no way to bake one into a USB, -# a repo, or a first-boot script and have it still be valid. +# NO CLAIM TOKEN. This is deliberate and important. # -# Claiming is only required on the FIRST start. Re-running later without -# PLEX_CLAIM is fine and will not re-claim. +# The migration carries MachineIdentifier, ProcessedMachineIdentifier and the +# online token across in Preferences.xml, so this server IS the existing +# server: already claimed, shared users still invited, clients still pointed +# at it. Claiming would mint a NEW identity and throw all of that away — the +# exact thing the migration exists to preserve. +# +# If you find yourself reaching for https://plex.tv/claim, stop. Something has +# gone wrong with the Preferences.xml step in migration/README.md § M3. # ============================================================================= set -uo pipefail +PLEXDIR="/srv/plex" +CFG="$PLEXDIR/config/Library/Application Support/Plex Media Server" REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -DEST="/srv/plex" MASTER="/srv/secrets/stacks.env" -# --- gate 1: mounts must be live BEFORE Plex ever scans ---------------------- -# If Plex scans a library whose mount is missing, it sees zero files. With a -# read-write NAS account, "empty trash after scan" would then delete the -# library's records — and Plex has the permission to act on that. Never let -# Plex start against absent mounts. -echo " checking NAS mounts before starting Plex" +# The six shares, named exactly as the database stores them. +MOUNTS=( + "/mnt/nas/media" + "/mnt/nas/Radio Shows" + "/mnt/nas/Education Videos" + "/mnt/nas/Health" + "/mnt/nas/Home Movies" + "/mnt/nas/Pictures" +) + +ok(){ printf ' \033[1;32m[ok]\033[0m %s\n' "$*"; } +bad(){ printf ' \033[1;31m[FAIL]\033[0m %s\n' "$*"; } +note(){ printf ' %s\n' "$*"; } + +[ "$(id -u)" -eq 0 ] || { bad "must run as root"; exit 1; } + +# --------------------------------------------------------------------------- +# Gate 1 — every mount live BEFORE Plex ever opens the database. +# +# If Plex scans a library whose mount is absent it sees zero files. The NAS +# account is read-write, so with autoEmptyTrash on that becomes deletion it +# has permission to perform. gen-preferences.ps1 forces autoEmptyTrash=0, but +# this gate is the belt to that braces. +# --------------------------------------------------------------------------- +echo " checking the six NAS mounts" missing=0 -for mp in /mnt/nas/audiobooks /mnt/nas/education-videos /mnt/nas/health \ - /mnt/nas/home-movies /mnt/nas/media /mnt/nas/music-organized \ - /mnt/nas/pictures /mnt/nas/radio-shows; do - if ls "$mp" >/dev/null 2>&1 && mountpoint -q "$mp"; then - printf ' [ok] %s\n' "$mp" +for mp in "${MOUNTS[@]}"; do + if mountpoint -q "$mp" && ls "$mp" >/dev/null 2>&1; then + ok "$mp" else - printf ' [FAIL] %s not mounted\n' "$mp" + bad "$mp not mounted" missing=1 fi done if [ "$missing" -ne 0 ]; then echo - echo " REFUSING to start Plex with missing mounts." - echo " Fix with: sudo /srv/mediabox-bootstrap/scripts/20-cifs.sh --verify" + bad "REFUSING to start Plex with missing mounts." + note "fix: sudo /srv/mediabox-bootstrap/scripts/20-cifs.sh --verify" exit 1 fi -# --- gate 2: Quick Sync ----------------------------------------------------- -if [ ! -e /dev/dri/renderD128 ]; then - echo " [WARN] /dev/dri/renderD128 missing — iGPU not enabled in BIOS." - echo " Plex will still run, but every transcode will be software." - RENDER_GID=993 +# --------------------------------------------------------------------------- +# Gate 2 — the database must actually be migrated. +# +# Starting Plex against an un-remapped database means every item shows as +# unavailable, and the recovery is messier than simply not starting. +# --------------------------------------------------------------------------- +DB="$CFG/Plug-in Support/Databases/com.plexapp.plugins.library.db" +if [ ! -f "$DB" ]; then + bad "no database at $DB" + note "restore the data directory first — migration/README.md § M3" + exit 1 +fi +if grep -qac 'korval' "$DB" 2>/dev/null && grep -qac '\\\\korval\\' "$DB" 2>/dev/null; then + bad "database still contains Windows UNC paths — remap has not been run" + note "run: sudo /srv/mediabox-bootstrap/migration/migrate-db.sh \"$DB\"" + exit 1 +fi +ok "database present and remapped" + +# --------------------------------------------------------------------------- +# Gate 3 — identity preserved? +# --------------------------------------------------------------------------- +PREFS="$CFG/Preferences.xml" +if [ -f "$PREFS" ] && grep -q 'ProcessedMachineIdentifier=' "$PREFS"; then + ok "server identity present in Preferences.xml (no claim needed)" else + bad "Preferences.xml missing or has no ProcessedMachineIdentifier" + note "generate it on the Windows box: migration/gen-preferences.ps1" + note "without it this becomes a NEW server and shared users are lost" + exit 1 +fi + +# --------------------------------------------------------------------------- +# Quick Sync +# --------------------------------------------------------------------------- +if [ -e /dev/dri/renderD128 ]; then RENDER_GID="$(stat -c '%g' /dev/dri/renderD128)" - echo " render node gid: $RENDER_GID" + ok "render node gid $RENDER_GID" +else + RENDER_GID=993 + note "[warn] /dev/dri/renderD128 missing — iGPU not enabled in BIOS." + note " Plex will run, but every transcode will be software." fi export RENDER_GID -install -d -m 0755 "$DEST" -install -d -m 0755 "$DEST/config" -chown -R 3000:3000 "$DEST" -install -m 0644 "$REPO_DIR/plex/docker-compose.yml" "$DEST/docker-compose.yml" +# --------------------------------------------------------------------------- +# Deploy +# --------------------------------------------------------------------------- +install -d -m 0755 "$PLEXDIR" +install -m 0644 "$REPO_DIR/plex/docker-compose.yml" "$PLEXDIR/docker-compose.yml" +chown -R 3000:3000 "$PLEXDIR/config" -# --- env -------------------------------------------------------------------- set -a TZ="$(grep -E '^TZ=' "$MASTER" 2>/dev/null | head -1 | cut -d= -f2- || echo America/New_York)" -PLEX_ADVERTISE_URL="$(grep -E '^PLEX_ADVERTISE_URL=' "$MASTER" 2>/dev/null | head -1 | cut -d= -f2- || echo http://10.0.1.20:32400)" -PLEX_CLAIM="${PLEX_CLAIM:-}" set +a -if [ -n "$PLEX_CLAIM" ]; then - echo " claim token supplied (expires 4 min from issue — moving now)" -else - echo " no PLEX_CLAIM given; assuming this server is already claimed" -fi +cd "$PLEXDIR" || exit 1 +docker compose up -d || { bad "compose up failed"; exit 1; } -cd "$DEST" || exit 1 -docker compose up -d || { echo " [FAIL] compose up failed"; exit 1; } - -# The claim token must not linger anywhere on disk. -unset PLEX_CLAIM - -echo " waiting for Plex to answer on 32400" +echo " waiting for Plex" for i in $(seq 1 60); do if curl -fsS -m 3 "http://127.0.0.1:32400/identity" >/dev/null 2>&1; then - echo " [ok] Plex is up" + ok "Plex is up" break fi sleep 3 done -# --- gate 3: disable auto-empty-trash, permanently --------------------------- -# This is the single most dangerous default on this box. If a CIFS mount is -# missing at scan time Plex sees an empty library; with autoEmptyTrash on it -# begins trimming, and the NAS account is read-write. Turn it off in the -# config rather than trusting a UI checkbox to stay ticked. -PREFS="$DEST/config/Library/Application Support/Plex Media Server/Preferences.xml" -if [ -f "$PREFS" ]; then - if grep -q 'autoEmptyTrash="0"' "$PREFS"; then - echo " [ok] autoEmptyTrash already disabled" - else - echo " disabling autoEmptyTrash (requires a Plex restart)" - docker compose stop plex >/dev/null 2>&1 - cp -a "$PREFS" "${PREFS}.bak.$(date +%Y%m%d%H%M%S)" - if grep -q 'autoEmptyTrash=' "$PREFS"; then - sed -i 's/autoEmptyTrash="[^"]*"/autoEmptyTrash="0"/' "$PREFS" - else - sed -i 's//dev/null 2>&1 - echo " [ok] autoEmptyTrash=0 written" - fi +# --------------------------------------------------------------------------- +# Verify the migration actually landed +# --------------------------------------------------------------------------- +ID="$(curl -fsS -m 5 http://127.0.0.1:32400/identity 2>/dev/null | grep -o 'machineIdentifier="[^"]*"' | cut -d'"' -f2)" +if [ "$ID" = "eb69ec8a9f38b64eeafe911e26d89baeaa78f0e3" ]; then + ok "machineIdentifier matches the original server — identity preserved" else - cat <<'MSG' - [WARN] Preferences.xml not written yet (first start is still initialising). - Re-run this script once Plex has fully started to disable - autoEmptyTrash, OR untick it by hand: - Settings > Library > "Empty trash automatically after every scan" - Do this BEFORE adding any library. With read-write NAS credentials a - missing mount plus this setting deletes media records. -MSG + bad "machineIdentifier is $ID, expected eb69ec8a9f38b64eeafe911e26d89baeaa78f0e3" + note "this is a DIFFERENT server. Shared users and client registrations are lost." + note "stop, and check the Preferences.xml step before adding anything." fi cat <<'NEXT' - Next: - * Open http://10.0.1.20:32400/web and confirm the server is claimed. - * Add libraries pointing at /media/... (the container paths), not /mnt/nas. - * Verify a transcode is using Quick Sync: - docker exec plex ls -l /dev/dri - # start a transcode, then: - intel_gpu_top # Video/VideoEnhance rows should be busy + Verify before you walk away: + * https://plex.tv/claim was NOT used and must not be. + * Movies should show 1,360 titles; TV Shows 23,493 episodes. + * Continue Watching should be populated — that is the whole point. + * A remote client that already had this server should still see it + without re-adding. + * Force a transcode, then: sudo intel_gpu_top + Video / VideoEnhance rows should be busy. NEXT exit 0