Personal-use NixOS configuration
1# based on https://github.com/tofu-salad/emby-server-flake/tree/a8c30e4160ee9c06ff1e875cd08dfd952868a5fb
2
3{
4 config,
5 pkgs,
6 lib,
7 ...
8}:
9
10let
11 pkgs-internal = import ../packages { inherit pkgs; };
12
13 inherit (lib)
14 mkIf
15 getExe
16 mkEnableOption
17 mkOption
18 ;
19
20 inherit (lib.types)
21 str
22 path
23 bool
24 package
25 ;
26 cfg = config.services.emby;
27in
28{
29 options = {
30 services.emby = {
31 enable = mkEnableOption "Emby Media Server";
32
33 package = mkOption {
34 type = package;
35 default = pkgs-internal.emby-server;
36 description = "The Emby package to use.";
37 };
38
39 user = mkOption {
40 type = str;
41 default = "emby";
42 description = "User account under which Emby runs.";
43 };
44
45 group = mkOption {
46 type = str;
47 default = "emby";
48 description = "Group under which Emby runs.";
49 };
50
51 dataDir = mkOption {
52 type = path;
53 default = "/var/lib/emby";
54 description = ''
55 Base data directory where Emby stores its program data.
56 This is passed to Emby with the `-programdata` flag.
57 '';
58 };
59
60 openFirewall = mkOption {
61 type = bool;
62 default = false;
63 description = ''
64 Open the default ports in the firewall for the media server.
65 Opens port 8096 (HTTP) and 8920 (HTTPS).
66 '';
67 };
68 };
69 };
70
71 config = mkIf cfg.enable {
72 systemd = {
73 tmpfiles.settings.embyDirs = {
74 "${cfg.dataDir}"."d" = {
75 mode = "700";
76 inherit (cfg) user group;
77 };
78 # Emby creates subdirectories automatically, but we ensure the base exists
79 "${cfg.dataDir}/plugins"."d" = {
80 mode = "755";
81 inherit (cfg) user group;
82 };
83 "${cfg.dataDir}/logs"."d" = {
84 mode = "755";
85 inherit (cfg) user group;
86 };
87 };
88
89 services.emby = {
90 description = "Emby Media Server";
91 after = [ "network-online.target" ];
92 wants = [ "network-online.target" ];
93 wantedBy = [ "multi-user.target" ];
94
95 serviceConfig = {
96 Type = "simple";
97 User = cfg.user;
98 Group = cfg.group;
99 UMask = "0077";
100 WorkingDirectory = cfg.dataDir;
101 ExecStart = "${getExe cfg.package} -programdata '${cfg.dataDir}'";
102 Restart = "on-failure";
103 TimeoutSec = 15;
104 SuccessExitStatus = [
105 "0"
106 "143"
107 ];
108
109 # Security options (adapted from Jellyfin module):
110 NoNewPrivileges = true;
111 SystemCallArchitectures = "native";
112 # AF_NETLINK needed because Emby monitors the network connection
113 RestrictAddressFamilies = [
114 "AF_UNIX"
115 "AF_INET"
116 "AF_INET6"
117 "AF_NETLINK"
118 ];
119 RestrictNamespaces = !config.boot.isContainer;
120 RestrictRealtime = true;
121 RestrictSUIDSGID = true;
122 ProtectControlGroups = !config.boot.isContainer;
123 ProtectHostname = true;
124 ProtectKernelLogs = !config.boot.isContainer;
125 ProtectKernelModules = !config.boot.isContainer;
126 ProtectKernelTunables = !config.boot.isContainer;
127 LockPersonality = true;
128 PrivateTmp = !config.boot.isContainer;
129 # needed for hardware acceleration
130 PrivateDevices = false;
131 PrivateUsers = true;
132 RemoveIPC = true;
133
134 SystemCallFilter = [
135 "~@clock"
136 "~@aio"
137 "~@chown"
138 "~@cpu-emulation"
139 "~@debug"
140 "~@keyring"
141 "~@memlock"
142 "~@module"
143 "~@mount"
144 "~@obsolete"
145 "~@privileged"
146 "~@raw-io"
147 "~@reboot"
148 "~@setuid"
149 "~@swap"
150 ];
151 SystemCallErrorNumber = "EPERM";
152 };
153 };
154 };
155
156 users.users = mkIf (cfg.user == "emby") {
157 emby = {
158 inherit (cfg) group;
159 isSystemUser = true;
160 };
161 };
162
163 users.groups = mkIf (cfg.group == "emby") {
164 emby = { };
165 };
166
167 networking.firewall = mkIf cfg.openFirewall {
168 # Emby default ports
169 allowedTCPPorts = [
170 8096 # HTTP
171 8920 # HTTPS
172 ];
173 };
174 };
175}