diff --git a/README.md b/README.md index 9830e8e9c..cad8e61ce 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,294 @@ -

Jellyfin

-

The Free Software Media System

+# jellyfin-ha + +**A fork of [Jellyfin](https://github.com/jellyfin/jellyfin) adding high-availability transcoding support for multi-pod Kubernetes deployments.** + +[![License: GPL v2](https://img.shields.io/badge/License-GPL_v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![.NET 10](https://img.shields.io/badge/.NET-10.0-purple)](https://dotnet.microsoft.com/download/dotnet/10.0) +[![Upstream](https://img.shields.io/badge/upstream-jellyfin%2Fjellyfin-informational)](https://github.com/jellyfin/jellyfin) --- -

-Logo Banner -
-
- -GPL 2.0 License - - -Current Release - - -Translation Status - - -Docker Pull Count - -
- -Donate - - -Submit Feature Requests - - -Chat on Matrix - - -Release RSS Feed - - -Master Commits RSS Feed - -

+## What is this? + +Jellyfin's default assumption is that exactly one server instance is running at a time. Transcode state is held entirely in-memory — when the process dies, so do all active HLS streams. For homelab deployments that want Kubernetes-managed redundancy (rolling restarts, node drain, pod rescheduling), that's a problem. + +This fork adds a thin HA layer on top of unmodified Jellyfin core: + +- **`ITranscodeSessionStore`** — a new interface for durable, distributed transcode session tracking +- **`RedisTranscodeSessionStore`** — a Redis-backed implementation using atomic Lua takeover scripts and TTL-based lease expiry +- **`NullTranscodeSessionStore`** — a no-op fallback so single-instance deployments work with zero configuration change +- **Lease-aware `DeleteTranscodeFileTask`** — coordinates cleanup across replicas so a restarting pod doesn't delete segments another pod is actively streaming +- **`SessionManager` HA recovery** — safe takeover of live HLS streams when a pod takes over after lease expiry +- **PostgreSQL database provider** — alternative to SQLite for shared-database HA setups (experimental, under `src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL`) --- -Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. It is an alternative to the proprietary Emby and Plex, to provide media from a dedicated server to end-user devices via multiple apps. Jellyfin is descended from Emby's 3.5.2 release and ported to the .NET platform to enable full cross-platform support. +## Architecture -There are no strings attached, no premium licenses or features, and no hidden agendas: just a team that wants to build something better and work together to achieve it. We welcome anyone who is interested in joining us in our quest! +``` +┌─────────────┐ ┌─────────────┐ +│ Jellyfin │ │ Jellyfin │ +│ Pod A │ │ Pod B │ +│ │ │ │ +│ ┌─────────┐ │ │ ┌─────────┐ │ +│ │Transcode│ │ │ │Transcode│ │ +│ │Manager │ │ │ │Manager │ │ +│ └────┬────┘ │ │ └────┬────┘ │ +└──────┼──────┘ └──────┼──────┘ + │ │ + └─────────┬─────────┘ + │ + ┌───────▼───────┐ + │ Redis │ ← ITranscodeSessionStore + │ (lease store)│ TTL-based ownership + └───────────────┘ -For further details, please see [our documentation page](https://jellyfin.org/docs/). To receive the latest updates, get help with Jellyfin, and join the community, please visit [one of our communication channels](https://jellyfin.org/docs/general/getting-help). For more information about the project, please see our [about page](https://jellyfin.org/docs/general/about). + ┌─────────────────────┐ + │ Shared NAS / NFS │ ← HLS segments + manifests + │ (shared storage) │ + └─────────────────────┘ +``` -Want to get started?
-Check out our downloads page or our installation guide, then see our quick start guide. You can also build from source.
+**How takeover works:** -Something not working right?
-Open an Issue on GitHub.
- -Want to contribute?
-Check out our contributing choose-your-own-adventure to see where you can help, then see our contributing guide and our community standards.
- -New idea or improvement?
-Check out our feature request hub.
- -Don't see Jellyfin in your language?
-Check out our Weblate instance to help translate Jellyfin and its subprojects.
- - -Detailed Translation Status - +1. Pod A starts an HLS transcode and writes a `TranscodeSession` to Redis with a 30-second lease. +2. Pod A renews the lease every `LeaseDurationSeconds / 2` seconds. +3. If Pod A dies, the lease expires in Redis after 30 seconds. +4. Pod B receives a client request for the same play session, calls `TryTakeoverAsync`, and atomically claims ownership via a Lua script. +5. Pod B resumes FFmpeg from the last durable segment index. The client sees a brief stutter, not an error. --- -## Jellyfin Server +## Quick Start -This repository contains the code for Jellyfin's backend server. Note that this is only one of many projects under the Jellyfin GitHub [organization](https://github.com/jellyfin/) on GitHub. If you want to contribute, you can start by checking out our [documentation](https://jellyfin.org/docs/general/contributing/index.html) to see what to work on. +### Single instance (no Redis) -## Server Development +No configuration required. `NullTranscodeSessionStore` is used automatically. Behavior is identical to upstream Jellyfin. -These instructions will help you get set up with a local development environment in order to contribute to this repository. Before you start, please be sure to completely read our [guidelines on development contributions](https://jellyfin.org/docs/general/contributing/development.html). Note that this project is supported on all major operating systems except FreeBSD, which is still incompatible. +```bash +dotnet run --project Jellyfin.Server/Jellyfin.Server.csproj -- \ + --datadir /var/lib/jellyfin \ + --webdir /usr/share/jellyfin/web +``` + +### HA mode with Redis + +Set the `Jellyfin:TranscodeStore:RedisConnectionString` configuration key. You can pass it as an environment variable, a `DOTNET_` prefixed env var, or in a JSON config file. + +**Environment variable:** + +```bash +export Jellyfin__TranscodeStore__RedisConnectionString="redis:6379" +export Jellyfin__TranscodeStore__LeaseDurationSeconds="30" + +dotnet run --project Jellyfin.Server/Jellyfin.Server.csproj -- \ + --datadir /var/lib/jellyfin \ + --webdir /usr/share/jellyfin/web +``` + +**`appsettings.json` section:** + +```json +{ + "Jellyfin": { + "TranscodeStore": { + "RedisConnectionString": "redis:6379,abortConnect=false", + "LeaseDurationSeconds": 30 + } + } +} +``` + +When `RedisConnectionString` is set, `RedisTranscodeSessionStore` is registered in DI. If the Redis connection fails at startup, the server throws and refuses to start — this is intentional so you don't silently fall back to broken HA behavior. + +--- + +## Configuration Reference + +| Key | Default | Description | +|-----|---------|-------------| +| `Jellyfin:TranscodeStore:RedisConnectionString` | _(empty)_ | StackExchange.Redis connection string. Empty = single-instance mode. | +| `Jellyfin:TranscodeStore:LeaseDurationSeconds` | `30` | How long a pod's transcode lease is valid before another pod may take over. | + +### Redis connection string examples + +``` +# Standalone Redis +redis:6379 + +# With password +redis:6379,password=secret + +# With TLS +redis.example.com:6380,ssl=true,abortConnect=false + +# Redis Sentinel +sentinel-host:26379,serviceName=mymaster +``` + +Standard [StackExchange.Redis connection string format](https://stackexchange.github.io/StackExchange.Redis/Configuration) is accepted. + +--- + +## Docker / Kubernetes + +The `Dockerfile.runtime` in this repo produces a runtime-only image. The `.NET publish` step is intended to run on the CI host (not inside Docker) for I/O performance reasons. + +```bash +# Build locally +dotnet publish Jellyfin.Server/Jellyfin.Server.csproj \ + --configuration Release \ + --runtime linux-x64 \ + --self-contained false \ + --output ./publish-output + +# Build image +docker build -f Dockerfile.runtime -t jellyfin-ha:local \ + --platform linux/amd64 . +``` + +**Kubernetes environment variables for HA:** + +```yaml +env: + - name: Jellyfin__TranscodeStore__RedisConnectionString + valueFrom: + secretKeyRef: + name: jellyfin-redis + key: connection-string + - name: Jellyfin__TranscodeStore__LeaseDurationSeconds + value: "30" + - name: JELLYFIN_HA_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +``` + +Shared storage (NFS, Longhorn RWX, or similar) must be mounted at the same path on all pods for segment file access to work across pod boundaries. + +--- + +## PostgreSQL (experimental) + +This fork includes a PostgreSQL database provider under `src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL`. It is experimental — the SQLite provider remains the default and the recommended choice for most deployments. + +To use PostgreSQL, set the migration provider at startup and run migrations: + +```bash +dotnet ef migrations add InitialCreate \ + --project "src/Jellyfin.Database/Jellyfin.Database.Providers.PostgreSQL" \ + -- --migration-provider Jellyfin-PostgreSQL +``` + +See `src/Jellyfin.Database/readme.md` for full migration instructions. + +--- + +## Building and Testing ### Prerequisites -Before the project can be built, you must first install the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet) on your system. +- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) + +### Build + +```bash +dotnet build Jellyfin.Server/Jellyfin.Server.csproj +``` + +### Run all tests + +```bash +dotnet test Jellyfin.sln \ + --configuration Release \ + --filter "Category!=RequiresDocker&FullyQualifiedName!~Integration" +``` + +### Run HA-specific tests + +The transcode session store and HA recovery tests live in: + +- `tests/Jellyfin.Server.Implementations.Tests/MediaEncoding/RedisTranscodeSessionStoreTests.cs` +- `tests/Jellyfin.MediaEncoding.Tests/Fakes/InMemoryTranscodeSessionStore.cs` + +```bash +dotnet test tests/Jellyfin.Server.Implementations.Tests \ + --configuration Release \ + --filter "FullyQualifiedName~TranscodeSession" +``` + +### Run with code coverage + +```bash +dotnet test Jellyfin.sln \ + --configuration Release \ + --collect:"XPlat Code Coverage" \ + --settings tests/coverletArgs.runsettings +``` + +--- + +## Project Structure + +``` +MediaBrowser.Controller/MediaEncoding/ + ITranscodeSessionStore.cs ← Interface (DI contract) + TranscodeSession.cs ← Session record model + TranscodeStoreOptions.cs ← Configuration options + NullTranscodeSessionStore.cs ← No-op, single-instance fallback + +Emby.Server.Implementations/MediaEncoding/ + RedisTranscodeSessionStore.cs ← Redis-backed HA implementation + +src/Jellyfin.Database/ + Jellyfin.Database.Providers.PostgreSQL/ ← Experimental PostgreSQL provider + +tests/ + Jellyfin.Server.Implementations.Tests/MediaEncoding/ + RedisTranscodeSessionStoreTests.cs + Jellyfin.MediaEncoding.Tests/Fakes/ + InMemoryTranscodeSessionStore.cs +``` + +--- + +## Contributing + +This is a personal experiment, not an officially maintained fork. Issues and PRs are welcome but response time may vary. + +If you're interested in getting proper HA transcoding into upstream Jellyfin, that conversation belongs in the [upstream repo](https://github.com/jellyfin/jellyfin). The changes here are deliberately narrow and designed to be upstream-friendly if there's maintainer interest. + +**Code conventions** follow the upstream Jellyfin rules: +- `async`/`await` everywhere — no `.Result` or `.Wait()` +- All public members need XML doc comments +- Use `Directory.Packages.props` for NuGet versions — never add `Version=` to a `` +- `.NET 10` required +- Warnings are treated as errors + +--- + +## Relationship to upstream + +This fork tracks [jellyfin/jellyfin](https://github.com/jellyfin/jellyfin) `master`. The HA additions are intentionally isolated to: + +1. New interfaces and models in `MediaBrowser.Controller` +2. New implementations in `Emby.Server.Implementations` +3. DI wiring in `Jellyfin.Server/CoreAppHost.cs` +4. New test projects + +No core Jellyfin logic was modified — only extended via existing DI extension points. + +--- + +## License + +GPL-2.0, same as upstream Jellyfin. See [LICENSE](LICENSE). + +--- + +*Upstream README preserved below for reference.* + +--- Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET 6 development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2022) and [Visual Studio Code](https://code.visualstudio.com/Download). @@ -94,13 +306,12 @@ git clone https://github.com/jellyfin/jellyfin.git The server is configured to host the static files required for the [web client](https://github.com/jellyfin/jellyfin-web) in addition to serving the backend by default. Before you can run the server, you will need to get a copy of the web client since they are not included in this repository directly. -Note that it is also possible to [host the web client separately](#hosting-the-web-client-separately) from the web server with some additional configuration, in which case you can skip this step. +Note that it is recommended for development to [host the web client separately](#hosting-the-web-client-separately) from the web server with some additional configuration, in which case you can skip this step. -There are three options to get the files for the web client. +There are two options to get the files for the web client. -1. Download one of the finished builds from the [Azure DevOps pipeline](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=27). You can download the build for a specific release by looking at the [branches tab](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=27&_a=summary&repositoryFilter=6&view=branches) of the pipelines page. -2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web) -3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web` +1. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web) +2. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web` ### Running The Server @@ -133,7 +344,7 @@ A second option is to build the project and then run the resulting executable fi ```bash dotnet build # Build the project -cd Jellyfin.Server/bin/Debug/net9.0 # Change into the build output directory +cd Jellyfin.Server/bin/Debug/net10.0 # Change into the build output directory ``` 2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`. @@ -198,5 +409,5 @@ This project is supported by:
DigitalOcean   -JetBrains logo +JetBrains logo