jellyfin sidecar: server registry, video façade and byte pass-through

officer-jellyfin owns the whole Jellyfin contract: the instance URL, the access
token, the Jellyfin user it belongs to and the DeviceId its sessions are keyed
by. The platform side is a 17-line proxy holding no credentials.

Servers are a registry, not a single row — this machine runs four instances and
the owner switches between them. The password is never stored: it is traded once
for an access token through AuthenticateByName, and only that token is persisted,
encrypted.

Two doors. /_officer/* is a hand-written JSON façade for the things the browser
should not have to know — the user id in the path, the Fields lists that decide
whether a grid has posters, the PlaybackInfo negotiation. /_jf/* is a GET-only,
allow-listed byte pass-through for images, video, HLS and subtitles; it keeps
Jellyfin's own paths because a master playlist references its segments
relatively, so any renaming would mean rewriting m3u8 bodies.

TranscodingUrl arrives with api_key=<access token> in its query string and would
otherwise be handed straight to a video element. It is stripped before anything
is returned; the pass-through re-adds the credential as a header.

Video only — Officer's own player owns audio, so music collections are filtered
out of the library list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 17:59:50 +00:00
co-authored by Claude Opus 5
parent f2052fbdaa
commit 904edefd62
15 changed files with 1515 additions and 0 deletions
+2
View File
@@ -26,6 +26,7 @@ import { slskdRouter } from './api/slskd/router';
import { headscaleRouter } from './api/headscale/router';
import { transmissionRouter } from './api/transmission/router';
import { invoiceshelfRouter } from './api/invoiceshelf/router';
import { jellyfinRouter } from './api/jellyfin/router';
import { photosRouter } from './api/photos/router';
import { walletRouter } from './api/wallet/router';
import { vpnRouter } from './api/vpn/router';
@@ -169,6 +170,7 @@ protectedRouter.route('/notify', notifyRouter);
protectedRouter.route('/headscale', headscaleRouter);
protectedRouter.route('/transmission', transmissionRouter);
protectedRouter.route('/invoiceshelf', invoiceshelfRouter);
protectedRouter.route('/jellyfin', jellyfinRouter);
protectedRouter.route('/photos', photosRouter);
protectedRouter.route('/wallet', walletRouter);
protectedRouter.route('/vpn', vpnRouter);