Add Jellyfin as a commented-out Plex alternative - #57
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds an optional Jellyfin Compose service, registers Jellyfin in the dashboard, centralizes state-aware launch URLs, adds dashboard tests, implements Bazarr YAML credential discovery, and documents setup and compatibility details. ChangesJellyfin variant
Bazarr credential discovery
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Operator
participant DockerCompose
participant Jellyfin
participant Dashboard
Operator->>DockerCompose: Enable Jellyfin and disable Plex
DockerCompose->>Jellyfin: Start the jellyfin container
Dashboard->>Jellyfin: Check service availability on port 8096
Jellyfin-->>Dashboard: Report service state
sequenceDiagram
participant Dashboard
participant DockerConfig
participant BazarrDiscovery
Dashboard->>DockerConfig: Read bazarr/config/config.yaml
DockerConfig-->>BazarrDiscovery: Return YAML content
BazarrDiscovery->>BazarrDiscovery: Extract auth.apikey and general.base_url
BazarrDiscovery-->>Dashboard: Return discovered credentials or waiting state
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
docs/superpowers/plans/2026-08-06-jellyfin-variant.md (3)
202-218: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd language identifiers to the environment code fences.
Static analysis reports MD040 at Line [202] and Line [211]. Use
dotenvor another valid language identifier after both opening fences.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/plans/2026-08-06-jellyfin-variant.md` around lines 202 - 218, Add a valid language identifier, preferably dotenv, to both opening environment-variable code fences surrounding the Plex configuration in the Jellyfin variant plan, resolving the MD040 reports while leaving the configuration content unchanged.Source: Linters/SAST tools
81-87: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse a portable scratch directory.
Line [81] hard-codes an absolute path from one development environment. Use
mktemp -dand a cleanup trap so other contributors can run this validation step without editing the plan.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/plans/2026-08-06-jellyfin-variant.md` around lines 81 - 87, Update the scratch-directory setup in the validation commands around SCRATCH to create a portable temporary directory with mktemp -d, and add a cleanup trap that removes it on exit. Preserve the existing copy, sed transformation, and docker compose validation steps while eliminating the hard-coded development-specific path.
84-87: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winRestrict the uncommenting rewrite to the Jellyfin block.
The
sedexpression runs over the entire copied file. Any unrelated commented section with the same indentation can also become active. Bound the substitution between the Jellyfin header and the monitoring header.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/plans/2026-08-06-jellyfin-variant.md` around lines 84 - 87, The sed rewrite should only uncomment lines within the Jellyfin section, bounded by the Jellyfin header and the monitoring header. Update the command while preserving the existing handling of Jellyfin service lines and leaving all prose and unrelated commented sections unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CLAUDE.md`:
- Around line 74-76: Update the host-network inventory entry near the
architecture notes to state that Plex or optional Jellyfin may use host
networking, one at a time, matching the commented service description near the
Jellyfin note.
In `@docs/superpowers/plans/2026-08-06-jellyfin-variant.md`:
- Around line 62-71: Update the validation setup around the .env generation
block to avoid copying over or deleting the user's .env. Create a uniquely named
temporary environment file, append the CI values there, pass it explicitly via
--env-file to validation commands, and add a trap to remove only that temporary
file on success or failure.
In `@docs/superpowers/specs/2026-08-06-jellyfin-variant-design.md`:
- Around line 59-64: Update
docs/superpowers/specs/2026-08-06-jellyfin-variant-design.md lines 59-64 to
state that Jellyfin adds no environment variables while preserving
RADARR_API_KEY and SONARR_API_KEY as post-first-boot exceptions not consumed by
Compose. Update docs/superpowers/plans/2026-08-06-jellyfin-variant.md lines
11-18 to remove the universal claim that every .env.example variable is consumed
by Compose, while retaining the existing exception contract.
- Around line 15-20: Make the Jellyfin swap valid without a Plex service by
removing or replacing the tautulli.depends_on: plex dependency and documenting
any required Tautulli handling. Apply the corresponding change in
docs/superpowers/specs/2026-08-06-jellyfin-variant-design.md (15-20),
docs/superpowers/plans/2026-08-06-jellyfin-variant.md (37-44),
docker-compose.yml (20-26), README.md (147-151), and CLAUDE.md (74-81), keeping
Plex as the default and Jellyfin as the commented-out alternative.
In `@README.md`:
- Line 151: Update the Jellyfin URL in the setup instructions to render the host
placeholder literally as code, wrapping the complete http://<host>:8096 address
in backticks or replacing the placeholder with HOST.
- Around line 149-151: Update the Jellyfin swap instructions to explicitly stop
and remove the existing Plex container, unblock port 8096 if necessary, then
enable Jellyfin before running docker compose up -d. Apply the corresponding
guidance in README.md (149-151), docker-compose.yml (20-22),
docs/superpowers/specs/2026-08-06-jellyfin-variant-design.md (15-18),
docs/superpowers/plans/2026-08-06-jellyfin-variant.md (38-40), and CLAUDE.md
(74-77); each site requires the cleanup-and-enable instruction.
---
Nitpick comments:
In `@docs/superpowers/plans/2026-08-06-jellyfin-variant.md`:
- Around line 202-218: Add a valid language identifier, preferably dotenv, to
both opening environment-variable code fences surrounding the Plex configuration
in the Jellyfin variant plan, resolving the MD040 reports while leaving the
configuration content unchanged.
- Around line 81-87: Update the scratch-directory setup in the validation
commands around SCRATCH to create a portable temporary directory with mktemp -d,
and add a cleanup trap that removes it on exit. Preserve the existing copy, sed
transformation, and docker compose validation steps while eliminating the
hard-coded development-specific path.
- Around line 84-87: The sed rewrite should only uncomment lines within the
Jellyfin section, bounded by the Jellyfin header and the monitoring header.
Update the command while preserving the existing handling of Jellyfin service
lines and leaving all prose and unrelated commented sections unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f5a6e077-210f-4a09-8ccb-badac337ad28
📒 Files selected for processing (7)
.env.exampleCLAUDE.mdREADME.mddashboard/server/src/services.tsdocker-compose.ymldocs/superpowers/plans/2026-08-06-jellyfin-variant.mddocs/superpowers/specs/2026-08-06-jellyfin-variant-design.md
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds Jellyfin as an opt-in (commented-out) alternative to Plex, updates the dashboard’s service catalog to recognize it, and documents swap steps + companion compatibility caveats.
Changes:
- Added a commented-out
jellyfinservice block todocker-compose.ymlfor a manual Plex↔Jellyfin swap. - Added Jellyfin to the dashboard server’s static
SERVICEScatalog. - Documented the swap and limitations in
README.md,.env.example, andCLAUDE.md(plus added spec/plan docs).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/superpowers/specs/2026-08-06-jellyfin-variant-design.md | New spec describing the Jellyfin swap approach and compatibility constraints. |
| docs/superpowers/plans/2026-08-06-jellyfin-variant.md | New implementation plan detailing steps/commands to apply the change set. |
| docker-compose.yml | Adds a commented Jellyfin service block to swap in for Plex. |
| dashboard/server/src/services.ts | Adds a Jellyfin entry to the dashboard service catalog. |
| README.md | Documents “Using Jellyfin instead of Plex” with swap steps + compatibility table. |
| CLAUDE.md | Adds an architecture note about the Jellyfin swap and Plex-only companions. |
| .env.example | Adds a comment pointing Jellyfin users to the compose block/README (no new vars). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| | Works with Jellyfin as-is | Plex-only (won't work against Jellyfin) | | ||
| |---------------------------|------------------------------------------| | ||
| | Radarr, Sonarr, Prowlarr, Bazarr | Tautulli (Plex analytics) | | ||
| | Transmission | Watchlistarr (syncs the *Plex* watchlist) | | ||
| | Seerr (supports a Jellyfin backend) | Kometa / Plex Meta Manager | | ||
| | | Maintainerr | |
There was a problem hiding this comment.
Not reproducing this. The rows are | | Kometa / Plex Meta Manager | — a single leading pipe followed by an empty first cell, which is a valid 2-column row. No || appears in the file (grep -n "^||" README.md is empty).
| SCRATCH="/tmp/claude-1000/-home-ongo-Desktop-projects-AutoPlexx/455cc4a1-500d-4c6a-8a27-2296ec5f36d5/scratchpad" | ||
| mkdir -p "$SCRATCH" | ||
| cp docker-compose.yml "$SCRATCH/docker-compose.jellyfin.yml" | ||
| # Uncomment ONLY the jellyfin service lines (the `# ` / `# jellyfin:` forms), | ||
| # leaving the `# ----`/prose comment lines alone. | ||
| sed -i -E 's/^ # (jellyfin:)/ \1/; s/^ # (.*)$/ \1/' "$SCRATCH/docker-compose.jellyfin.yml" | ||
| docker compose -f "$SCRATCH/docker-compose.jellyfin.yml" --env-file .env config --quiet && echo "JELLYFIN BLOCK OK" |
There was a problem hiding this comment.
Fixed in 65f022e. The validation steps now use mktemp -d with a cleanup trap, and pass a throwaway env file via --env-file instead of writing the repo's .env — which also covers CodeRabbit's separate finding that cp .env.example .env / rm -f .env would clobber a contributor's real credentials.
| @@ -0,0 +1,104 @@ | |||
| # Jellyfin as a Plex alternative | |||
|
|
|||
| **Issue:** #56 ("JellyFin Variant") | |||
There was a problem hiding this comment.
Fixed in 65f022e — "JellyFin" → "Jellyfin" in the issue reference line.
`tautulli` declared `depends_on: - plex`, and compose rejects a project
whose depends_on names an undefined service. So step 1 of the documented
swap — comment out `plex:` — made every compose command fail:
service "tautulli" depends on undefined service "plex":
invalid compose project
Not a degraded Tautulli: `config`, `up -d` and `down` all failed, for all
25 services. The dependency bought nothing anyway, since Plex is
host-network and Tautulli is on monitoring_network, so compose can
neither link nor meaningfully order them.
Verified by reconstructing the swap: `docker compose config` went from
exit 1 to exit 0, resolving 27 services with `jellyfin` and no `plex`.
Also corrects the compatibility table, which listed Seerr as working
"as-is". Seerr does support a Jellyfin backend, but the media server is
chosen during its setup and doesn't follow the compose swap — so a user
who onboarded against Plex would have had requests silently going
nowhere.
Surfaces Jellyfin in the README beyond the one collapsed block: a badge,
a Media Server table row, a Why bullet, and a note on the claim-token
prerequisite. The compatibility section is promoted from <details> to a
real heading because GitHub generates anchors only from headings, so the
new links to #using-jellyfin-instead-of-plex would otherwise be dead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The catalog lists Jellyfin, which ships commented out of compose, so every default (Plex) install rendered a Media tile reading "Not installed" whose Open affordance linked to :8096 and connection-refused. Three of the four link sites decided openability from `port === null` alone, which can't distinguish "publishes no UI" from "isn't here". CommandSearch had it worse than the tiles: an absent service was a keyboard-navigable result, so Enter opened a dead tab. Adds `launchUrl()` as the single place that decision is made, returning null for both reasons, and routes Launcher, Sidebar, CommandSearch and Header's Request button through it. Header already had the check inline and now shares the helper. `down` still yields a URL on purpose — the container exists and the user may be about to start it. Only `absent` suppresses the link. CommandSearch's `uiless` bucket becomes `unopenable` since it now holds two different reasons, and its footer reads "nothing to open" rather than "no web UI", which would have been wrong for an uninstalled service that does publish a port. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
dashboard/web/src/components/CommandSearch.tsx (1)
240-249: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDo not show “No matching service” when unopenable matches exist.
If a query matches only an absent or UI-less service,
openable.lengthis zero andunopenable.lengthis positive. The menu then shows bothNo matching serviceandAlso matched, nothing to open: .... Render the no-match row only when both arrays are empty.Proposed fix
- {openable.length === 0 && ( + {openable.length === 0 && unopenable.length === 0 && (🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@dashboard/web/src/components/CommandSearch.tsx` around lines 240 - 249, Update the no-match row rendering in the CommandSearch component so “No matching service” appears only when both openable and unopenable arrays are empty. Preserve the existing unopenable notice when unopenable matches exist.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@dashboard/web/src/components/CommandSearch.tsx`:
- Around line 240-249: Update the no-match row rendering in the CommandSearch
component so “No matching service” appears only when both openable and
unopenable arrays are empty. Preserve the existing unopenable notice when
unopenable matches exist.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e74c459f-289e-4c9a-805c-e2d90960fbf5
📒 Files selected for processing (8)
CLAUDE.mdREADME.mddashboard/web/src/app/Header.tsxdashboard/web/src/app/Sidebar.tsxdashboard/web/src/components/CommandSearch.tsxdashboard/web/src/types.tsdashboard/web/src/views/Launcher.tsxdocker-compose.yml
🚧 Files skipped from review as they are similar to previous changes (2)
- docker-compose.yml
- README.md
Running the dashboard against a real host showed the previous commit's rule was too broad. Suppressing every `absent` service assumed absent means "not installed", but a catalog entry also goes absent when its container is simply renamed. On a host running plexms, transmission-vpn and grafana-grafana-1, five services reported absent while up and serving, and lost working launch links. Adds `optional: true` in the catalog, set on plex and jellyfin — the two halves of the swap, where exactly one is uncommented and the other's port genuinely leads nowhere. Only those lose a link when absent. Everything else keeps it: an absent Radarr is a real problem, and hiding its link would hide the problem too. Also fixes a regression this rule introduced. `buildReport` reports every service absent on a cold start with the socket proxy unreachable, so suppression keyed on `absent` alone would strip both media links exactly when the launcher matters most — a dead upstream blanking the page, which this app does not do. `stateKnown` threads the report's `reachable` flag down so absent only counts when Docker was actually reached. Gives the web workspace a test suite; it had no test script at all, so none of this behavior was covered. That meant extracting the pure ranking logic to search.ts, since anything importing the component tree reaches import.meta.glob in ServiceIcon, which node --test cannot evaluate. 50 server + 16 web tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Code reviewFound 1 issue:
AutoPlexx/dashboard/web/src/app/Header.tsx Lines 97 to 102 in e2ade2d 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Two regressions from the previous commit's narrowing of `launchUrl`. RequestButton had delegated its own absence check to `launchUrl`, so narrowing suppression to `optional` services silently un-hid it: Seerr isn't optional, so an absent Seerr yielded a live href, the null guard passed, and the only other branch tests `down` rather than `absent`. The result was an active Request link to a dead :5055 where previously no control was shown at all. "Can I link to this?" and "is this service actually here?" are different questions, and the second now has its own predicate — `isMissing()` — rather than being inferred from a null `launchUrl`. `launchUrl` returning a URL for an absent non-optional service is correct and deliberate; the Launcher tile keeps its link so a renamed container stays visible as a problem. The header shortcut is the one place that should step aside. Second, `stateKnown` was derived from `reachable`, which conflates the two failure modes `buildReport` is careful to distinguish. An outage after a successful poll returns the last good report — real states, just stale — while only the cold path has never observed anything. Keying off `reachable` meant every transient blip re-linked every absent optional service for the duration. The report now carries `statesKnown` explicitly so the client stops inferring it: true whenever the states were observed, false only on the cold path. Both are covered: docker.test.ts now asserts statesKnown on each failure mode, and types.test.ts pins the two questions apart. 50 server + 18 web. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…writes
Bazarr's panel could not work on any install. `discoverArr` handled it
alongside Sonarr/Radarr/Prowlarr and read `/discover/bazarr/config.xml`,
but Bazarr is Python rather than .NET and writes no such file anywhere in
its tree. Discovery therefore always missed, leaving the integration
permanently `waiting` behind the hint "Start Bazarr and open it once" —
advice that could never help, since Bazarr had already written the config
we weren't reading.
Its key lives at `config/config.yaml`, nested one level inside the config
directory, so the existing ${USERDIR}/bazarr/config mount already exposes
it and no compose change is needed.
Adds `yamlValue`, scoped to a top-level section for the same reason
`iniValue` is: Bazarr records an `apikey` under a dozen sections,
including `radarr:` and `sonarr:` holding those services' credentials.
A document-wide search for the first `apikey` happens to work today only
because sections are alphabetised and `auth` sorts early — it would hand
back someone else's key the moment a provider sorting before it gained
one. Verified against a real config that the key selected differs from
both the Radarr and Sonarr keys in the same file.
Found by auditing the out-of-the-box path rather than the wired-up test
instance, which had masked it. All six sources now discover: 56 server +
18 web tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@dashboard/server/src/discovery.ts`:
- Around line 156-158: Update the value parsing logic near the rawLine
extraction to remove YAML inline comments only when the value is unquoted, while
preserving # characters inside quoted values; ensure the example produces
bazarrkey123. Add a regression test covering an unquoted value followed by an
inline comment.
- Around line 175-194: Update Bazarr discovery and the integration response
boundary to use the existing Result<T> shape, wrapping _discoverOne and
_discoverAll with safely(). Return { available, reason, hint } for failures, and
update getCredentials() so failed refreshes are cached as Unavailable only
without replacing the last good Bazarr credential. Preserve the live credential
when a subsequent discovery attempt fails.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ebed3577-821f-474e-9116-9a1b9baee772
📒 Files selected for processing (3)
CLAUDE.mddashboard/server/src/discovery.test.tsdashboard/server/src/discovery.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- CLAUDE.md
Only Transmission implemented `hintFor`. Every other source passed no hint at all, so a failure surfaced a bare reason — "upstream timed out", "host not found" — with no next step, and the natural guess is to go re-check an API key that was never at fault. CLAUDE.md requires a hint to name the actual fix; five sources named nothing. This was not hypothetical. Debugging the live instance, every card read "upstream timed out" while the cause was that the dashboard shared no network with the containers it was calling. The reason was accurate and useless. With this change that same failure reads: Nothing resolved Seerr's hostname. It has to be defined in docker-compose.yml and share a network with the dashboard — or set SEERR_URL in .env to reach it another way. `upstreamHint` generalises Transmission's per-service version: a rejected credential points at the API key, an unresolved host points at compose networking, a refused connection points at a service still starting, and a timeout points at both reachability and the URL override. `safely` now takes a hint function as well as a fixed string, because the useful next step depends on how the call failed. Prometheus previously had a fixed hint that named node-exporter whatever went wrong; it now gets the same treatment as the rest. Verified end to end by pointing SEERR_URL at an unresolvable host against the live instance. 64 server + 18 web tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A first `docker compose up` on this stack is a couple of dozen pulls at once, which is exactly when Docker Hub's anonymous rate limit is most likely to bite and least likely to be understood — the failure reads as a broken compose file, not a quota. On the host used to test this branch, anonymous Hub pulls were refused outright and had to be worked around twice. lscr.io is LinuxServer's own registry serving the same images, and is what they recommend. Each of the six tags was confirmed to resolve there before switching. Worth being clear about the limits of this: it moves 6 of 28 images. Everything else — tautulli, grafana, telegraf, watchtower, portainer, prom/*, watchlistarr, transmission-openvpn, cleanarr, requestrr, timescale, redis — has no equivalent mirror and still comes from Hub, so this reduces exposure rather than removing it. Prometheus and node-exporter also publish to quay.io if that trade is wanted later. Compose validates in both the default and Jellyfin-swapped states. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bazarr's config.yaml is round-tripped by ruamel, so a comment a user adds by hand survives every rewrite. `apikey: bazarrkey123 # generated key` was parsed as the whole string including the comment, and every Bazarr call then authenticated with a credential that could never match. Strip a trailing comment only from the unquoted form — inside quotes a `#` is data, and the previous unconditional quote-stripping also mangled a quoted value that carried a comment after it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`docker compose up -d` only manages services it can still see. Once `plex:` is commented out the old container is an orphan: it keeps running on host networking, and the user ends up with both media servers indexing one library — exactly what the README's "pick one media server" warning exists to prevent. Put `docker compose rm -sf plex` first, before the file is edited, in every place the swap is described: README, the compose block's own header comment, CLAUDE.md, and the spec/plan. Also from review: use a throwaway `--env-file` under `mktemp -d` in the plan's validation steps rather than writing the contributor's real `.env`, label the bare code fences, and fix the "JellyFin" spelling and the overbroad "every var is consumed by compose" claim, which drops the documented RADARR_API_KEY / SONARR_API_KEY exception. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes #56.
Adds Jellyfin as a drop-in, opt-in alternative to Plex without changing the default experience. A user switches by commenting out the
plex:service and uncommentingjellyfin:.What's in here
jellyfin:service directly underplex:(linuxserver/jellyfin, host network,:8096, no claim token,container_name: jellyfin).server/src/services.ts) so its panel self-heals the moment the service is enabled; rendersabsentfor Plex users until then..env.example(no new var), and aCLAUDE.mdnote.Companion compatibility (documented in the README)
seerr-team/seerrnatively supports a Jellyfin backend).Explicitly out of scope
Wiring Jellyfin-native companions (Jellystat, Seerr backend reconfig). Left as a follow-up.
Verification
docker compose config --quietpasses on the default file, and on a scratch copy with the Jellyfin block uncommented and Plex commented.npm ci && npm run typecheck && npm run lint && npm test && npm run buildpass indashboard/(50/50 tests).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation