# Plex Migration — Windows → Linux, nothing lost > ## ✅ EXECUTED 2026-08-01 — M0 through M4 complete in one day > > Plex is live on Linux with its original identity (`eb69ec8a…`), watch state > intact, direct play verified with a real session. In soak (M5). This file is > now the **record of the procedure**; deviations that a future re-run must > know about: > > 1. **VACUUM is mandatory after the remap.** The no-backslash gate greps the > raw DB file and hits deleted rows lingering on SQLite **free pages**. > Plain sqlite3/python cannot open the DB (custom `icu_root` collation — > do NOT fake it); run VACUUM with **Plex's own SQLite** via > `docker run --entrypoint`. > 2. **Expect phantom rows from dead eras.** 1,715 backslash rows survived > the remap legitimately: Sync+ cache from old D:/E:/G: drive letters > (+ orphan `media_items`), 6 rows for a dead `\\tesla` NAS, and 115 UNC > blobs in `download_queue_items.decision_result`. All deleted; 3 real > theme-music paths re-pathed. The remap itself was correct — these > predate the current library roots. > 3. **Verify staged copies with `rsync -an --delete --stats`** (0 transferred > / 0 deleted), never `du -sb` equality — directory apparent sizes differ > across filesystems. > 4. **First boot needs `systemctl start --no-block`** in cloud-init runcmd — > `enable` alone misses the boot it runs in (fixed in `user-data`). > 5. Post-go-live, the Plex image pin was **reversed** (Mike, 2026-08-02): > `:latest` + watchtower daily 05:00 with ntfy. Watchtower needs > `DOCKER_API_VERSION=1.44` against Engine 29. > > Cleanup 2026-08-02: staging dir + `.pre-remap`/`.pre-syncfix` sidecars > deleted. Rollback net = NAS `media_pc` backup + Plex's scheduled DB backups. 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. **Revised 2026-07-31:** no Windows-side robocopy, `D:` itself is the backup until everything on it is copied to the NAS `media_pc` share from Linux, and the 337 GB `Media/` cache lives on the 3 TB ext4 disk — **not the SSD** — from day one. `D:` is therefore formatted **before** Plex first starts, not after the soak. ## 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. Calibre (25.4 GB) and `Nikola iPad` (1.2 GB) also survive — they move to the NAS `media_pc` share. Nothing on `D:` is abandoned. 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 (~27 GB minus `Media/`) | | 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 | DB + `Metadata/` on the SSD · `Media/` (337 GB) on the 3 TB ext4 disk | | Backup | Full copy on NAS `media_pc` (write share), made from Linux before `D:` is formatted | | NAS mounts | 6 library shares read-only + `media_pc` read-write, at `/mnt/nas/` | --- ## M0 — Prepare, while Windows is still running **Disable automatic trash emptying first.** The registry confirms `autoEmptyTrash = 1`. The new NAS account is read-only on the library shares, so Plex can no longer delete *files* — but a missing mount at scan time would still purge library *records*. Settings → Library → untick *Empty trash automatically after every scan*. Generate the Linux settings file from the registry. **Write it to `D:`, not `C:` — `C:` is the install target and gets wiped:** ```powershell powershell -ExecutionPolicy Bypass -File .\gen-preferences.ps1 -Out D:\mbx\Preferences.xml [xml](Get-Content -Raw D:\mbx\Preferences.xml) | Out-Null; 'XML OK' ``` **Stop Plex Media Server completely** — tray icon → Exit, then confirm no `Plex Media Server.exe` remains. This is the database's final clean shutdown; a copy taken mid-WAL is inconsistent and you will not find out until much later. Do not start Plex on Windows again after this. There is **no robocopy here**. `D:` is untouched by the install and is the backup until M2 copies it off. > **GATE M0** — `D:\mbx\Preferences.xml` exists, parses, and contains > `MachineIdentifier`. Plex is fully exited. --- ## M1 — Install Linux Follow the main `runbook.md` phases 2–8 (BIOS — the iGPU toggle is mandatory, Quick Sync is the transcode device — then USB, install, first boot, NAS credentials and the seven mounts, shell-mcp cutover, `vainfo`). The autoinstall targets Disk 0 by serial `1808AE802176` and leaves the 3 TB disk untouched. > **GATE M1** — runbook gates 4–8 all pass. In particular: all seven mounts > `[ok]`, the six library shares refuse writes, `media_pc` accepts them, and > `vainfo` lists H.264/HEVC entrypoints. --- ## M2 — Copy everything off `D:` Mount the NTFS disk read-only and copy all of it to the NAS write share: ```bash DEV=/dev/$(lsblk -dno NAME,SERIAL | awk '$2=="WD-WCC7K3JAEDR3"{print $1}') sudo mkdir -p /mnt/dwin sudo mount -t ntfs3 -o ro "${DEV}1" /mnt/dwin # partition number per lsblk # 1) Full PMS backup — excludes match the old robocopy list sudo rsync -a --info=progress2 \ --exclude='Cache/' --exclude='Codecs/' --exclude='Drivers/' \ --exclude='Crash Reports/' --exclude='Logs/' --exclude='Updates/' \ "/mnt/dwin/Plex/Local Data/Plex Media Server/" \ "/mnt/nas/media_pc/backups/mediabox/PMS/" # 2) The generated settings file sudo rsync -a "/mnt/dwin/mbx/" "/mnt/nas/media_pc/backups/mediabox/mbx/" # 3) Everything else worth keeping — nothing on D: is abandoned sudo rsync -a --info=progress2 "/mnt/dwin/Calibre/" "/mnt/nas/media_pc/Calibre/" sudo rsync -a --info=progress2 "/mnt/dwin/Nikola iPad/" "/mnt/nas/media_pc/Nikola iPad/" ``` `Codecs` and `Drivers` are **Windows binaries** — carrying them to Linux breaks transcoding. `Cache` is regenerable. Expect this to take a long time — `Metadata/` and `Media/` are millions of small files, and small-file writes over CIFS are slow. Plan on hours, possibly overnight. It only has to happen once. > **GATE M2** — rsync exits 0 on all four copies. Spot-check on the NAS that > `backups/mediabox/PMS/Plug-in Support/Databases/`, `Metadata/` and `Media/` > are present and that `Calibre/` and `Nikola iPad/` file counts look right. > **Do not proceed to M3 on a copy you have not listed.** *Optional rehearsal:* before M3, run `migrate-db.sh` on a scratch copy of the database from `/mnt/dwin` and start a throwaway Plex against it on an `--internal` Docker network (no internet, so it cannot collide with the server identity). Browse it and confirm counts and watch state. This proves the migration while `D:` is still intact. --- ## M3 — Stage, format `D:`, place the data `Media/` cannot stay on the SSD (337 GB, Mike's call 2026-07-31) and cannot live on the NAS (network cost + RAID space on disposable cache). Its home is the 3 TB disk — which currently holds the source data. So: stage through the SSD, format, move. ```bash # 1) Restore the config (everything EXCEPT Media/) to its real home on the SSD sudo install -d -o 3000 -g 3000 /srv/plex/config sudo rsync -a --info=progress2 --exclude='Media/' \ "/mnt/dwin/Plex/Local Data/Plex Media Server/" \ "/srv/plex/config/Library/Application Support/Plex Media Server/" \ --exclude='Cache/' --exclude='Codecs/' --exclude='Drivers/' \ --exclude='Crash Reports/' --exclude='Logs/' --exclude='Updates/' sudo install -o 3000 -g 3000 -m 0644 /mnt/dwin/mbx/Preferences.xml \ "/srv/plex/config/Library/Application Support/Plex Media Server/Preferences.xml" # 2) Stage Media/ on the SSD — temporary, ~337 GB into ~400 GB free. Check first: df -h / sudo rsync -a --info=progress2 \ "/mnt/dwin/Plex/Local Data/Plex Media Server/Media/" \ /srv/plex-media-staging/ # 3) Unmount and format D: — BY SERIAL, never /dev/sdX sudo umount /mnt/dwin lsblk -o NAME,SERIAL,SIZE,FSTYPE DEV=/dev/$(lsblk -dno NAME,SERIAL | awk '$2=="WD-WCC7K3JAEDR3"{print $1}') echo "$DEV" # sanity-check this before continuing sudo wipefs -a "$DEV" sudo parted -s "$DEV" mklabel gpt mkpart primary ext4 0% 100% sudo mkfs.ext4 -L mediabox-data "${DEV}1" UUID=$(blkid -s UUID -o value "${DEV}1") echo "UUID=$UUID /srv/data ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab sudo mkdir -p /srv/data && sudo systemctl daemon-reload && sudo mount /srv/data # 4) Move Media/ to its permanent home (fast local move, no NAS round trip) sudo install -d -o 3000 -g 3000 /srv/data/plex-media sudo rsync -a --info=progress2 --remove-source-files /srv/plex-media-staging/ /srv/data/plex-media/ sudo find /srv/plex-media-staging -type d -empty -delete sudo chown -R 3000:3000 /srv/plex/config /srv/data/plex-media ``` The staging copy through the SSD is deliberate: local disk-to-disk at full SATA speed instead of a 337 GB round trip through gigabit CIFS. The NAS copy from M2 is the *backup*, not the restore source — unless something here fails, in which case it is exactly that. > **GATE M3** — `df -h /srv/data` shows ~2.7 TB, survives a reboot. > `/srv/data/plex-media` and the config tree are owned `3000:3000`. The > staging directory is gone and the SSD is back to mostly free. --- ## M4 — Remap and first start ```bash 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 RENDER_GID=$(stat -c '%g' /dev/dri/renderD128) \ /srv/mediabox-bootstrap/scripts/50-plex.sh ``` `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. **As executed:** the no-backslash gate additionally required deleting phantom Sync+ cache rows from dead drive-letter eras, 6 dead `\\tesla` rows and 115 `download_queue_items` UNC blobs, then **VACUUM with Plex's own SQLite** (see the banner at the top) before the raw-file grep came back clean. No claim token. The server already has its identity. The compose bind-mounts `/srv/data/plex-media` over `.../Plex Media Server/Media`, so the migrated thumbnails are in place from the first start. `scripts/60-media-relocate.sh` is **no longer needed** — `Media/` is born on `/srv/data`. What still matters from it is the verification: after first start, force thumbnail generation on one title and confirm the write lands under `/srv/data/plex-media` — that is the `EXDEV` cross-filesystem question, which can only be tested, not reasoned about. > **GATE M4** — `migrate-db.sh` exits 0 with `200/200 sampled files resolve on > disk`. `plex.thewichersfamily.com` loads and shows *Magi Plex* with Movies > 1,360 and 23,493 episodes; watch history and Continue Watching 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. > The one-title thumbnail test writes under `/srv/data/plex-media`. --- ## M5 — Soak One to two weeks. 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". **Leave the NAS `media_pc` backup in place** — it is the durable copy of the old server. Prune it later if the space is ever needed; that is a deliberate decision, not a cleanup step. --- ## If something goes wrong Before M3 step 3 (the format), `D:` is intact and everything is reversible on the data side — Windows itself is gone after M1, but a reinstall from the USB takes ~20 minutes and every copy still exists. After the format, the NAS `media_pc` backup is the recovery source: it outlives every step here. (The on-box `.pre-remap`/`.pre-syncfix` sidecars were deleted 2026-08-02 after the soak-start all-clear.)