emby-watchparty
A synchronized watch party application for Emby media servers. Watch videos together with friends in real-time, no matt…
Declarative configuration engine for Jellyfin — apply and sync server settings from YAML via the Jellyfin API.
git clone https://github.com/venkyr77/jellarr.gitvenkyr77/jellarrJellarr is an open-source tool designed to simplify declarative configuration management for Jellyfin media servers. Inspired by Configarr's approach to *arr stack automation, Jellarr uses Jellyfin's official REST API to safely apply configuration changes through version-controlled YAML files.
By streamlining server configuration, Jellarr saves time, enhances consistency across environments, and reduces manual intervention.
Managing Jellyfin configuration becomes painful at scale:
Existing solutions have limitations. While declarative-jellyfin pioneered declarative Jellyfin configuration, it takes a risky approach:
Jellarr takes a different approach:
# With Nix nix run github:venkyr77/jellarr/v0.0.1 # With Docker docker pull ghcr.io/venkyr77/jellarr:v0.0.1 # Download binary (requires Node.js 24+) ./jellarr-v0.0.1
Example config (config/config.yml):
version: 1
base_url: "http://localhost:8096"
system:
enableMetrics: true
pluginRepositories:
- name: "Jellyfin Official"
url: "https://repo.jellyfin.org/releases/plugin/manifest.json"
enabled: true
encoding:
enableHardwareEncoding: true
hardwareAccelerationType: "vaapi"
vaapiDevice: "/dev/dri/renderD128"
Add to your flake.nix:
{
inputs.jellarr.url = "github:venkyr77/jellarr";
outputs = { self, nixpkgs, jellarr, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
jellarr.nixosModules.default
({ config, ... }: {
services.jellarr = {
enable = true;
user = "jellyfin";
group = "jellyfin";
environmentFile = config.sops.templates.jellarr-env.path;
config = {
base_url = "http://localhost:8096";
system.enableMetrics = true;
};
};
})
];
};
};
}
Or run directly:
JELLARR_API_KEY=your_api_key nix run github:venkyr77/jellarr/v0.0.1
docker pull ghcr.io/venkyr77/jellarr:v0.0.1
With docker-compose:
services:
jellarr:
image: ghcr.io/venkyr77/jellarr:v0.0.1
container_name: jellarr
environment:
- JELLARR_API_KEY=${JELLARR_API_KEY}
- TZ=Etc/UTC
volumes:
- ./config:/config
restart: "no"
Download from releases (requires Node.js 24+):
curl -LO https://github.com/venkyr77/jellarr/releases/download/v0.0.2/jellarr-v0.0.2.cjs JELLARR_API_KEY=your_api_key node jellarr-v0.0.2.cjs --configFile path/to/config.yml
Note: Requires Node.js 24+ installed on your system.
Already have a configured Jellyfin server? Use dump to export its current
configuration as a starting point:
JELLARR_API_KEY=your_api_key jellarr dump --baseUrl http://localhost:8096 > config.yml
This exports system settings, encoding options, libraries, branding, users (without passwords), and plugin configurations. Edit the output to:
password or passwordFile to usersSee dumped-example.yml for sample output.
Jellarr uses a YAML configuration file (default: config/config.yml).
version: 1
base_url: "http://localhost:8096"
system:
enableMetrics: true # Enable Prometheus metrics endpoint
pluginRepositories:
- name: "Jellyfin Official"
url: "https://repo.jellyfin.org/releases/plugin/manifest.json"
enabled: true
trickplayOptions:
enableHwAcceleration: true
enableHwEncoding: true
version: 1
base_url: "http://localhost:8096"
encoding:
enableHardwareEncoding: true
hardwareAccelerationType: "vaapi" # none, amf, qsv, nvenc, v4l2m2m, vaapi, videotoolbox, rkmpp
vaapiDevice: "/dev/dri/renderD128"
# or qsv device
qsvDevice: "/dev/dri/renderD128"
hardwareDecodingCodecs:
- h264
- hevc
- mpeg2video
- vc1
- vp8
- vp9
- av1
enableDecodingColorDepth10Hevc: true
enableDecodingColorDepth10HevcRext: true
enableDecodingColorDepth12HevcRext: true
enableDecodingColorDepth10Vp9: true
allowHevcEncoding: false
allowAv1Encoding: false
version: 1
base_url: "http://localhost:8096"
library:
virtualFolders:
- name: "Movies"
collectionType: "movies"
libraryOptions:
pathInfos:
- path: "/data/movies"
- name: "TV Shows"
collectionType: "tvshows"
libraryOptions:
pathInfos:
- path: "/data/tv"
version: 1
base_url: "http://localhost:8096"
branding:
loginDisclaimer: |
Configured by <a href="https://github.com/venkyr77/jellarr">Jellarr</a>
customCss: |
@import url("https://cdn.jsdelivr.net/npm/jellyskin@latest/dist/main.css");
splashscreenEnabled: false
version: 1
base_url: "http://localhost:8096"
users:
# Regular user with plaintext password (development only)
- name: "regular-user"
password: "secure-password"
# Regular user with password file (production recommended)
- name: "viewer-user"
passwordFile: "/run/secrets/viewer-password"
# Admin user with policy configuration
- name: "admin-user"
passwordFile: "/run/secrets/admin-password"
policy:
isAdministrator: true
loginAttemptsBeforeLockout: 3
Password Security:
password field for development/testing onlypasswordFile for production - file contains only the
plaintext password (whitespace is trimmed)password or
passwordFile (not both)sops-nix Integration:
{
sops.secrets = {
jellarr-api-key.sopsFile = ../../../../secrets/jellarr-api-key;
viewer-user-password.sopsFile = ../../../../secrets/viewer-user-password;
admin-user-password.sopsFile = ../../../../secrets/admin-user-password;
};
services.jellarr = {
enable = true;
environmentFile = config.sops.templates.jellarr-env.path;
config = {
base_url = "http://localhost:8096";
users = [
{
name = "viewer-user";
passwordFile = config.sops.secrets.viewer-user-password.path;
}
{
name = "admin-user";
passwordFile = config.sops.secrets.admin-user-password.path;
}
];
};
};
}
version: 1
base_url: "http://localhost:8096"
system: {}
startup:
completeStartupWizard: true # Mark startup wizard as complete
Useful for automated deployments where you want to skip the interactive startup wizard.
version: 1
base_url: "http://localhost:8096"
plugins:
# Install plugin by name (from configured repositories)
- name: "Trakt"
# Install and configure plugin
- name: "Trakt"
configuration:
TraktUsers:
- ExtraLogging: true
# Multiple plugins
- name: "Playback Reporting"
- name: "Fanart"
configuration:
EnableImages: true
How it works:
system.pluginRepositoriesconfiguration field accepts arbitrary key-value pairs specific to each
pluginFinding plugin configuration keys:
To discover available configuration options for a plugin, you can query the Jellyfin API:
# Get plugin ID
curl -s -H "X-Emby-Token: $API_KEY" \
"http://localhost:8096/Plugins" | jq '.[] | select(.Name == "Trakt")'
# Get plugin configuration
curl -s -H "X-Emby-Token: $API_KEY" \
"http://localhost:8096/Plugins/{pluginId}/Configuration"
{
sops = {
secrets.jellarr-api-key.sopsFile = ./secrets/jellarr.env;
templates.jellarr-env = {
content = ''
JELLARR_API_KEY=${config.sops.placeholder.jellarr-api-key}
'';
owner = config.services.jellarr.user;
group = config.services.jellarr.group;
};
};
services.jellarr = {
enable = true;
environmentFile = config.sops.templates.jellarr-env.path;
config = { /* ... */ };
};
}
export JELLARR_API_KEY=your_api_key jellarr
docker run -e JELLARR_API_KEY=your_api_key \ -v ./config:/config:ro \ ghcr.io/venkyr77/jellarr:v0.0.1
For NixOS deployments where Jellarr runs on the same host as Jellyfin, you can use the bootstrap feature to automatically provision the API key into Jellyfin's database:
{
sops.secrets.jellarr-api-key.sopsFile = ./secrets/jellarr-api-key;
services.jellarr = {
enable = true;
config = {
base_url = "http://localhost:8096";
# ... your config ...
};
# Bootstrap: automatically inserts API key into Jellyfin's database
bootstrap = {
enable = true;
apiKeyFile = config.sops.secrets.jellarr-api-key.path;
# Optional settings (showing defaults):
# apiKeyName = "jellarr";
# jellyfinDataDir = "/var/lib/jellyfin";
# jellyfinService = "jellyfin.service";
};
};
}
How it works:
jellarr-api-key-bootstrap systemd service runs after Jellyfin startsjellarr service has After=jellarr-api-key-bootstrap.service, ensuring
proper orderingImportant notes:
root (required for stopping/starting Jellyfin
and writing to the database)environmentFileFull configuration example with VAAPI hardware acceleration:
version: 1
base_url: "http://localhost:8096"
system:
enableMetrics: true
pluginRepositories:
- name: "Jellyfin Official"
url: "https://repo.jellyfin.org/releases/plugin/manifest.json"
enabled: true
trickplayOptions:
enableHwAcceleration: true
enableHwEncoding: true
encoding:
enableHardwareEncoding: true
hardwareAccelerationType: "vaapi"
vaapiDevice: "/dev/dri/renderD128"
hardwareDecodingCodecs:
["h264", "hevc", "mpeg2video", "vc1", "vp8", "vp9", "av1"]
enableDecodingColorDepth10Hevc: true
enableDecodingColorDepth10Vp9: true
enableDecodingColorDepth10HevcRext: true
enableDecodingColorDepth12HevcRext: true
allowHevcEncoding: false
allowAv1Encoding: false
library:
virtualFolders:
- name: "Movies"
collectionType: "movies"
libraryOptions:
pathInfos:
- path: "/mnt/movies/English"
branding:
loginDisclaimer: |
Configured by <a href="https://github.com/venkyr77/jellarr">Jellarr</a>
customCss: |
@import url("https://cdn.jsdelivr.net/npm/jellyskin@latest/dist/main.css");
splashscreenEnabled: false
users:
- name: "regular-user"
password: "secure-password"
- name: "viewer-user"
passwordFile: "/run/secrets/viewer-password"
- name: "admin-user"
passwordFile: "/run/secrets/admin-password"
policy:
isAdministrator: true
loginAttemptsBeforeLockout: 3
plugins:
- name: "Trakt"
configuration:
TraktUsers:
- ExtraLogging: true
- name: "Playback Reporting"
startup:
completeStartupWizard: true
Jellarr is built in TypeScript with a strict pipeline pattern:
src/cli/index.ts) - Commander.js entry pointsrc/pipeline/index.ts) - Main orchestration:
src/apply/) - Handle configuration updates per feature:
calculateDiff() - Pure calculation, returns schema or undefinedapply() - Side effects, calls Jellyfin APIKey Design Principles:
pnpm test # Tests with Vitest pnpm typecheck # TypeScript validation pnpm eslint # Code linting pnpm build # Build with esbuild # Full validation pipeline npm run build && tsc --noEmit && pnpm eslint && pnpm test && nix fmt
nix build .#default # Build CLI package nix build .#docker-image # Build Docker image nix flake check # Run checks nix fmt # Format project files
| Feature | Jellarr | declarative-jellyfin |
|---|---|---|
| Method | Official REST API | Direct XML/DB manipulation |
| Service Impact | Zero (never stops Jellyfin) | Stops/starts multiple times |
| Platform | Cross-platform (Docker, any OS) | NixOS-only |
| Dependencies | Node.js 24+ | systemd, sqlite, jellyfin running on same host |
| Safety | API validates all changes | Direct file/DB writes |
| Future-proof | API contract stability | Breaks on internal changes |
| Type Safety | TypeScript + Zod + OpenAPI | Bash scripts |
| Testing | Comprehensive unit tests | Complex NixOS VM tests |
declarative-jellyfin's approach:
~/.config/jellyfin/encoding.xml directlyJellarr's approach:
/System/Configuration and similar API endpointsContributions welcome! See CONTRIBUTING.md for guidelines.
© 2025 Jellarr contributors
more like this
A synchronized watch party application for Emby media servers. Watch videos together with friends in real-time, no matt…
YiMao · 云海求片助手 — 双核心 Telegram 影视求片机器人。订阅模式:TMDB 智能搜索一键订阅 / 趣味求片模式:AI 生成五层地狱闯关,通关解锁优先求片。深度集成 MoviePilot + Emby/Jellyfin。…
search projects, people, and tags