Files
oikos/infrastructure/media-permissions.md
Claudio 685d6a9d48 Decommission LXC 109 (syncthing)
Container had been stopped since 2026-04-21 and was never re-enabled.
pct destroy 109 --purge cleaned up vm-109-disk-0 on local-lvm and the
config file. /mnt/library/syncthing subtree was already empty at the
time of destruction and is retained as an empty dir (no real data to
migrate or back up).

- README.md, containers/index.md: removed row, moved to "recently
  destroyed" table
- hosts/hubris.md: dropped from /mnt/library subtree list, updated
  containers/index summary line, added changelog entry
- infrastructure/media-permissions.md: dropped from membership table
  and onboarding example, generalised pct-exec gotcha hostname,
  added changelog
- vms/100-zimaos.md: dropped from "existing fleet" enumeration
- containers/102-nfs-export.md: dropped from bind-mount sibling list
  (7 LXCs now, not 8)
- containers/109-syncthing.md: deleted

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:12:32 +02:00

7.1 KiB

Media permissions — media GID 10000

Standard for any LXC reading/writing /mnt/library on hubris. Applied 2026-04-20.

Standard

Every LXC that mounts /mnt/library participates in a shared media group with GID 10000. Shared subtrees are owned by that group with the setgid bit (drwxrwsr-x, mode 2775), so new files auto-inherit the right group regardless of which container wrote them.

Why

/mnt/library is a cross-container storage pool. *arr writes, jellyfin reads, mulita scans, paperless ingests. Without a shared group, each container sees files as nobody:nogroup (unprivileged) or www-data (privileged 1:1) and the permission web collapses into one-off chmods. GID 10000 bridges privileged and unprivileged containers.

Onboarding a new LXC

  1. pct set <id> -mp0 /mnt/library,mp=/mnt/library (if not already mounted).
  2. Inside the container:
    groupadd -g 10000 media
    usermod -aG media <service-user>   # for every user that needs library access
    
  3. If the container is unprivileged (check pct config <id> | grep unprivileged), append this idmap block to /etc/pve/lxc/<id>.conf (back up first):
    lxc.idmap: u 0 100000 65536
    lxc.idmap: g 0 100000 10000
    lxc.idmap: g 10000 10000 1
    lxc.idmap: g 10001 110001 55535
    
    Then pct stop <id> && pct start <id>.
  4. For systemd services running with User=root (not typical), add a drop-in with SupplementaryGroups=media. Systemd skips initgroups() for User=root.
  5. pct exec sessions don't get supplementary groups (no initgroups). Use sudo -i or su - <user> inside the container to verify membership interactively. Real services use initgroups and work correctly.

State snapshot

Host

  • Group media GID 10000 exists.
  • /etc/subgid has root:100000:65536 AND root:10000:1 (second line required for unprivileged LXCs to receive GID 10000).
  • Shared subtrees owned :media mode 2775 (drwxrwsr-x, setgid):
    • movies, tv, music, anime, podcasts — jellyfin libraries
    • audiobooks, audiobookshelf-metadata, books, comics — audiobookshelf / booklore
    • downloads — *arr stack output
    • images — photoprism / immich / mulita
    • roms — emu frontends
    • syncthing — empty subtree, retained for archaeology (LXC 109 destroyed 2026-05-14)
  • Container-specific subtrees intentionally not migrated (keep their own owner:group):
    • documents (paperless, www-data:www-data 750)
    • homecloud (nextcloud — its own permission model, easy to break)
    • marimo (marimo venv) — LXC since destroyed; review whether subtree still serves a purpose
    • notes, sophia (single-container use); heaper — orphaned data subtree (LXC since destroyed 2026-05-14, 224 MiB retained)
    • repos (owner UID 102 GID 105 from inside gitea — don't touch)

LXCs with media-group membership

ID Name Priv Media-group members
101 jellyfin unpriv + idmap jellyfin
103 paperless priv www-data
104 gitea priv www-data, gitea
105 apps priv www-data
114 nextcloud priv www-data
119 sophia priv www-data
120 mule-images priv www-data
122 arriman priv www-data, audiobookshelf, radarr, sonarr, lidarr, prowlarr, qbittorrent, bazarr, jellyseerr, mylar, jackett, overseerr, plex, arr

Some entries from earlier snapshots — 100 (arr-yunohost), 107 (marimo), 109 (syncthing), 110 (photoprism), 112 (immich), 116 (heaper) — referenced LXCs that have since been destroyed. See containers/index.

Config backups: /root/101.conf.bak.*, /root/109.conf.bak.* (109 destroyed 2026-05-14).

Gotchas

  • apps (105) is a Docker host. Adding media to the LXC alone is not enough for Docker containers inside. Each Docker container needs its GID passed in explicitly: --group-add 10000 or user: "<uid>:10000" in compose. Booklore, audiobookshelf-in-docker, etc. need this per-container.
  • pct exec does NOT run initgroups. So pct exec <id> -- id shows only the primary group. For interactive verification, use pct exec <id> -- sudo -i -u root id or su - <user> -c id. Real systemd services work fine.
  • systemd User=root skips initgroups — explicit SupplementaryGroups=media drop-in needed.
  • pct restore or template rebuilds wipe in-container group membership and unprivileged-LXC idmap blocks. Re-apply from this page.
  • /etc/subgid must retain both root:100000:65536 AND root:10000:1. Dropping the second breaks startup of any unprivileged LXC with the idmap block.
  • *arr "Set Permissions" options can override the setgid inheritance by explicitly chown'ing files. Leave those off, or set the group to media. Relevant to Sonarr/Radarr/qBittorrent on arriman (122).
  • Nextcloud files under /mnt/library/homecloud are deliberately NOT in the media group. NC manages its own permission model. See nextcloud (114).
  • *arr stack on arriman required MEDIACENTER_GID=10000 (not 13000) in .env because s6-setuidgid only honors the primary PGID; group_add: doesn't propagate. See arriman (122).
  • Hubris host
  • All container pages list whether they're in the standard

Changelog

2026-05-14 — LXC 109 (syncthing) destroyed

Removed the syncthing row from the membership table and the syncthing-as-User=root example from the onboarding section. /mnt/library/syncthing subtree was already empty and retained as an empty dir.

2026-05-14 — LXC 116 (heaper) destroyed

Removed the heaper row from the LXC membership table and noted the orphaned /mnt/library/heaper subtree (224 MiB retained). See host changelog.

2026-04-28 — wiki entry created

Initial documentation.

2026-04-26 — MEDIACENTER_GID fix on arriman (122)

qBit was erroring every torrent with "Permission denied" because MEDIACENTER_GID=13000 was set as a supplementary GID via group_add:. Changed to 10000 (primary GID); fix described above is now standard.

2026-04-20 — standard rolled out

GID 10000 hostgroup, idmap blocks for unprivileged LXCs, setgid 2775 on shared subtrees, media membership for service users in every participating LXC.