# Plex Migration — Windows → Linux, nothing lost Supersedes the earlier "forward-only, fresh Plex" decision. That call was made before the library was measured. At **27 TB, 25,148 video items and 337 GB of already-generated preview cache**, rebuilding fresh costs one to three weeks of saturated NAS link and permanently loses every manual match fix. Migration is the cheaper and lower-risk option. ## What survives Watch history and resume points across all items, play counts, ratings, collections, playlists, every manual match correction, poster and artwork choices, added-at dates (so Recently Added stays meaningful), the 337 GB preview cache, and **the server identity** — shared users stay invited, clients do not need to re-add the server, and there is no claim step at all. Plex's own article calls cross-OS moves "possible but not officially supported and not covered here." That is a documentation gap, not a technical barrier. The database is platform-independent; what Plex declines to help with is mapping the settings, which `gen-preferences.ps1` does. ## Facts everything below depends on | | | |---|---| | Source | PMS **1.43.3.10828**, Windows 10, data dir `D:\Plex\Local Data` | | Server identity | `machineIdentifier eb69ec8a…` — preserved, not regenerated | | Data directory | ~374 GB total → ~364 GB after pruning | | Library | 1,360 movies · 23,493 episodes · 295 other video · 20,798 audio | | Path rows | 80,126 `media_parts` · 14,837 `media_streams` · 11 → 9 roots | | Target layout | Whole data dir on the 500 GB SSD (~75 GB free) | | NAS mounts | 6 shares at `/mnt/nas/` | --- ## M0 — Prepare, while Windows is still running **Disable automatic trash emptying first.** This is step 1 of Plex's own procedure and it matters more here than usual: the registry confirms `autoEmptyTrash = 1`, and the new NAS account is read-write. A missing mount at scan time would mean Plex deletes library records it has permission to delete. Settings → Library → untick *Empty trash automatically after every scan*. Generate the Linux settings file from the registry: ```powershell powershell -ExecutionPolicy Bypass -File .\gen-preferences.ps1 -Out C:\mbx\Preferences.xml [xml](Get-Content -Raw C:\mbx\Preferences.xml) | Out-Null; 'XML OK' ``` **Stop Plex Media Server completely** — tray icon → Exit, then confirm no `Plex Media Server.exe` remains. The database must not be open when copied, or the WAL will be mid-flight and the copy inconsistent. Copy the data directory to the NAS. Skip what must not travel: ```powershell robocopy "D:\Plex\Local Data\Plex Media Server" "\\korval\Share\backups\mediabox\PMS" ` /E /COPY:DAT /R:2 /W:5 /MT:16 ` /XD "Cache" "Codecs" "Drivers" "Crash Reports" "Logs" "Updates" ` /LOG:C:\mbx\pms-copy.log /TEE ``` `Codecs` and `Drivers` are **Windows binaries** — copying them to Linux breaks transcoding. `Cache` is regenerable. Excluding them also trims the copy. > **GATE M0** — robocopy reports 0 failures. `Plug-in Support\Databases\`, > `Metadata\` and `Media\` are all present on the NAS. **Do not proceed on a > copy you have not listed.** --- ## M1 — Dry run, before anything is destroyed The whole migration can be rehearsed while Windows still works. This is the step that makes the rest safe. On arrsstack (or any Linux host with the NAS mounted), take **only** the database and Preferences — a couple of GB, not 364 — and run the remap: ```bash sudo ./migrate-db.sh /path/to/copy/com.plexapp.plugins.library.db ``` Then start a throwaway Plex against it **with no internet access**, so it cannot contact plex.tv and collide with the live server that shares its identity: ```bash docker network create --internal plex-dryrun docker run --rm -d --name plex-dryrun --network plex-dryrun \ -v /path/to/dryrun-config:/config \ -v "/mnt/nas/media:/mnt/nas/media:ro" \ lscr.io/linuxserver/plex:latest ``` > **GATE M1** — browse the unclaimed server directly and confirm: Movies shows > **1,360**, TV Shows shows **23,493 episodes**, watch state and resume points > are present, and a title's file path resolves. If this passes, the migration > is proven. If it fails you have lost an afternoon and nothing else. --- ## M2 — Install Linux Follow the main `runbook.md` phases 2–5 (BIOS, USB, install, first boot). The autoinstall targets Disk 0 by serial `1808AE802176` and leaves the 3 TB disk untouched. Write the NAS credentials and bring up the six mounts: ```bash printf 'username=mediabox\npassword=\ndomain=WORKGROUP\n' \ | sudo tee /etc/cifs/korval.cred >/dev/null sudo chmod 600 /etc/cifs/korval.cred sudo systemctl daemon-reload sudo /srv/mediabox-bootstrap/scripts/20-cifs.sh --verify ``` > **GATE M2** — all six mounts report `[ok]`, and `stat -c '%u:%g' /mnt/nas/media` > returns `3000:3000`. Wrong ownership means Plex hits permission errors on > every file. --- ## M3 — Restore and remap ```bash sudo install -d -o 3000 -g 3000 /srv/plex/config sudo rsync -a --info=progress2 \ "/mnt/nas/Share/backups/mediabox/PMS/" \ "/srv/plex/config/Library/Application Support/Plex Media Server/" sudo install -o 3000 -g 3000 -m 0644 /path/to/Preferences.xml \ "/srv/plex/config/Library/Application Support/Plex Media Server/Preferences.xml" sudo /srv/mediabox-bootstrap/migration/migrate-db.sh \ "/srv/plex/config/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db" sudo chown -R 3000:3000 /srv/plex/config ``` `migrate-db.sh` keeps a `.pre-remap` rollback copy, asserts that no Windows paths remain, that the counts moved 1:1, that the "Dr. Korval" tag and the four Liaden book summaries are **untouched**, and finally stats 200 random remapped paths against the real filesystem. > **GATE M3** — `migrate-db.sh` exits 0 with the sample check reporting > `200/200 sampled files resolve on disk`. That last line is the one that > matters; SQL running without error proves nothing. --- ## M4 — First start ```bash sudo RENDER_GID=$(stat -c '%g' /dev/dri/renderD128) \ /srv/mediabox-bootstrap/scripts/50-plex.sh ``` No claim token. The server already has its identity. > **GATE M4** — `plex.thewichersfamily.com` loads and shows *Magi Plex* with > your libraries. Watch history and Continue Watching are intact. A remote > client that already had this server still sees it without re-adding. > Play something and confirm `intel_gpu_top` shows the video engine working. --- ## M5 — Soak One to two weeks on the supported layout. Confirm mounts survive a NAS reboot, the GPU survives a kernel update, and the box comes back **unattended** from a real power cut. That last one is the acceptance test for "headless". --- ## M6 — Relocate the preview cache (optional, post-soak) Only after M5. Moves the 337 GB `Media/` directory to the 3 TB ext4 disk so future thumbnail growth (~28 MB per content-hour) stops consuming the SSD. ```bash sudo /srv/mediabox-bootstrap/scripts/60-media-relocate.sh ``` Plex does **not** support relocating this directory. The script performs the move, enables the bind mount, and then forces thumbnail generation on a single title to prove writes succeed across the filesystem boundary — the one failure mode that cannot be reasoned about, only tested. Rollback is moving it back. --- ## If something goes wrong Before M2, everything is reversible — Windows still boots. After M2 the recovery path is *reinstall*, not *roll back*: the USB rebuilds the box in about twenty minutes and the data directory is still on the NAS. The database copy on the NAS outlives every step here, and `migrate-db.sh` leaves a `.pre-remap` copy beside every database it touches.