feat(nixos): add Palworld Tracion guest

This commit was merged in pull request #39.
This commit is contained in:
2026-09-10 14:37:50 -04:00
parent 8bd9ff166e
commit 9e7469796d
9 changed files with 525 additions and 17 deletions

View File

@@ -0,0 +1,148 @@
{
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.";
};
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;
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 ];
};
}
);
}

View File

@@ -59,6 +59,12 @@
default = [ ];
description = "UDP ports opened for the game service.";
};
tcpPorts = lib.mkOption {
type = lib.types.listOf lib.types.port;
default = [ ];
description = "TCP ports opened for the game service.";
};
};
config = lib.mkMerge [
@@ -159,6 +165,7 @@
};
networking.firewall.allowedUDPPorts = service.udpPorts;
networking.firewall.allowedTCPPorts = service.tcpPorts;
}
))
];

View File

@@ -18,13 +18,6 @@ or begin
return 2
end
set untracked_lines (git status --porcelain --untracked-files=normal | string match -r '^\?\? ')
if test (count $untracked_lines) -gt 0
echo "deploy-to: refusing to deploy untracked files" >&2
echo "deploy-to: git add new files that Nix must see, or remove them" >&2
return 1
end
git fetch origin
or return $status