From e60af68a0e743ff79cfc7e6ca8250855a043d31f Mon Sep 17 00:00:00 2001 From: arcnmx Date: Sun, 13 Oct 2024 14:27:35 -0700 Subject: [PATCH] chore(minecraft): instanced server module --- modules/nixos/minecraft/java.nix | 369 +++++++++++++++++-------------- 1 file changed, 209 insertions(+), 160 deletions(-) diff --git a/modules/nixos/minecraft/java.nix b/modules/nixos/minecraft/java.nix index f8e9fa9b..aa336bf2 100644 --- a/modules/nixos/minecraft/java.nix +++ b/modules/nixos/minecraft/java.nix @@ -1,74 +1,90 @@ -{ - config, - gensokyo-zone, - lib, - pkgs, - ... -}: let - inherit (gensokyo-zone.lib) mapOptionDefaults; - inherit (lib.options) mkOption mkEnableOption mkPackageOption; - inherit (lib.modules) mkIf mkMerge mkAfter mkOptionDefault; - inherit (lib.strings) escapeShellArgs; - inherit (lib.meta) getExe; - inherit (config.lib.minecraft) mkAllowPlayerType writeWhiteList writeOps; - cfg = config.services.minecraft-java-server; - defaultPort = 25565; -in { - options.services.minecraft-java-server = with lib.types; { - enable = mkEnableOption "minecraft java edition server"; +let + javaServerModule = { + config, + nixosConfig, + gensokyo-zone, + lib, + pkgs, + ... + }: let + inherit (gensokyo-zone.lib) mapOptionDefaults unmerged; + inherit (lib.options) mkOption mkEnableOption mkPackageOption; + inherit (lib.modules) mkIf mkAfter mkOptionDefault; + inherit (lib.strings) escapeShellArgs; + inherit (lib.meta) getExe; + inherit (nixosConfig.lib.minecraft) mkAllowPlayerType writeWhiteList writeOps; + defaultPort = 25565; + in { + options = with lib.types; { + enable = mkEnableOption "minecraft java edition server"; - openFirewall = mkOption { - type = bool; - default = false; - }; - port = mkOption { - type = port; - default = defaultPort; + openFirewall = mkOption { + type = bool; + default = false; + }; + port = mkOption { + type = port; + default = defaultPort; + }; + + jre.package = mkPackageOption pkgs "jre" {}; + + dataDir = mkOption { + type = path; + default = "/var/lib/minecraft-java"; + description = '' + Directory to store Minecraft database and other state/data files. + ''; + }; + + argsFiles = mkOption { + type = listOf str; + default = ["user_jvm_args.txt"]; + }; + + jvmOpts = mkOption { + type = listOf str; + default = []; + example = ["-Xmx4G"]; + }; + + user = mkOption { + type = str; + default = "minecraft-bedrock"; + }; + group = mkOption { + type = str; + default = config.user; + }; + + serverProperties = mkOption { + type = attrsOf (oneOf [bool int str float]); + }; + + allowPlayers = mkOption { + type = nullOr (attrsOf (mkAllowPlayerType {})); + default = null; + }; + + conf = { + systemdService = mkOption { + type = unmerged.types.attrs; + }; + systemdSocket = mkOption { + type = unmerged.types.attrs; + }; + users = mkOption { + type = unmerged.types.attrs; + default = {}; + }; + networkingFirewall = mkOption { + type = unmerged.types.attrs; + default = {}; + }; + }; }; - jre.package = mkPackageOption pkgs "jre" {}; - - dataDir = mkOption { - type = path; - default = "/var/lib/minecraft-java"; - description = '' - Directory to store Minecraft database and other state/data files. - ''; - }; - - argsFiles = mkOption { - type = listOf str; - default = ["user_jvm_args.txt"]; - }; - - jvmOpts = mkOption { - type = listOf str; - default = []; - example = ["-Xmx4G"]; - }; - - user = mkOption { - type = str; - default = "minecraft-bedrock"; - }; - group = mkOption { - type = str; - default = cfg.user; - }; - - serverProperties = mkOption { - type = attrsOf (oneOf [bool int str float]); - }; - - allowPlayers = mkOption { - type = nullOr (attrsOf (mkAllowPlayerType {})); - default = null; - }; - }; - - config = let - confService.services.minecraft-java-server = { - # TODO: fill with defaults + config = { serverProperties = mapOptionDefaults { enable-jmx-monitoring = false; "rcon.port" = 25575; @@ -81,7 +97,7 @@ in { level-name = "world"; motd = "A Minecraft Server"; #"query.port" = defaultPort; - "query.port" = cfg.serverProperties.server-port or defaultPort; + "query.port" = config.serverProperties.server-port or defaultPort; pvp = true; generate-structures = true; max-chained-neighbor-updates = 1000000; @@ -129,108 +145,141 @@ in { resource-pack-sha1 = ""; max-world-size = 29999984; }; - }; - conf.users = mkIf (cfg.user == "minecraft-bedrock") { - users.${cfg.user} = { - inherit (cfg) group; - description = "Minecraft server service user"; - home = cfg.dataDir; - createHome = true; - isSystemUser = true; + + conf.users = mkIf (config.user == "minecraft-bedrock") { + users.${config.user} = { + inherit (config) group; + description = "Minecraft server service user"; + home = config.dataDir; + createHome = true; + isSystemUser = true; + }; + groups.${config.group} = {}; }; - groups.${cfg.group} = {}; - }; - conf.systemd.services.minecraft-java-server = let - execStartArgs = - map (argsFile: "@${argsFile}") cfg.argsFiles - ++ cfg.jvmOpts; - execStop = pkgs.writeShellScriptBin "minecraft-java-stop" '' - echo /stop > ${config.systemd.sockets.minecraft-java-server.socketConfig.ListenFIFO} || true + conf.systemdService = let + execStartArgs = + map (argsFile: "@${argsFile}") config.argsFiles + ++ config.jvmOpts; + execStop = pkgs.writeShellScriptBin "minecraft-java-stop" '' + echo /stop > ${nixosConfig.systemd.sockets.minecraft-java-server.socketConfig.ListenFIFO} || true - if [[ -n ''${MAINPID-} ]]; then - # Wait for the PID of the minecraft server to disappear before - # returning, so systemd doesn't attempt to SIGKILL it. - while kill -0 "$MAINPID" 2> /dev/null; do - sleep 1s - done - fi - ''; - in { - description = "Minecraft Kat Kitchen Server"; - wantedBy = ["multi-user.target"]; - requires = ["minecraft-java-server.socket"]; - after = ["network.target" "minecraft-java-server.socket"]; + if [[ -n ''${MAINPID-} ]]; then + # Wait for the PID of the minecraft server to disappear before + # returning, so systemd doesn't attempt to SIGKILL it. + while kill -0 "$MAINPID" 2> /dev/null; do + sleep 1s + done + fi + ''; + in { + description = "Minecraft Kat Kitchen Server"; + wantedBy = ["multi-user.target"]; + requires = ["minecraft-java-server.socket"]; + after = ["network.target" "minecraft-java-server.socket"]; - restartIfChanged = false; - restartTriggers = [ - cfg.dataDir - cfg.jvmOpts - cfg.argsFiles - ]; - - path = [cfg.jre.package]; - script = mkAfter '' - exec java ${escapeShellArgs execStartArgs} - ''; - - serviceConfig = { - BindReadOnlyPaths = mkIf (cfg.allowPlayers != null) [ - "${writeWhiteList cfg.allowPlayers}:${cfg.dataDir}/whitelist.json" - "${writeOps cfg.allowPlayers}:${cfg.dataDir}/ops.json" + restartIfChanged = false; + restartTriggers = [ + config.dataDir + config.jvmOpts + config.argsFiles ]; - ExecStop = getExe execStop; - Restart = "always"; - RestartSec = 3; - User = cfg.user; - WorkingDirectory = cfg.dataDir; - RuntimeDirectory = "minecraft-java"; - StandardInput = "socket"; - StandardOutput = "journal"; - StandardError = "journal"; + path = [config.jre.package]; + script = mkAfter '' + exec java ${escapeShellArgs execStartArgs} + ''; - # Hardening - CapabilityBoundingSet = [""]; - DeviceAllow = [""]; - LockPersonality = true; - PrivateDevices = true; - PrivateTmp = true; - PrivateUsers = true; - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - RestrictAddressFamilies = ["AF_INET" "AF_INET6" "AF_UNIX"]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - UMask = "0077"; + serviceConfig = { + BindReadOnlyPaths = mkIf (config.allowPlayers != null) [ + "${writeWhiteList config.allowPlayers}:${config.dataDir}/whitelist.json" + "${writeOps config.allowPlayers}:${config.dataDir}/ops.json" + ]; + ExecStop = getExe execStop; + Restart = "always"; + RestartSec = 3; + User = config.user; + WorkingDirectory = config.dataDir; + RuntimeDirectory = "minecraft-java"; + + StandardInput = "socket"; + StandardOutput = "journal"; + StandardError = "journal"; + + # Hardening + CapabilityBoundingSet = [""]; + DeviceAllow = [""]; + LockPersonality = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RestrictAddressFamilies = ["AF_INET" "AF_INET6" "AF_UNIX"]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + UMask = "0077"; + }; + }; + conf.systemdSocket = { + partOf = ["minecraft-java-server.service"]; + socketConfig = { + ListenFIFO = "/run/minecraft-java/stdin"; + SocketMode = "0660"; + SocketUser = mkOptionDefault config.user; + SocketGroup = mkOptionDefault config.group; + RemoveOnStop = true; + FlushPending = true; + }; + }; + + conf.networkingFirewall = mkIf config.openFirewall { + allowedUDPPorts = config.port; }; }; - conf.systemd.sockets.minecraft-java-server = { - partOf = ["minecraft-java-server.service"]; - socketConfig = { - ListenFIFO = "/run/minecraft-java/stdin"; - SocketMode = "0660"; - SocketUser = mkOptionDefault cfg.user; - SocketGroup = mkOptionDefault cfg.group; - RemoveOnStop = true; - FlushPending = true; + }; +in { + pkgs, + config, + gensokyo-zone, + lib, + ... +}: let + inherit (gensokyo-zone.lib) unmerged; + inherit (lib.options) mkOption; + inherit (lib.modules) mkIf mkMerge; + cfg = config.services.minecraft-java-server; +in { + # TODO: attrsOf submodule + options.services.minecraft-java-server = with lib.types; mkOption { + type = submoduleWith { + modules = [javaServerModule]; + specialArgs = { + inherit gensokyo-zone pkgs; + nixosConfig = config; }; }; + default = {}; + }; - conf.networking.firewall = mkIf cfg.openFirewall { - allowedUDPPorts = cfg.port; + config = let + serviceConf.users = unmerged.mergeAttrs cfg.conf.users; + serviceConf.systemd.services.minecraft-java-server = unmerged.mergeAttrs cfg.conf.systemdService; + serviceConf.systemd.sockets.minecraft-java-server = unmerged.mergeAttrs cfg.conf.systemdSocket; + serviceConf.networking.firewall = unmerged.mergeAttrs cfg.conf.networkingFirewall; + conf.lib.minecraft = { + inherit javaServerModule; }; - in - mkMerge [ - confService - (mkIf cfg.enable conf) - ]; + in mkMerge [ + (mkIf cfg.enable serviceConf) + conf + ]; }