@@ -8,16 +8,13 @@ with lib; let
88 inherit ( config ) nixflix ;
99 inherit ( config . nixflix ) globals ;
1010 cfg = config . nixflix . jellyfin ;
11- stateDir = "${ nixflix . stateDir } /jellyfin" ;
1211
13- # Convert camelCase to PascalCase
1412 toPascalCase = str : let
1513 firstChar = substring 0 1 str ;
1614 rest = substring 1 ( - 1 ) str ;
1715 in
1816 ( toUpper firstChar ) + rest ;
1917
20- # Recursive function to convert Nix attrs to XML
2118 attrsToXml = indent : attrs :
2219 concatStringsSep "\n " (
2320 mapAttrsToList ( name : value : let
@@ -44,251 +41,21 @@ with lib; let
4441 attrs
4542 ) ;
4643
47- networkXmlContent = ''
44+ mkXmlContent = tagName : attrs : ''
4845 <?xml version="1.0" encoding="utf-8"?>
49- <NetworkConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
50- ${ attrsToXml " " cfg . network }
51- </NetworkConfiguration >
46+ <${ tagName } xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
47+ ${ attrsToXml " " attrs }
48+ </${ tagName } >
5249 '' ;
53- in {
54- options . nixflix . jellyfin = {
55- enable = mkOption {
56- type = types . bool ;
57- default = false ;
58- description = "Whether to enable Jellyfin media server" ;
59- } ;
60-
61- package = mkOption {
62- type = types . package ;
63- default = pkgs . jellyfin ;
64- defaultText = literalExpression "pkgs.jellyfin" ;
65- description = "Jellyfin package to use" ;
66- } ;
67-
68- user = mkOption {
69- type = types . str ;
70- default = "jellyfin" ;
71- description = "User under which the service runs" ;
72- } ;
73-
74- group = mkOption {
75- type = types . str ;
76- default = globals . libraryOwner . group ;
77- defaultText = literalExpression "config.nixflix.globals.libraryOwner.group" ;
78- description = "Group under which the service runs" ;
79- } ;
80-
81- dataDir = mkOption {
82- type = types . path ;
83- default = stateDir ;
84- defaultText = literalExpression ''"'' ${config.nixflix.stateDir}/jellyfin"'' ;
85- description = "Directory containing the Jellyfin data files" ;
86- } ;
87-
88- configDir = mkOption {
89- type = types . path ;
90- default = "${ stateDir } /config" ;
91- defaultText = literalExpression ''"'' ${config.nixflix.stateDir}/jellyfin/config"'' ;
92- description = ''
93- Directory containing the server configuration files,
94- passed with `--configdir` see [configuration-directory](https://jellyfin.org/docs/general/administration/configuration/#configuration-directory)
95- '' ;
96- } ;
97-
98- cacheDir = mkOption {
99- type = types . path ;
100- default = "/var/cache/jellyfin" ;
101- description = ''
102- Directory containing the jellyfin server cache,
103- passed with `--cachedir` see [#cache-directory](https://jellyfin.org/docs/general/administration/configuration/#cache-directory)
104- '' ;
105- } ;
106-
107- logDir = mkOption {
108- type = types . path ;
109- default = "${ stateDir } /log" ;
110- defaultText = literalExpression ''"'' ${config.nixflix.stateDir}/jellyfin/log"'' ;
111- description = ''
112- Directory where the Jellyfin logs will be stored,
113- passed with `--logdir` see [#log-directory](https://jellyfin.org/docs/general/administration/configuration/#log-directory)
114- '' ;
115- } ;
116-
117- apiKeyPath = mkOption {
118- type = types . nullOr types . path ;
119- default = null ;
120- description = ''
121- Path to file containing the Jellyfin API key.
122- This is optional and currently not used for configuration,
123- but available for future API-based configuration enhancements.
124- '' ;
125- } ;
126-
127- openFirewall = mkOption {
128- type = types . bool ;
129- default = false ;
130- description = ''
131- Open ports in the firewall for Jellyfin.
132- TCP ports 8096 and 8920, UDP ports 1900 and 7359.
133- '' ;
134- } ;
135-
136- vpn = {
137- enable = mkOption {
138- type = types . bool ;
139- default = false ;
140- description = ''
141- Whether to route Jellyfin traffic through the VPN.
142- When false (default), Jellyfin bypasses the VPN.
143- When true, Jellyfin routes through the VPN (requires nixflix.mullvad.enable = true).
144- '' ;
145- } ;
146- } ;
147-
148- network = {
149- autoDiscovery = mkOption {
150- type = types . bool ;
151- default = true ;
152- description = "Enable auto-discovery" ;
153- } ;
154-
155- baseUrl = mkOption {
156- type = types . str ;
157- default =
158- if nixflix . nginx . enable
159- then "/jellyfin"
160- else "" ;
161- defaultText = literalExpression ''if config.nixflix.serviceNameIsUrlBase then "/jellyfin" else ""'' ;
162- description = "Base URL for Jellyfin (URL prefix)" ;
163- } ;
164-
165- certificatePassword = mkOption {
166- type = types . str ;
167- default = "" ;
168- description = "Certificate password" ;
169- } ;
17050
171- certificatePath = mkOption {
172- type = types . str ;
173- default = "" ;
174- description = "Path to certificate file" ;
175- } ;
176-
177- enableHttps = mkOption {
178- type = types . bool ;
179- default = false ;
180- description = "Enable HTTPS" ;
181- } ;
182-
183- enableIPv4 = mkOption {
184- type = types . bool ;
185- default = true ;
186- description = "Enable IPv4" ;
187- } ;
188-
189- enableIPv6 = mkOption {
190- type = types . bool ;
191- default = false ;
192- description = "Enable IPv6" ;
193- } ;
194-
195- enablePublishedServerUriByRequest = mkOption {
196- type = types . bool ;
197- default = false ;
198- description = "Enable published server URI by request" ;
199- } ;
200-
201- enableRemoteAccess = mkOption {
202- type = types . bool ;
203- default = true ;
204- description = "Enable remote access" ;
205- } ;
206-
207- enableUPnP = mkOption {
208- type = types . bool ;
209- default = false ;
210- description = "Enable UPnP" ;
211- } ;
212-
213- ignoreVirtualInterfaces = mkOption {
214- type = types . bool ;
215- default = true ;
216- description = "Ignore virtual interfaces" ;
217- } ;
218-
219- internalHttpPort = mkOption {
220- type = types . ints . between 0 65535 ;
221- default = 8096 ;
222- description = "Internal HTTP port" ;
223- } ;
224-
225- internalHttpsPort = mkOption {
226- type = types . ints . between 0 65535 ;
227- default = 8920 ;
228- description = "Internal HTTPS port" ;
229- } ;
230-
231- isRemoteIPFilterBlacklist = mkOption {
232- type = types . bool ;
233- default = false ;
234- description = "Is remote IP filter a blacklist" ;
235- } ;
236-
237- knownProxies = mkOption {
238- type = types . listOf types . str ;
239- default = [ ] ;
240- description = "List of known proxies" ;
241- } ;
242-
243- localNetworkAddresses = mkOption {
244- type = types . bool ;
245- default = false ;
246- description = "Local network addresses" ;
247- } ;
248-
249- localNetworkSubnets = mkOption {
250- type = types . listOf types . str ;
251- default = [ ] ;
252- description = "List of local network subnets" ;
253- } ;
254-
255- publicHttpPort = mkOption {
256- type = types . ints . between 0 65535 ;
257- default = 8096 ;
258- description = "Public HTTP port" ;
259- } ;
260-
261- publicHttpsPort = mkOption {
262- type = types . ints . between 0 65535 ;
263- default = 8920 ;
264- description = "Public HTTPS port" ;
265- } ;
266-
267- publishedServerUriBySubnet = mkOption {
268- type = types . listOf types . str ;
269- default = [ ] ;
270- description = "List of published server URIs by subnet" ;
271- } ;
272-
273- remoteIpFilter = mkOption {
274- type = types . listOf types . str ;
275- default = [ ] ;
276- description = "Remote IP filter list" ;
277- } ;
278-
279- requireHttps = mkOption {
280- type = types . bool ;
281- default = false ;
282- description = "Require HTTPS" ;
283- } ;
284-
285- virtualInterfaceNames = mkOption {
286- type = types . listOf types . str ;
287- default = [ "veth" ] ;
288- description = "List of virtual interface names" ;
289- } ;
290- } ;
291- } ;
51+ networkXmlContent = mkXmlContent "NetworkConfiguration" cfg . network ;
52+ brandingXmlContent = mkXmlContent "BrandingOptions" cfg . branding ;
53+ encodingXmlContent = mkXmlContent "EncodingOptions" cfg . encoding ;
54+ systemXmlContent = mkXmlContent "ServerConfiguration" cfg . system ;
55+ in {
56+ imports = [
57+ ./options
58+ ] ;
29259
29360 config = mkIf ( nixflix . enable && cfg . enable ) {
29461 assertions = [
31683 "d '${ cfg . logDir } ' 0750 ${ cfg . user } ${ cfg . group } - -"
31784 ] ;
31885
319- environment . etc . "jellyfin/network.xml.template" . text = networkXmlContent ;
86+ environment . etc = {
87+ "jellyfin/network.xml.template" . text = networkXmlContent ;
88+ "jellyfin/branding.xml.template" . text = brandingXmlContent ;
89+ "jellyfin/encoding.xml.template" . text = encodingXmlContent ;
90+ "jellyfin/system.xml.template" . text = systemXmlContent ;
91+ } ;
32092
32193 systemd . services . jellyfin = {
32294 description = "Jellyfin Media Server" ;
@@ -334,7 +106,12 @@ in {
334106 TimeoutSec = 15 ;
335107 SuccessExitStatus = "0 143" ;
336108
337- ExecStartPre = "${ pkgs . coreutils } /bin/install -m 640 /etc/jellyfin/network.xml.template '${ cfg . configDir } /network.xml'" ;
109+ ExecStartPre = pkgs . writeShellScript "jellyfin-setup-config" ''
110+ ${ pkgs . coreutils } /bin/install -m 640 /etc/jellyfin/network.xml.template '${ cfg . configDir } /network.xml'
111+ ${ pkgs . coreutils } /bin/install -m 640 /etc/jellyfin/branding.xml.template '${ cfg . configDir } /branding.xml'
112+ ${ pkgs . coreutils } /bin/install -m 640 /etc/jellyfin/encoding.xml.template '${ cfg . configDir } /encoding.xml'
113+ ${ pkgs . coreutils } /bin/install -m 640 /etc/jellyfin/system.xml.template '${ cfg . configDir } /system.xml'
114+ '' ;
338115
339116 ExecStart =
340117 if ( config . nixflix . mullvad . enable && ! cfg . vpn . enable )
@@ -412,16 +189,6 @@ in {
412189
413190 proxy_set_header X-Real-IP $remote_addr;
414191 proxy_buffering off;
415-
416- ${
417- if nixflix . theme . enable
418- then ''
419- proxy_set_header Accept-Encoding "";
420- sub_filter '</body>' '<style>@import url("https://theme-park.dev/css/base/jellyfin/${ nixflix . theme . name } .css");</style></body>';
421- sub_filter_once on;
422- ''
423- else ""
424- }
425192 '' ;
426193 } ;
427194 "${ cfg . network . baseUrl } /socket" = {
0 commit comments