Bootstrap tree for the media box Linux conversion
Autoinstall lays down a thin base (sshd, key, DHCP, Docker CE, /srv) and hands
off to this repo on first boot. Everything interesting stays in git so it is
reviewable and re-runnable, rather than frozen onto a USB nobody can diff.
Stages, all idempotent:
00-preflight asserts hardware/BIOS state, changes nothing. Catches a BIOS
update having silently re-enabled Secure Boot, which would stop
the NVIDIA DKMS module loading on a box with no keyboard.
10-secrets ADD-ONLY seeder for /srv/secrets/stacks.env. Never overwrites an
existing key. Verified against a pre-populated file: existing
values, unrelated keys, the operator tier and existing manifest
lines all survive byte-for-byte; a second run is a no-op.
20-cifs the 8 shares Plex actually uses (Share is excluded, it is not a
library root). \040 escaping, nofail + x-systemd.automount +
_netdev. Managed-block rewrite verified not to duplicate or to
drop the root fstab entry.
30-nvidia nvidia-driver-580 explicitly: 580 is the LAST branch supporting
Pascal, and the -open modules need Turing+. Pins against newer
branches. Not in late-commands because DKMS needs the installed
kernel, not the installer's.
40-shell-mcp builds the native MCP locally for amd64; refuses to finish
unless /sse returns 401 without a token.
50-plex run by hand: PLEX_CLAIM expires in 4 minutes. Refuses to start
against missing mounts and disables autoEmptyTrash, which with
read-write NAS credentials is the most dangerous default here.
shell-mcp was built on arm64 originally. It builds clean on amd64 (whole dep
tree resolves to prebuilt manylinux x86_64 wheels, no compiler needed), but
dependencies are now pinned - the original installed mcp/starlette/uvicorn
unpinned and starlette has since gone 1.x. Port moved to 8103 so NPM host 42
can simply be repointed, and the tool description now says media box rather
than arrsstack.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# 50-plex — deploy Plex. HUMAN-IN-THE-LOOP, run by hand, not from bootstrap.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Claiming is only required on the FIRST start. Re-running later without
|
||||
# PLEX_CLAIM is fine and will not re-claim.
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
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"
|
||||
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"
|
||||
else
|
||||
printf ' [FAIL] %s not mounted\n' "$mp"
|
||||
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"
|
||||
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
|
||||
else
|
||||
RENDER_GID="$(stat -c '%g' /dev/dri/renderD128)"
|
||||
echo " render node gid: $RENDER_GID"
|
||||
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"
|
||||
|
||||
# --- 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 "$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"
|
||||
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"
|
||||
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/<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
|
||||
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
|
||||
NEXT
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user