50-plex: drop claim flow, gate on migrated DB and preserved identity

The forward-only decision was reversed: the server identity is now
migrated, not minted. Claiming would destroy the exact thing the
migration exists to preserve.

- remove every PLEX_CLAIM path
- check the six real mount points by exact share name
- refuse to start against an un-remapped database
- refuse to start without ProcessedMachineIdentifier in Preferences.xml
- assert machineIdentifier after start
This commit is contained in:
2026-07-31 17:56:10 +01:00
parent 351eab41ac
commit 9a3e824b90
+111 -88
View File
@@ -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"
# ---------------------------------------------------------------------------
# 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
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/<Preferences /<Preferences autoEmptyTrash="0" /' "$PREFS"
fi
chown 3000:3000 "$PREFS"
docker compose start plex >/dev/null 2>&1
echo " [ok] autoEmptyTrash=0 written"
fi
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