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.target" ];
50 wantedBy = [ "multi-user.target" ];
51
52 # This is mostly follows: https://github.com/jellyfin/jellyfin/blob/master/fedora/jellyfin.service
53 # Upstream also disable some hardenings when running in LXC, we do the same with the isContainer option
54 serviceConfig = rec {
55 Type = "simple";
56 User = cfg.user;
57 Group = cfg.group;
58 StateDirectory = "jellyfin";
59 StateDirectoryMode = "0700";
60 CacheDirectory = "jellyfin";
61 CacheDirectoryMode = "0700";
62 UMask = "0077";
63 WorkingDirectory = "/var/lib/jellyfin";
64 ExecStart = "${cfg.package}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
65 Restart = "on-failure";
66 TimeoutSec = 15;
67 SuccessExitStatus = ["0" "143"];
68
69 # Security options:
70 NoNewPrivileges = true;
71 SystemCallArchitectures = "native";
72 # AF_NETLINK needed because Jellyfin monitors the network connection
73 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
74 RestrictNamespaces = !config.boot.isContainer;
75 RestrictRealtime = true;
76 RestrictSUIDSGID = true;
77 ProtectControlGroups = !config.boot.isContainer;
78 ProtectHostname = true;
79 ProtectKernelLogs = !config.boot.isContainer;
80 ProtectKernelModules = !config.boot.isContainer;
81 ProtectKernelTunables = !config.boot.isContainer;
82 LockPersonality = true;
83 PrivateTmp = !config.boot.isContainer;
84 # needed for hardware acceleration
85 PrivateDevices = false;
86 PrivateUsers = true;
87 RemoveIPC = true;
88
89 SystemCallFilter = [
90 "~@clock"
91 "~@aio"
92 "~@chown"
93 "~@cpu-emulation"
94 "~@debug"
95 "~@keyring"
96 "~@memlock"
97 "~@module"
98 "~@mount"
99 "~@obsolete"
100 "~@privileged"
101 "~@raw-io"
102 "~@reboot"
103 "~@setuid"
104 "~@swap"
105 ];
106 SystemCallErrorNumber = "EPERM";
107 };
108 };
109
110 users.users = mkIf (cfg.user == "jellyfin") {
111 jellyfin = {
112 group = cfg.group;
113 isSystemUser = true;
114 };
115 };
116
117 users.groups = mkIf (cfg.group == "jellyfin") {
118 jellyfin = {};
119 };
120
121 networking.firewall = mkIf cfg.openFirewall {
122 # from https://jellyfin.org/docs/general/networking/index.html
123 allowedTCPPorts = [ 8096 8920 ];
124 allowedUDPPorts = [ 1900 7359 ];
125 };
126
127 };
128
129 meta.maintainers = with lib.maintainers; [ minijackson ];
130}