Render Palworld secrets through its documented settings file and remove stale RCON configuration. Harden generated commands, centralize shared modules, and codify the reviewed Nix conventions.
192 lines
6.0 KiB
Nix
192 lines
6.0 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
{
|
|
options.modules.services.steamcmd = {
|
|
enable = lib.mkEnableOption "SteamCMD-managed game server";
|
|
|
|
steamDir = lib.mkOption {
|
|
type = lib.types.str;
|
|
example = "/data/steamcmd";
|
|
description = "Directory where SteamCMD stores metadata and cache files.";
|
|
};
|
|
|
|
serviceName = lib.mkOption {
|
|
type = lib.types.strMatching "[A-Za-z0-9_.@-]+";
|
|
example = "valheim";
|
|
description = "Name of the systemd service that runs the game server.";
|
|
};
|
|
|
|
appId = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
example = 896660;
|
|
description = "Steam app ID for the dedicated server package.";
|
|
};
|
|
|
|
installDir = lib.mkOption {
|
|
type = lib.types.str;
|
|
example = "/data/servers/valheim/main";
|
|
description = "Directory where SteamCMD installs the server files and where the game service starts.";
|
|
};
|
|
|
|
stateDir = lib.mkOption {
|
|
type = lib.types.str;
|
|
example = "/data/state/valheim/main";
|
|
description = "Writable directory for durable game server state.";
|
|
};
|
|
|
|
executable = lib.mkOption {
|
|
type = lib.types.str;
|
|
example = "./valheim_server.x86_64";
|
|
description = "Server executable, relative to installDir or absolute.";
|
|
};
|
|
|
|
arguments = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
example = [
|
|
"-name"
|
|
"main"
|
|
];
|
|
description = "Arguments passed to the server executable.";
|
|
};
|
|
|
|
udpPorts = lib.mkOption {
|
|
type = lib.types.listOf lib.types.port;
|
|
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.";
|
|
};
|
|
|
|
autoStart = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Start the game service after the guest reaches network readiness.";
|
|
};
|
|
};
|
|
|
|
config = lib.mkMerge [
|
|
{
|
|
registry."stable-uid".${config.networking.hostName}.steamcmd = 20003;
|
|
}
|
|
|
|
(lib.mkIf config.modules.services.steamcmd.enable (
|
|
let
|
|
group = "storage";
|
|
|
|
service = config.modules.services.steamcmd;
|
|
user = "steamcmd";
|
|
prepareScript = pkgs.writeShellScript "steamcmd-${service.serviceName}-prepare" ''
|
|
set -eu
|
|
|
|
${lib.getExe' pkgs.coreutils "install"} -d \
|
|
-o ${user} \
|
|
-g ${group} \
|
|
-m 0770 \
|
|
-- \
|
|
${lib.escapeShellArg service.steamDir} \
|
|
${lib.escapeShellArg service.installDir} \
|
|
${lib.escapeShellArg service.stateDir}
|
|
'';
|
|
|
|
updateScript = pkgs.writeShellScript "steamcmd-${service.serviceName}-update" ''
|
|
set -eu
|
|
|
|
export HOME=${lib.escapeShellArg service.steamDir}
|
|
exec ${lib.getExe pkgs.steamcmd} \
|
|
+force_install_dir ${lib.escapeShellArg service.installDir} \
|
|
+login anonymous \
|
|
+app_update ${toString service.appId} validate \
|
|
+quit
|
|
'';
|
|
|
|
startScript = pkgs.writeShellScript "steamcmd-${service.serviceName}-start" ''
|
|
set -eu
|
|
|
|
cd -- ${lib.escapeShellArg service.installDir}
|
|
export LD_LIBRARY_PATH=linux64:''${LD_LIBRARY_PATH-}
|
|
exec ${lib.getExe pkgs.steam-run} ${lib.escapeShellArg service.executable} ${lib.escapeShellArgs service.arguments}
|
|
'';
|
|
in
|
|
{
|
|
users.users.${user} = {
|
|
isSystemUser = true;
|
|
uid = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
|
inherit group;
|
|
home = service.steamDir;
|
|
};
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d ${service.steamDir} 0770 ${user} ${group} - -"
|
|
"Z ${service.steamDir} 0770 ${user} ${group} - -"
|
|
"d ${service.installDir} 0770 ${user} ${group} - -"
|
|
"Z ${service.installDir} 0770 ${user} ${group} - -"
|
|
"d ${service.stateDir} 0770 ${user} ${group} - -"
|
|
"Z ${service.stateDir} 0770 ${user} ${group} - -"
|
|
];
|
|
|
|
systemd.services = {
|
|
"steamcmd-${service.serviceName}-update" = {
|
|
description = "Update ${service.serviceName} with SteamCMD";
|
|
after = [ "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = user;
|
|
Group = group;
|
|
WorkingDirectory = "-${service.steamDir}";
|
|
ExecStartPre = "+${prepareScript}";
|
|
ExecStart = updateScript;
|
|
TimeoutStartSec = "15min";
|
|
};
|
|
};
|
|
|
|
"${service.serviceName}-autostart" = {
|
|
description = "Queue ${service.serviceName} game server startup";
|
|
after = [ "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
wantedBy = lib.mkIf service.autoStart [ "multi-user.target" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = "${lib.getExe' pkgs.systemd "systemctl"} --no-block -- start ${service.serviceName}.service";
|
|
};
|
|
};
|
|
|
|
${service.serviceName} = {
|
|
description = "${service.serviceName} game server";
|
|
after = [
|
|
"network-online.target"
|
|
"steamcmd-${service.serviceName}-update.service"
|
|
];
|
|
wants = [
|
|
"network-online.target"
|
|
"steamcmd-${service.serviceName}-update.service"
|
|
];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = user;
|
|
Group = group;
|
|
WorkingDirectory = "-${service.installDir}";
|
|
ExecStartPre = "+${prepareScript}";
|
|
ExecStart = startScript;
|
|
Restart = "on-failure";
|
|
TimeoutStartSec = "15min";
|
|
};
|
|
};
|
|
};
|
|
|
|
networking.firewall.allowedUDPPorts = service.udpPorts;
|
|
networking.firewall.allowedTCPPorts = service.tcpPorts;
|
|
}
|
|
))
|
|
];
|
|
}
|