{ config, lib, pkgs, ... }: with lib; let cfg = config.services.minecraft-server; # 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 = { enable = mkOption { type = types.bool; default = false; description = '' If enabled, start a Minecraft Server. The server data will be loaded from and saved to . ''; }; 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. ''; }; dataDir = mkOption { type = types.path; default = "/var/lib/minecraft"; description = '' Directory to store Minecraft database and other state/data files. ''; }; openFirewall = mkOption { type = types.bool; default = false; description = '' 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.separatedString " "; default = "-Xmx2048M -Xms2048M"; # 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"; home = cfg.dataDir; createHome = true; uid = config.ids.uids.minecraft; }; systemd.services.minecraft-server = { description = "Minecraft Server Service"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; 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 (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."; } ]; }; }