Problem: node and cross-cutting narratives lived at the repo root
(containers/, vms/, infrastructure/, host .md files), interleaved with the
machine-readable substrate.
Change:
- Move containers/ -> knowledge/wiki/containers/, vms/ -> knowledge/wiki/vms/,
infrastructure/ -> knowledge/wiki/infrastructure/, hosts/{hubris,strong}.md ->
knowledge/wiki/hosts/, infrastructure/references/ -> knowledge/sources/references/,
GLOSSARY.md -> knowledge/GLOSSARY.md.
- Add knowledge/{index.md,log.md,sources/index.md} scaffolding.
- Rewrite all relative links repo-wide via a path-resolving mapper (inbound +
outbound + between-moved-files), including .hermes/, runbooks, operations,
investigations, plans, README, AGENTS.
- Repoint inventory.yaml doc_page fields and regenerate hosts/*.yaml (which
embed doc_page); update oikos/gen-topology.py output path, candidate doc
paths, and footer links; update code-comment doc paths.
Substrate untouched in place: inventory.yaml, hosts/*.yaml (regenerated,
idempotent), oikos/ code, mcp/, secrets/, bin/.
Verification:
- Logical broken-link set identical to pre-move baseline (net 128 -> 127; the
topology regen fixed one, introduced none). Remaining are pre-existing refs
to destroyed/archived nodes, out of scope for this move.
- gen-topology.py --check exit 0 (in sync); cards carry knowledge/wiki/ doc paths.
- build_host_files.py idempotent; all inventory doc_page targets resolve.
- MCP contract verified: get_page/search_docs/get_changelog resolve moved pages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.5 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
pct set <id> -mp0 /mnt/library,mp=/mnt/library(if not already mounted).- Inside the container:
groupadd -g 10000 media usermod -aG media <service-user> # for every user that needs library access - If the container is unprivileged (check
pct config <id> | grep unprivileged), append this idmap block to/etc/pve/lxc/<id>.conf(back up first):Thenlxc.idmap: u 0 100000 65536 lxc.idmap: g 0 100000 10000 lxc.idmap: g 10000 10000 1 lxc.idmap: g 10001 110001 55535pct stop <id> && pct start <id>. - For systemd services running with
User=root(not typical), add a drop-in withSupplementaryGroups=media. Systemd skipsinitgroups()forUser=root. pct execsessions don't get supplementary groups (no initgroups). Usesudo -iorsu - <user>inside the container to verify membership interactively. Real services useinitgroupsand work correctly.
State snapshot
Host
- Group
mediaGID 10000 exists. /etc/subgidhasroot:100000:65536ANDroot:10000:1(second line required for unprivileged LXCs to receive GID 10000).- Shared subtrees owned
:mediamode2775(drwxrwsr-x, setgid):movies,tv,music,anime,podcasts— jellyfin librariesaudiobooks,audiobookshelf-metadata,books,comics— audiobookshelf / grimmorydownloads— *arr stack outputimages— photoprism / immich / mulitaroms— emu frontendssyncthing— 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 purposenotes,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 |
| 130 | grimmory | priv | Docker container uses GROUP_ID=10000 env var (linuxserver pattern) — no in-LXC group needed |
| 132 | rclone | priv | read-only mount; runs as root → reads all subtrees. No media group needed |
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) and grimmory (130) are Docker hosts. Adding
mediato the LXC alone is not enough for Docker containers inside. Each Docker container needs its GID passed in explicitly:--group-add 10000,user: "<uid>:10000", orGROUP_ID=10000(linuxserver images) in compose. Grimmory, audiobookshelf-in-docker, etc. need this per-container. pct execdoes NOT run initgroups. Sopct exec <id> -- idshows only the primary group. For interactive verification, usepct exec <id> -- sudo -i -u root idorsu - <user> -c id. Real systemd services work fine.- systemd
User=rootskips initgroups — explicitSupplementaryGroups=mediadrop-in needed. pct restoreor template rebuilds wipe in-container group membership and unprivileged-LXC idmap blocks. Re-apply from this page./etc/subgidmust retain bothroot:100000:65536ANDroot: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/homecloudare 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.envbecause s6-setuidgid only honors the primary PGID;group_add:doesn't propagate. See arriman (122).
Related
- 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.