grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / games / minecraft-server.nix
blobbc56feffb748592c5fbbc95f9883b879dcb14dd7
1 { config, lib, pkgs, ... }:
2 let
3   cfg = config.services.minecraft-server;
5   # We don't allow eula=false anyways
6   eulaFile = builtins.toFile "eula.txt" ''
7     # eula.txt managed by NixOS Configuration
8     eula=true
9   '';
11   whitelistFile = pkgs.writeText "whitelist.json"
12     (builtins.toJSON
13       (lib.mapAttrsToList (n: v: { name = n; uuid = v; }) cfg.whitelist));
15   cfgToString = v: if builtins.isBool v then lib.boolToString v else toString v;
17   serverPropertiesFile = pkgs.writeText "server.properties" (''
18     # server.properties managed by NixOS configuration
19   '' + lib.concatStringsSep "\n" (lib.mapAttrsToList
20     (n: v: "${n}=${cfgToString v}") cfg.serverProperties));
22   stopScript = pkgs.writeShellScript "minecraft-server-stop" ''
23     echo stop > ${config.systemd.sockets.minecraft-server.socketConfig.ListenFIFO}
25     # Wait for the PID of the minecraft server to disappear before
26     # returning, so systemd doesn't attempt to SIGKILL it.
27     while kill -0 "$1" 2> /dev/null; do
28       sleep 1s
29     done
30   '';
32   # To be able to open the firewall, we need to read out port values in the
33   # server properties, but fall back to the defaults when those don't exist.
34   # These defaults are from https://minecraft.gamepedia.com/Server.properties#Java_Edition_3
35   defaultServerPort = 25565;
37   serverPort = cfg.serverProperties.server-port or defaultServerPort;
39   rconPort = if cfg.serverProperties.enable-rcon or false
40     then cfg.serverProperties."rcon.port" or 25575
41     else null;
43   queryPort = if cfg.serverProperties.enable-query or false
44     then cfg.serverProperties."query.port" or 25565
45     else null;
47 in {
48   options = {
49     services.minecraft-server = {
51       enable = lib.mkOption {
52         type = lib.types.bool;
53         default = false;
54         description = ''
55           If enabled, start a Minecraft Server. The server
56           data will be loaded from and saved to
57           {option}`services.minecraft-server.dataDir`.
58         '';
59       };
61       declarative = lib.mkOption {
62         type = lib.types.bool;
63         default = false;
64         description = ''
65           Whether to use a declarative Minecraft server configuration.
66           Only if set to `true`, the options
67           {option}`services.minecraft-server.whitelist` and
68           {option}`services.minecraft-server.serverProperties` will be
69           applied.
70         '';
71       };
73       eula = lib.mkOption {
74         type = lib.types.bool;
75         default = false;
76         description = ''
77           Whether you agree to
78           [
79           Mojangs EULA](https://account.mojang.com/documents/minecraft_eula). This option must be set to
80           `true` to run Minecraft server.
81         '';
82       };
84       dataDir = lib.mkOption {
85         type = lib.types.path;
86         default = "/var/lib/minecraft";
87         description = ''
88           Directory to store Minecraft database and other state/data files.
89         '';
90       };
92       openFirewall = lib.mkOption {
93         type = lib.types.bool;
94         default = false;
95         description = ''
96           Whether to open ports in the firewall for the server.
97         '';
98       };
100       whitelist = lib.mkOption {
101         type = let
102           minecraftUUID = lib.types.strMatching
103             "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // {
104               description = "Minecraft UUID";
105             };
106           in lib.types.attrsOf minecraftUUID;
107         default = {};
108         description = ''
109           Whitelisted players, only has an effect when
110           {option}`services.minecraft-server.declarative` is
111           `true` and the whitelist is enabled
112           via {option}`services.minecraft-server.serverProperties` by
113           setting `white-list` to `true`.
114           This is a mapping from Minecraft usernames to UUIDs.
115           You can use <https://mcuuid.net/> to get a
116           Minecraft UUID for a username.
117         '';
118         example = lib.literalExpression ''
119           {
120             username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
121             username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
122           };
123         '';
124       };
126       serverProperties = lib.mkOption {
127         type = with lib.types; attrsOf (oneOf [ bool int str ]);
128         default = {};
129         example = lib.literalExpression ''
130           {
131             server-port = 43000;
132             difficulty = 3;
133             gamemode = 1;
134             max-players = 5;
135             motd = "NixOS Minecraft server!";
136             white-list = true;
137             enable-rcon = true;
138             "rcon.password" = "hunter2";
139           }
140         '';
141         description = ''
142           Minecraft server properties for the server.properties file. Only has
143           an effect when {option}`services.minecraft-server.declarative`
144           is set to `true`. See
145           <https://minecraft.gamepedia.com/Server.properties#Java_Edition_3>
146           for documentation on these values.
147         '';
148       };
150       package = lib.mkPackageOption pkgs "minecraft-server" {
151         example = "minecraft-server_1_12_2";
152       };
154       jvmOpts = lib.mkOption {
155         type = lib.types.separatedString " ";
156         default = "-Xmx2048M -Xms2048M";
157         # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
158         example = "-Xms4092M -Xmx4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
159           + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
160           + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
161         description = "JVM options for the Minecraft server.";
162       };
163     };
164   };
166   config = lib.mkIf cfg.enable {
168     users.users.minecraft = {
169       description     = "Minecraft server service user";
170       home            = cfg.dataDir;
171       createHome      = true;
172       isSystemUser    = true;
173       group           = "minecraft";
174     };
175     users.groups.minecraft = {};
177     systemd.sockets.minecraft-server = {
178       bindsTo = [ "minecraft-server.service" ];
179       socketConfig = {
180         ListenFIFO = "/run/minecraft-server.stdin";
181         SocketMode = "0660";
182         SocketUser = "minecraft";
183         SocketGroup = "minecraft";
184         RemoveOnStop = true;
185         FlushPending = true;
186       };
187     };
189     systemd.services.minecraft-server = {
190       description   = "Minecraft Server Service";
191       wantedBy      = [ "multi-user.target" ];
192       requires      = [ "minecraft-server.socket" ];
193       after         = [ "network.target" "minecraft-server.socket" ];
195       serviceConfig = {
196         ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
197         ExecStop = "${stopScript} $MAINPID";
198         Restart = "always";
199         User = "minecraft";
200         WorkingDirectory = cfg.dataDir;
202         StandardInput = "socket";
203         StandardOutput = "journal";
204         StandardError = "journal";
206         # Hardening
207         CapabilityBoundingSet = [ "" ];
208         DeviceAllow = [ "" ];
209         LockPersonality = true;
210         PrivateDevices = true;
211         PrivateTmp = true;
212         PrivateUsers = true;
213         ProtectClock = true;
214         ProtectControlGroups = true;
215         ProtectHome = true;
216         ProtectHostname = true;
217         ProtectKernelLogs = true;
218         ProtectKernelModules = true;
219         ProtectKernelTunables = true;
220         ProtectProc = "invisible";
221         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
222         RestrictNamespaces = true;
223         RestrictRealtime = true;
224         RestrictSUIDSGID = true;
225         SystemCallArchitectures = "native";
226         UMask = "0077";
227       };
229       preStart = ''
230         ln -sf ${eulaFile} eula.txt
231       '' + (if cfg.declarative then ''
233         if [ -e .declarative ]; then
235           # Was declarative before, no need to back up anything
236           ln -sf ${whitelistFile} whitelist.json
237           cp -f ${serverPropertiesFile} server.properties
239         else
241           # Declarative for the first time, backup stateful files
242           ln -sb --suffix=.stateful ${whitelistFile} whitelist.json
243           cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
245           # server.properties must have write permissions, because every time
246           # the server starts it first parses the file and then regenerates it..
247           chmod +w server.properties
248           echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
249             > .declarative
251         fi
252       '' else ''
253         if [ -e .declarative ]; then
254           rm .declarative
255         fi
256       '');
257     };
259     networking.firewall = lib.mkIf cfg.openFirewall (if cfg.declarative then {
260       allowedUDPPorts = [ serverPort ];
261       allowedTCPPorts = [ serverPort ]
262         ++ lib.optional (queryPort != null) queryPort
263         ++ lib.optional (rconPort != null) rconPort;
264     } else {
265       allowedUDPPorts = [ defaultServerPort ];
266       allowedTCPPorts = [ defaultServerPort ];
267     });
269     assertions = [
270       { assertion = cfg.eula;
271         message = "You must agree to Mojangs EULA to run minecraft-server."
272           + " Read https://account.mojang.com/documents/minecraft_eula and"
273           + " set `services.minecraft-server.eula` to `true` if you agree.";
274       }
275     ];
277   };