{ config, lib, pkgs, ... }: { options.modules.services.games.palworld = { enable = lib.mkEnableOption "Palworld dedicated server"; steamDir = lib.mkOption { type = lib.types.str; example = "/data/steamcmd"; description = "Directory where SteamCMD stores metadata and cache files."; }; installDir = lib.mkOption { type = lib.types.str; example = "/data/servers/palworld/tracion"; description = "Directory where SteamCMD installs the Palworld server files."; }; stateDir = lib.mkOption { type = lib.types.str; example = "/data/state/palworld/tracion"; description = "Directory where Palworld stores durable server state."; }; serverName = lib.mkOption { type = lib.types.str; default = "palworld"; description = "Name shown for the Palworld server."; }; port = lib.mkOption { type = lib.types.port; default = 8211; description = "UDP port for the Palworld server."; }; publicIP = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Public IP address advertised by the Palworld server."; }; maxPlayers = lib.mkOption { type = lib.types.ints.between 1 32; default = 32; description = "Maximum player count for the Palworld server."; }; publicLobby = lib.mkOption { type = lib.types.bool; default = true; description = "Advertise the server in Palworld's public lobby."; }; autoStart = lib.mkOption { type = lib.types.bool; default = true; description = "Start the Palworld server after the guest reaches network readiness."; }; rcon = { enable = lib.mkEnableOption "Palworld RCON" // { default = true; }; port = lib.mkOption { type = lib.types.port; default = 25575; description = "TCP port for Palworld RCON."; }; }; passwordFile = lib.mkOption { type = lib.types.path; description = "Runtime file containing the Palworld server password."; }; adminPasswordFile = lib.mkOption { type = lib.types.path; description = "Runtime file containing the Palworld admin password."; }; }; config = lib.mkIf config.modules.services.games.palworld.enable ( let service = config.modules.services.games.palworld; savedDir = "${service.installDir}/Pal/Saved"; startScript = pkgs.writeShellScript "palworld-start" '' set -eu ${lib.getExe' pkgs.coreutils "install"} -d \ -m 0770 \ ${lib.escapeShellArg service.stateDir} \ ${lib.escapeShellArg (builtins.dirOf savedDir)} if [ -L ${lib.escapeShellArg savedDir} ]; then current_target="$(${lib.getExe' pkgs.coreutils "readlink"} ${lib.escapeShellArg savedDir})" if [ "$current_target" != ${lib.escapeShellArg service.stateDir} ]; then ${lib.getExe' pkgs.coreutils "rm"} ${lib.escapeShellArg savedDir} fi fi if [ -d ${lib.escapeShellArg savedDir} ] && [ ! -L ${lib.escapeShellArg savedDir} ]; then if ${lib.getExe' pkgs.findutils "find"} ${lib.escapeShellArg service.stateDir} -mindepth 1 -maxdepth 1 | ${lib.getExe' pkgs.gnugrep "grep"} -q .; then ${lib.getExe' pkgs.coreutils "rm"} -rf ${lib.escapeShellArg savedDir} else ${lib.getExe' pkgs.coreutils "cp"} -a ${lib.escapeShellArg savedDir}/. ${lib.escapeShellArg service.stateDir}/ ${lib.getExe' pkgs.coreutils "rm"} -rf ${lib.escapeShellArg savedDir} fi fi if [ ! -e ${lib.escapeShellArg savedDir} ]; then ${lib.getExe' pkgs.coreutils "ln"} -s ${lib.escapeShellArg service.stateDir} ${lib.escapeShellArg savedDir} fi password="$(${lib.getExe' pkgs.coreutils "tr"} -d '\n' < ${lib.escapeShellArg service.passwordFile})" admin_password="$(${lib.getExe' pkgs.coreutils "tr"} -d '\n' < ${lib.escapeShellArg service.adminPasswordFile})" exec ./PalServer.sh \ "$@" \ "-serverpassword=$password" \ "-adminpassword=$admin_password" ''; in { modules.services.steamcmd = { enable = true; inherit (service) steamDir installDir stateDir; serviceName = "palworld-${lib.toLower service.serverName}"; appId = 2394010; inherit (service) autoStart; executable = "${startScript}"; arguments = [ "-useperfthreads" "-NoAsyncLoadingThread" "-UseMultithreadForDS" "-port=${toString service.port}" "-publicport=${toString service.port}" "-servername=${service.serverName}" "-players=${toString service.maxPlayers}" ] ++ lib.optional service.publicLobby "-publiclobby" ++ lib.optionals (service.publicIP != null) [ "-publicip=${service.publicIP}" ] ++ lib.optionals service.rcon.enable [ "-rcon" "-RCONPort=${toString service.rcon.port}" ]; udpPorts = [ service.port ]; tcpPorts = lib.optional service.rcon.enable service.rcon.port; }; } ); }