Files
dev/configs/nixos/modules/services/games/palworld.nix
2026-09-16 00:16:08 -04:00

155 lines
4.9 KiB
Nix

{
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
${pkgs.coreutils}/bin/install -d \
-m 0770 \
${lib.escapeShellArg service.stateDir} \
${lib.escapeShellArg (builtins.dirOf savedDir)}
if [ -L ${lib.escapeShellArg savedDir} ]; then
current_target="$(${pkgs.coreutils}/bin/readlink ${lib.escapeShellArg savedDir})"
if [ "$current_target" != ${lib.escapeShellArg service.stateDir} ]; then
${pkgs.coreutils}/bin/rm ${lib.escapeShellArg savedDir}
fi
fi
if [ -d ${lib.escapeShellArg savedDir} ] && [ ! -L ${lib.escapeShellArg savedDir} ]; then
if ${pkgs.findutils}/bin/find ${lib.escapeShellArg service.stateDir} -mindepth 1 -maxdepth 1 | ${pkgs.gnugrep}/bin/grep -q .; then
${pkgs.coreutils}/bin/rm -rf ${lib.escapeShellArg savedDir}
else
${pkgs.coreutils}/bin/cp -a ${lib.escapeShellArg savedDir}/. ${lib.escapeShellArg service.stateDir}/
${pkgs.coreutils}/bin/rm -rf ${lib.escapeShellArg savedDir}
fi
fi
if [ ! -e ${lib.escapeShellArg savedDir} ]; then
${pkgs.coreutils}/bin/ln -s ${lib.escapeShellArg service.stateDir} ${lib.escapeShellArg savedDir}
fi
password="$(${pkgs.coreutils}/bin/tr -d '\n' < ${lib.escapeShellArg service.passwordFile})"
admin_password="$(${pkgs.coreutils}/bin/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.optional service.rcon.enable "-rcon";
udpPorts = [ service.port ];
tcpPorts = [ service.port ];
};
}
);
}