Files

277 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<exact share name>` |
---
## 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.)