summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorChris Ostrouchov <chris.ostrouchov@gmail.com>2018-08-20 19:35:22 -0400
committerSilvan Mosberger <infinisil@icloud.com>2019-02-03 02:16:11 +0100
commit58c89ec26a5d3c34e34bcb9f23db9fc62d7cf98a (patch)
tree5fadc37612bc7b13d0677d97a1ab74c6e54d563c /nixos
parent9080b2eadff00772cc4d9aac3c0ec362f9d8748e (diff)
downloadnixpkgs-58c89ec26a5d3c34e34bcb9f23db9fc62d7cf98a.tar
nixpkgs-58c89ec26a5d3c34e34bcb9f23db9fc62d7cf98a.tar.gz
nixpkgs-58c89ec26a5d3c34e34bcb9f23db9fc62d7cf98a.tar.bz2
nixpkgs-58c89ec26a5d3c34e34bcb9f23db9fc62d7cf98a.tar.lz
nixpkgs-58c89ec26a5d3c34e34bcb9f23db9fc62d7cf98a.tar.xz
nixpkgs-58c89ec26a5d3c34e34bcb9f23db9fc62d7cf98a.tar.zst
nixpkgs-58c89ec26a5d3c34e34bcb9f23db9fc62d7cf98a.zip
nixos/mincraft-server: refactor
 - allow for options to (added 2 options):
   - agree to eula (eula.txt) true/false will create symlink over
     existing eula.txt to `/nix/store/...`.
   - whitelist users (optional and will symlink over existing
     whitelist.json and create backup)
   - server.properties can be configured with the serverProperties
     option. If there is an existing server.properties it will
     copy it to a server.properties.old to keep the old
     one. server.properties MUST be writable thus symlinking is not
     an option.
  - all ports that are stated in `server.properties` are exposed
    properly in the firewall.

(infinisil) nixos/minecraft-server: Fix, refactor and polish

Adds an option `declarative` (defaulted to false), in order to stay
(mostly) backwards compatible. The only thing that's not backwards
compatible is that you now need to agree to the EULA on evaluation time,
but that's guarded by an assertion and therefore doesn't need a release
note.
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/services/games/minecraft-server.nix201
1 files changed, 181 insertions, 20 deletions
diff --git a/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index f50d2897843..7d26d150165 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -4,8 +4,41 @@ with lib;
 
 let
   cfg = config.services.minecraft-server;
-in
-{
+
+  # We don't allow eula=false anyways
+  eulaFile = builtins.toFile "eula.txt" ''
+    # eula.txt managed by NixOS Configuration
+    eula=true
+  '';
+
+  whitelistFile = pkgs.writeText "whitelist.json"
+    (builtins.toJSON
+      (mapAttrsToList (n: v: { name = n; uuid = v; }) cfg.whitelist));
+
+  cfgToString = v: if builtins.isBool v then boolToString v else toString v;
+
+  serverPropertiesFile = pkgs.writeText "server.properties" (''
+    # server.properties managed by NixOS configuration
+  '' + concatStringsSep "\n" (mapAttrsToList
+    (n: v: "${n}=${cfgToString v}") cfg.serverProperties));
+
+
+  # To be able to open the firewall, we need to read out port values in the
+  # server properties, but fall back to the defaults when those don't exist.
+  # These defaults are from https://minecraft.gamepedia.com/Server.properties#Java_Edition_3
+  defaultServerPort = 25565;
+
+  serverPort = cfg.serverProperties.server-port or defaultServerPort;
+
+  rconPort = if cfg.serverProperties.enable-rcon or false
+    then cfg.serverProperties."rcon.port" or 25575
+    else null;
+
+  queryPort = if cfg.serverProperties.enable-query or false
+    then cfg.serverProperties."query.port" or 25565
+    else null;
+
+in {
   options = {
     services.minecraft-server = {
 
@@ -13,10 +46,32 @@ in
         type = types.bool;
         default = false;
         description = ''
-          If enabled, start a Minecraft Server. The listening port for
-          the server is always <literal>25565</literal>. The server
+          If enabled, start a Minecraft Server. The server
           data will be loaded from and saved to
-          <literal>${cfg.dataDir}</literal>.
+          <option>services.minecraft-server.dataDir</option>.
+        '';
+      };
+
+      declarative = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use a declarative Minecraft server configuration.
+          Only if set to <literal>true</literal>, the options
+          <option>services.minecraft-server.whitelist</option> and
+          <option>services.minecraft-server.serverProperties</option> will be
+          applied.
+        '';
+      };
+
+      eula = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether you agree to
+          <link xlink:href="https://account.mojang.com/documents/minecraft_eula">
+          Mojangs EULA</link>. This option must be set to
+          <literal>true</literal> to run Minecraft server.
         '';
       };
 
@@ -24,7 +79,7 @@ in
         type = types.path;
         default = "/var/lib/minecraft";
         description = ''
-          Directory to store minecraft database and other state/data files.
+          Directory to store Minecraft database and other state/data files.
         '';
       };
 
@@ -32,21 +87,84 @@ in
         type = types.bool;
         default = false;
         description = ''
-          Whether to open ports in the firewall (if enabled) for the server.
+          Whether to open ports in the firewall for the server.
+        '';
+      };
+
+      whitelist = mkOption {
+        type = let
+          minecraftUUID = types.strMatching
+            "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // {
+              description = "Minecraft UUID";
+            };
+          in types.attrsOf minecraftUUID;
+        default = {};
+        description = ''
+          Whitelisted players, only has an effect when
+          <option>services.minecraft-server.declarative</option> is
+          <literal>true</literal> and the whitelist is enabled
+          via <option>services.minecraft-server.serverProperties</option> by
+          setting <literal>white-list</literal> to <literal>true</literal>.
+          This is a mapping from Minecraft usernames to UUIDs.
+          You can use <link xlink:href="https://mcuuid.net/"/> to get a
+          Minecraft UUID for a username.
+        '';
+        example = literalExample ''
+          {
+            username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+            username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
+          };
+        '';
+      };
+
+      serverProperties = mkOption {
+        type = with types; attrsOf (either bool (either int str));
+        default = {};
+        example = literalExample ''
+          {
+            server-port = 43000;
+            difficulty = 3;
+            gamemode = 1;
+            max-players = 5;
+            motd = "NixOS Minecraft server!";
+            white-list = true;
+            enable-rcon = true;
+            "rcon.password" = "hunter2";
+          }
+        '';
+        description = ''
+          Minecraft server properties for the server.properties file. Only has
+          an effect when <option>services.minecraft-server.declarative</option>
+          is set to <literal>true</literal>. See
+          <link xlink:href="https://minecraft.gamepedia.com/Server.properties#Java_Edition_3"/>
+          for documentation on these values.
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.minecraft-server;
+        defaultText = "pkgs.minecraft-server";
+        example = literalExample "pkgs.minecraft-server_1_12_2";
+        description = "Version of minecraft-server to run.";
+      };
+
       jvmOpts = mkOption {
-        type = types.str;
+        type = types.separatedString " ";
         default = "-Xmx2048M -Xms2048M";
-        description = "JVM options for the Minecraft Service.";
+        # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
+        example = "-Xmx2048M -Xms4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
+          + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
+          + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
+        description = "JVM options for the Minecraft server.";
       };
     };
   };
 
   config = mkIf cfg.enable {
+
     users.users.minecraft = {
-      description     = "Minecraft Server Service user";
+      description     = "Minecraft server service user";
       home            = cfg.dataDir;
       createHome      = true;
       uid             = config.ids.uids.minecraft;
@@ -57,17 +175,60 @@ in
       wantedBy      = [ "multi-user.target" ];
       after         = [ "network.target" ];
 
-      serviceConfig.Restart = "always";
-      serviceConfig.User    = "minecraft";
-      script = ''
-        cd ${cfg.dataDir}
-        exec ${pkgs.minecraft-server}/bin/minecraft-server ${cfg.jvmOpts}
-      '';
-    };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
+        Restart = "always";
+        User = "minecraft";
+        WorkingDirectory = cfg.dataDir;
+      };
+
+      preStart = ''
+        ln -sf ${eulaFile} eula.txt
+      '' + (if cfg.declarative then ''
+
+        if [ -e .declarative ]; then
+
+          # Was declarative before, no need to back up anything
+          ln -sf ${whitelistFile} whitelist.json
+          cp -f ${serverPropertiesFile} server.properties
+
+        else
 
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedUDPPorts = [ 25565 ];
-      allowedTCPPorts = [ 25565 ];
+          # Declarative for the first time, backup stateful files
+          ln -sb --suffix=.stateful ${whitelistFile} whitelist.json
+          cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
+
+          # server.properties must have write permissions, because every time
+          # the server starts it first parses the file and then regenerates it..
+          chmod +w server.properties
+          echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
+            > .declarative
+
+        fi
+      '' else ''
+        if [ -e .declarative ]; then
+          rm .declarative
+        fi
+      '');
     };
+
+    networking.firewall = mkIf cfg.openFirewall (if cfg.declarative then {
+      allowedUDPPorts = [ serverPort ];
+      allowedTCPPorts = [ serverPort ]
+        ++ optional (! isNull queryPort) queryPort
+        ++ optional (! isNull rconPort) rconPort;
+    } else {
+      allowedUDPPorts = [ defaultServerPort ];
+      allowedTCPPorts = [ defaultServerPort ];
+    });
+
+    assertions = [
+      { assertion = cfg.eula;
+        message = "You must agree to Mojangs EULA to run minecraft-server."
+          + " Read https://account.mojang.com/documents/minecraft_eula and"
+          + " set `services.minecraft-server.eula` to `true` if you agree.";
+      }
+    ];
+
   };
 }