diff --git a/hosts b/hosts
index 0e2104f..2f1afcb 100644
--- a/hosts
+++ b/hosts
@@ -69,6 +69,9 @@ logs0.pyrocufflink.blue
[home-assistant]
+[jellyfin]
+file0.pyrocufflink.blue
+
[jenkins-slave]
[journal2ntfy:children]
diff --git a/jellyfin.yml b/jellyfin.yml
new file mode 100644
index 0000000..c8f80d6
--- /dev/null
+++ b/jellyfin.yml
@@ -0,0 +1,5 @@
+- hosts: jellyfin
+ roles:
+ - role: apache
+ tags: apache
+ - jellyfin
diff --git a/roles/jellyfin/defaults/main.yml b/roles/jellyfin/defaults/main.yml
new file mode 100644
index 0000000..737e2ae
--- /dev/null
+++ b/roles/jellyfin/defaults/main.yml
@@ -0,0 +1,13 @@
+jellyfin_version: latest
+jellyfin_container_image: docker.io/jellyfin/jellyfin
+jellyfin_media_dirs:
+- /srv/cifs/Music
+- /srv/cifs/Movies
+- /srv/cifs/TV Shows
+jellyfin_server_name: jellyfin.pyrocufflink.blue
+jellyfin_server_url: https://{{ jellyfin_server_name }}
+
+jellyfin_ssl_certificate: >-
+ {{ apache_ssl_certificate }}
+jellyfin_ssl_certificate_key: >-
+ {{ apache_ssl_certificate_key }}
diff --git a/roles/jellyfin/handlers/main.yml b/roles/jellyfin/handlers/main.yml
new file mode 100644
index 0000000..b830b5f
--- /dev/null
+++ b/roles/jellyfin/handlers/main.yml
@@ -0,0 +1,9 @@
+- name: reload systemd
+ systemd:
+ daemon_reload: true
+
+- name: restart jellyfin
+ systemd:
+ name: jellyfin
+ state: restarted
+
diff --git a/roles/jellyfin/tasks/deploy.yml b/roles/jellyfin/tasks/deploy.yml
new file mode 100644
index 0000000..45031a3
--- /dev/null
+++ b/roles/jellyfin/tasks/deploy.yml
@@ -0,0 +1,79 @@
+- name: ensure jellyfin group exists
+ group:
+ name: jellyfin
+ gid: 201
+ system: true
+ state: present
+ tags:
+ - user
+ - group
+- name: ensure jellyfin user exists
+ user:
+ name: jellyfin
+ uid: 201
+ group: jellyfin
+ system: true
+ home: /
+ createhome: false
+ state: present
+ tags:
+ - user
+
+- name: ensure jellyfin cache directory exists
+ file:
+ path: /var/cache/jellyfin
+ owner: jellyfin
+ group: jellyfin
+ mode: u=rwx,go=
+ state: directory
+ tags:
+ - datadir
+- name: ensure jellyfin data directory exists
+ file:
+ path: /var/lib/jellyfin
+ owner: jellyfin
+ group: jellyfin
+ mode: u=rwx,og=rx
+ state: directory
+ tags:
+ - datadir
+
+- name: ensure jellyfin environment is configured
+ template:
+ src: jellyfin.env.j2
+ dest: /etc/sysconfig/jellyfin
+ owner: root
+ group: root
+ mode: u=rw,go=
+ tags:
+ - config
+
+- name: ensure jellyfin.container systemd unit exists
+ template:
+ src: jellyfin.container.j2
+ dest: /etc/containers/systemd/jellyfin.container
+ owner: root
+ group: root
+ mode: u=rw,go=r
+ notify:
+ - reload systemd
+ - restart jellyfin
+ tags:
+ - systemd
+ - container
+
+- name: flush handlers
+ meta: flush_handlers
+
+- name: ensure jellyfin starts at boot
+ systemd:
+ name: jellyfin
+ enabled: true
+ tags:
+ - service
+- name: ensure jellyfin is running
+ systemd:
+ name: jellyfin
+ state: started
+ tags:
+ - service
diff --git a/roles/jellyfin/tasks/httpd-proxy.yml b/roles/jellyfin/tasks/httpd-proxy.yml
new file mode 100644
index 0000000..8f8a74a
--- /dev/null
+++ b/roles/jellyfin/tasks/httpd-proxy.yml
@@ -0,0 +1,19 @@
+- name: ensure apache is configured to proxy for jellyfin
+ template:
+ src: jellyfin.httpd.conf.j2
+ dest: /etc/httpd/conf.d/jellyfin.conf
+ owner: root
+ group: root
+ mode: u=rw,go=r
+ notify:
+ - reload httpd
+ tags:
+ - apache
+
+- name: ensure selinux is configured for apache reverse proxy
+ seboolean:
+ name: httpd_can_network_connect
+ state: true
+ persistent: true
+ tags:
+ - selinux
diff --git a/roles/jellyfin/tasks/install.yml b/roles/jellyfin/tasks/install.yml
new file mode 100644
index 0000000..16632c8
--- /dev/null
+++ b/roles/jellyfin/tasks/install.yml
@@ -0,0 +1,12 @@
+- name: ensure podman is installed
+ package:
+ name:
+ - container-selinux
+ - podman
+ state: present
+
+- name: ensure jellyfin container image is present
+ podman_image:
+ name: '{{ jellyfin_container_image }}:{{ jellyfin_version }}'
+ state: present
+
diff --git a/roles/jellyfin/tasks/main.yml b/roles/jellyfin/tasks/main.yml
new file mode 100644
index 0000000..2d0d063
--- /dev/null
+++ b/roles/jellyfin/tasks/main.yml
@@ -0,0 +1,9 @@
+- block:
+ - import_tasks: install.yml
+ tags:
+ - install
+ - import_tasks: deploy.yml
+ - import_tasks: httpd-proxy.yml
+ tags:
+ - jellyfin
+
diff --git a/roles/jellyfin/templates/jellyfin.container.j2 b/roles/jellyfin/templates/jellyfin.container.j2
new file mode 100644
index 0000000..5cd45aa
--- /dev/null
+++ b/roles/jellyfin/templates/jellyfin.container.j2
@@ -0,0 +1,39 @@
+[Unit]
+Description=Jellyfin Media Server
+Wants=network.target
+After=network.target
+
+[Container]
+Image={{ jellyfin_container_image }}:{{ jellyfin_version }}
+#UserNS=keep-id
+User=201
+Group=201
+EnvironmentFile=/etc/sysconfig/jellyfin
+Volume=/var/lib/jellyfin:/config:rw,z
+Volume=/var/cache/jellyfin:/cache:rw,z
+{% for path in jellyfin_media_dirs %}
+Volume={{ path }}:/media/{{ path | basename }}:ro
+{% endfor %}
+Network=host
+NoNewPrivileges=yes
+
+[Service]
+#MemoryDenyWriteExecute=yes
+PrivateTmp=yes
+ProtectClock=yes
+ProtectHome=yes
+ProtectKernelLogs=yes
+ProtectKernelModules=yes
+ProtectKernelTunables=yes
+ProtectProc=invisible
+ProtectSystem=strict
+ReadWritePaths=/var/lib/jellyfin
+ReadWritePaths=/var/lib/containers/storage
+ReadWritePaths=/var/cache/jellyfin
+RestrictRealtime=yes
+RestrictSUIDSGID=yes
+SuccessExitStatus=0 143
+UMask=0077
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/jellyfin/templates/jellyfin.env.j2 b/roles/jellyfin/templates/jellyfin.env.j2
new file mode 100644
index 0000000..5b19a4e
--- /dev/null
+++ b/roles/jellyfin/templates/jellyfin.env.j2
@@ -0,0 +1 @@
+JELLYFIN_PublishedServerUrl={{ jellyfin_server_url }}
diff --git a/roles/jellyfin/templates/jellyfin.httpd.conf.j2 b/roles/jellyfin/templates/jellyfin.httpd.conf.j2
new file mode 100644
index 0000000..af6cb97
--- /dev/null
+++ b/roles/jellyfin/templates/jellyfin.httpd.conf.j2
@@ -0,0 +1,27 @@
+
+ ServerName {{ jellyfin_server_name }}
+
+ RewriteEngine On
+ RewriteCond %{HTTPS} !on
+ RewriteRule /.* https://%{SERVER_NAME}$0 [R=301,L]
+
+
+
+ ServerName {{ jellyfin_server_name }}
+
+ SSLCertificateFile {{ jellyfin_ssl_certificate }}
+ SSLCertificateKeyFile {{ jellyfin_ssl_certificate_key }}
+ SSLCertificateChainFile {{ jellyfin_ssl_certificate }}
+
+ ProxyPreserveHost On
+ ProxyRequests Off
+
+ RewriteEngine On
+ RewriteCond %{HTTP:Upgrade} =websocket [NC]
+ RewriteRule /(.*) ws://localhost:8096/$1 [P,L]
+ RewriteRule /(.*) http://localhost:8096/$1 [P,L]
+ ProxyPassReverse / http://localhost:8096/
+
+ Header always set \
+ Strict-Transport-Security "max-age=63072000; includeSubDomains"
+