Stashfin is a focused Jellyfin-compatible bridge from Stash to clients such as Infuse. It exposes a browsable media catalog while keeping video bytes out of the bridge.
Control plane: Infuse talks to Stashfin using the Jellyfin API. Stashfin reads metadata from Stash over GraphQL and writes back playback activity, resume position, and ratings.
Media plane: Stashfin returns a 302 redirect to a Stash-generated stream
URL. Infuse then streams directly from Stash using its normal HTTP Range
requests. Video chunks never pass through the Go service.
This split is the main design constraint: Stashfin translates protocol and metadata, but it is not a second media server.
- Four libraries:
Scenes,Performers,Studios, and hierarchicalTags. - Scene metadata including artwork, overview, cast, studio, tags, rating, runtime, Date Added, Release Date, and play state.
- Performer, studio, and tag folders that open directly to matching scenes.
- Separate performer identities for browsable folders and Jellyfin cast people.
- Sorting by name, added date, release date, runtime, play count, play date, and rating where Stash supports the corresponding sort.
- Generated library covers built from recent Stash artwork, with placeholder filtering and image-tag cache busting.
- Playback progress, resume position, completed plays, and numeric ratings written back to Stash.
- Direct stream redirects with no video proxying or transcoding in Stashfin.
git clone https://github.com/BaksiLi/stashfin.git
cd stashfin
cp .env.example .envSet at least:
STASHFIN_SERVER_ID=a-stable-random-id
STASHFIN_USER=stashfin
STASHFIN_PASSWORD=change-me
STASH_INTERNAL_URL=http://stash:9999
STASH_PUBLIC_URL=http://your-stash-host:9999
STASH_API_KEY=your-stash-api-keyThen start the service:
docker compose up -d --buildAdd http://your-stashfin-host:19998 as a Jellyfin share in Infuse. The
container listens on 8096; Compose publishes it on 19998 by default.
Tagged releases also publish Linux binaries and multi-architecture container
images at ghcr.io/baksili/stashfin.
STASH_INTERNAL_URLis the URL Stashfin uses to reach Stash, commonlyhttp://stash:9999on a shared Docker network.STASH_PUBLIC_URLis the URL the playback client can reach over LAN or VPN.
Stashfin rewrites the internal origin in Stash-generated stream URLs to the public origin before redirecting the client.
| Variable | Default | Purpose |
|---|---|---|
STASHFIN_PUBLISHED_PORT |
19998 |
Host port used by Compose |
STASHFIN_SERVER_NAME |
Stashfin |
Name shown to Jellyfin clients |
STASHFIN_SERVER_ID |
required | Stable server identity |
STASHFIN_USER |
stashfin |
Display name returned to Jellyfin clients |
STASHFIN_PASSWORD |
required | The single login credential |
STASH_INTERNAL_URL |
http://stash:9999 |
Stash URL reachable from Stashfin |
STASH_PUBLIC_URL |
required | Stash URL reachable from the client |
STASH_API_KEY |
required | Stash GraphQL and stream credential |
STASH_TIMEOUT |
15s |
Stash request timeout |
See .env.example and compose.yaml for the full deployment shape, including optional Traefik labels.
The v1 release is exercised against:
- Stash
v0.30.1 - Infuse
8.4.4on iPadOS
Stashfin reports a Jellyfin-compatible server version to clients. Its own build
version is available in the X-Stashfin-Version response header and
GET /healthz.
Stashfin intentionally implements the Jellyfin surface needed by this workflow, not the complete Jellyfin API. Unknown endpoints return conservative empty responses where that keeps clients functional.
Infuse requires a username field when adding a Jellyfin share, but Stashfin is
deliberately password-only. Any non-sensitive username can be entered in Infuse;
the configured STASHFIN_USER remains the stable display name and user ID.
This project is designed for a trusted LAN or VPN. Stash stream URLs may contain the Stash API key, so do not expose the direct Stash media endpoint to the public internet without a purpose-built boundary that keeps the credential server-side.
Stashfin deliberately does not include transcoding, stream byte forwarding, a configuration UI, client-profile matrices, playlist editing, or series emulation. Those features belong only if a concrete client workflow requires them without compromising the direct-media architecture.
The project draws useful compatibility lessons from feldorn/Stash-Jellyfin-Proxy, especially around hierarchy, image caching, and Jellyfin client behavior.
go test ./...
go vet ./...
go run ./cmd/stashfinRelease history is recorded in CHANGELOG.md.