Same gap as the photos plugin: users:list updated an in-memory array and
nothing acted on it, so a person added to Officer later reached Video only if
somebody re-ran provisioning by hand. The platform broadcasts on every create
and delete and re-sends on every registration — nothing was listening.
Safe to run unattended without changing what it does, because runProvisioning
already draws the line in the right place: it creates the missing accounts and
tokens them, and it RECORDS an adopted account without touching it. Minting for
an adopted account resets a password its owner knows, and that stays behind
includeAdopted, which only a person can ask for.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Provisioning already created each member's Jellyfin account and signed in as
them to get a token — its own header says the token exists so "the person's
Officer session can already read their library". It was then dropped, because
linkJellyfinAccount had nowhere to put it.
So every non-owner resolved to no config and got a 503 setup form, asking them
to connect a Jellyfin server with a password nobody knows. It reads as a
permissions fault and is not one: the grant, the role and the account list were
all correct.
jellyfin_accounts now carries access_token and device_id, getConfig falls back
to the link row when the caller has no registry of their own, and there is no
path from there to the owner's token — a member resolving to the owner's
credential would read the owner's watch state and every library.
POST /_config/:id/provision repairs accounts linked before the column existed.
It resets the password to mint, which is free on an account Officer created
(random, discarded, known to nobody) and destructive on an adopted one, so
adopted accounts are skipped unless includeAdopted says otherwise.
Applied on alpha: conceiba and guipinto minted and verified against the live
server; fedra and diogo skipped as adopted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
the decision and the execution existed; nothing called them. connecting a
server would have given a working media connection and zero accounts.
the trigger is registering an ADMIN server, not installing the plugin. at
install there is no credential, so there is nothing to provision with — and
this is also the right moment for a re-run, since a revoked token or a moved
server both come back through the same door.
the sidecar now holds the pushed account list in memory. each users:list
REPLACES what was held rather than merging: it is the platform's current truth,
not a cache. persisting it would create a second copy of the platform's users
that could disagree with it, and the recovery from disagreement is re-reading
the platform anyway.
`hasProvisionableUsers()` distinguishes never-told from told-there-is-nobody.
an empty list means a server with only an owner; no list at all means the push
has not arrived or the plugin is not entitled. provisioning nothing is right
for the first and a silent failure for the second.
awaited, so the response says what happened instead of the owner finding it in
a log later. and it never throws: registering the server already succeeded by
then, and losing it to a provisioning error would leave them with no media
access AND no accounts, which is worse than the accounts alone.
mapping rows are written one at a time as each result is known, not batched at
the end. a create that succeeds upstream but whose row fails to write leaves an
orphan account officer will try to create again — the narrower that window, the
better.
found while wiring: the first edit landed the block in editServer as well as
addServer, where `auth` does not exist. caught by tsgo, removed.
Eighth plugin out, and the second resurrection: like soulseek, hono.ts carried
the router import and its mount commented out since 2026-08-13, with the
permission reduced to api: [], so the server booted clean while every panel
request 404'd. jellyfin_servers was also absent from the drizzle barrel, so
bun db:push would have dropped it.
Easier than soulseek in one specific way: mountPrefix derives /jellyfin from
the appName, which is exactly what the router already hardcoded, so no frontend
call site needed repointing. Soulseek needed 47 because its appName and its old
prefix differed. The frontend also already routed through one helper, so there
were five call sites rather than forty-seven.
Self-contained, unlike transmission and soulseek: jellyfin_servers is a
registry this plugin owns outright — label, URL, encrypted token, one active —
because more than one Jellyfin is a thing someone has, and more than one
Transmission is not.
Carries its own .prettierignore for the vendored hls.mjs, rather than leaving a
stale rule behind in the platform naming a path that no longer exists.