From 4336b307bdfe543b881cefdc9d4011f4fb77e9a6 Mon Sep 17 00:00:00 2001 From: Scott Techau Date: Sat, 20 Jan 2024 12:51:30 -0800 Subject: [PATCH 1/8] Improve robustness of package updater script --- apps/updater/main.ts | 10 +++++++--- apps/updater/prefetch.sh | 12 ++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/updater/main.ts b/apps/updater/main.ts index 9ad4291..5a8bc1e 100644 --- a/apps/updater/main.ts +++ b/apps/updater/main.ts @@ -1,6 +1,8 @@ import { parseArgs } from 'https://deno.land/std@0.208.0/cli/parse_args.ts'; import { LockFile, SteamObject, parseObject } from './parser.ts' +const utf8Decoder = new TextDecoder(); + /** * Recursively find all files matching `fileName` under `baseDir` * @@ -31,7 +33,7 @@ const getAppInfo = async (appId: number): Promise => { ], }); const app_info_bytes = (await steamcmd.output()).stdout; - const app_info_str = new TextDecoder().decode(app_info_bytes); + const app_info_str = utf8Decoder.decode(app_info_bytes); const info_start = `"${appId}"`; const info_start_idx = app_info_str.indexOf(info_start); const app_info_obj = app_info_str.slice(info_start_idx + info_start.length); @@ -140,11 +142,13 @@ async function prefetch( const output = await command.output(); if (!output.success) { - console.error(new TextDecoder().decode(output.stdout)); + console.error(utf8Decoder.decode(output.stdout)); throw new Error(`Prefetching failed with code ${output.code}`); } - return new TextDecoder().decode(output.stdout); + return utf8Decoder + .decode(output.stdout) + .trim(); } function usage(): never { diff --git a/apps/updater/prefetch.sh b/apps/updater/prefetch.sh index 54a3283..727995d 100755 --- a/apps/updater/prefetch.sh +++ b/apps/updater/prefetch.sh @@ -4,6 +4,9 @@ set -e +# Redirect all stdout to stderr, but save reference to original stdout +exec 3>&1 >&2 + # Enable new nix features export NIX_CONFIG="experimental-features = nix-command" @@ -23,15 +26,16 @@ if [ -n "$debug" ]; then args+=(-debug) fi -echo "DepotDownloader ${args[*]} -dir ${downloadDir}" >&2 +echo "DepotDownloader ${args[*]} -dir ${downloadDir}" DepotDownloader \ "${args[@]}" \ - -dir "${downloadDir}" >&2 + -dir "${downloadDir}" if [ -n "$addToStore" ]; then echo "Adding depot to store" - nix store add-path --name "${name:?}" "${downloadDir}" >&2 + nix store add-path --name "${name:?}" "${downloadDir}" fi -nix hash path "${downloadDir}" + +nix hash path "${downloadDir}" >&3 rm -rf "${downloadDir}" \ No newline at end of file From 7e29f00b29144efc778487813f06b493d23c11a1 Mon Sep 17 00:00:00 2001 From: Scott Techau Date: Sat, 20 Jan 2024 19:53:04 -0800 Subject: [PATCH 2/8] Add palworld package --- pkgs/default.nix | 1 + pkgs/palworld/default.nix | 23 +++++++++++++++++++++++ pkgs/palworld/lock.json | 15 +++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 pkgs/palworld/default.nix create mode 100644 pkgs/palworld/lock.json diff --git a/pkgs/default.nix b/pkgs/default.nix index a879c0d..20d599d 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -5,6 +5,7 @@ }: let pkgsToImport = { "7-days-to-die" = ./7-days-to-die; + palworld = ./palworld; stationeers = ./stationeers; }; diff --git a/pkgs/palworld/default.nix b/pkgs/palworld/default.nix new file mode 100644 index 0000000..415be4c --- /dev/null +++ b/pkgs/palworld/default.nix @@ -0,0 +1,23 @@ +{ + lib, + mkSteamPackage, + gcc-unwrapped, +}: +mkSteamPackage { + lockFile = ./lock.json; + + buildInputs = [ + gcc-unwrapped + ]; + + meta = with lib; { + description = "Palworld Dedicated Server"; + homepage = "https://steamdb.info/app/2394010/"; + changelog = "https://store.steampowered.com/news/app/1623730?updates=true"; + sourceProvenance = with sourceTypes; [ + binaryNativeCode # Steam games are always going to contain some native binary component. + ]; + license = licenses.unfree; + platforms = ["x86_64-linux"]; + }; +} diff --git a/pkgs/palworld/lock.json b/pkgs/palworld/lock.json new file mode 100644 index 0000000..a7eeb30 --- /dev/null +++ b/pkgs/palworld/lock.json @@ -0,0 +1,15 @@ +{ + "appId": 2394010, + "depotId": 2394012, + "name": "palworld-server", + "branches": { + "public": "13225464" + }, + "builds": { + "13225464": { + "hash": "sha256-NEnskCOl031yb0+jmsWkFHMZVVrRzM4BLLVZGko1Jk8=", + "manifestId": "4603741190199642564", + "version": "13225464" + } + } +} \ No newline at end of file From d2433ef11db317d66b1a2c742cd7f6315253d19d Mon Sep 17 00:00:00 2001 From: Scott Techau Date: Thu, 1 Feb 2024 19:48:00 -0800 Subject: [PATCH 3/8] Fix bug in mkDirs that added an extra layer of directories --- modules/lib.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/lib.nix b/modules/lib.nix index 1c6a0d9..4809250 100644 --- a/modules/lib.nix +++ b/modules/lib.nix @@ -107,7 +107,7 @@ in { echo "${n} already exists and isn't a directory, moving" mv "${n}" "${n}.bak" fi - ${pkgs.rsync}/bin/rsync -avu "${v}" "${n}" + ${pkgs.rsync}/bin/rsync -avu "${v}/" "${n}" '') dirs)); } From d730c2a7deefd1396249decbdc593af11901625a Mon Sep 17 00:00:00 2001 From: Scott Techau Date: Thu, 1 Feb 2024 20:45:55 -0800 Subject: [PATCH 4/8] WIP: Palworld module --- modules/palworld/default.nix | 65 +++++++++++++++++++++++++++++++ modules/palworld/default.test.nix | 33 ++++++++++++++++ modules/palworld/options.nix | 57 +++++++++++++++++++++++++++ modules/servers/default.nix | 1 + 4 files changed, 156 insertions(+) create mode 100644 modules/palworld/default.nix create mode 100644 modules/palworld/default.test.nix create mode 100644 modules/palworld/options.nix diff --git a/modules/palworld/default.nix b/modules/palworld/default.nix new file mode 100644 index 0000000..66feec4 --- /dev/null +++ b/modules/palworld/default.nix @@ -0,0 +1,65 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + baseCfg = config.services.steam-servers; + cfg = baseCfg.palworld; + enabledServers = filterAttrs (_: conf: conf.enable) cfg; +in { + imports = [./options.nix]; + + config = mkIf (enabledServers != {}) { + networking.firewall = + mkMerge + (map + (conf: + mkIf conf.openFirewall { + # allowedUDPPorts = [conf.config.UpdatePort conf.config.GamePort]; + allowedUDPPorts = [8211]; + }) + (builtins.attrValues enabledServers)); + + services.steam-servers.servers = + mapAttrs' + (name: conf: + nameValuePair "palworld-${name}" { + # inherit args; + inherit (conf) enable datadir; + + symlinks = { + "${baseCfg.datadir}/.steam/sdk64/steamclient.so" = "${pkgs.steamworks-sdk-redist}/lib/steamclient.so"; + "Pal/Binaries" = "${conf.package}/Pal/Binaries"; + "Pal/Content" = "${conf.package}/Pal/Content"; + "Pal/Plugins" = "${conf.package}/Pal/Plugins"; + Engine = "${conf.package}/Engine"; + }; + + dirs = { + }; + + files = { + # Copy start script since it derefernces symlinks to find the server root dir + "PalServer.sh" = "${conf.package}/PalServer.sh"; + # "Pal/Saved/Config/LinuxServer/PalWorldSettings.ini" = settingsFile; + }; + + executable = "./PalServer.sh"; + }) + cfg; + + systemd.services = + mapAttrs' + ( + name: _conf: + nameValuePair "palworld-${name}" { + path = with pkgs; [ + xdg-user-dirs + ]; + } + ) + enabledServers; + }; +} diff --git a/modules/palworld/default.test.nix b/modules/palworld/default.test.nix new file mode 100644 index 0000000..c0e62c9 --- /dev/null +++ b/modules/palworld/default.test.nix @@ -0,0 +1,33 @@ +{lib, ...}: +with lib; { + name = "palworld"; + + nodes = { + server = { + virtualisation = { + cores = 8; + memorySize = 16 * 1024; + diskSize = 8 * 1024; + + forwardPorts = [ + { + from = "guest"; + guest.port = 8211; + guest.address = "10.0.2.10"; + host.port = 8211; + host.address = "0.0.0.0"; + } + ]; + }; + + services.steam-servers.palworld.test = { + enable = true; + openFirewall = true; + }; + }; + }; + + testScript = '' + server.wait_for_unit("palworld-test.service") + ''; +} diff --git a/modules/palworld/options.nix b/modules/palworld/options.nix new file mode 100644 index 0000000..e496f2b --- /dev/null +++ b/modules/palworld/options.nix @@ -0,0 +1,57 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + baseCfg = config.services.steam-servers; + moduleLib = import ../lib.nix lib; + inherit (moduleLib) mkOpt; + + settingsFormat = pkgs.formats.ini {}; + + serverModule = {name, ...}: { + options = { + enable = mkEnableOption (mdDoc "Palworld Dedicated Server"); + + package = mkOption { + type = types.package; + default = pkgs.palworld; + defaultText = literalExpression "pkgs.palworld"; + description = mdDoc "Package to use for Palworld binary"; + }; + + datadir = mkOption { + type = types.path; + default = "${baseCfg.datadir}/palworld/${name}"; + defaultText = literalExpression "\${services.steam-servers.datadir}/palworld/\${name}"; + description = mdDoc '' + Directory to store save state of the game server. (eg world, saves, etc) + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = mdDoc "Whether to open ports in the firewall."; + }; + + worldSettings = mkOption { + inherit (settingsFormat) type; + default = {}; + description = mdDoc "World settings used to generate PalWorldSettings.ini"; + }; + + extraArgs = mkOpt (with types; listOf str) [] "Extra command line arguments to pass to the server"; + }; + }; +in { + options.services.steam-servers.palworld = mkOption { + type = types.attrsOf (types.submodule serverModule); + default = {}; + description = mdDoc '' + Options to configure one or more Stationers servers. + ''; + }; +} diff --git a/modules/servers/default.nix b/modules/servers/default.nix index 2004e5c..989f0e7 100644 --- a/modules/servers/default.nix +++ b/modules/servers/default.nix @@ -29,6 +29,7 @@ in { script = '' cd ${conf.datadir} + chmod +x ${conf.executable} ${conf.executable} ${concatStringsSep " \\\n" conf.args} ''; From e237b5600b28301c0c91a9c8b2ce66415a3584d5 Mon Sep 17 00:00:00 2001 From: Scott Techau Date: Fri, 2 Feb 2024 00:28:34 -0800 Subject: [PATCH 5/8] Update palworld version --- pkgs/palworld/lock.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/palworld/lock.json b/pkgs/palworld/lock.json index a7eeb30..5adf4d5 100644 --- a/pkgs/palworld/lock.json +++ b/pkgs/palworld/lock.json @@ -3,13 +3,18 @@ "depotId": 2394012, "name": "palworld-server", "branches": { - "public": "13225464" + "public": "13312441" }, "builds": { "13225464": { "hash": "sha256-NEnskCOl031yb0+jmsWkFHMZVVrRzM4BLLVZGko1Jk8=", "manifestId": "4603741190199642564", "version": "13225464" + }, + "13312441": { + "hash": "sha256-IoI5Up+Vj6Rti9BhPH2e1o+TbpjIGmcWCc5bCV8M2Ps=", + "manifestId": "4190579964382773830", + "version": "13312441" } } } \ No newline at end of file From b40baf46dc3648a693372b82bee91c93fd1dc70f Mon Sep 17 00:00:00 2001 From: Scott Techau Date: Sat, 3 Feb 2024 12:30:28 -0800 Subject: [PATCH 6/8] Improve robustness of building the server directory --- modules/default.nix | 35 ++++++++++++----------------------- modules/lib.nix | 1 + modules/servers/default.nix | 13 +++++++++++-- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/modules/default.nix b/modules/default.nix index 3b52ef4..ab00276 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -22,11 +22,13 @@ in { flake.nixosModules.default = { config, + pkgs, lib, ... }: with lib; let cfg = config.services.steam-servers; + userHome = config.users.users.${cfg.user}.home; anyServersEnabled = any (conf: conf.enable) @@ -44,33 +46,20 @@ in { inputs.steam-fetcher.overlays.default ]; - # Can't use tmpfiles because tmpfiles won't create directories with different owner than parent - systemd.services."make-steam-servers-dir" = let - services = - map - (name: "${name}.service") - (builtins.attrNames cfg.servers); - in { - wantedBy = services; - before = services; - - script = '' - mkdir -p ${cfg.datadir} - chown ${cfg.user}:${cfg.group} ${cfg.datadir} - ''; - - serviceConfig = { - Type = "oneshot"; - }; - }; - - users.users.${cfg.user} = { + users.users."${cfg.user}" = { isSystemUser = true; home = "${cfg.datadir}"; - group = "${cfg.group}"; + createHome = true; + homeMode = "750"; + inherit (cfg) group; }; - users.groups.${cfg.group} = {}; + users.groups."${cfg.group}" = {}; + + systemd.tmpfiles.rules = [ + "d ${userHome}/.steam 0755 ${cfg.user} ${cfg.user} - -" + "L+ ${userHome}/.steam/sdk64 - - - - ${pkgs.steamworks-sdk-redist}/lib" + ]; }; }; } diff --git a/modules/lib.nix b/modules/lib.nix index 4809250..cd6113f 100644 --- a/modules/lib.nix +++ b/modules/lib.nix @@ -108,6 +108,7 @@ in { mv "${n}" "${n}.bak" fi ${pkgs.rsync}/bin/rsync -avu "${v}/" "${n}" + chmod -R u+w "${n}" '') dirs)); } diff --git a/modules/servers/default.nix b/modules/servers/default.nix index 989f0e7..13501c3 100644 --- a/modules/servers/default.nix +++ b/modules/servers/default.nix @@ -29,7 +29,7 @@ in { script = '' cd ${conf.datadir} - chmod +x ${conf.executable} + chmod +x ${conf.executable} || true # Still try to start if this fails ${conf.executable} ${concatStringsSep " \\\n" conf.args} ''; @@ -68,6 +68,16 @@ in { User = mkDefault "${baseCfg.user}"; Group = mkDefault "${baseCfg.group}"; + RuntimeDirectory = mkDefault "steam-servers"; + RuntimeDirectoryPreserve = mkDefault true; + + # These don't use mkDefault as they are inherent to how this module works + # Type = "forking"; + # GuessMainPID = true; + + PrivateDevices = mkDefault true; + PrivateTmp = mkDefault true; + PrivateUsers = mkDefault true; ProtectClock = mkDefault true; ProtectProc = mkDefault "noaccess"; ProtectKernelLogs = mkDefault true; @@ -75,7 +85,6 @@ in { ProtectKernelTunables = mkDefault true; ProtectControlGroups = mkDefault true; ProtectHostname = mkDefault true; - PrivateDevices = mkDefault true; RestrictRealtime = mkDefault true; RestrictNamespaces = mkDefault true; LockPersonality = mkDefault true; From f010cdb606c2b9e6b76d92215054774e6cdc62f9 Mon Sep 17 00:00:00 2001 From: Scott Techau Date: Sat, 3 Feb 2024 12:32:35 -0800 Subject: [PATCH 7/8] Fix palworld module --- modules/palworld/default.nix | 58 +++++++++++++++++++++++++------ modules/palworld/default.test.nix | 10 ------ modules/palworld/options.nix | 11 ++++-- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/modules/palworld/default.nix b/modules/palworld/default.nix index 66feec4..8660b57 100644 --- a/modules/palworld/default.nix +++ b/modules/palworld/default.nix @@ -8,6 +8,30 @@ with lib; let baseCfg = config.services.steam-servers; cfg = baseCfg.palworld; enabledServers = filterAttrs (_: conf: conf.enable) cfg; + # settingsFormat = pkgs.formats.ini {}; + settingsFormat = { + generate = name: value: let + optionSettings = + mapAttrsToList + (optName: optVal: let + optType = builtins.typeOf optVal; + encodedVal = + if optType == "string" + then "\"${optVal}\"" + else if optType == "bool" + then + if optVal + then "True" + else "False" + else optVal; + in "${optName}=${encodedVal}") + value; + in + builtins.toFile name '' + [/Script/Pal.PalGameWorldSettings] + OptionSettings=(${concatStringsSep "," optionSettings}) + ''; + }; in { imports = [./options.nix]; @@ -24,29 +48,35 @@ in { services.steam-servers.servers = mapAttrs' - (name: conf: + (name: conf: let + settingsFile = settingsFormat.generate "PalWorldSettings.ini" conf.worldSettings; + in nameValuePair "palworld-${name}" { # inherit args; inherit (conf) enable datadir; - symlinks = { - "${baseCfg.datadir}/.steam/sdk64/steamclient.so" = "${pkgs.steamworks-sdk-redist}/lib/steamclient.so"; - "Pal/Binaries" = "${conf.package}/Pal/Binaries"; - "Pal/Content" = "${conf.package}/Pal/Content"; - "Pal/Plugins" = "${conf.package}/Pal/Plugins"; - Engine = "${conf.package}/Engine"; - }; - dirs = { + Pal = "${conf.package}/Pal"; + Engine = "${conf.package}/Engine"; }; files = { # Copy start script since it derefernces symlinks to find the server root dir "PalServer.sh" = "${conf.package}/PalServer.sh"; - # "Pal/Saved/Config/LinuxServer/PalWorldSettings.ini" = settingsFile; + + "Pal/Saved/Config/LinuxServer/PalWorldSettings.ini" = settingsFile; }; - executable = "./PalServer.sh"; + executable = "chmod +x ${conf.datadir}/PalServer.sh; ${pkgs.steam-run}/bin/steam-run ${conf.datadir}/PalServer.sh"; + + args = + [ + "-port=${toString conf.port}" + "-useperfthreads" + "-NoAsyncLoadingThread" + "-UseMultithreadForDS" + ] + ++ conf.extraArgs; }) cfg; @@ -58,6 +88,12 @@ in { path = with pkgs; [ xdg-user-dirs ]; + + serviceConfig = { + # Palworld needs namespaces and system calls + RestrictNamespaces = false; + SystemCallFilter = []; + }; } ) enabledServers; diff --git a/modules/palworld/default.test.nix b/modules/palworld/default.test.nix index c0e62c9..2776e97 100644 --- a/modules/palworld/default.test.nix +++ b/modules/palworld/default.test.nix @@ -8,16 +8,6 @@ with lib; { cores = 8; memorySize = 16 * 1024; diskSize = 8 * 1024; - - forwardPorts = [ - { - from = "guest"; - guest.port = 8211; - guest.address = "10.0.2.10"; - host.port = 8211; - host.address = "0.0.0.0"; - } - ]; }; services.steam-servers.palworld.test = { diff --git a/modules/palworld/options.nix b/modules/palworld/options.nix index e496f2b..4e8b6cd 100644 --- a/modules/palworld/options.nix +++ b/modules/palworld/options.nix @@ -9,8 +9,6 @@ with lib; let moduleLib = import ../lib.nix lib; inherit (moduleLib) mkOpt; - settingsFormat = pkgs.formats.ini {}; - serverModule = {name, ...}: { options = { enable = mkEnableOption (mdDoc "Palworld Dedicated Server"); @@ -37,8 +35,15 @@ with lib; let description = mdDoc "Whether to open ports in the firewall."; }; + port = mkOption { + type = types.port; + default = 8211; + description = mdDoc "UDP port to listen on"; + }; + worldSettings = mkOption { - inherit (settingsFormat) type; + # inherit (settingsFormat) type; + type = types.attrs; default = {}; description = mdDoc "World settings used to generate PalWorldSettings.ini"; }; From c351f45d6adab10ef374c72cf67f77c6343313e2 Mon Sep 17 00:00:00 2001 From: Scott Techau Date: Sat, 3 Feb 2024 12:46:40 -0800 Subject: [PATCH 8/8] Don't automatically chmod +x all servers --- modules/servers/default.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/servers/default.nix b/modules/servers/default.nix index 13501c3..7654be5 100644 --- a/modules/servers/default.nix +++ b/modules/servers/default.nix @@ -29,7 +29,6 @@ in { script = '' cd ${conf.datadir} - chmod +x ${conf.executable} || true # Still try to start if this fails ${conf.executable} ${concatStringsSep " \\\n" conf.args} '';