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