Skip to content

Host Facts (Debian 13)

Current host-specific values used by the storage and Compose configuration.

The public repository records hardware roles and reusable behavior, not the live deployment map. Credentials stay in ignored .env files. Addresses, hostnames, ports, interface names, disk serials, and by-id device paths stay in the ignored Ansible site.yml or on the host. Filesystem UUIDs remain in /etc/fstab and snapraid.conf on the host. The GIDs below are assigned by the OS at install time, so always re-read them rather than trusting a copy. See Security boundaries.

Disk addressing

sdX letters are not stable across reboots, and they differ from the old Unraid assignments. Always address disks by /dev/disk/by-id/ (model + serial). See storage-layout.md for the role table. Find the real links with ls -l /dev/disk/by-id/.

Role Class Filesystem Notes
parity 28 TB SATA HDD XFS (created at cutover) outside the mergerfs pool
disk1–disk2 28 TB SATA HDD XFS mergerfs branches
disk3–disk5 12 TB SATA HDD XFS mergerfs branches
cache 2 TB NVMe btrfs (reused Unraid pool, never mkfs) appdata + in-progress usenet downloads (SAB incomplete/ staging)
OS 1 TB SATA SSD ext4 (/) + EFI + swap separate disk, outside the array/cache layout
  • The 1 TB SATA SSD is the Debian OS disk.
  • The cache NVMe exposes two nvme-… by-id names (one with a trailing _1). Both resolve to the same partition. Mount whichever …-part1 link actually exists on the host, and never reformat it.

Ownership (media tree)

Unraid wrote all media as numeric UID 99 / GID 100 (nobody:users). To keep permissions and hardlinks intact, containers run with PUID=99 / PGID=100. Do not chown the multi-TB tree; matching the IDs is free, rewriting ownership on that much data is not. The admin login user is a member of group users (100), which gives it group-level read access to the media.

GPU (Plex hardware transcoding)

  • Intel Arc A380, bare metal (drivers, not passthrough).
  • Driver stack: firmware-intel-graphics (non-free-firmware) plus intel-media-va-driver-non-free (non-free), which provides the iHD VA-API driver. Verify with vainfo (expect HEVC Main10 decode + encode, AV1, and VAEntrypointVideoProc for HDR/DV tone mapping) and intel_gpu_top.
  • Render node: /dev/dri/renderD128 (crw-rw---- root render), plus card0.
  • Containers need the device mapped and the render/video groups added:
devices:
  - /dev/dri:/dev/dri
group_add:
  - "992"   # render - re-verify: getent group render
  - "44"    # video  - re-verify: getent group video

Use numeric GIDs, because the container's /etc/group has no render group. On this host render=992 and video=44; read them fresh with getent group render video after any reinstall.

Docker

  • data-root: default /var/lib/docker on the OS SSD (not moved to the cache). The OS SSD has plenty of room for image layers, and it keeps Docker's storage off the reused btrfs cache. Decided before the first image pull. Note the running engine (Docker 29) uses the containerd image store, so docker info reports the storage driver as overlayfs (the containerd snapshotter), not overlay2. Side effect: cAdvisor cannot read per-container stats on this setup, which is why per-container CPU/memory comes from the docker-stats textfile collector instead.
  • Stale systemd scope on docker restart / stop+start: fails with Unit docker-<id>.scope was already loaded or has a fragment file. Root cause: systemd 257 has not yet garbage-collected the just-stopped container's transient scope when runc recreates a scope with the same name (derived from the container ID, which restart reuses). runc >= 1.1.6 already resets failed units; this GC race variant is still open upstream (systemd#39141, same class as crun#1138 and podman#24010). Prevention: restart by recreating, i.e. docker compose up -d --force-recreate <svc>. A recreate gets a new container ID and therefore a new scope name, so the collision cannot happen. If you use docker restart anyway, run sudo systemctl daemon-reload (or systemctl reset-failed 'docker-*.scope') first; the same commands recover after a hit. Do NOT switch to the cgroupfs driver to dodge this: monitoring relies on systemd cgroup paths.
  • A daemon restart orphans socket bind-mounts, and here it silently kills log ingestion. When dockerd restarts it recreates /run/docker.sock with a new inode. Any long-running container that bind-mounts the socket keeps pointing at the old, now-dead inode until it is recreated. Only socket-proxy-monitoring mounts the socket here; when it goes stale its HAProxy backend returns 503 to Alloy, so Alloy stops tailing every container and Loki goes quiet while all metrics still look healthy. This bit us on 2026-07-18: an unattended docker-ce upgrade (which is intentional, see the APT section) restarted the daemon and log ingestion was dead for ~29h before anyone noticed. Two mitigations are in place: the restart-socket-consumers.service systemd unit (PartOf=docker.service, installed by the docker_host role as DOCKER-7) recreates the socket proxy and bounces Alloy whenever the daemon restarts, and the LokiLogIngestionStalled Prometheus alert fires if received log lines hit zero for 20 minutes as a backstop.

APT

Debian 13 (Trixie), classic /etc/apt/sources.list. Components enabled: main contrib non-free non-free-firmware (needed for the Intel firmware and media driver).

Access

Admin access is key-only SSH on a non-default port as the sudo login user (passwordless sudo). Root SSH login and password auth are disabled. This hardening was Phase E of the migration and has been in place since the cutover; if you ever rebuild, verify key login works before turning the rest of it on.