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