1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6 cfg = config.services.jellyfin;
7in
8{
9 options = {
10 services.jellyfin = {
11 enable = mkEnableOption (lib.mdDoc "Jellyfin Media Server");
12
13 user = mkOption {
14 type = types.str;
15 default = "jellyfin";
16 description = lib.mdDoc "User account under which Jellyfin runs.";
17 };
18
19 package = mkOption {
20 type = types.package;
21 default = pkgs.jellyfin;
22 defaultText = literalExpression "pkgs.jellyfin";
23 description = lib.mdDoc ''
24 Jellyfin package to use.
25 '';
26 };
27
28 group = mkOption {
29 type = types.str;
30 default = "jellyfin";
31 description = lib.mdDoc "Group under which jellyfin runs.";
32 };
33
34 openFirewall = mkOption {
35 type = types.bool;
36 default = false;
37 description = lib.mdDoc ''
38 Open the default ports in the firewall for the media server. The
39 HTTP/HTTPS ports can be changed in the Web UI, so this option should
40 only be used if they are unchanged.
41 '';
42 };
43 };
44 };
45
46 config = mkIf cfg.enable {
47 systemd.services.jellyfin = {
48 description = "Jellyfin Media Server";
49 after = [ "network-online.target" ];
50 wants = [ "network-online.target" ];
51 wantedBy = [ "multi-user.target" ];
52
53 # This is mostly follows: https://github.com/jellyfin/jellyfin/blob/master/fedora/jellyfin.service
54 # Upstream also disable some hardenings when running in LXC, we do the same with the isContainer option
55 serviceConfig = rec {
56 Type = "simple";
57 User = cfg.user;
58 Group = cfg.group;
59 StateDirectory = "jellyfin";
60 StateDirectoryMode = "0700";
61 CacheDirectory = "jellyfin";
62 CacheDirectoryMode = "0700";
63 UMask = "0077";
64 WorkingDirectory = "/var/lib/jellyfin";
65 ExecStart = "${cfg.package}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
66 Restart = "on-failure";
67 TimeoutSec = 15;
68 SuccessExitStatus = ["0" "143"];
69
70 # Security options:
71 NoNewPrivileges = true;
72 SystemCallArchitectures = "native";
73 # AF_NETLINK needed because Jellyfin monitors the network connection
74 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
75 RestrictNamespaces = !config.boot.isContainer;
76 RestrictRealtime = true;
77 RestrictSUIDSGID = true;
78 ProtectControlGroups = !config.boot.isContainer;
79 ProtectHostname = true;
80 ProtectKernelLogs = !config.boot.isContainer;
81 ProtectKernelModules = !config.boot.isContainer;
82 ProtectKernelTunables = !config.boot.isContainer;
83 LockPersonality = true;
84 PrivateTmp = !config.boot.isContainer;
85 # needed for hardware acceleration
86 PrivateDevices = false;
87 PrivateUsers = true;
88 RemoveIPC = true;
89
90 SystemCallFilter = [
91 "~@clock"
92 "~@aio"
93 "~@chown"
94 "~@cpu-emulation"
95 "~@debug"
96 "~@keyring"
97 "~@memlock"
98 "~@module"
99 "~@mount"
100 "~@obsolete"
101 "~@privileged"
102 "~@raw-io"
103 "~@reboot"
104 "~@setuid"
105 "~@swap"
106 ];
107 SystemCallErrorNumber = "EPERM";
108 };
109 };
110
111 users.users = mkIf (cfg.user == "jellyfin") {
112 jellyfin = {
113 group = cfg.group;
114 isSystemUser = true;
115 };
116 };
117
118 users.groups = mkIf (cfg.group == "jellyfin") {
119 jellyfin = {};
120 };
121
122 networking.firewall = mkIf cfg.openFirewall {
123 # from https://jellyfin.org/docs/general/networking/index.html
124 allowedTCPPorts = [ 8096 8920 ];
125 allowedUDPPorts = [ 1900 7359 ];
126 };
127
128 };
129
130 meta.maintainers = with lib.maintainers; [ minijackson ];
131}