This guide is aimed at those who wish to automate the mods of this repository so that they persist across version updates.
To do this, we're going to make use of Ansible playbooks which are YAML files that automate certain
Linux tasks. In this case they will be used to update packages, modify javascript files within jellyfin-web
and copying files/directories
to the jellyfin-web
directory.
- In this guide, I will refer to "remote machine" and "host machine" a lot. To clarify what this means, the remote machine is the one you will run Ansible from, the host machine is the one which has your Jellyfin instance installed on it.
- For the time being, these playbooks are designed for Jellyfin version >10.10.0 on a bare-metal Debian install. Please create pull requests if you'd like to contribute playbooks for other environments. That said, most of these tasks, with the exception of package updates and systemd service events should work on most Linux distros.
- This does not include every mod (yet). Please create an issue or pull request if there's a particular one you'd like to be added.
- Do not rely on this to work forever, the Jellyfin team often make mod-breaking changes across versions (especially major versions). Running these playbooks in this case may cause Jellyfin or parts of it to stop working, in which case there is a playbook at the bottom of this document which you can run to reinstall the
jellyfin-web
package, which should restore all the modified files to their original state. - These playbooks come in the form of snippets within this document and not complete YAML files. The reasoning for this is that the user should only add snippets they want, and some snippets require the user to edit certain strings in order to match their configuration. Copy the snippets one by one into your own
jellyfin-mods.yaml
file.
- Jellyfin version >10.10.0 (including
jellyfin-server
,jellyfin-web
andjellyfin-ffmpeg7
) on the host machine. - A remote machine to run the playbook from. I believe there are ways to run Ansible locally but as far as I know the intent is to run it remotely and thus I have written the files with this in mind.
- SSH keys from remote to host machine.
- Ansible Core or Ansible Full (install instructions) on the remote machine.
- An Ansible
inventory
file for the remote machine. This file should contain the IP or hostname of your host machine and be placed in the same directory as yourjellyfin-mods.yaml
file for ease of use.
For ease of use, here's an example of how I have these files arranged on my remote machine:
/home/jane/
├─ jellyfin-mods
│ ├── inventory
└── └── jellyfin-mods.yaml
With all the prerequisites fulfilled and your files created, navigate to the directory holding these files in your remote machine's terminal and run the following:
ansible-playbook -K -i inventory jellyfin-mods.yaml
-K
tells Ansible to ask for a sudo password. It needs to do this as many of the steps we're about to perform require root access, just the same as it does when performing these mods without Ansible.
-i
specifies the inventory file which is needed to tell Ansible which machine to perform these tasks on.
Ansible will now ask for the sudo password of the user on the host machine by querying BECOME password:
. Type it in and press enter. Then Ansible will perform each step specified in your jellyfin-mods.yaml
file on the host machine.
I recommend linting and checking your files first. You can do this by issuing these commands:
ansible-lint jellyfin-mods.yaml
ansible-playbook -C -D -K -i inventory jellyfin-mods.yaml
-C
specifies a dry run of the playbook, as in running it without performing any changes. -D
will display a diff on all the files you change.
YAML files are whitespace sensitive. In order to avoid issues, make sure this is respected. You can lint your playbook before running it by issuing the following command:
ansible-lint jellyfin-mods.yaml
This ansible playbook requires certain global parameters to be set before we can add any other snippets. This needs to be added to the very top of your playbook file like this:
---
- name: jellyfin-mods
hosts: all
remote_user: jane
tasks:
You must edit the remote_user: jane
section, replacing jane
with the username on your host machine you intend to run the playbook as. This user must have sudo
access as the files we intend to edit are owned by root. The rest can be left as is.
I recommend making this the first task to automate version updating, however if you're already running the latest packages this task won't do anything anyway, so it's up to you.
- name: Updating "jellyfin" packages...
ansible.builtin.apt:
name:
- jellyfin
- jellyfin-server
- jellyfin-web
- jellyfin-ffmpeg7
state: latest
update_cache: true
become: true
This should be included in any playbook that edits javascript files as the changes may not apply or persist if jellyfin is running at the same time. At the bottom of this document you will find a snippet that starts jellyfin back up after the modifications have been made. If you're going to use this you should also use the other as jellyfin otherwise won't run after the changes have been made.
- name: Stopping Jellyfin Service...
ansible.builtin.service:
name: jellyfin
state: stopped
become: true
Some of the files we're editing will change names depending on the version we're updating to/from. Therefore we should add steps which look for those files and then call on them when needed.
- name: Finding the 73233.*.chunk.js file...
ansible.builtin.find:
paths: /usr/share/jellyfin/web/
patterns: '73233.*.chunk.js'
register: chunk_73233_js_file
- name: Finding session-login-index-html.*.chunk.js file...
ansible.builtin.find:
paths: /usr/share/jellyfin/web/
patterns: 'session-login-index-html.*.chunk.js'
register: session_login_chunk_js_file
Copy directory with custom branding assets to jellyfin-web
in order to change logo icons and images.
For this one, you will need a pre-made directory with all the files you want to replace inside the jellyfin-web
directory. It should look like this:
/home/jane/jellyfin-logos/
├── 065a953504e441fe245c.png
├── 0b37f660ac0f7f01ab41.png
├── 0d2b37694d352e7e4c59.svg
├── 0df719b48efcaef953df.png
├── 106a7abc109fb5e78742.png
├── 142d834c201895a46a01.png
├── 16fc81178d1aee54f6cc.png
├── 23a72f5d56f82554aeab.png
├── 379bab68d056910336f9.png
├── 39209dd2362c0db7c673.png
├── 3f3fe0fd3a0b637b5030.png
├── 3fa90c593184d5737eb3.png
├── 6a2e2e6b4186720e5d4f.png
├── 6de874568a98308c4a74.png
├── 74ce2b743c33a4197e5c.gif
├── a962662957ebbb8eb436.png
├── assets
│ ├── img
│ │ ├── banner-dark.png
│ │ ├── banner-light.png
│ │ └── icon-transparent.png
│ └── splash
│ ├── ipadpro1_splash_l.png
│ ├── ipadpro1_splash.png
│ ├── ipadpro2_splash_l.png
│ ├── ipadpro2_splash.png
│ ├── ipadpro3_splash_l.png
│ ├── ipadpro3_splash.png
│ ├── ipad_splash_l.png
│ ├── ipad_splash.png
│ ├── iphone5_splash_l.png
│ ├── iphone5_splash.png
│ ├── iphone6_splash_l.png
│ ├── iphone6_splash.png
│ ├── iphoneplus_splash_l.png
│ ├── iphoneplus_splash.png
│ ├── iphonexr_splash_l.png
│ ├── iphonexr_splash.png
│ ├── iphonexsmax_splash_l.png
│ ├── iphonexsmax_splash.png
│ ├── iphonex_splash_l.png
│ └── iphonex_splash.png
├── baafa93a783b76e667ec.png
├── baba78f2a106d9baee83.png
├── bbb3e6d43389ba0d436c.png
├── bc8d51405ec040305a87.ico
├── cb6e840e08726299bf8f.svg
├── d0e56683308a17dba86d.png
├── d28a57b1e61f9f0dabd9.png
├── d31413d3f03c0873ccbb.png
├── d6ecf2254db85ff3b545.png
├── e62987a12a58b24f383a.png
├── eb8bef4f19b6ad227f46.png
├── f3bc149017432b87da2e.gif
├── f5bbb798cb2c65908633.png
├── f94ebf203ea0c91a47c6.png
├── favicon.ico
├── favicon.png
├── touchicon114.png
├── touchicon144.png
├── touchicon512.png
├── touchicon72.png
└── touchicon.png
I'm not going to detail what each of these files do, but the idea is to completely customize the branding of Jellyfin. In order to do that, copy these files FROM the jellyfin-web
directory, open them in your image editor of choice (.ico
files should be edited using a favicon editor) and replace the Jellyfin logo in each of them with your own.
Once you have such a folder set up, paste the following snippet into your playbook:
- name: Copying custom branding assets to Jellyfin Web directory...
ansible.builtin.copy:
src: /home/jane/jellyfin-logos/
remote_src: true
dest: /usr/share/jellyfin/web
mode: preserve
become: true
Enables image backdrops in Jellyfin by default for all users.
- name: Enabling backdrops for all users...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/main.jellyfin.bundle.js
regexp: 'enableBackdrops:function\(\){return L}'
replace: "enableBackdrops:function(){return _}"
become: true
Useful if you have intro-skipper enabled with the "Skip Credits" button.
- name: Disabling Next Episode info for all users...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/main.jellyfin.bundle.js
regexp: 'enableNextVideoInfoOverlay:function\(\){return R}'
replace: "enableNextVideoInfoOverlay:function(){return M}"
become: true
If you have set up this section correctly, or if you want to use the stock Jellyfin logo you do not need to edit anything in this snippet. Otherwise, replace the URL in img src="/web/assets/img/banner-light.png"
with the URL to a different image. As the name of the file we're trying to modify here changes in different Jellyfin versions, we have to look for it using known patterns. Please ensure you have added the step from here before proceeding.
- name: Adding banner to sidebar...
ansible.builtin.replace:
path: "{{ item.path }}"
regexp: >-
<div style="height:\.5em;"><\/div>
replace: >-
<div style="height:.5em;"></div>',n+='<a href="#/home.html"><img
src="/web/assets/img/banner-light.png" width=250px style="padding:
5px;display:block; margin-left: auto; margin-right: auto;
margin-top: 10px; margin-bottom: 10px;"></a>
become: true
loop: "{{ chunk_73233_js_file.files }}"
If you have set up this section correctly, or if you want to use the stock Jellyfin logo you do not need to edit anything in this snippet. Otherwise, replace the URL in img src="/web/assets/img/banner-light.png"
with the URL to a different image. Just like the previous step, let's find the file using known patterns.
- name: Adding logo to login page...
ansible.builtin.replace:
path: "{{ item.path }}"
regexp: '<div class="padded-left padded-right padded-bottom-page margin-auto-y">'
replace: >-
<div class="padded-left padded-right padded-bottom-page margin-auto-y">
<img src="/web/assets/img/banner-light.png" width=350px style="padding:
0px;display:block; margin-left: auto; margin-right: auto;">
become: true
loop: "{{ session_login_chunk_js_file.files }}"
- name: Setting pagination to infinite for all users...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/main.jellyfin.bundle.js
regexp: 'var t=parseInt\(this\.get\("libraryPageSize",!1\),10\);return 0===t\?0:t\|\|100}}'
replace: 'var t=parseInt(this.get("libraryPageSize",!1),10);return 0===t?0:t||0}}'
become: true
NOTE: If you're using a different Jellyfin theme than dark
, you must edit the file path in the second step of this snippet to the theme you're using (or you can duplicate the second step for every theme inside /usr/share/jellyfin/web/themes/
).
- name: Replacing banner with icon (1/2)...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/index.html
regexp: '{\.splashLogo{background-image:url\(assets\/img\/banner-light\.png\)}}'
replace: "{.splashLogo{background-image:url(assets/img/icon-transparent.png)}}"
become: true
- name: Replacing banner with icon (2/2)...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/themes/dark/theme.css
regexp: 'url\(\.\./\.\./assets/img/banner-light\.png\)'
replace: "url(../../assets/img/icon-transparent.png)"
become: true
In this snippet you need to change every instance of CHANGEME
to whatever you want your new page title to be. Again, be mindful of finding files with variable names.
- name: Changing page title (step 1/6)...
ansible.builtin.replace:
path: "{{ item.path }}"
regexp: 'document\.title="Jellyfin"'
replace: 'document.title="CHANGEME"'
become: true
loop: "{{ chunk_73233_js_file.files }}"
- name: Changing page title (step 2/6)...
ansible.builtin.replace:
path: "{{ item.path }}"
regexp: 'document\.title=e\|\|"Jellyfin"'
replace: 'document.title=e||"CHANGEME"'
become: true
loop: "{{ chunk_73233_js_file.files }}"
- name: Changing page title (step 3/6)...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/index.html
regexp: '<meta name="application-name" content="Jellyfin">'
replace: >-
<meta name="application-name" content="CHANGEME">
become: true
- name: Changing page title (step 4/6)...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/fd4301fdc170fd202474.json
regexp: >-
\"name\": \"Jellyfin\"
replace: >-
"name": "CHANGEME"
become: true
- name: Changing page title (step 5/6)...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/fd4301fdc170fd202474.json
regexp: >-
\"short_name\": \"Jellyfin\"
replace: >-
"short_name": "CHANGEME"
become: true
- name: Changing page title (step 6/6)...
ansible.builtin.replace:
path: /usr/share/jellyfin/web/index.html
regexp: '<title>Jellyfin<\/title>'
replace: >-
<title>CHANGEME</title>
become: true
Put the following snippet at the bottom of jellyfin-mods.yaml
if you've included this snippet at an earlier point.
- name: Starting Jellyfin Service...
ansible.builtin.service:
name: jellyfin
state: restarted
become: true
For this, create a new file in your playbook directory called restore-jf-web.yaml
restore-jf-web.yaml
---
- name: Restore Jellyfin...
hosts: all
remote_user: jane
tasks:
- name: Reinstalling jellyfin-web (1/2)...
ansible.builtin.apt:
name: jellyfin-web
state: absent
become: true
- name: Reinstalling jellyfin-web (2/2)...
ansible.builtin.apt:
name: jellyfin-web
state: latest
update_cache: true
become: true
Again, change the user as outlined here to a sudo user on the host machine. Alternatively, just run the following in the host machine's shell:
sudo apt --reinstall install jellyfin-web