Files
dev/configs/nixos/modules/services/steamcmd.nix

173 lines
5.3 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.str;
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.";
};
};
config = lib.mkMerge [
{
registry."stable-uid".${config.networking.hostName}.steamcmd = 20003;
}
(lib.mkIf config.modules.services.steamcmd.enable (
let
service = config.modules.services.steamcmd;
user = "steamcmd";
group = "storage";
prepareScript = pkgs.writeShellScript "steamcmd-${service.serviceName}-prepare" ''
set -eu
${pkgs.coreutils}/bin/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;
};
};
${service.serviceName} = {
description = "${service.serviceName} game server";
after = [
"network-online.target"
"steamcmd-${service.serviceName}-update.service"
];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = user;
Group = group;
WorkingDirectory = "-${service.installDir}";
ExecStartPre = [
"+${prepareScript}"
updateScript
];
ExecStart = startScript;
Restart = "on-failure";
};
};
};
networking.firewall.allowedUDPPorts = service.udpPorts;
networking.firewall.allowedTCPPorts = service.tcpPorts;
}
))
];
}