initial commit

This commit is contained in:
2026-06-30 13:51:06 -04:00
commit d529284141
10 changed files with 439 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
# /etc/group.containers
# Source of truth for container service groups. Sync to all Docker VMs
# and the NFS server using sync-container-users.sh. Append-only — add new
# lines here when adding containers, never renumber existing ones.
#
# Primary per-app groups mirror passwd.containers UIDs (2000-2099).
# Shared groups start at 3000 for cross-container access.
# format: name:x:GID:secondary,members
2fauth:x:2000:
audiobookshelf:x:2001:
authelia:x:2002:
bazarr:x:2003:
cronjobs:x:2004:
dashy:x:2005:
dingus-archiver:x:2006:
finance-app:x:2007:
firefly3:x:2008:
gitea:x:2009:
gotify:x:2010:
immich:x:2011:
it-tools:x:2012:
jellyfin:x:2013:
joplin:x:2014:
kiwix:x:2015:
music-orchestrator:x:2016:
nginxproxy:x:2017:
ombi:x:2018:
picoshare:x:2019:
prowlarr:x:2020:
qbittorrent:x:2021:
radarr:x:2022:
romm:x:2023:
sonarr:x:2024:
tandoor_recipes:x:2025:
vault:x:2026:
#
# Shared groups (cross-container access, GID range 3000+)
# mediapipeline: *arr apps + qbittorrent + jellyfin share the media/download
# folders (e.g. /var/NFSFolder/AppData/media). Ombi is request-only (talks
# to APIs, doesn't touch the filesystem) so it's excluded.
mediapipeline:x:3000:bazarr,prowlarr,qbittorrent,radarr,sonarr,jellyfin
#
# finance-app and dingus-archiver are isolated custom apps with no shared
# access — their primary groups above (2007, 2006) already cover them.
# No secondary group needed unless that changes.

View File

@@ -0,0 +1,34 @@
# /etc/passwd.containers
# Source of truth for container service accounts. Sync to all Docker VMs
# and the NFS server using sync-container-users.sh. Append-only — add new
# lines here when adding containers, never renumber existing ones.
#
# UID range reserved: 2000-2099 (container service accounts)
# format: name:x:UID:GID::/nonexistent:/usr/sbin/nologin
2fauth:x:2000:2000::/nonexistent:/usr/sbin/nologin
audiobookshelf:x:2001:2001::/nonexistent:/usr/sbin/nologin
authelia:x:2002:2002::/nonexistent:/usr/sbin/nologin
bazarr:x:2003:2003::/nonexistent:/usr/sbin/nologin
cronjobs:x:2004:2004::/nonexistent:/usr/sbin/nologin
dashy:x:2005:2005::/nonexistent:/usr/sbin/nologin
dingus-archiver:x:2006:2006::/nonexistent:/usr/sbin/nologin
finance-app:x:2007:2007::/nonexistent:/usr/sbin/nologin
firefly3:x:2008:2008::/nonexistent:/usr/sbin/nologin
gitea:x:2009:2009::/nonexistent:/usr/sbin/nologin
gotify:x:2010:2010::/nonexistent:/usr/sbin/nologin
immich:x:2011:2011::/nonexistent:/usr/sbin/nologin
it-tools:x:2012:2012::/nonexistent:/usr/sbin/nologin
jellyfin:x:2013:2013::/nonexistent:/usr/sbin/nologin
joplin:x:2014:2014::/nonexistent:/usr/sbin/nologin
kiwix:x:2015:2015::/nonexistent:/usr/sbin/nologin
music-orchestrator:x:2016:2016::/nonexistent:/usr/sbin/nologin
nginxproxy:x:2017:2017::/nonexistent:/usr/sbin/nologin
ombi:x:2018:2018::/nonexistent:/usr/sbin/nologin
picoshare:x:2019:2019::/nonexistent:/usr/sbin/nologin
prowlarr:x:2020:2020::/nonexistent:/usr/sbin/nologin
qbittorrent:x:2021:2021::/nonexistent:/usr/sbin/nologin
radarr:x:2022:2022::/nonexistent:/usr/sbin/nologin
romm:x:2023:2023::/nonexistent:/usr/sbin/nologin
sonarr:x:2024:2024::/nonexistent:/usr/sbin/nologin
tandoor_recipes:x:2025:2025::/nonexistent:/usr/sbin/nologin
vault:x:2026:2026::/nonexistent:/usr/sbin/nologin

View File

@@ -0,0 +1,45 @@
---
# roles/container_isolation/tasks/jerick_access.yml
#
# jerick needs read-write access across every app folder for admin/
# maintenance purposes, without weakening isolation between apps from
# each other. Achieved by making jerick a secondary member of every
# app's primary group, combined with 770 permissions on app folders
# (see nfs_folders.yml) — owner: full access, group (incl. jerick):
# full access, others: none.
- name: Check whether jerick exists on this host (read-only, does not create the account)
ansible.builtin.getent:
database: passwd
key: "{{ jerick_user }}"
register: jerick_lookup
failed_when: false
- name: Warn if jerick user does not exist on this host
ansible.builtin.debug:
msg: >-
WARNING: user '{{ jerick_user }}' was not found on this host.
This role does not create jerick's account (assumed to be a
pre-existing human user, not a service account). Create it first,
then re-run this role so jerick gets added to the app groups below.
when: jerick_lookup.failed | default(false)
- name: Add jerick to every app's primary group
ansible.builtin.user:
name: "{{ jerick_user }}"
groups: "{{ item.name }}"
append: true
loop: "{{ container_apps }}"
loop_control:
label: "{{ jerick_user }} -> {{ item.name }}"
when: not (jerick_lookup.failed | default(false))
- name: Add jerick to every shared group
ansible.builtin.user:
name: "{{ jerick_user }}"
groups: "{{ item.name }}"
append: true
loop: "{{ shared_groups }}"
loop_control:
label: "{{ jerick_user }} -> {{ item.name }}"
when: not (jerick_lookup.failed | default(false))

View File

@@ -0,0 +1,9 @@
---
# roles/container_isolation/tasks/main.yml
- name: Sync container service groups and users (all hosts)
import_tasks: sync_users.yml
- name: Add jerick to every app and shared group (all hosts)
import_tasks: jerick_access.yml

View File

@@ -0,0 +1,49 @@
---
# roles/container_isolation/tasks/sync_users.yml
#
# Creates each app's primary group + user, and each shared group, using
# Ansible's built-in group/user modules instead of hand-rolled file
# appends — this is idempotent and safe to re-run, and avoids the
# malformed-/etc/passwd risk of manual editing.
- name: Create primary group for each container app
ansible.builtin.group:
name: "{{ item.name }}"
gid: "{{ item.uid }}"
state: present
loop: "{{ container_apps }}"
loop_control:
label: "{{ item.name }} (gid {{ item.uid }})"
- name: Create shared (secondary) groups
ansible.builtin.group:
name: "{{ item.name }}"
gid: "{{ item.gid }}"
state: present
loop: "{{ shared_groups }}"
loop_control:
label: "{{ item.name }} (gid {{ item.gid }})"
- name: Create service user for each container app
ansible.builtin.user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
group: "{{ item.name }}"
shell: /usr/sbin/nologin
home: /nonexistent
create_home: false
system: true
state: present
loop: "{{ container_apps }}"
loop_control:
label: "{{ item.name }} (uid {{ item.uid }})"
- name: Add app users to their shared groups
ansible.builtin.user:
name: "{{ item.0.name }}"
groups: "{{ item.1.name }}"
append: true
loop: "{{ container_apps | product(shared_groups) | list }}"
loop_control:
label: "{{ item.0.name }} -> {{ item.1.name }}"
when: item.0.name in item.1.members

View File

@@ -0,0 +1,58 @@
---
# roles/container_isolation/vars/main.yml
#
# Single source of truth for container service accounts. Mirrors
# files/passwd.containers and files/group.containers — keep in sync if
# you edit one, edit the other. Append-only: never renumber existing
# UIDs/GIDs once folders have been chowned on the NFS server.
jerick_user: jerick
# Every container service account. uid == primary gid for each.
# jerick is added as a secondary member of every app's primary group
# in the group sync task, giving him rw access (770) without weakening
# isolation between apps.
container_apps:
- { name: "2fauth", uid: 2000 }
- { name: "audiobookshelf", uid: 2001 }
- { name: "authelia", uid: 2002 }
- { name: "bazarr", uid: 2003 }
- { name: "cronjobs", uid: 2004 }
- { name: "dashy", uid: 2005 }
- { name: "dingus-archiver", uid: 2006 }
- { name: "finance-app", uid: 2007 }
- { name: "firefly3", uid: 2008 }
- { name: "gitea", uid: 2009 }
- { name: "gotify", uid: 2010 }
- { name: "immich", uid: 2011 }
- { name: "it-tools", uid: 2012 }
- { name: "jellyfin", uid: 2013 }
- { name: "joplin", uid: 2014 }
- { name: "kiwix", uid: 2015 }
- { name: "music-orchestrator", uid: 2016 }
- { name: "nginxproxy", uid: 2017 }
- { name: "ombi", uid: 2018 }
- { name: "picoshare", uid: 2019 }
- { name: "prowlarr", uid: 2020 }
- { name: "qbittorrent", uid: 2021 }
- { name: "radarr", uid: 2022 }
- { name: "romm", uid: 2023 }
- { name: "sonarr", uid: 2024 }
- { name: "tandoor_recipes", uid: 2025 }
- { name: "vault", uid: 2026 }
# Shared secondary groups for cross-container access. "members" lists
# app names from container_apps that need to be added to this group
# IN ADDITION to their own primary group. jerick is added automatically
# to every shared group as well as every per-app primary group — see
# tasks/sync_users.yml.
shared_groups:
- name: mediapipeline
gid: 3000
members:
- bazarr
- prowlarr
- qbittorrent
- radarr
- sonarr
- jellyfin