diff --git a/configs/nixos/hosts/pikachu/default.nix b/configs/nixos/hosts/pikachu/default.nix index 9172899..ce98f0a 100644 --- a/configs/nixos/hosts/pikachu/default.nix +++ b/configs/nixos/hosts/pikachu/default.nix @@ -66,6 +66,37 @@ registry.address.pikachu.self = "10.23.10.102"; + modules.services.steamcmd = { + enable = true; + steamDir = "/pikachu/data/steamcmd"; + serviceName = "valheim"; + appId = 896660; + installDir = "/pikachu/data/servers/valheim/main"; + stateDir = "/pikachu/media/games/servers/valheim/main"; + executable = "./valheim_server.x86_64"; + arguments = [ + "-nographics" + "-batchmode" + "-name" + "main" + "-port" + "2456" + "-world" + "main" + "-savedir" + config.modules.services.steamcmd.stateDir + "-password" + "foobarbaz" + "-public" + "0" + ]; + udpPorts = [ + 2456 + 2457 + 2458 + ]; + }; + guests.actualbudget = { enable = true; vlan = 20; diff --git a/configs/nixos/modules/services/steamcmd.nix b/configs/nixos/modules/services/steamcmd.nix new file mode 100644 index 0000000..3172af3 --- /dev/null +++ b/configs/nixos/modules/services/steamcmd.nix @@ -0,0 +1,165 @@ +{ + 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."; + }; + }; + + 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; + } + )) + ]; +}