Skip to content

Media tier: Jellyfin — placement answers the terminal-VLAN problem #138

Closed

Description

@Gerrrt

Sub-issue of #95.

What and why

Jellyfin GPL-2.0 C#/deb/Docker — awesome-selfhosted README.md:1397.

Clients exist for Roku, Android TV, iOS and Kodi, which is the property that decides this category — a media server nobody's television can reach is not a media server.

Why this is on 40 and not 99

ADR-0008 treats this as the more interesting half of the placement decision, and it is worth restating because it is easy to undo by accident:

CasaBonita (40) is terminal by design and its televisions need a media server. Any design placing the media server elsewhere has to punch a rule through that terminal property.

Putting Jellyfin with the televisions means they reach it natively. The terminal property of VLAN 40 is preserved by placement rather than by exception — no inbound rule, no hole in the segment.

The corollary is that the only new rule is 50 → 40, so a workstation on Hicks can administer the NAS and the server. Directional, trusted into less trusted, consistent with every other rule in the estate.

What it needs

  • The NAS, per Plan and build the NAS on VLAN 40 #95 — capacity and redundancy are that issue's questions, not this one's.
  • Hardware transcoding. An N100 does QSV well; without it, one 4K transcode saturates the box. Worth confirming the client mix first, because direct play needs none of it.
  • Admin credentials in secrets/*.sops.yaml.
  • A decision on whether the library is backed up at all. ADR-0008 is explicit that this data is replaceable and its loss is "annoying rather than catastrophic" — so a deliberate no is a perfectly good answer, as long as it is deliberate and written down.

The failure-domain point worth keeping

ADR-0008's compensation for running two hosts:

a failure of the media box costs a film night, and a failure of the sensitive box costs a restore — never both at once.

Anything that later couples these two tiers gives that up. Worth remembering when something proposes to share a database, a proxy or an auth layer across both.

Which VLAN this touches

🟡 40 — CasaBonita (media)
🟠 50 — Hicks (trusted) — the administrative direction

Security review

  • Does not weaken the default-deny posture between VLANs — adds 50→40, authorised by ADR-0008; document it in docs/network.md and docs/security.md as it is created, per the warning in Write a superseding ADR: 0002 says two inter-VLAN rules, there are three #104. VLAN 40 stays terminal for inbound
  • Any new credential goes in secrets/*.sops.yaml
  • Any new device is added to docs/network.md with an OUI-truncated MAC — the NAS itself

Depends on

#95 (the NAS).


Note

Status, 2026-09-17. The stack is authored. It is not deployed.

The rule this issue asks for exists. The 50→40 pass was created on
2026-09-16 and verified in position from morpheus, as two rules rather
than one with a port list: Hicks reaches 10.0.40.30 on 443 and on 8096.
443 is not the 22 ADR-0016's table names — that port assumed a box
administered over SSH, and smaug runs TrueNAS
(ADR-0040).
docs/network.md and docs/security.md were both edited the same day, which is
what the security-review box above asked for. VLAN 40's Reaches column is
unchanged: every pass reaches in.

stacks/media/
exists
, authored ahead of the pool the way stacks/sensitive was authored
ahead of trinity. Jellyfin alone — ADR-0016 builds it alone and defers Plex
(#139) against a test nobody has run. Everything non-obvious in it was read off
the pinned image on 2026-09-16 rather than out of upstream's documentation:
non-root at 65534 with /config and /cache already 0:0 777 so no chown
sidecar is needed, read_only, cap_drop: ALL, the library mounted :ro, a
curl healthcheck keyed on status code only because a fresh instance answers
200 OK with the body Degraded, and mem_limit: 2g over a measured 65 MiB
idle RSS. No reverse proxy, deliberately: the clients are televisions, and a
proxy would add a hop, a certificate every television must trust, and a second
thing to be down.

RENDER_GID=107, read off smaug — stat -c '%g %G' /dev/dri/renderD128
returns 107 render.

The transcoding question this issue raised is two-thirds answered

This issue said hardware transcoding is "worth confirming". Two of the three
steps are confirmed and the third is not, and the distinction matters because
three-quarters of a condition met reads like a condition met:

  • ✅ The silicon — HD P630, Quick Sync present on Kaby Lake, Active Video: IGD
    set in the BIOS.
  • ✅ The kernel — a render node exists, which means a driver bound to the P630.
  • ❌ A container actually using it. Untested.

That last one is ADR-0040's reopen condition,
and this issue is where it gets tested — build-the-nas.md §6, after the pool.
If Quick Sync does not reach a container cleanly under TrueNAS, ADR-0040's
decision 2 reopens and the media stack leaves this repository for catalogue
apps. Getting it wrong never fails loudly: Jellyfin falls back to software
transcoding and one 4K stream saturates the box, which presents as "the film
stutters" rather than as an error. Check it before the library exists, not
after.

What is left

The two Exos X20 drives, the pool erebor, the erebor/media dataset and its
share — build-the-nas.md §1–§5 — and then §6 deploys this. The library-backup
decision this issue asks for is made: erebor/media is not backed up and
erebor/apps (Jellyfin's database and config) is, per §4, which is the
deliberate no this issue said would be a perfectly good answer.

Depends on #413, not #95, which
closed on 2026-09-04.

Activity

  1. added this to the Wave 3 — NAS milestone on Sep 11, 2026
  2. added
    seq/3Step 3 within its milestone; same number = can run in parallel
    on Sep 11, 2026
  3. added
    mediastacks/media on smaug (VLAN 40)
    seq/2Step 2 within its milestone; same number = can run in parallel
    and removed
    seq/3Step 3 within its milestone; same number = can run in parallel
    on Sep 17, 2026
  4. Gerrrt commented on Sep 17, 2026

    @Gerrrt
    OwnerAuthor

    Body updated: the 50→40 rule exists (as 443 and 8096), stacks/media/ is
    authored and measured, RENDER_GID=107, and the transcoding question this
    issue raised is two-thirds answered — the container half is ADR-0040's reopen
    condition and is still untested. See
    the build comment on #413.

    One thing found while writing that up and filed separately as
    #484: compose.yaml says the
    jellyfin-config volume is backed up, build-the-nas.md §4 says erebor/apps
    is backed up, and nothing backs either of them up — no entry in
    scripts/backup-volumes.sh and no sentinel. Worth fixing before the data
    exists rather than after.

  5. Gerrrt commented on Sep 19, 2026

    @Gerrrt
    OwnerAuthor

    2026-09-19 — deployed, and the device half of the reopen condition passes. stacks/media runs on smaug under TrueNAS's Docker since this morning (#522). Inside media-jellyfin:

    crw-rw---- 1 root 107 226, 128 renderD128
    uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup),107
    

    The render node is present and the process carries the render group, which is what devices: and group_add were written to do. What that does not prove is a transcode. ADR-0040's condition closes when Jellyfin's dashboard reports a hardware transcode on a played file, and that is being set up now — QSV in Playback → Transcoding, one file through the media share, played with the quality forced down.

    Also read today: everyone@ Read had to be added to erebor/media's ACL for this container to see the library at all. The SMB preset grants builtin_users and nothing else, and uid 65534 is in neither.

    Refs #138 #413

  6. Gerrrt commented on Sep 19, 2026

    @Gerrrt
    OwnerAuthor

    2026-09-19, later — the transcode passed. ADR-0040's reopen condition is closed.

    A 10-second 1080p H.264 clip, played in the browser at a forced 480p, produced this ffmpeg line inside media-jellyfin:

    -init_hw_device vaapi=va:,vendor_id=0x8086,driver=iHD -init_hw_device qsv=qs@va -hwaccel vaapi -hwaccel_output_format vaapi ... -codec:v:0 h264_qsv ... scale_vaapi=w=960:h=540 ... hwmap=derive_device=qsv
    Stream mapping: Stream #0:0 -> #0:0 (h264 (native) -> h264 (h264_qsv))
    frame=  300 ... speed=5.03x elapsed=0:00:00.76
    

    libva opened the iHD driver, the decode ran through VAAPI, the scale ran on the GPU and the encode was h264_qsv, at five times real time. That is the claim a device node in ls could not make, and it is made. Decision 2 stands; the stack stays on smaug. Recorded in #524.

    What this issue still wants, none of it blocking: a television playing something (the point of the placement), the admin credential's home — Jellyfin's admin is in the operator's password manager and this stack has no secrets/ file yet — and a way for anyone to put films on the share, which #523 found is not possible from a workstation today.

    Refs #138 #413 #523

  7. Gerrrt commented on Sep 19, 2026

    @Gerrrt
    OwnerAuthor

    Closing on #522 and #524.

    Jellyfin runs on smaug under TrueNAS's Docker; the 50 → 40 rule exists as two passes; and ADR-0040's reopen condition — the container half of hardware transcoding — passed on 2026-09-19: VAAPI decode on iHD, scale_vaapi, h264_qsv encode, five times real time. Decision 2 stands and the stack stays on smaug. The library-backup decision, a deliberate no, stands as written in stacks/media/README.md.

    The three residuals in the comment above each have a tracker now, which is what was keeping this open:

    Plex's deferral test — whether any screen on CasaBonita lacks a working Jellyfin client — is now live rather than blocked, and sits on #139.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmediastacks/media on smaug (VLAN 40)seq/2Step 2 within its milestone; same number = can run in parallel

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions