vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / networking / syncplay.nix
blob22808248abbc4861b8da3227675344e06f4f39ab
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.syncplay;
8   cmdArgs =
9     [ "--port" cfg.port ]
10     ++ optionals (cfg.isolateRooms) [ "--isolate-rooms" ]
11     ++ optionals (!cfg.ready) [ "--disable-ready" ]
12     ++ optionals (!cfg.chat) [ "--disable-chat" ]
13     ++ optionals (cfg.salt != null) [ "--salt" cfg.salt ]
14     ++ optionals (cfg.motdFile != null) [ "--motd-file" cfg.motdFile ]
15     ++ optionals (cfg.roomsDBFile != null) [ "--rooms-db-file" cfg.roomsDBFile ]
16     ++ optionals (cfg.permanentRoomsFile != null) [ "--permanent-rooms-file" cfg.permanentRoomsFile ]
17     ++ [ "--max-chat-message-length" cfg.maxChatMessageLength ]
18     ++ [ "--max-username-length" cfg.maxUsernameLength ]
19     ++ optionals (cfg.statsDBFile != null) [ "--stats-db-file" cfg.statsDBFile ]
20     ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ]
21     ++ optionals cfg.ipv4Only [ "--ipv4-only" ]
22     ++ optionals cfg.ipv6Only [ "--ipv6-only" ]
23     ++ optionals (cfg.interfaceIpv4 != "") [ "--interface-ipv4" cfg.interfaceIpv4 ]
24     ++ optionals (cfg.interfaceIpv6 != "") [ "--interface-ipv6" cfg.interfaceIpv6 ]
25     ++ cfg.extraArgs;
27   useACMEHostDir = optionalString (cfg.useACMEHost != null) config.security.acme.certs.${cfg.useACMEHost}.directory;
30   imports = [
31     (mkRemovedOptionModule [ "services" "syncplay" "user" ]
32       "The syncplay service now uses DynamicUser, override the systemd unit settings if you need the old functionality.")
33     (mkRemovedOptionModule [ "services" "syncplay" "group" ]
34       "The syncplay service now uses DynamicUser, override the systemd unit settings if you need the old functionality.")
35   ];
37   options = {
38     services.syncplay = {
39       enable = mkOption {
40         type = types.bool;
41         default = false;
42         description = ''
43           If enabled, start the Syncplay server.
44         '';
45       };
47       port = mkOption {
48         type = types.port;
49         default = 8999;
50         description = ''
51           TCP port to bind to.
52         '';
53       };
55       passwordFile = mkOption {
56         type = types.nullOr types.path;
57         default = null;
58         description = ''
59           Path to the file that contains the server password. If
60           `null`, the server doesn't require a password.
61         '';
62       };
64       isolateRooms = mkOption {
65         type = types.bool;
66         default = false;
67         description = ''
68           Enable room isolation.
69         '';
70       };
72       ready = mkOption {
73         type = types.bool;
74         default = true;
75         description = ''
76           Check readiness of users.
77         '';
78       };
80       chat = mkOption {
81         type = types.bool;
82         default = true;
83         description = ''
84           Chat with users in the same room.
85         '';
86       };
88       salt = mkOption {
89         type = types.nullOr types.str;
90         default = null;
91         description = ''
92           Salt to allow room operator passwords generated by this server
93           instance to still work when the server is restarted.  The salt will be
94           readable in the nix store and the processlist.  If this is not
95           intended use `saltFile` instead.  Mutually exclusive with
96           {option}`services.syncplay.saltFile`.
97         '';
98       };
100       saltFile = mkOption {
101         type = types.nullOr types.path;
102         default = null;
103         description = ''
104           Path to the file that contains the server salt.  This allows room
105           operator passwords generated by this server instance to still work
106           when the server is restarted.  `null`, the server doesn't load the
107           salt from a file.  Mutually exclusive with
108           {option}`services.syncplay.salt`.
109         '';
110       };
112       motd = mkOption {
113         type = types.nullOr types.str;
114         default = null;
115         description = ''
116           Text to display when users join. The motd will be readable in the nix store
117           and the processlist.  If this is not intended use `motdFile` instead.
118           Will be overriden by {option}`services.syncplay.motdFile`.
119         '';
120       };
122       motdFile = mkOption {
123         type = types.nullOr types.str;
124         default = if cfg.motd != null then (builtins.toFile "motd" cfg.motd) else null;
125         defaultText = literalExpression ''if services.syncplay.motd != null then (builtins.toFile "motd" services.syncplay.motd) else null'';
126         description = ''
127           Path to text to display when users join.
128           Will override {option}`services.syncplay.motd`.
129         '';
130       };
132       roomsDBFile = mkOption {
133         type = types.nullOr types.str;
134         default = null;
135         example = "rooms.db";
136         description = ''
137           Path to SQLite database file to store room states.
138           Relative to the working directory provided by systemd.
139         '';
140       };
142       permanentRooms = mkOption {
143         type = types.listOf types.str;
144         default = [ ];
145         description = ''
146           List of rooms that will be listed even if the room is empty.
147           Will be overriden by {option}`services.syncplay.permanentRoomsFile`.
148         '';
149       };
151       permanentRoomsFile = mkOption {
152         type = types.nullOr types.str;
153         default = if cfg.permanentRooms != [ ] then (builtins.toFile "perm" (builtins.concatStringsSep "\n" cfg.permanentRooms)) else null;
154         defaultText = literalExpression ''if services.syncplay.permanentRooms != [ ] then (builtins.toFile "perm" (builtins.concatStringsSep "\n" services.syncplay.permanentRooms)) else null'';
155         description = ''
156           File with list of rooms that will be listed even if the room is empty,
157           newline delimited.
158           Will override {option}`services.syncplay.permanentRooms`.
159         '';
160       };
162       maxChatMessageLength = mkOption {
163         type = types.ints.unsigned;
164         default = 150;
165         description = ''
166           Maximum number of characters in a chat message.
167         '';
168       };
170       maxUsernameLength = mkOption {
171         type = types.ints.unsigned;
172         default = 16;
173         description = ''
174           Maximum number of characters in a username.
175         '';
176       };
178       statsDBFile = mkOption {
179         type = types.nullOr types.str;
180         default = null;
181         example = "stats.db";
182         description = ''
183           Path to SQLite database file to store stats.
184           Relative to the working directory provided by systemd.
185         '';
186       };
188       certDir = mkOption {
189         type = types.nullOr types.path;
190         default = null;
191         description = ''
192           TLS certificates directory to use for encryption. See
193           <https://github.com/Syncplay/syncplay/wiki/TLS-support>.
194         '';
195       };
197       useACMEHost = mkOption {
198         type = types.nullOr types.str;
199         default = null;
200         example = "syncplay.example.com";
201         description = ''
202           If set, use NixOS-generated ACME certificate with the specified name for TLS.
204           Note that it requires {option}`security.acme` to be setup, e.g., credentials provided if using DNS-01 validation.
205         '';
206       };
208       ipv4Only = mkOption {
209         type = types.bool;
210         default = false;
211         description = ''
212           Listen only on IPv4 when strting the server.
213         '';
214       };
216       ipv6Only = mkOption {
217         type = types.bool;
218         default = false;
219         description = ''
220           Listen only on IPv6 when strting the server.
221         '';
222       };
224       interfaceIpv4 = mkOption {
225         type = types.str;
226         default = "";
227         description = ''
228           The IP address to bind to for IPv4. Leaving it empty defaults to using all.
229         '';
230       };
232       interfaceIpv6 = mkOption {
233         type = types.str;
234         default = "";
235         description = ''
236           The IP address to bind to for IPv6. Leaving it empty defaults to using all.
237         '';
238       };
240       extraArgs = mkOption {
241         type = types.listOf types.str;
242         default = [ ];
243         description = ''
244           Additional arguments to be passed to the service.
245         '';
246       };
248       package = mkOption {
249         type = types.package;
250         default = pkgs.syncplay-nogui;
251         defaultText = literalExpression "pkgs.syncplay-nogui";
252         description = ''
253           Package to use for syncplay.
254         '';
255       };
256     };
257   };
259   config = mkIf cfg.enable {
260     assertions = [
261       {
262         assertion = cfg.salt == null || cfg.saltFile == null;
263         message = "services.syncplay.salt and services.syncplay.saltFile are mutually exclusive.";
264       }
265       {
266         assertion = cfg.certDir == null || cfg.useACMEHost == null;
267         message = "services.syncplay.certDir and services.syncplay.useACMEHost are mutually exclusive.";
268       }
269       {
270         assertion = !cfg.ipv4Only || !cfg.ipv6Only;
271         message = "services.syncplay.ipv4Only and services.syncplay.ipv6Only are mutually exclusive.";
272       }
273     ];
275     warnings = optional (cfg.interfaceIpv4 != "" && cfg.ipv6Only) "You have specified services.syncplay.interfaceIpv4 but IPv4 is disabled by services.syncplay.ipv6Only."
276       ++ optional (cfg.interfaceIpv6 != "" && cfg.ipv4Only) "You have specified services.syncplay.interfaceIpv6 but IPv6 is disabled by services.syncplay.ipv4Only.";
278     security.acme.certs = mkIf (cfg.useACMEHost != null) {
279       "${cfg.useACMEHost}".reloadServices = [ "syncplay.service" ];
280     };
282     networking.firewall.allowedTCPPorts = [ cfg.port ];
283     systemd.services.syncplay = {
284       description = "Syncplay Service";
285       wantedBy = [ "multi-user.target" ];
286       wants = [ "network-online.target" ];
287       after = [ "network-online.target" ];
289       serviceConfig = {
290         DynamicUser = true;
291         StateDirectory = "syncplay";
292         WorkingDirectory = "%S/syncplay";
293         LoadCredential = optional (cfg.passwordFile != null) "password:${cfg.passwordFile}"
294           ++ optional (cfg.saltFile != null) "salt:${cfg.saltFile}"
295           ++ optionals (cfg.useACMEHost != null) [
296           "cert.pem:${useACMEHostDir}/cert.pem"
297           "privkey.pem:${useACMEHostDir}/key.pem"
298           "chain.pem:${useACMEHostDir}/chain.pem"
299         ];
300       };
302       script = ''
303         ${optionalString (cfg.passwordFile != null) ''
304           export SYNCPLAY_PASSWORD=$(cat "''${CREDENTIALS_DIRECTORY}/password")
305         ''}
306         ${optionalString (cfg.saltFile != null) ''
307           export SYNCPLAY_SALT=$(cat "''${CREDENTIALS_DIRECTORY}/salt")
308         ''}
309         exec ${cfg.package}/bin/syncplay-server ${escapeShellArgs cmdArgs} ${optionalString (cfg.useACMEHost != null) "--tls $CREDENTIALS_DIRECTORY"}
310       '';
311     };
312   };