Full Plex migration: preserve everything, no rebuild
Reverses the forward-only decision. That call predated measuring the library; at 27 TB / 25,148 video items / 337 GB of generated preview cache, rebuilding costs 1-3 weeks of saturated gigabit and permanently loses every manual match fix across 23,493 episodes. Migration preserves watch history, resume points, collections, playlists, manual matches, artwork choices, added-at dates, the 337 GB cache, and server identity - so shared users stay invited, clients do not re-add, and there is no claim step at all. migration/remap.sql Four path columns remapped: media_parts.file (80,126), section_locations .root_path (11 -> 9), media_streams.url (14,837) and metadata_items.guid (9). Two formats, not one: backslash/UNC for the first two, file:// with %20 encoding for the last two - decoding those %20s would break every subtitle reference containing a space, which given share names like Radio Shows is most of them. A scan of all 80 text columns across 82 tables found SIX columns matching korval. Only four are paths. taggings.text is one row reading Dr. Korval, and metadata_items.summary is four Liaden Universe blurbs about Clan Korval. A bare REPLACE on the word would have corrupted a cast credit and four book summaries, so every statement is anchored to a path prefix. Proven against a synthetic database built from the real path shapes: 12/12, including both false positives surviving byte-identical. Also drops the Audio Books and Music Organized roots - configured as library roots but holding 0 files / 0 bytes. The consolidation had already happened; only the dead roots remained. migration/migrate-db.sh Runs the remap through Plex own SQLite build borrowed from the container image, keeps a .pre-remap rollback, asserts the counts moved 1:1 and the false positives did not, then stats 200 random remapped paths against the real filesystem. That last check is the one that matters - SQL running without error proves nothing. migration/gen-preferences.ps1 Plex live settings store on Windows is the REGISTRY, not Preferences.xml, and the two disagree here. Folds ~50 values into one Linux file, dropping Windows-only keys including the per-GPU limit keyed by 10de:1b81, the GTX 1070 PCI ID. Preserves MachineIdentifier and the online token, which is what keeps the server identity. The existing Preferences.xml is malformed anyway (duplicate allowedNetworks) and will not parse strictly. scripts/20-cifs.sh Eight shares down to six. Mount points now mirror the share names verbatim - /mnt/nas/Home Movies, space and all - because that reduces the remap to one uniform prefix substitution instead of eleven special cases. New requirement this creates: \040 escaping in BOTH fstab fields, not just the share name. Verified, plus a round-trip check that a remapped DB path lands under the generated mount point. plex/docker-compose.yml Six read-only NAS binds whose source path equals target path, so database, host and container agree with no translation. No PLEX_CLAIM. Phase 2 Media relocation present but commented. scripts/60-media-relocate.sh Post-soak, optional: moves the 337 GB cache to the 3TB ext4 disk so future growth (~28 MB per content-hour) stops eating the SSD. Plex does not support this, so the script forces generation on one title afterwards to prove writes survive the EXDEV boundary, and rollback is deleting a compose override. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+79
-35
@@ -1,58 +1,102 @@
|
||||
services:
|
||||
plex:
|
||||
# Pin the tag. The migrated database is from PMS 1.43.3.10828 and SQLite
|
||||
# schema upgrades are ONE-WAY — the server here must be equal or newer, and
|
||||
# you do not want a surprise release performing an unexpected schema
|
||||
# migration on build night. Set this to a concrete tag before first start.
|
||||
image: lscr.io/linuxserver/plex:latest
|
||||
container_name: plex
|
||||
restart: unless-stopped
|
||||
|
||||
# NOT OPTIONAL. Plex's local discovery (GDM), DLNA and the Plex-for-client
|
||||
# "found a local server" path all rely on broadcast traffic that a bridge
|
||||
# network silently eats. This is why Plex does not sit behind NPM the way
|
||||
# every other service here does — NPM proxy host 17 already points
|
||||
# plex.thewichersfamily.com at 10.0.1.20:32400 and needs no change.
|
||||
# NOT OPTIONAL. Plex's local discovery (GDM) and client auto-detection rely
|
||||
# on broadcast traffic that a bridge network silently eats. (DLNA is off on
|
||||
# this server, so discovery — not DLNA — is the reason.) This is also why
|
||||
# Plex does not sit behind NPM like everything else here: proxy host 17
|
||||
# already points plex.thewichersfamily.com at 10.0.1.20:32400 and needs no
|
||||
# change, because the IP does not move.
|
||||
network_mode: host
|
||||
|
||||
environment:
|
||||
- PUID=3000 # MUST match uid= on the CIFS mounts
|
||||
- PGID=3000 # MUST match gid= on the CIFS mounts
|
||||
- PUID=3000 # MUST equal uid= on the CIFS mounts
|
||||
- PGID=3000 # MUST equal gid= on the CIFS mounts
|
||||
- TZ=${TZ:-America/New_York}
|
||||
- VERSION=docker
|
||||
# PLEX_CLAIM is intentionally absent from this file. Claim tokens expire
|
||||
# four minutes after they are issued, so one can never be committed to a
|
||||
# repo, written to a USB, or baked into an image. It is injected at first
|
||||
# start by 50-plex.sh and never persisted.
|
||||
- PLEX_CLAIM=${PLEX_CLAIM:-}
|
||||
- ADVERTISE_IP=${PLEX_ADVERTISE_URL:-http://10.0.1.20:32400}
|
||||
# No PLEX_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 intact, clients not needing to re-add it.
|
||||
# Claiming would create a NEW server identity and throw that away.
|
||||
|
||||
devices:
|
||||
# Quick Sync. The i7-8700K's UHD 630 is the transcode target: no session
|
||||
# cap, and better HDR tone mapping than the Pascal-era NVENC on the 1070.
|
||||
# Quick Sync on the UHD 630. No session cap and better HDR tone mapping
|
||||
# than the Pascal-era NVENC on the 1070.
|
||||
- /dev/dri:/dev/dri
|
||||
|
||||
group_add:
|
||||
# The render node is root:render, and PGID 3000 is not in that group.
|
||||
# Resolved from the live host by 50-plex.sh — do not hardcode, the gid
|
||||
# differs between distro releases.
|
||||
# Render node is root:render and PGID 3000 is not in that group.
|
||||
# Resolved from the live host by 50-plex.sh — the gid differs across
|
||||
# distro releases, so do not hardcode it.
|
||||
- "${RENDER_GID:-993}"
|
||||
|
||||
volumes:
|
||||
# Config lives on the SSD, deliberately. The Plex SQLite database is the
|
||||
# most latency-sensitive thing on this box; it does not belong on the
|
||||
# 3TB spinner.
|
||||
- /srv/plex/config:/config
|
||||
# ---- Plex data directory -------------------------------------------
|
||||
# Whole directory on the SSD: database, Metadata/ (25 GB of posters) and
|
||||
# Media/ (337 GB of migrated preview cache). ~364 GB in ~440 GB usable.
|
||||
# Fully supported layout — no split, no bind-mount trickery.
|
||||
- type: bind
|
||||
source: /srv/plex/config
|
||||
target: /config
|
||||
|
||||
# NAS media, read-write per the account scope. Mounted read-only INTO the
|
||||
# container for every library that Plex has no business writing to.
|
||||
- /mnt/nas/audiobooks:/media/audiobooks:ro
|
||||
- /mnt/nas/education-videos:/media/education-videos:ro
|
||||
- /mnt/nas/health:/media/health:ro
|
||||
- /mnt/nas/home-movies:/media/home-movies:ro
|
||||
- /mnt/nas/media:/media/media:ro
|
||||
- /mnt/nas/music-organized:/media/music-organized:ro
|
||||
- /mnt/nas/pictures:/media/pictures:ro
|
||||
- /mnt/nas/radio-shows:/media/radio-shows:ro
|
||||
# ---- PHASE 2 (post-soak, optional) ----------------------------------
|
||||
# Relocate the 337 GB preview cache to the 3 TB ext4 disk so future
|
||||
# thumbnail growth (~28 MB per content-hour) stops consuming the SSD.
|
||||
# Long-form syntax deliberately: the target path contains spaces, which
|
||||
# the short "a:b" form handles badly. Docker orders mounts by target
|
||||
# depth, so this correctly lands inside the /config mount above.
|
||||
#
|
||||
# Plex does NOT support relocating this directory. Enable it only after
|
||||
# running scripts/60-media-relocate.sh, which performs the move and then
|
||||
# forces thumbnail generation on a single title to prove that writes
|
||||
# succeed across the filesystem boundary. Rollback is moving it back.
|
||||
#
|
||||
# - type: bind
|
||||
# source: /srv/data/plex-media
|
||||
# target: /config/Library/Application Support/Plex Media Server/Media
|
||||
|
||||
# ---- NAS media, mounted at the SAME paths the database stores -------
|
||||
# The remap rewrites \\korval\X -> /mnt/nas/X, so the container must see
|
||||
# exactly /mnt/nas/X. Six separate entries rather than one bind of
|
||||
# /mnt/nas, because a plain bind does not carry the submounts beneath it.
|
||||
# Read-only: Plex has no reason to write to the library, and the NAS
|
||||
# account is read-write.
|
||||
- type: bind
|
||||
source: /mnt/nas/media
|
||||
target: /mnt/nas/media
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: /mnt/nas/Radio Shows
|
||||
target: /mnt/nas/Radio Shows
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: /mnt/nas/Education Videos
|
||||
target: /mnt/nas/Education Videos
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: /mnt/nas/Health
|
||||
target: /mnt/nas/Health
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: /mnt/nas/Home Movies
|
||||
target: /mnt/nas/Home Movies
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: /mnt/nas/Pictures
|
||||
target: /mnt/nas/Pictures
|
||||
read_only: true
|
||||
|
||||
tmpfs:
|
||||
# Transcodes are throwaway. Keeping them in RAM saves a great deal of
|
||||
# SSD write wear. 4G of the box's 16G, capped so a pathological transcode
|
||||
# cannot pressure the rest of the system.
|
||||
# Matches TranscoderTempDirectory=/transcode in the generated
|
||||
# Preferences.xml. Transcodes are throwaway; keeping them in RAM saves
|
||||
# a great deal of SSD write wear. 4G of 16G, capped so a pathological
|
||||
# transcode cannot pressure the rest of the system.
|
||||
- /transcode:rw,size=4g,mode=1777
|
||||
|
||||
Reference in New Issue
Block a user