{"id":33281727,"url":"https://github.com/venkyr77/jellarr","last_synced_at":"2026-06-08T00:31:34.515Z","repository":{"id":321218056,"uuid":"1084824026","full_name":"venkyr77/jellarr","owner":"venkyr77","description":"Declarative configuration engine for Jellyfin — apply and sync server settings from YAML via the Jellyfin API.","archived":false,"fork":false,"pushed_at":"2026-05-24T02:56:57.000Z","size":409,"stargazers_count":154,"open_issues_count":21,"forks_count":15,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-05-24T04:34:59.893Z","etag":null,"topics":["agplv3","api","auto","configuration","declarative","homelab","jellyfin","media-server","openapi","self-hosted","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/venkyr77.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-28T08:00:35.000Z","updated_at":"2026-05-21T23:04:41.000Z","dependencies_parsed_at":"2025-10-28T14:35:06.636Z","dependency_job_id":"bccc9e18-f012-4dcc-ae81-1d378028cb93","html_url":"https://github.com/venkyr77/jellarr","commit_stats":null,"previous_names":["venkyr77/jellarr"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/venkyr77/jellarr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venkyr77%2Fjellarr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venkyr77%2Fjellarr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venkyr77%2Fjellarr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venkyr77%2Fjellarr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/venkyr77","download_url":"https://codeload.github.com/venkyr77/jellarr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venkyr77%2Fjellarr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34043822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-07T02:00:07.652Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agplv3","api","auto","configuration","declarative","homelab","jellyfin","media-server","openapi","self-hosted","typescript"],"created_at":"2025-11-17T13:05:32.754Z","updated_at":"2026-06-08T00:31:34.502Z","avatar_url":"https://github.com/venkyr77.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Jellarr\n\n**Jellarr** is an open-source tool designed to simplify declarative\nconfiguration management for [Jellyfin](https://jellyfin.org) media servers.\nInspired by [Configarr](https://github.com/raydak-labs/configarr)'s approach to\n\\*arr stack automation, Jellarr uses Jellyfin's official REST API to safely\napply configuration changes through version-controlled YAML files.\n\nBy streamlining server configuration, Jellarr saves time, enhances consistency\nacross environments, and reduces manual intervention.\n\n---\n\n## Why Jellarr?\n\nManaging Jellyfin configuration becomes painful at scale:\n\n- **Configuration drift** across dev/staging/prod environments\n- **No version control** for settings changes\n- **Manual clicking** through web UI doesn't scale to multiple servers\n- **No automation** for configuration-as-code workflows\n\nExisting solutions have limitations. While\n[declarative-jellyfin](https://github.com/Sveske-Juice/declarative-jellyfin)\npioneered declarative Jellyfin configuration, it takes a risky approach:\n\n- Directly manipulates XML files and SQLite databases\n- Stops/starts Jellyfin service multiple times during configuration\n- Reimplements Jellyfin's internal UUID generation in bash\n- NixOS-only with hardcoded systemd and path dependencies\n- Breaks when Jellyfin changes internals\n  ([example issue](https://github.com/Sveske-Juice/declarative-jellyfin/issues/13))\n\n**Jellarr takes a different approach:**\n\n- ✅ **API-based** — Uses Jellyfin's official REST API, never touches internal\n  files or databases\n- ✅ **Zero service interruption** — Jellyfin keeps running, no restarts\n  required\n- ✅ **Cross-platform** — Works on Docker, bare metal, any OS (not just NixOS)\n- ✅ **Type-safe** — OpenAPI-generated types catch errors at build time\n- ✅ **Future-proof** — Relies on stable API contracts, not reverse-engineered\n  internals\n- ✅ **Production-ready** — Idempotent, safe to run anytime via cron/systemd\n  timers\n\n---\n\n## Quick Start\n\n```bash\n# With Nix\nnix run github:venkyr77/jellarr/v0.0.1\n\n# With Docker\ndocker pull ghcr.io/venkyr77/jellarr:v0.0.1\n\n# Download binary (requires Node.js 24+)\n./jellarr-v0.0.1\n```\n\n**Example config** (`config/config.yml`):\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nsystem:\n  enableMetrics: true\n  pluginRepositories:\n    - name: \"Jellyfin Official\"\n      url: \"https://repo.jellyfin.org/releases/plugin/manifest.json\"\n      enabled: true\nencoding:\n  enableHardwareEncoding: true\n  hardwareAccelerationType: \"vaapi\"\n  vaapiDevice: \"/dev/dri/renderD128\"\n```\n\n---\n\n## Installation\n\n### Nix Flake (Recommended)\n\nAdd to your `flake.nix`:\n\n```nix\n{\n  inputs.jellarr.url = \"github:venkyr77/jellarr\";\n\n  outputs = { self, nixpkgs, jellarr, ... }: {\n    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {\n      modules = [\n        jellarr.nixosModules.default\n        ({ config, ... }: {\n          services.jellarr = {\n            enable = true;\n            user = \"jellyfin\";\n            group = \"jellyfin\";\n            environmentFile = config.sops.templates.jellarr-env.path;\n            config = {\n              base_url = \"http://localhost:8096\";\n              system.enableMetrics = true;\n            };\n          };\n        })\n      ];\n    };\n  };\n}\n```\n\nOr run directly:\n\n```bash\nJELLARR_API_KEY=your_api_key nix run github:venkyr77/jellarr/v0.0.1\n```\n\n### Docker (Recommended)\n\n```bash\ndocker pull ghcr.io/venkyr77/jellarr:v0.0.1\n```\n\n**With docker-compose:**\n\n```yaml\nservices:\n  jellarr:\n    image: ghcr.io/venkyr77/jellarr:v0.0.1\n    container_name: jellarr\n    environment:\n      - JELLARR_API_KEY=${JELLARR_API_KEY}\n      - TZ=Etc/UTC\n    volumes:\n      - ./config:/config\n    restart: \"no\"\n```\n\n### Bundle Download\n\nDownload from [releases](https://github.com/venkyr77/jellarr/releases) (requires\nNode.js 24+):\n\n```bash\ncurl -LO https://github.com/venkyr77/jellarr/releases/download/v0.0.2/jellarr-v0.0.2.cjs\nJELLARR_API_KEY=your_api_key node jellarr-v0.0.2.cjs --configFile path/to/config.yml\n```\n\n**Note:** Requires Node.js 24+ installed on your system.\n\n---\n\n## Export Existing Configuration (Experimental)\n\nAlready have a configured Jellyfin server? Use `dump` to export its current\nconfiguration as a starting point:\n\n```bash\nJELLARR_API_KEY=your_api_key jellarr dump --baseUrl http://localhost:8096 \u003e config.yml\n```\n\nThis exports system settings, encoding options, libraries, branding, users\n(without passwords), and plugin configurations. Edit the output to:\n\n- Remove fields you don't want to manage\n- Add `password` or `passwordFile` to users\n- Remove any default values you don't need\n\nSee [`dumped-example.yml`](./dumped-example.yml) for sample output.\n\n---\n\n## Configuration\n\nJellarr uses a YAML configuration file (default: `config/config.yml`).\n\n### System Configuration\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nsystem:\n  enableMetrics: true # Enable Prometheus metrics endpoint\n  pluginRepositories:\n    - name: \"Jellyfin Official\"\n      url: \"https://repo.jellyfin.org/releases/plugin/manifest.json\"\n      enabled: true\n  trickplayOptions:\n    enableHwAcceleration: true\n    enableHwEncoding: true\n```\n\n### Encoding Configuration\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nencoding:\n  enableHardwareEncoding: true\n  hardwareAccelerationType: \"vaapi\" # none, amf, qsv, nvenc, v4l2m2m, vaapi, videotoolbox, rkmpp\n  vaapiDevice: \"/dev/dri/renderD128\"\n  # or qsv device\n  qsvDevice: \"/dev/dri/renderD128\"\n  hardwareDecodingCodecs:\n    - h264\n    - hevc\n    - mpeg2video\n    - vc1\n    - vp8\n    - vp9\n    - av1\n  enableDecodingColorDepth10Hevc: true\n  enableDecodingColorDepth10HevcRext: true\n  enableDecodingColorDepth12HevcRext: true\n  enableDecodingColorDepth10Vp9: true\n  allowHevcEncoding: false\n  allowAv1Encoding: false\n```\n\n### Library Configuration\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nlibrary:\n  virtualFolders:\n    - name: \"Movies\"\n      collectionType: \"movies\"\n      libraryOptions:\n        pathInfos:\n          - path: \"/data/movies\"\n    - name: \"TV Shows\"\n      collectionType: \"tvshows\"\n      libraryOptions:\n        pathInfos:\n          - path: \"/data/tv\"\n```\n\n### Branding Configuration\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nbranding:\n  loginDisclaimer: |\n    Configured by \u003ca href=\"https://github.com/venkyr77/jellarr\"\u003eJellarr\u003c/a\u003e\n  customCss: |\n    @import url(\"https://cdn.jsdelivr.net/npm/jellyskin@latest/dist/main.css\");\n  splashscreenEnabled: false\n```\n\n### User Management\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nusers:\n  # Regular user with plaintext password (development only)\n  - name: \"regular-user\"\n    password: \"secure-password\"\n\n  # Regular user with password file (production recommended)\n  - name: \"viewer-user\"\n    passwordFile: \"/run/secrets/viewer-password\"\n\n  # Admin user with policy configuration\n  - name: \"admin-user\"\n    passwordFile: \"/run/secrets/admin-password\"\n    policy:\n      isAdministrator: true\n      loginAttemptsBeforeLockout: 3\n```\n\n**Password Security:**\n\n- **plaintext password**: Use `password` field for development/testing only\n- **password file**: Use `passwordFile` for production - file contains only the\n  plaintext password (whitespace is trimmed)\n- **Exactly one required**: Each user must specify either `password` or\n  `passwordFile` (not both)\n\n**sops-nix Integration:**\n\n```nix\n{\n  sops.secrets = {\n    jellarr-api-key.sopsFile = ../../../../secrets/jellarr-api-key;\n    viewer-user-password.sopsFile = ../../../../secrets/viewer-user-password;\n    admin-user-password.sopsFile = ../../../../secrets/admin-user-password;\n  };\n\n  services.jellarr = {\n    enable = true;\n    environmentFile = config.sops.templates.jellarr-env.path;\n    config = {\n      base_url = \"http://localhost:8096\";\n      users = [\n        {\n          name = \"viewer-user\";\n          passwordFile = config.sops.secrets.viewer-user-password.path;\n        }\n        {\n          name = \"admin-user\";\n          passwordFile = config.sops.secrets.admin-user-password.path;\n        }\n      ];\n    };\n  };\n}\n```\n\n### Startup Configuration\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nsystem: {}\nstartup:\n  completeStartupWizard: true # Mark startup wizard as complete\n```\n\nUseful for automated deployments where you want to skip the interactive startup\nwizard.\n\n### Plugin Management\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nplugins:\n  # Install plugin by name (from configured repositories)\n  - name: \"Trakt\"\n\n  # Install and configure plugin\n  - name: \"Trakt\"\n    configuration:\n      TraktUsers:\n        - ExtraLogging: true\n\n  # Multiple plugins\n  - name: \"Playback Reporting\"\n  - name: \"Fanart\"\n    configuration:\n      EnableImages: true\n```\n\n**How it works:**\n\n- Plugins are installed from repositories configured in\n  `system.pluginRepositories`\n- Plugin names must match exactly (case-sensitive)\n- The `configuration` field accepts arbitrary key-value pairs specific to each\n  plugin\n- Only specified configuration fields are updated; unspecified fields are\n  preserved\n- Plugin configurations are applied after installation, allowing newly installed\n  plugins to be configured in the same run\n\n**Finding plugin configuration keys:**\n\nTo discover available configuration options for a plugin, you can query the\nJellyfin API:\n\n```bash\n# Get plugin ID\ncurl -s -H \"X-Emby-Token: $API_KEY\" \\\n  \"http://localhost:8096/Plugins\" | jq '.[] | select(.Name == \"Trakt\")'\n\n# Get plugin configuration\ncurl -s -H \"X-Emby-Token: $API_KEY\" \\\n  \"http://localhost:8096/Plugins/{pluginId}/Configuration\"\n```\n\n---\n\n## Secret Management\n\n### With sops-nix (nix only)\n\n```nix\n{\n  sops = {\n    secrets.jellarr-api-key.sopsFile = ./secrets/jellarr.env;\n    templates.jellarr-env = {\n      content = ''\n        JELLARR_API_KEY=${config.sops.placeholder.jellarr-api-key}\n      '';\n      owner = config.services.jellarr.user;\n      group = config.services.jellarr.group;\n    };\n  };\n\n  services.jellarr = {\n    enable = true;\n    environmentFile = config.sops.templates.jellarr-env.path;\n    config = { /* ... */ };\n  };\n}\n```\n\n### With Environment Variable\n\n```bash\nexport JELLARR_API_KEY=your_api_key\njellarr\n```\n\n### With Docker\n\n```bash\ndocker run -e JELLARR_API_KEY=your_api_key \\\n  -v ./config:/config:ro \\\n  ghcr.io/venkyr77/jellarr:v0.0.1\n```\n\n### API Key Bootstrap (NixOS, Same Host Only)\n\nFor NixOS deployments where Jellarr runs on the **same host** as Jellyfin, you\ncan use the bootstrap feature to automatically provision the API key into\nJellyfin's database:\n\n```nix\n{\n  sops.secrets.jellarr-api-key.sopsFile = ./secrets/jellarr-api-key;\n\n  services.jellarr = {\n    enable = true;\n    config = {\n      base_url = \"http://localhost:8096\";\n      # ... your config ...\n    };\n\n    # Bootstrap: automatically inserts API key into Jellyfin's database\n    bootstrap = {\n      enable = true;\n      apiKeyFile = config.sops.secrets.jellarr-api-key.path;\n      # Optional settings (showing defaults):\n      # apiKeyName = \"jellarr\";\n      # jellyfinDataDir = \"/var/lib/jellyfin\";\n      # jellyfinService = \"jellyfin.service\";\n    };\n  };\n}\n```\n\n**How it works:**\n\n1. The `jellarr-api-key-bootstrap` systemd service runs after Jellyfin starts\n2. It waits for Jellyfin's database to exist\n3. If the API key doesn't already exist, it stops Jellyfin, inserts the key into\n   the SQLite database, and restarts Jellyfin\n4. The `jellarr` service has `After=jellarr-api-key-bootstrap.service`, ensuring\n   proper ordering\n\n**Important notes:**\n\n- This **only works when Jellarr and Jellyfin are on the same host** - it\n  requires direct access to Jellyfin's database file\n- The bootstrap service runs as `root` (required for stopping/starting Jellyfin\n  and writing to the database)\n- The insertion is idempotent - if a key with the same name exists, it skips\n- For deployments where Jellarr runs on a **different host** than Jellyfin, you\n  must provision the API key manually (via Jellyfin's web UI or a separate\n  script) and provide it via `environmentFile`\n\n---\n\n## Full Configuration Example\n\nFull configuration example with VAAPI hardware acceleration:\n\n```yaml\nversion: 1\nbase_url: \"http://localhost:8096\"\nsystem:\n  enableMetrics: true\n  pluginRepositories:\n    - name: \"Jellyfin Official\"\n      url: \"https://repo.jellyfin.org/releases/plugin/manifest.json\"\n      enabled: true\n  trickplayOptions:\n    enableHwAcceleration: true\n    enableHwEncoding: true\nencoding:\n  enableHardwareEncoding: true\n  hardwareAccelerationType: \"vaapi\"\n  vaapiDevice: \"/dev/dri/renderD128\"\n  hardwareDecodingCodecs:\n    [\"h264\", \"hevc\", \"mpeg2video\", \"vc1\", \"vp8\", \"vp9\", \"av1\"]\n  enableDecodingColorDepth10Hevc: true\n  enableDecodingColorDepth10Vp9: true\n  enableDecodingColorDepth10HevcRext: true\n  enableDecodingColorDepth12HevcRext: true\n  allowHevcEncoding: false\n  allowAv1Encoding: false\nlibrary:\n  virtualFolders:\n    - name: \"Movies\"\n      collectionType: \"movies\"\n      libraryOptions:\n        pathInfos:\n          - path: \"/mnt/movies/English\"\nbranding:\n  loginDisclaimer: |\n    Configured by \u003ca href=\"https://github.com/venkyr77/jellarr\"\u003eJellarr\u003c/a\u003e\n  customCss: |\n    @import url(\"https://cdn.jsdelivr.net/npm/jellyskin@latest/dist/main.css\");\n  splashscreenEnabled: false\nusers:\n  - name: \"regular-user\"\n    password: \"secure-password\"\n  - name: \"viewer-user\"\n    passwordFile: \"/run/secrets/viewer-password\"\n  - name: \"admin-user\"\n    passwordFile: \"/run/secrets/admin-password\"\n    policy:\n      isAdministrator: true\n      loginAttemptsBeforeLockout: 3\nplugins:\n  - name: \"Trakt\"\n    configuration:\n      TraktUsers:\n        - ExtraLogging: true\n  - name: \"Playback Reporting\"\nstartup:\n  completeStartupWizard: true\n```\n\n---\n\n## Architecture\n\n**Jellarr** is built in TypeScript with a strict pipeline pattern:\n\n1. **CLI** (`src/cli/index.ts`) - Commander.js entry point\n2. **Pipeline** (`src/pipeline/index.ts`) - Main orchestration:\n   - Reads YAML config file\n   - Validates with strict Zod schemas\n   - Creates authenticated Jellyfin API client\n   - Fetches current server configuration\n   - Applies ONLY specified changes idempotently\n3. **Apply Modules** (`src/apply/`) - Handle configuration updates per feature:\n   - `calculateDiff()` - Pure calculation, returns schema or undefined\n   - `apply()` - Side effects, calls Jellyfin API\n\n**Key Design Principles:**\n\n- **Selective updates:** Only modifies explicitly configured fields\n- **Idempotent:** Safe to run multiple times\n- **Type-safe:** OpenAPI-generated types + Zod validation\n- **Calculate/Apply pattern:** Separation of pure logic from side effects\n- **Comprehensive test coverage**\n\n---\n\n## Development\n\n```bash\npnpm test        # Tests with Vitest\npnpm typecheck   # TypeScript validation\npnpm eslint      # Code linting\npnpm build       # Build with esbuild\n\n# Full validation pipeline\nnpm run build \u0026\u0026 tsc --noEmit \u0026\u0026 pnpm eslint \u0026\u0026 pnpm test \u0026\u0026 nix fmt\n```\n\n### Nix Development\n\n```bash\nnix build .#default      # Build CLI package\nnix build .#docker-image # Build Docker image\nnix flake check          # Run checks\nnix fmt                  # Format project files\n```\n\n---\n\n## Comparison: Jellarr vs declarative-jellyfin\n\n| Feature            | Jellarr                         | declarative-jellyfin                           |\n| ------------------ | ------------------------------- | ---------------------------------------------- |\n| **Method**         | Official REST API               | Direct XML/DB manipulation                     |\n| **Service Impact** | Zero (never stops Jellyfin)     | Stops/starts multiple times                    |\n| **Platform**       | Cross-platform (Docker, any OS) | NixOS-only                                     |\n| **Dependencies**   | Node.js 24+                     | systemd, sqlite, jellyfin running on same host |\n| **Safety**         | API validates all changes       | Direct file/DB writes                          |\n| **Future-proof**   | API contract stability          | Breaks on internal changes                     |\n| **Type Safety**    | TypeScript + Zod + OpenAPI      | Bash scripts                                   |\n| **Testing**        | Comprehensive unit tests        | Complex NixOS VM tests                         |\n\n**declarative-jellyfin's approach:**\n\n- Writes `~/.config/jellyfin/encoding.xml` directly\n- Manipulates SQLite database with hardcoded schema\n- Reimplements Jellyfin's UUID algorithm in bash\n- Requires stopping Jellyfin service during configuration\n- [Known issues with timing/conflicts](https://github.com/Sveske-Juice/declarative-jellyfin/issues/13)\n\n**Jellarr's approach:**\n\n- Uses `/System/Configuration` and similar API endpoints\n- Never touches internal files or databases\n- Jellyfin validates all changes\n- Zero service interruption\n- Works on any platform where Jellyfin runs\n\n---\n\n## Contributing\n\nContributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n---\n\n## License\n\n[GNU AGPL-3.0 License](https://www.gnu.org/licenses/agpl-3.0.html)\n\n© 2025 Jellarr contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvenkyr77%2Fjellarr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvenkyr77%2Fjellarr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvenkyr77%2Fjellarr/lists"}