diff --git a/dankserver/plex/quadlet/plex.container b/dankserver/plex/quadlet/plex.container index 6f61e82..ae41905 100644 --- a/dankserver/plex/quadlet/plex.container +++ b/dankserver/plex/quadlet/plex.container @@ -1,14 +1,13 @@ [Unit] -Description=Plex Media Server (quadlet, userns=host) +Description=Plex Media Server (quadlet, rootless fleet identity) After=network-online.target Wants=network-online.target [Container] Image=lscr.io/linuxserver/plex:1.43.3.10896-cb3ebc72d-ls322 ContainerName=plex -UserNS=host -Environment=PUID=997 -Environment=PGID=984 +Environment=PUID=0 +Environment=PGID=0 Environment=TZ=America/Los_Angeles Volume=/var/lib/plexmediaserver:/config Volume=/mnt/entropy:/mnt/entropy:ro diff --git a/dankserver/tautulli/quadlet/tautulli.container b/dankserver/tautulli/quadlet/tautulli.container index 653cf13..955d14e 100644 --- a/dankserver/tautulli/quadlet/tautulli.container +++ b/dankserver/tautulli/quadlet/tautulli.container @@ -1,14 +1,13 @@ [Unit] -Description=Tautulli (quadlet, userns=host) +Description=Tautulli (quadlet, rootless fleet identity) After=network-online.target Wants=network-online.target [Container] Image=lscr.io/linuxserver/tautulli:v2.18.1-ls242 -UserNS=host Network=dankserver_appnet:alias=tautulli -Environment=PUID=1000 -Environment=PGID=1000 +Environment=PUID=0 +Environment=PGID=0 Environment=TZ=America/Los_Angeles Volume=/home/dank/Containers/tautulli:/config PublishPort=8181:8181 diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh index 3793de2..cfefecd 100755 --- a/scripts/deploy-remote.sh +++ b/scripts/deploy-remote.sh @@ -672,10 +672,15 @@ deploy_plex() { if ! systemctl --user is-active -q plex.service; then # First cutover: retire the native package unit BEFORE starting the # container — both cannot own 32400. Mask (not disable): on these hosts - # `systemctl disable` deletes the unit file itself; mask keeps an - # instant byte-safe rollback while nothing is signed off yet. + # `systemctl disable` deletes the unit file itself; mask keeps a + # one-command rollback while nothing is signed off yet. run_privileged systemctl stop plexmediaserver.service run_privileged systemctl mask plexmediaserver.service + # Rootless identity model: container root maps to the dank user + # (uid 1000) — real uid 997 is not mappable rootless. Adopt the data + # root into the fleet convention (jellyfin et al. run as 1000:1000). + # Rollback chowns it back to 997:984 before restarting the native unit. + run_privileged chown -R 1000:1000 /var/lib/plexmediaserver fi systemctl --user restart plex.service # Readiness gate: /identity answers MediaContainer XML once PMS is up; diff --git a/serverAppConfigs-docs/Dankserver/Plex.md b/serverAppConfigs-docs/Dankserver/Plex.md index a4c1bd9..847e51b 100644 --- a/serverAppConfigs-docs/Dankserver/Plex.md +++ b/serverAppConfigs-docs/Dankserver/Plex.md @@ -18,7 +18,7 @@ public traffic can be fronted by Caddy like every other vhost. | Image | `lscr.io/linuxserver/plex:1.43.3.10896-cb3ebc72d-ls322` (pinned; verified == the ghcr `latest` manifest digest at pin time) | | Data root | `/var/lib/plexmediaserver` bind-mounted **in place** as `/config` (RW) — the migration moved zero bytes | | Media | `/mnt/entropy` mounted at the same absolute path **read-only** — every library path in the PMS database stayed valid, zero re-scan | -| Identity | `UserNS=host` (quadlet key for `--userns=host`) + `PUID=997 PGID=984` — container writes land owned 997:984, byte-compatible with the former native unit | +| Identity | fleet rootless model — container root maps to the `dank` user (1000:1000); the deployer chowns the data root on first cutover | | Port | `32400/tcp` published on `0.0.0.0` — zero client reconfiguration | | Network | `dankserver_appnet`, alias `plex`; Tautulli reaches it at `http://plex:32400` | | GPU | none by decision — CPU decode only; the RTX 3090 stays dedicated to `frank-default-small` | @@ -30,18 +30,21 @@ public traffic can be fronted by Caddy like every other vhost. The migration moved zero bytes: the container bind-mounts the native data root where it already lived, and the media archive re-mounts at `/mnt/entropy` inside the container, so every absolute library path in the -PMS database keeps resolving. `UsernsMode=host` + `PUID=997/PGID=984` makes -the container's file identity indistinguishable from the old service's — -no chown, no re-scan. Rollback is byte-safe at any time before decommission. +PMS database keeps resolving. The one metadata change: rootless podman +cannot map the real host uid 997, so the fleet model applies — container +root runs as the `dank` user, and the deployer adopts the data root with +`chown -R 1000:1000` on first cutover (rollback chowns it back). ## Cutover & rollback - First deploy stops and **masks** `plexmediaserver.service` (never - `disable` — on these hosts `disable` deletes the unit file), then starts - the quadlet; the deployer blocks until `/identity` answers on 32400. + `disable` — on these hosts `disable` deletes the unit file), chowns + `/var/lib/plexmediaserver` to `1000:1000`, then starts the quadlet; the + deployer blocks until `/identity` answers on 32400. - Rollback lever while masked: `systemctl --user stop plex.service && sudo - systemctl unmask plexmediaserver.service && sudo systemctl start - plexmediaserver.service`. + chown -R 997:984 /var/lib/plexmediaserver && sudo systemctl unmask + plexmediaserver.service && sudo systemctl start plexmediaserver.service` + (ownership-only churn — zero bytes ever moved). - Decommission (`sudo apt-get -y purge plexmediaserver`) only after explicit operator sign-off. diff --git a/serverAppConfigs-docs/Dankserver/Tautulli.md b/serverAppConfigs-docs/Dankserver/Tautulli.md index 390d8cf..47839d7 100644 --- a/serverAppConfigs-docs/Dankserver/Tautulli.md +++ b/serverAppConfigs-docs/Dankserver/Tautulli.md @@ -16,7 +16,7 @@ it; it talks to PMS over the appnet at `http://plex:32400`). | Runtime | podman quadlet, `tautulli.service` (user manager) — migrated from snap `snap.tautulli.tautulli.service` | | Image | `lscr.io/linuxserver/tautulli:v2.18.1-ls242` (pinned; verified == the ghcr `latest` manifest digest at pin time) | | Data root | `/home/dank/Containers/tautulli` bind-mounted as `/config` (fresh at cutover — the snap's `/var/snap/tautulli/common` was empty) | -| Identity | `UserNS=host` (quadlet key for `--userns=host`) + `PUID=1000 PGID=1000` | +| Identity | fleet rootless model — container root maps to the `dank` user (1000:1000) | | Port | `8181/tcp` published on `0.0.0.0` | | Network | `dankserver_appnet`, alias `tautulli` | | PMS URL | `http://plex:32400` — seeded into `config.ini` by the deployer on first start | diff --git a/tests/dankserver_quadlet.bats b/tests/dankserver_quadlet.bats index 93e5a5e..6e4c26c 100755 --- a/tests/dankserver_quadlet.bats +++ b/tests/dankserver_quadlet.bats @@ -67,14 +67,16 @@ load helpers grep -q '^PublishPort=8181:8181$' "$REPO_ROOT/dankserver/tautulli/quadlet/tautulli.container" || return 1 } -@test "plex keeps the native unit's identity (UserNS=host, PUID=997, PGID=984)" { - # Zero-chown ownership contract: /var/lib/plexmediaserver is bind-mounted - # in place, so every file the container writes must land owned 997:984 — - # byte-identical to the former plexmediaserver.service identity. UserNS= - # is the quadlet v1 key (UsernsMode= is compose syntax and the generator - # hard-fails the whole unit on it); pin the exact keys. +@test "plex runs the fleet identity model (PUID=0/PGID=0, no userns remap)" { + # Rootless podman cannot map the real host uid 997 (subuid range is + # 100000:65536), so the fleet model applies: container root maps to the + # dank user and the deployer chowns /var/lib/plexmediaserver to + # 1000:1000 on first cutover; rollback chowns back to 997:984. Pin both + # keys and the ABSENCE of any userns key — UsernsMode= is compose syntax + # (the v1 generator hard-fails the whole unit on it) and UserNS= would + # remap the container away from the fleet identity. unit="$REPO_ROOT/dankserver/plex/quadlet/plex.container" - grep -q '^UserNS=host$' "$unit" || return 1 - grep -q '^Environment=PUID=997$' "$unit" || return 1 - grep -q '^Environment=PGID=984$' "$unit" || return 1 + grep -q '^Environment=PUID=0$' "$unit" || return 1 + grep -q '^Environment=PGID=0$' "$unit" || return 1 + ! grep -qE '^(UserNS|UsernsMode)=' "$unit" || return 1 }