Personal Nix setup
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Palworld server

+503
+4
machines/ramune/configuration.nix
··· 51 51 caddy.enable = true; 52 52 vaultwarden.enable = true; 53 53 }; 54 + games = { 55 + enable = true; 56 + palworld.enable = true; 57 + }; 54 58 }; 55 59 56 60 system.stateVersion = "24.11";
+1
modules/default.nix
··· 6 6 ./apps 7 7 ./desktop 8 8 ./fonts 9 + ./games 9 10 ./nvim 10 11 ./router 11 12 ./server
+54
modules/games/default.nix
··· 1 + { lib, config, ... }: 2 + 3 + with lib; let 4 + cfg = config.modules.games; 5 + in { 6 + options.modules.games = { 7 + enable = mkOption { 8 + default = false; 9 + example = true; 10 + description = "Whether to enable game server options."; 11 + type = types.bool; 12 + }; 13 + 14 + datadir = mkOption { 15 + type = types.path; 16 + default = "/var/lib/games"; 17 + description = "Base directory for all game servers created with this module."; 18 + example = "/mnt/nfs/steam"; 19 + }; 20 + 21 + user = mkOption { 22 + type = types.str; 23 + default = "games"; 24 + description = "User to use when running game servers and creating top-level resources"; 25 + }; 26 + 27 + group = mkOption { 28 + type = types.str; 29 + default = cfg.user; 30 + defaultText = literalExpression "\${cfg.user}"; 31 + description = "Group to use when running game servers"; 32 + }; 33 + }; 34 + 35 + config = mkIf cfg.enable { 36 + users.users."${cfg.user}" = { 37 + home = "${cfg.datadir}"; 38 + group = cfg.group; 39 + isSystemUser = true; 40 + createHome = true; 41 + homeMode = "750"; 42 + }; 43 + 44 + users.groups."${cfg.group}" = {}; 45 + 46 + systemd.tmpfiles.rules = [ 47 + "d ${cfg.datadir}/.steam 0755 ${cfg.user} ${cfg.group} - -" 48 + ]; 49 + }; 50 + 51 + imports = [ 52 + ./palworld.nix 53 + ]; 54 + }
+53
modules/games/lib/fetchSteam.nix
··· 1 + { lib, pkgs, ... }: 2 + 3 + with lib; makeOverridable ( 4 + { 5 + name ? "steamapp-${appId}-${depotId}-${manifestId}", 6 + appId, 7 + depotId, 8 + manifestId, 9 + hash ? "", 10 + branch ? null, 11 + fileList ? null, 12 + debug ? false, 13 + passthru ? { }, 14 + meta ? { }, 15 + } @ args: 16 + let 17 + fileListArg = 18 + if isList fileList then 19 + builtins.toFile "steam-files-list.txt" (concatLines fileList) 20 + else 21 + fileList; 22 + 23 + downloadArgs = 24 + [ 25 + "-app" appId 26 + "-depot" depotId 27 + "-manifest" manifestId 28 + ] 29 + ++ optionals (branch != null) [ "-beta" branch ] 30 + ++ optionals (fileList != null) [ "-filelist" fileListArg ] 31 + ++ optionals debug [ "-debug" ]; 32 + 33 + drvArgs = { 34 + depsBuildBuild = [ pkgs.depotdownloader ]; 35 + 36 + strictDeps = true; 37 + 38 + outputHashAlgo = "sha256"; 39 + outputHashMode = "recursive"; 40 + outputHash = if hash != "" then hash else fakeHash; 41 + 42 + env.SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; 43 + 44 + pos = builtins.unsafeGetAttrPos "manifestId" args; 45 + 46 + inherit passthru; 47 + } // optionalAttrs (args ? meta) { inherit meta; }; 48 + in 49 + pkgs.runCommand name drvArgs '' 50 + HOME=$PWD DepotDownloader -dir "$out" ${escapeShellArgs downloadArgs} 51 + rm -r "$out"/.DepotDownloader 52 + '' 53 + )
+35
modules/games/lib/mkSteamPackage.nix
··· 1 + { lib, pkgs, ... } @ inputs: 2 + 3 + with lib; 4 + let 5 + fetchSteam = (import ./fetchSteam.nix) inputs; 6 + inherit ((import ./steamworks.nix) inputs) steamworks-sdk-redist; 7 + in 8 + { 9 + name, 10 + hash ? "", 11 + version, 12 + appId, 13 + depotId, 14 + manifestId, 15 + ... 16 + } @ args: let 17 + mkDerivationArgs = builtins.removeAttrs args [ "name" "appId" "depotId" "manifestId" "hash" ]; 18 + in pkgs.stdenv.mkDerivation (rec { 19 + pname = name; 20 + src = fetchSteam { 21 + inherit name appId depotId manifestId hash; 22 + }; 23 + dontBuild = true; 24 + dontConfigure = true; 25 + dontFixup = true; 26 + installPhase = '' 27 + runHook preInstall 28 + 29 + mkdir -p $out 30 + mv ./* $out 31 + chmod 755 -R $out 32 + 33 + runHook postInstall 34 + ''; 35 + } // mkDerivationArgs)
+52
modules/games/lib/mkWrappedBox64.nix
··· 1 + { lib, pkgs, ... } @ inputs: 2 + 3 + with lib; 4 + let 5 + inherit ((import ./steamworks.nix) inputs) steamworks-sdk-redist; 6 + in { 7 + logLevel ? 0, 8 + env ? {}, 9 + libs ? [], 10 + extraWrapperArgs ? [], 11 + }: let 12 + box64Bin = "${pkgs.box64}/bin/box64"; 13 + runpaths = with pkgs; [ 14 + steamworks-sdk-redist 15 + glibc 16 + libxcrypt 17 + libGL 18 + libdrm 19 + mesa # for libgbm 20 + udev 21 + libudev0-shim 22 + libva 23 + vulkan-loader 24 + ]; 25 + combinedEnv = { 26 + BOX64_DYNAREC_STRONGMEM = 1; 27 + BOX64_DYNAREC_BIGBLOCK = 1; 28 + BOX64_DYNAREC_SAFEFLAGS = 1; 29 + BOX64_DYNAREC_FASTROUND = 1; 30 + BOX64_DYNAREC_FASTNAN = 1; 31 + BOX64_DYNAREC_X87DOUBLE = 0; 32 + } // env; 33 + in pkgs.stdenv.mkDerivation rec { 34 + name = "box64-wrapped"; 35 + 36 + dontUnpack = true; 37 + dontConfigure = true; 38 + dontBuild = true; 39 + 40 + nativeBuildInputs = [ pkgs.makeWrapper ]; 41 + buildInputs = runpaths ++ libs; 42 + 43 + installPhase = '' 44 + runHook preInstall 45 + makeWrapper "${box64Bin}" "$out/bin/box64" \ 46 + ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${toString value}' ") env)} \ 47 + --set BOX64_LOG "${toString logLevel}" \ 48 + --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath buildInputs} \ 49 + ${lib.strings.concatStringsSep " " extraWrapperArgs} 50 + runHook postInstall 51 + ''; 52 + }
+73
modules/games/lib/serverScripts.nix
··· 1 + { lib, pkgs, ... }: 2 + 3 + with lib; 4 + { 5 + mkSymlinks = name: symlinks: 6 + pkgs.writeShellScript "${name}-symlinks" 7 + (concatStringsSep "\n" 8 + (mapAttrsToList 9 + (n: v: '' 10 + if [[ -L "${n}" ]]; then 11 + unlink "${n}" 12 + elif [[ -e "${n}" ]]; then 13 + echo "${n} already exists, moving" 14 + mv "${n}" "${n}.bak" 15 + fi 16 + mkdir -p "$(dirname "${n}")" 17 + ln -sf "${v}" "${n}" 18 + '') 19 + symlinks)); 20 + 21 + mkFiles = name: files: 22 + pkgs.writeShellScript "${name}-files" 23 + (concatStringsSep "\n" 24 + (mapAttrsToList 25 + (n: v: '' 26 + if [[ -L "${n}" ]]; then 27 + unlink "${n}" 28 + elif ${pkgs.diffutils}/bin/cmp -s "${n}" "${v}"; then 29 + rm "${n}" 30 + elif [[ -e "${n}" ]]; then 31 + echo "${n} already exists, moving" 32 + mv "${n}" "${n}.bak" 33 + fi 34 + mkdir -p $(dirname "${n}") 35 + ${pkgs.gawk}/bin/awk '{ 36 + for(varname in ENVIRON) 37 + gsub("@"varname"@", ENVIRON[varname]) 38 + print 39 + }' "${v}" > "${n}" 40 + chmod --reference="${v}" "${n}" 41 + '') 42 + files)); 43 + 44 + mkDirs = name: dirs: 45 + pkgs.writeShellScript "${name}-dirs" 46 + (concatStringsSep "\n" 47 + (mapAttrsToList 48 + (n: v: '' 49 + if [[ -L "${n}" ]]; then 50 + unlink "${n}" 51 + elif [[ ! -d "${n}" ]]; then 52 + echo "${n} already exists and isn't a directory, moving" 53 + mv "${n}" "${n}.bak" 54 + fi 55 + ${pkgs.rsync}/bin/rsync -avu "${v}/" "${n}" 56 + chmod -R u+w "${n}" 57 + '') 58 + dirs)); 59 + 60 + rmSymlinks = name: symlinks: 61 + pkgs.writeShellScript "${name}-rm-symlinks" 62 + ( 63 + concatStringsSep "\n" 64 + (mapAttrsToList (n: _v: "unlink \"${n}\"") symlinks) 65 + ); 66 + 67 + rmFiles = name: files: 68 + pkgs.writeShellScript "${name}-rm-symlinks" 69 + ( 70 + concatStringsSep "\n" 71 + (mapAttrsToList (n: _v: "rm \"${n}\"") files) 72 + ); 73 + }
+42
modules/games/lib/steamworks.nix
··· 1 + { lib, pkgs, ... } @ inputs: 2 + 3 + with lib; 4 + let 5 + fetchSteam = (import ./fetchSteam.nix) inputs; 6 + in { 7 + steamworks-sdk-redist = pkgs.stdenv.mkDerivation { 8 + pname = "steamworks-sdk-redist"; 9 + version = "unstable-2024-05-30"; 10 + 11 + # Steamworks SDK Redist with steamclient.so. 12 + # https://steamdb.info/app/1007/depots 13 + src = fetchSteam { 14 + appId = "1007"; 15 + depotId = "1006"; 16 + manifestId = "7138471031118904166"; 17 + hash = "sha256-OtPI1kAx6+9G09IEr2kYchyvxlPl3rzx/ai/xEVG4oM="; 18 + }; 19 + 20 + dontConfigure = true; 21 + dontBuild = true; 22 + 23 + installPhase = '' 24 + runHook preInstall 25 + 26 + mkdir -p $out/lib 27 + cp linux64/steamclient.so $out/lib 28 + chmod +x $out/lib/steamclient.so 29 + 30 + runHook postInstall 31 + ''; 32 + 33 + meta = { 34 + description = "Steamworks SDK Redist"; 35 + sourceProvenance = [ sourceTypes.binaryNativeCode ]; 36 + license = licenses.unfree; 37 + badPlatforms = [ 38 + { hasSharedLibraries = false; } 39 + ]; 40 + }; 41 + }; 42 + }
+189
modules/games/palworld.nix
··· 1 + { lib, config, pkgs, ... } @ args: 2 + 3 + with lib; 4 + let 5 + isEnabled = config.modules.games.enable && config.modules.games.palworld.enable; 6 + baseCfg = config.modules.games; 7 + cfg = config.modules.games.palworld; 8 + 9 + name = "palworld-server"; 10 + serverScripts = (import ./lib/serverScripts.nix) args; 11 + mkWrappedBox64 = (import ./lib/mkWrappedBox64.nix) args; 12 + mkSteamPackage = (import ./lib/mkSteamPackage.nix) args; 13 + inherit ((import ./lib/steamworks.nix) args) steamworks-sdk-redist; 14 + 15 + generateSettings = name: value: let 16 + optionSettings = 17 + mapAttrsToList 18 + (optName: optVal: let 19 + optType = builtins.typeOf optVal; 20 + encodedVal = 21 + if optType == "string" 22 + then "\"${optVal}\"" 23 + else if optType == "bool" 24 + then 25 + if optVal 26 + then "True" 27 + else "False" 28 + else toString optVal; 29 + in "${optName}=${encodedVal}") 30 + value; 31 + in 32 + builtins.toFile name '' 33 + [/Script/Pal.PalGameWorldSettings] 34 + OptionSettings=(${concatStringsSep "," optionSettings}) 35 + ''; 36 + 37 + wrappedBox64 = mkWrappedBox64 { 38 + libs = [ pkgs.pkgsCross.gnu64.libgcc ]; 39 + }; 40 + 41 + palworld-server = mkSteamPackage { 42 + name = "palworld-server"; 43 + version = "17082920"; 44 + appId = "2394010"; 45 + depotId = "2394012"; 46 + manifestId = "2423583208459052375"; 47 + hash = "sha256-gAFEDf/rKPQ5zTH8EJ93e4KKHUGi8uiYlPS7G2lWGWk="; 48 + meta = { 49 + description = "Palworld Dedicated Server"; 50 + homepage = "https://steamdb.info/app/2394010/"; 51 + changelog = "https://store.steampowered.com/news/app/1623730?updates=true"; 52 + sourceProvenance = with sourceTypes; [ sourceTypes.binaryNativeCode ]; 53 + }; 54 + }; 55 + 56 + baseSettings = { 57 + ServerName = "London Boroughs"; 58 + AllowConnectPlatform = "Xbox"; 59 + CoopPlayerMaxNum = cfg.maxPlayers; 60 + bIsUseBackupSaveData = true; 61 + RCONEnabled = false; 62 + RESTAPIEnabled = false; 63 + }; 64 + in 65 + { 66 + options.modules.games.palworld = { 67 + enable = mkOption { 68 + default = false; 69 + description = "Whether to enable Palworld Dedicated Server."; 70 + type = types.bool; 71 + }; 72 + 73 + package = mkOption { 74 + type = types.package; 75 + default = palworld-server; 76 + }; 77 + 78 + autostart = mkOption { 79 + default = false; 80 + type = types.bool; 81 + }; 82 + 83 + datadir = mkOption { 84 + type = types.path; 85 + default = "${baseCfg.datadir}/palworld"; 86 + }; 87 + 88 + ip = mkOption { 89 + type = types.nullOr types.str; 90 + default = "0.0.0.0"; 91 + }; 92 + 93 + port = mkOption { 94 + type = types.port; 95 + default = 8211; 96 + }; 97 + 98 + threads = mkOption { 99 + type = types.int; 100 + default = 4; 101 + }; 102 + 103 + maxPlayers = mkOption { 104 + type = types.int; 105 + default = 6; 106 + }; 107 + 108 + settings = mkOption { 109 + type = types.attrs; 110 + default = { 111 + PublicPort = 8211; 112 + PublicIP = cfg.ip; 113 + AllowConnectPlatform = "Xbox"; 114 + }; 115 + }; 116 + }; 117 + 118 + config = mkIf isEnabled { 119 + modules.router.nftables.capturePorts = [ cfg.port ]; 120 + networking.firewall.allowedUDPPorts = [ cfg.port ]; 121 + 122 + systemd.tmpfiles.rules = [ 123 + "d ${cfg.datadir} 0755 ${baseCfg.user} ${baseCfg.group} - -" 124 + ]; 125 + 126 + systemd.services."${name}" = let 127 + dirs = { 128 + Pal = "${cfg.package}/Pal"; 129 + Engine = "${cfg.package}/Engine"; 130 + }; 131 + 132 + files = let 133 + settings = baseSettings // cfg.settings; 134 + in { 135 + "Pal/Binaries/Linux/steamclient.so" = "${steamworks-sdk-redist}/lib/steamclient.so"; 136 + "Pal/Saved/Config/LinuxServer/PalWorldSettings.ini" = generateSettings "PalWorldSettings.ini" settings; 137 + }; 138 + 139 + script = let 140 + args = [ 141 + "Pal" 142 + "-port=${toString cfg.port}" 143 + "-useperfthreads" 144 + "-NoAsyncLoadingThread" 145 + "-UseMultithreadForDS" 146 + "-players=${toString cfg.maxPlayers}" 147 + "-NumberOfWorkerThreadsServer=${toString cfg.threads}" 148 + ] ++ optionals (cfg.ip != null) [ "-publicip=${cfg.ip}" ]; 149 + executable = "${cfg.datadir}/Pal/Binaries/Linux/PalServer-Linux-Shipping"; 150 + command = "${wrappedBox64}/bin/box64 ${executable}"; 151 + in "${command} ${concatStringsSep " " args}"; 152 + in { 153 + wantedBy = mkIf cfg.autostart [ "multi-user.target" ]; 154 + after = [ "network.target" ]; 155 + path = with pkgs; [ xdg-user-dirs util-linux ]; 156 + 157 + inherit script; 158 + preStart = '' 159 + ${serverScripts.mkDirs name dirs} 160 + ${serverScripts.mkFiles name files} 161 + ''; 162 + 163 + serviceConfig = { 164 + Restart = "on-failure"; 165 + User = "${baseCfg.user}"; 166 + Group = "${baseCfg.group}"; 167 + WorkingDirectory = "${cfg.datadir}"; 168 + 169 + CPUWeight = 80; 170 + CPUQuota = "${toString ((cfg.threads + 1) * 100)}%"; 171 + 172 + PrivateDevices = true; 173 + PrivateTmp = true; 174 + PrivateUsers = true; 175 + ProtectClock = true; 176 + ProtectProc = "noaccess"; 177 + ProtectKernelLogs = true; 178 + ProtectKernelModules = true; 179 + ProtectKernelTunables = true; 180 + RestrictRealtime = true; 181 + LockPersonality = true; 182 + 183 + # Palworld needs namespaces and system calls 184 + RestrictNamespaces = false; 185 + SystemCallFilter = []; 186 + }; 187 + }; 188 + }; 189 + }