{ config, lib, pkgs, ... }: with lib; let cfg = config.services.minecraft-servers; eulaFile = builtins.toFile "eula.txt" '' # eula.txt managed by NixOS Configuration eula=true ''; whitelistFile = server: pkgs.writeText "whitelist.json" (builtins.toJSON (mapAttrsToList (n: v: { name = n; uuid = v; }) server.whitelist)); cfgToString = v: if builtins.isBool v then boolToString v else toString v; serverPropertiesFile = server: pkgs.writeText "server.properties" ('' # server.properties managed by NixOS configuration '' + concatStringsSep "\n" (mapAttrsToList (n: v: "${n}=${cfgToString v}") server.serverProperties)); tmux = "${getBin pkgs.tmux}/bin/tmux"; stopScript = name: pkgs.writeScript "minecraft-stop" '' #!${pkgs.runtimeShell} if ! [ -d "/proc/$1" ]; then exit 0 fi ${tmux} -S ${cfgdataDir}/${name}.sock send-keys Enter stop Enter ${getBin pkgs.coreutils}/bin/tail --pid-"$1" -f /dev/null ''; cfgdataDir = "/var/lib/minecraft"; mkService = name: inst: builtins.trace "${name} building" { name = "minecraft-server-${name}"; value = builtins.trace "${name}: value: build" { description = "Minecraft Server Service: ${name}"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = builtins.trace "${name}: value: serviceConfig: building" { ExecStart = '' ${tmux} -S ${cfgdataDir}/${name}.sock new \ -d ${inst.package}/bin/minecraft-server ${inst.jvmOpts} # ''; ExecStop = "${stopScript name} $MAINPID"; Restart = "always"; Type = "forking"; GuessMainPIP = true; User = "minecraft"; WorkingDirectory = "${cfgdataDir}/${name}"; }; preStart = '' ln -sf ${eulaFile} eula.txt '' + (if inst.declarative then '' if [ -e .declarative ]; then # Was declarative before, no need to back up anything ln -sf ${whitelistFile inst} whitelist.json cp -f ${serverPropertiesFile inst} server.properties else # Declarative for the first time, backup stateful files ln -sb --suffix=.stateful ${whitelistFile inst} whitelist.json cp -b --suffix=.stateful ${ serverPropertiesFile inst } 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 ''); postStart = '' ${pkgs.coreutils}/bin/chmod 660 ${cfgdataDir}/${name}.sock ${pkgs.coreutils}/bin/chgrp minecraft ${cfgdataDir}/${name}.sock ''; }; }; generatedServices = mapAttrs' mkService cfg.servers; in { options = { services.minecraft-servers = { enable = mkEnableOption "minecraft servers"; # TODO: verbose minecraft-servers enable option openFirewall = mkEnableOption "opening the firewall for each server by default"; # TODO: verbose outer openFirewall option dataDir = mkOption { type = types.path; default = "/var/lib/minecraft"; # TODO: verbose dataDir option }; servers = mkOption { type = types.attrsOf (types.submodule { enable = mkEnableOption "this minecraft server"; # TODO: verbose server enable option declarative = mkEnableOption "declarative server management"; # TODO: verbose declarative option eula = mkEnableOption "accepting the eula"; # TODO: verbose eula option openFirewall = mkOption { type = types.bool; default = true; # cfg.openFirewall; # TODO: openFirewall description }; jvmOpts = mkOption { type = types.separatedString " "; default = "-Xmx2048M -Xms2048M"; # TODO: verbose jvmOpts option }; package = mkOption { type = types.package; default = pkgs.minecraft-server; # TODO: verbose package option }; 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 = { }; # TODO: verbose whitelist option }; serverProperties = mkOption { type = with types; attrsOf (oneOf [ bool int str ]); default = { server-port = 25565; }; # TODO: verbose serverProperties option }; }); }; }; }; config = mkIf cfg.enable { users.users.minecraft = { description = "Minecraft server service user"; home = cfg.dataDir; createHome = true; isSystemUser = true; group = "minecraft"; }; users.groups.minecraft = { }; systemd.services = mkIf (cfg.enable && cfg.servers != { }) generatedServices; }; # networking.firewall = let # propertyFunc = attrsets.mapAttrsToList (name: value: # let props = value.serverProperties; # in (if value.declarative then [ # (props.server-port or 25565) # (if props.enable-rcon or false then # props."rcon.port" or 25575 # else # null) # (if props.enable-query or false then # props "query.port" or 25565 # else # null) # ] else # null)) cfg.servers; # openedPorts = lists.flatten [ serverPorts queryPorts rconPorts ]; # in { # allowedUDPPorts = openedPorts; # allowedTCPPorts = openedPorts; # }; # assertions = [{ # assertion = all (i: i.eula == true) (attrValues cfg.servers); # message = "You must agree to Mojangs EULA to run minecraft-server." # + " Read https://account.mojang.com/documents/minecraft_eula and" # + " set `services.minecraft-servers.servers..eula` to `true` for all servers if you agree."; # }]; }