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 25565. The server + If enabled, start a Minecraft Server. The server data will be loaded from and saved to - ${cfg.dataDir}. + . + ''; + }; + + declarative = mkOption { + type = types.bool; + default = false; + description = '' + Whether to use a declarative Minecraft server configuration. + Only if set to true, the options + and + will be + applied. + ''; + }; + + eula = mkOption { + type = types.bool; + default = false; + description = '' + Whether you agree to + + Mojangs EULA. This option must be set to + true 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 + is + true and the whitelist is enabled + via by + setting white-list to true. + This is a mapping from Minecraft usernames to UUIDs. + You can use 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 + is set to true. See + + 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 + + # 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 { - allowedUDPPorts = [ 25565 ]; - allowedTCPPorts = [ 25565 ]; - }; + 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."; + } + ]; + }; } diff --git a/pkgs/games/minecraft-server/default.nix b/pkgs/games/minecraft-server/default.nix index 050e0126fa7..c2f20f53e9a 100644 --- a/pkgs/games/minecraft-server/default.nix +++ b/pkgs/games/minecraft-server/default.nix @@ -1,37 +1,63 @@ { stdenv, fetchurl, jre }: -stdenv.mkDerivation rec { - name = "minecraft-server-${version}"; - version = "1.13.2"; +let + common = { version, sha256, url }: + stdenv.mkDerivation (rec { + name = "minecraft-server-${version}"; + inherit version; - src = fetchurl { - # Old url - # https://s3.amazonaws.com/Minecraft.Download/versions/${version}/minecraft_server.${version}.jar + src = fetchurl { + inherit url sha256; + }; + + preferLocalBuild = true; + + installPhase = '' + mkdir -p $out/bin $out/lib/minecraft + cp -v $src $out/lib/minecraft/server.jar + + cat > $out/bin/minecraft-server << EOF + #!/bin/sh + exec ${jre}/bin/java \$@ -jar $out/lib/minecraft/server.jar nogui + EOF + + chmod +x $out/bin/minecraft-server + ''; + + phases = "installPhase"; + + meta = { + description = "Minecraft Server"; + homepage = "https://minecraft.net"; + license = stdenv.lib.licenses.unfreeRedistributable; + platforms = stdenv.lib.platforms.unix; + maintainers = with stdenv.lib.maintainers; [ thoughtpolice tomberek costrouc]; + }; + }); + +in { + minecraft-server_1_13_2 = common { + version = "1.13.2"; url = "https://launcher.mojang.com/v1/objects/3737db93722a9e39eeada7c27e7aca28b144ffa7/server.jar"; sha256 = "13h8dxrrgqa1g6sd7aaw26779hcsqsyjm7xm0sknifn54lnamlzz"; }; - preferLocalBuild = true; - - installPhase = '' - mkdir -p $out/bin $out/lib/minecraft - cp -v $src $out/lib/minecraft/server.jar - - cat > $out/bin/minecraft-server << EOF - #!/bin/sh - exec ${jre}/bin/java \$@ -jar $out/lib/minecraft/server.jar nogui - EOF - - chmod +x $out/bin/minecraft-server - ''; - - phases = "installPhase"; - - meta = { - description = "Minecraft Server"; - homepage = "https://minecraft.net"; - license = stdenv.lib.licenses.unfreeRedistributable; - platforms = stdenv.lib.platforms.unix; - maintainers = [ stdenv.lib.maintainers.thoughtpolice stdenv.lib.maintainers.tomberek ]; + minecraft-server_1_13_1 = common { + version = "1.13.1"; + url = "https://launcher.mojang.com/mc/game/1.13.1/server/fe123682e9cb30031eae351764f653500b7396c9/server.jar"; + sha256 = "1lak29b7dm0w1cmzjn9gyix6qkszwg8xgb20hci2ki2ifrz099if"; }; + + minecraft-server_1_13_0 = common { + version = "1.13.0"; + url = "https://launcher.mojang.com/mc/game/1.13/server/d0caafb8438ebd206f99930cfaecfa6c9a13dca0/server.jar"; + sha256 = "1fahqnylxzbvc0fdsqk0x15z40mcc5b7shrckab1qcsdj0kkjvz7"; + }; + + minecraft-server_1_12_2 = common { + version = "1.12.2"; + url = "https://s3.amazonaws.com/Minecraft.Download/versions/1.12.2/minecraft_server.1.12.2.jar"; + sha256 = "0zhnac6yvkdgdaag0gb0fgrkgizbwrpf7s76yqdiknfswrs947zy"; + }; + } diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 035889134a0..a2a7d5185c1 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -20783,7 +20783,13 @@ in minecraft = callPackage ../games/minecraft { }; - minecraft-server = callPackage ../games/minecraft-server { }; + minecraft-server = minecraft-server_1_13_2; + + inherit (callPackages ../games/minecraft-server { }) + minecraft-server_1_13_2 + minecraft-server_1_13_1 + minecraft-server_1_13_0 + minecraft-server_1_12_2; moon-buggy = callPackage ../games/moon-buggy {};