1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 mkEnableOption
11 mkPackageOption
12 mkOption
13 maintainers
14 ;
15 inherit (lib.types)
16 bool
17 port
18 str
19 submodule
20 ;
21 cfg = config.services.navidrome;
22 settingsFormat = pkgs.formats.json { };
23in
24{
25 options = {
26 services.navidrome = {
27
28 enable = mkEnableOption "Navidrome music server";
29
30 package = mkPackageOption pkgs "navidrome" { };
31
32 settings = mkOption {
33 type = submodule {
34 freeformType = settingsFormat.type;
35
36 options = {
37 Address = mkOption {
38 default = "127.0.0.1";
39 description = "Address to run Navidrome on.";
40 type = str;
41 };
42
43 Port = mkOption {
44 default = 4533;
45 description = "Port to run Navidrome on.";
46 type = port;
47 };
48 };
49 };
50 default = { };
51 example = {
52 MusicFolder = "/mnt/music";
53 };
54 description = "Configuration for Navidrome, see <https://www.navidrome.org/docs/usage/configuration-options/> for supported values.";
55 };
56
57 user = mkOption {
58 type = str;
59 default = "navidrome";
60 description = "User under which Navidrome runs.";
61 };
62
63 group = mkOption {
64 type = str;
65 default = "navidrome";
66 description = "Group under which Navidrome runs.";
67 };
68
69 openFirewall = mkOption {
70 type = bool;
71 default = false;
72 description = "Whether to open the TCP port in the firewall";
73 };
74 };
75 };
76
77 config =
78 let
79 inherit (lib) mkIf optional getExe;
80 WorkingDirectory = "/var/lib/navidrome";
81 in
82 mkIf cfg.enable {
83 systemd = {
84 tmpfiles.settings.navidromeDirs = {
85 "${cfg.settings.DataFolder or WorkingDirectory}"."d" = {
86 mode = "700";
87 inherit (cfg) user group;
88 };
89 "${cfg.settings.CacheFolder or (WorkingDirectory + "/cache")}"."d" = {
90 mode = "700";
91 inherit (cfg) user group;
92 };
93 };
94 services.navidrome = {
95 description = "Navidrome Media Server";
96 after = [ "network.target" ];
97 wantedBy = [ "multi-user.target" ];
98 serviceConfig = {
99 ExecStart = ''
100 ${getExe cfg.package} --configfile ${settingsFormat.generate "navidrome.json" cfg.settings}
101 '';
102 User = cfg.user;
103 Group = cfg.group;
104 StateDirectory = "navidrome";
105 inherit WorkingDirectory;
106 RuntimeDirectory = "navidrome";
107 RootDirectory = "/run/navidrome";
108 ReadWritePaths = "";
109 BindPaths =
110 optional (cfg.settings ? DataFolder) cfg.settings.DataFolder
111 ++ optional (cfg.settings ? CacheFolder) cfg.settings.CacheFolder;
112 BindReadOnlyPaths = [
113 # navidrome uses online services to download additional album metadata / covers
114 "${
115 config.environment.etc."ssl/certs/ca-certificates.crt".source
116 }:/etc/ssl/certs/ca-certificates.crt"
117 builtins.storeDir
118 "/etc"
119 ] ++ optional (cfg.settings ? MusicFolder) cfg.settings.MusicFolder;
120 CapabilityBoundingSet = "";
121 RestrictAddressFamilies = [
122 "AF_UNIX"
123 "AF_INET"
124 "AF_INET6"
125 ];
126 RestrictNamespaces = true;
127 PrivateDevices = true;
128 PrivateUsers = true;
129 ProtectClock = true;
130 ProtectControlGroups = true;
131 ProtectHome = true;
132 ProtectKernelLogs = true;
133 ProtectKernelModules = true;
134 ProtectKernelTunables = true;
135 SystemCallArchitectures = "native";
136 SystemCallFilter = [
137 "@system-service"
138 "~@privileged"
139 ];
140 RestrictRealtime = true;
141 LockPersonality = true;
142 MemoryDenyWriteExecute = true;
143 UMask = "0066";
144 ProtectHostname = true;
145 };
146 };
147 };
148
149 users.users = mkIf (cfg.user == "navidrome") {
150 navidrome = {
151 inherit (cfg) group;
152 isSystemUser = true;
153 };
154 };
155
156 users.groups = mkIf (cfg.group == "navidrome") { navidrome = { }; };
157
158 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.Port ];
159 };
160 meta.maintainers = with maintainers; [ nu-nu-ko ];
161}