Personal Nix setup

Merge remote-tracking branch 'origin/palworld'

+19 -4
flake.nix
···
};
};
-
outputs = inputs: let
inherit (inputs.nixpkgs) lib;
inherit (import ./lib/system.nix inputs) mkSystem;
eachSystem = lib.genAttrs ["aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux"];
overlays = [
inputs.lix-module.overlays.default
inputs.nvim-plugins.overlays.default
inputs.android-sdk.overlays.default
···
inputs.system-shell.overlays.default
(self: super: {
zen-browser = inputs.zen-browser.packages.${self.system}.beta;
-
} // (import ./lib/pkgs self))
];
in {
darwinConfigurations."sprite" = mkSystem {
···
hostname = "sodacream";
};
-
packages = eachSystem (system: {
inherit (inputs.agenix.packages.${system}) agenix;
inherit (inputs.darwin.packages.${system}) darwin-rebuild;
-
} // (import ./lib/pkgs inputs.nixpkgs.legacyPackages.${system}));
apps = eachSystem (system: import ./lib/apps {
inherit lib;
···
};
};
+
outputs = inputs @ { self, ... }: let
inherit (inputs.nixpkgs) lib;
inherit (import ./lib/system.nix inputs) mkSystem;
eachSystem = lib.genAttrs ["aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux"];
overlays = [
+
self.overlays.default
inputs.lix-module.overlays.default
inputs.nvim-plugins.overlays.default
inputs.android-sdk.overlays.default
···
inputs.system-shell.overlays.default
(self: super: {
zen-browser = inputs.zen-browser.packages.${self.system}.beta;
+
})
];
in {
darwinConfigurations."sprite" = mkSystem {
···
hostname = "sodacream";
};
+
overlays = {
+
default = import ./lib/pkgs;
+
};
+
+
packages = eachSystem (system: let
+
pkgs = import inputs.nixpkgs {
+
inherit system;
+
overlays = [ self.overlays.default ];
+
};
+
in {
inherit (inputs.agenix.packages.${system}) agenix;
inherit (inputs.darwin.packages.${system}) darwin-rebuild;
+
} // {
+
inherit (pkgs)
+
steamworks-sdk-redist
+
systemd-transparent-udp-forwarderd
+
force-bind;
+
});
apps = eachSystem (system: import ./lib/apps {
inherit lib;
+12 -4
lib/pkgs/default.nix
···
-
pkgs: {
-
sf-pro = import ./sf-pro.nix pkgs;
-
sf-mono = import ./sf-mono.nix pkgs;
-
new-york = import ./new-york.nix pkgs;
}
···
+
self: super: {
+
sf-pro = import ./sf-pro.nix super;
+
sf-mono = import ./sf-mono.nix super;
+
new-york = import ./new-york.nix super;
+
+
fetchSteam = import ./fetch-steam.nix self super;
+
mkSteamPackage = import ./mk-steam-package.nix self super;
+
mkSteamWrapper = import ./mk-steam-wrapper.nix self super;
+
systemd-transparent-udp-forwarderd = import ./systemd-transparent-udp-forwarderd.nix self super;
+
force-bind = import ./force-bind-seccomp.nix self super;
+
steamworks-sdk-redist = import ./steamworks-sdk-redist.nix self super;
+
palworld-server = import ./palworld-server.nix self super;
}
+60
lib/pkgs/fetch-steam.nix
···
···
+
self: pkgs @ {
+
lib,
+
runCommand,
+
depotdownloader,
+
cacert,
+
...
+
}:
+
+
with lib;
+
makeOverridable (
+
{
+
name ? "steamapp-${appId}-${depotId}-${manifestId}",
+
appId,
+
depotId,
+
manifestId,
+
hash ? "",
+
branch ? null,
+
fileList ? null,
+
debug ? false,
+
passthru ? { },
+
meta ? { },
+
} @ args:
+
let
+
fileListArg =
+
if isList fileList then
+
builtins.toFile "steam-files-list.txt" (concatLines fileList)
+
else
+
fileList;
+
+
downloadArgs =
+
[
+
"-app" appId
+
"-depot" depotId
+
"-manifest" manifestId
+
]
+
++ optionals (branch != null) [ "-beta" branch ]
+
++ optionals (fileList != null) [ "-filelist" fileListArg ]
+
++ optionals debug [ "-debug" ];
+
+
drvArgs = {
+
depsBuildBuild = [ depotdownloader ];
+
+
strictDeps = true;
+
+
outputHashAlgo = "sha256";
+
outputHashMode = "recursive";
+
outputHash = if hash != "" then hash else fakeHash;
+
+
env.SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
+
+
pos = builtins.unsafeGetAttrPos "manifestId" args;
+
+
inherit passthru;
+
} // optionalAttrs (args ? meta) { inherit meta; };
+
in
+
runCommand name drvArgs ''
+
HOME=$PWD DepotDownloader -dir "$out" ${escapeShellArgs downloadArgs}
+
rm -r "$out"/.DepotDownloader
+
''
+
)
+55
lib/pkgs/force-bind-seccomp.nix
···
···
+
self: pkgs @ {
+
stdenv,
+
autoPatchelfHook,
+
fetchFromGitHub,
+
writeText,
+
...
+
}:
+
+
let
+
makefile = writeText "Makefile" ''
+
TARGETS = force-bind target-mkdir target-bind parent-socket-activate
+
+
all: $(TARGETS)
+
+
force-bind: main.c scm_functions.c
+
$(CC) $(CFLAGS) -o $@ $^
+
+
parent-socket-activate: parent_soocket_activate.c
+
$(CC) $(CFLAGS) -o $@ $^
+
+
target-mkdir: target_mkdir.c
+
$(CC) $(CFLAGS) -o $@ $^
+
+
target-bind: target_bind.c
+
$(CC) $(CFLAGS) -o $@ $^
+
+
.PHONY: all
+
'';
+
in stdenv.mkDerivation rec {
+
pname = "force-bind";
+
version = "0.0.1-4867c53";
+
+
nativeBuildInputs = [ autoPatchelfHook ];
+
buildInputs = with self; [ stdenv.cc.cc.lib stdenv.cc.libc.linuxHeaders ];
+
buildPhase = "make";
+
+
src = fetchFromGitHub {
+
owner = "kitten";
+
repo = "force-bind-seccomp";
+
rev = "0df29fbbe20f5c191c3b76951af090ab60d533e8";
+
sha256 = "sha256-SWdPacxJ2WmB+8b8uVpxrnlLuH3wAvFIDyfBclh0a/4=";
+
};
+
postPatch = ''
+
cp ${makefile} Makefile;
+
'';
+
installPhase = ''
+
runHook preInstall
+
install -Dm755 force-bind "$out/bin/$pname"
+
install -Dm755 target-bind "$out/bin/$pname-test-target-bind"
+
install -Dm755 parent-socket-activate "$out/bin/$pname-test-socket-activate"
+
runHook postInstall
+
'';
+
+
meta.mainProgram = pname;
+
}
+56
lib/pkgs/mk-steam-package.nix
···
···
+
self: pkgs @ {
+
lib,
+
stdenv,
+
autoPatchelfHook,
+
...
+
}:
+
+
with lib;
+
let
+
inherit (stdenv.hostPlatform) isAarch64;
+
in
+
{
+
name,
+
hash ? "",
+
version,
+
appId,
+
depotId,
+
manifestId,
+
...
+
} @ args: let
+
derivationArgs = builtins.removeAttrs args [ "name" "appId" "depotId" "manifestId" "hash" ];
+
in
+
stdenv.mkDerivation (rec {
+
pname = name;
+
src = self.fetchSteam {
+
inherit name appId depotId manifestId hash;
+
};
+
+
dontBuild = true;
+
dontConfigure = true;
+
dontFixup = isAarch64;
+
+
nativeBuildInputs = optionals (!isAarch64) [ autoPatchelfHook ];
+
appendRunpaths = with self; makeLibraryPath [
+
steamworks-sdk-redist
+
glibc
+
libxcrypt
+
libGL
+
libdrm
+
mesa # for libgbm
+
udev
+
libudev0-shim
+
libva
+
vulkan-loader
+
];
+
+
installPhase = ''
+
runHook preInstall
+
+
mkdir -p $out
+
mv ./* $out
+
chmod 755 -R $out
+
+
runHook postInstall
+
'';
+
} // derivationArgs)
+69
lib/pkgs/mk-steam-wrapper.nix
···
···
+
self: pkgs @ {
+
lib,
+
box64,
+
makeWrapper,
+
pkgsCross,
+
stdenv,
+
...
+
}:
+
+
with lib;
+
let
+
useBox64 = stdenv.hostPlatform.isAarch64;
+
+
defaultLibs = with self; [
+
steamworks-sdk-redist
+
glibc
+
libxcrypt
+
libGL
+
libdrm
+
mesa # for libgbm
+
udev
+
libudev0-shim
+
libva
+
vulkan-loader
+
];
+
+
defaultNativeLibs = optionals useBox64 [ pkgsCross.gnu64.libgcc ];
+
+
mkSteamWrapper = makeOverridable (
+
{
+
logLevel ? 0,
+
env ? {},
+
libs ? defaultLibs,
+
nativeLibs ? defaultNativeLibs,
+
extraWrapperArgs ? [],
+
}: let
+
runpaths = libs ++ optionals useBox64 nativeLibs;
+
combinedEnv = optionalAttrs useBox64 {
+
BOX64_LOG = logLevel;
+
BOX64_DYNAREC_STRONGMEM = 0;
+
} // env;
+
in bin:
+
stdenv.mkDerivation rec {
+
name = "box64-wrapper";
+
meta.mainProgram = name;
+
+
dontUnpack = true;
+
dontConfigure = true;
+
dontBuild = true;
+
+
nativeBuildInputs = [ makeWrapper ];
+
buildInputs = runpaths;
+
+
installPhase = let
+
outBin = if useBox64 then "${box64}/bin/box64" else bin;
+
targetBinFlag = if useBox64 then "--add-flags ${escapeShellArg bin}" else "";
+
in ''
+
runHook preInstall
+
makeWrapper "${outBin}" "$out/bin/${name}" \
+
${concatStrings (mapAttrsToList (name: value: "--set ${name} '${toString value}' ") combinedEnv)} \
+
--prefix LD_LIBRARY_PATH : ${makeLibraryPath runpaths} \
+
${targetBinFlag} \
+
${lib.strings.concatStringsSep " " extraWrapperArgs}
+
runHook postInstall
+
'';
+
}
+
);
+
in
+
mkSteamWrapper { }
+20
lib/pkgs/palworld-server.nix
···
···
+
self: {
+
lib,
+
...
+
}:
+
+
with lib;
+
self.mkSteamPackage {
+
name = "palworld-server";
+
version = "17403768";
+
appId = "2394010";
+
depotId = "2394012";
+
manifestId = "1367771460964183113";
+
hash = "sha256-L/lRfVmc3tkMiPSPJSg6uum0/DJB97dkuPzOA3LtDEw=";
+
meta = {
+
description = "Palworld Dedicated Server";
+
homepage = "https://steamdb.info/app/2394010/";
+
changelog = "https://store.steampowered.com/news/app/1623730?updates=true";
+
sourceProvenance = with sourceTypes; [ sourceTypes.binaryNativeCode ];
+
};
+
}
+42
lib/pkgs/steamworks-sdk-redist.nix
···
···
+
self: pkgs @ {
+
lib,
+
stdenv,
+
...
+
}:
+
+
with lib;
+
stdenv.mkDerivation {
+
pname = "steamworks-sdk-redist";
+
version = "unstable-2024-05-30";
+
+
# Steamworks SDK Redist with steamclient.so.
+
# https://steamdb.info/app/1007/depots
+
src = self.fetchSteam {
+
appId = "1007";
+
depotId = "1006";
+
manifestId = "7138471031118904166";
+
hash = "sha256-OtPI1kAx6+9G09IEr2kYchyvxlPl3rzx/ai/xEVG4oM=";
+
};
+
+
dontConfigure = true;
+
dontBuild = true;
+
+
installPhase = ''
+
runHook preInstall
+
+
mkdir -p $out/lib
+
cp linux64/steamclient.so $out/lib
+
chmod +x $out/lib/steamclient.so
+
+
runHook postInstall
+
'';
+
+
meta = {
+
description = "Steamworks SDK Redist";
+
sourceProvenance = [ sourceTypes.binaryNativeCode ];
+
license = licenses.unfree;
+
badPlatforms = [
+
{ hasSharedLibraries = false; }
+
];
+
};
+
}
+29
lib/pkgs/systemd-transparent-udp-forwarderd.nix
···
···
+
self: pkgs @ {
+
stdenv,
+
cmake,
+
pkg-config,
+
fetchFromGitHub,
+
systemdLibs,
+
...
+
}:
+
+
stdenv.mkDerivation rec {
+
pname = "systemd-transparent-udp-forwarderd";
+
version = "0.0.1-add-activity-timeout-shutdown";
+
nativeBuildInputs = [ cmake pkg-config ];
+
buildInputs = [ systemdLibs ];
+
src = fetchFromGitHub {
+
owner = "cecton";
+
repo = "systemd-transparent-udp-forwarderd";
+
rev = "69072ef1271f013cefa91c239455d197f68d3f8e";
+
sha256 = "sha256-c4pui1aHw35Jw1pjCBgLQTHrjt5nYI2IUD4FqkyEd/M=";
+
};
+
+
installPhase = ''
+
runHook preInstall
+
install -Dm755 systemd-transparent-udp-forwarderd "$out/bin/$pname"
+
runHook postInstall
+
'';
+
+
meta.mainProgram = pname;
+
}
+17
machines/ramune/configuration.nix
···
caddy.enable = true;
vaultwarden.enable = true;
};
};
system.stateVersion = "24.11";
···
caddy.enable = true;
vaultwarden.enable = true;
};
+
games = {
+
enable = true;
+
palworld = {
+
enable = true;
+
public = true;
+
ip = "51.38.68.193";
+
settings = {
+
ServerName = "London Boroughs";
+
AllowConnectPlatform = "Xbox";
+
PalEggDefaultHatchingTime = 1;
+
GuildPlayerMaxNum = 10;
+
bShowPlayerList = true;
+
bEnableNonLoginPenalty = false;
+
bUseAuth = false;
+
};
+
};
+
};
};
system.stateVersion = "24.11";
+1
modules/default.nix
···
./base
./apps
./desktop
./nvim
./router
./server
···
./base
./apps
./desktop
+
./games
./nvim
./router
./server
+54
modules/games/default.nix
···
···
+
{ lib, helpers, config, ... }:
+
+
with lib; let
+
cfg = config.modules.games;
+
in {
+
options.modules.games = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable game server options.";
+
type = types.bool;
+
};
+
+
datadir = mkOption {
+
type = types.path;
+
default = "/var/lib/games";
+
description = "Base directory for all game servers created with this module.";
+
example = "/mnt/nfs/steam";
+
};
+
+
user = mkOption {
+
type = types.str;
+
default = "games";
+
description = "User to use when running game servers and creating top-level resources";
+
};
+
+
group = mkOption {
+
type = types.str;
+
default = cfg.user;
+
defaultText = literalExpression "\${cfg.user}";
+
description = "Group to use when running game servers";
+
};
+
};
+
} // helpers.linuxAttrs {
+
config = mkIf cfg.enable {
+
users.users."${cfg.user}" = {
+
home = "${cfg.datadir}";
+
group = cfg.group;
+
isSystemUser = true;
+
createHome = true;
+
homeMode = "750";
+
};
+
+
users.groups."${cfg.group}" = {};
+
+
systemd.tmpfiles.rules = [
+
"d ${cfg.datadir}/.steam 0755 ${cfg.user} ${cfg.group} - -"
+
];
+
};
+
+
imports = [
+
./palworld
+
];
+
}
+73
modules/games/lib/scripts.nix
···
···
+
{ lib, pkgs, ... }:
+
+
with lib;
+
{
+
mkSymlinks = name: symlinks:
+
pkgs.writeShellScript "${name}-symlinks"
+
(concatStringsSep "\n"
+
(mapAttrsToList
+
(n: v: ''
+
if [[ -L "${n}" ]]; then
+
unlink "${n}"
+
elif [[ -e "${n}" ]]; then
+
echo "${n} already exists, moving"
+
mv "${n}" "${n}.bak"
+
fi
+
mkdir -p "$(dirname "${n}")"
+
ln -sf "${v}" "${n}"
+
'')
+
symlinks));
+
+
mkFiles = name: files:
+
pkgs.writeShellScript "${name}-files"
+
(concatStringsSep "\n"
+
(mapAttrsToList
+
(n: v: ''
+
if [[ -L "${n}" ]]; then
+
unlink "${n}"
+
elif ${pkgs.diffutils}/bin/cmp -s "${n}" "${v}"; then
+
rm "${n}"
+
elif [[ -e "${n}" ]]; then
+
echo "${n} already exists, moving"
+
mv "${n}" "${n}.bak"
+
fi
+
mkdir -p $(dirname "${n}")
+
${pkgs.gawk}/bin/awk '{
+
for(varname in ENVIRON)
+
gsub("@"varname"@", ENVIRON[varname])
+
print
+
}' "${v}" > "${n}"
+
chmod --reference="${v}" "${n}"
+
'')
+
files));
+
+
mkDirs = name: dirs:
+
pkgs.writeShellScript "${name}-dirs"
+
(concatStringsSep "\n"
+
(mapAttrsToList
+
(n: v: ''
+
if [[ -L "${n}" ]]; then
+
unlink "${n}"
+
elif [[ ! -d "${n}" ]]; then
+
echo "${n} already exists and isn't a directory, moving"
+
mv "${n}" "${n}.bak"
+
fi
+
${pkgs.rsync}/bin/rsync -avu "${v}/" "${n}"
+
chmod -R u+w "${n}"
+
'')
+
dirs));
+
+
rmSymlinks = name: symlinks:
+
pkgs.writeShellScript "${name}-rm-symlinks"
+
(
+
concatStringsSep "\n"
+
(mapAttrsToList (n: _v: "unlink \"${n}\"") symlinks)
+
);
+
+
rmFiles = name: files:
+
pkgs.writeShellScript "${name}-rm-symlinks"
+
(
+
concatStringsSep "\n"
+
(mapAttrsToList (n: _v: "rm \"${n}\"") files)
+
);
+
}
+86
modules/games/palworld/Engine.ini
···
···
+
[Core.System]
+
Paths=../../../Engine/Content
+
Paths=%GAMEDIR%Content
+
Paths=../../../Engine/Plugins/2D/Paper2D/Content
+
Paths=../../../Engine/Plugins/Animation/ControlRigSpline/Content
+
Paths=../../../Engine/Plugins/Animation/ControlRig/Content
+
Paths=../../../Engine/Plugins/Animation/IKRig/Content
+
Paths=../../../Engine/Plugins/Animation/MotionWarping/Content
+
Paths=../../../Engine/Plugins/Bridge/Content
+
Paths=../../../Engine/Plugins/Compositing/Composure/Content
+
Paths=../../../Engine/Plugins/Compositing/OpenColorIO/Content
+
Paths=../../../Engine/Plugins/Developer/AnimationSharing/Content
+
Paths=../../../Engine/Plugins/Developer/Concert/ConcertSync/ConcertSyncClient/Content
+
Paths=../../../Engine/Plugins/Editor/BlueprintHeaderView/Content
+
Paths=../../../Engine/Plugins/Editor/GeometryMode/Content
+
Paths=../../../Engine/Plugins/Editor/ModelingToolsEditorMode/Content
+
Paths=../../../Engine/Plugins/Editor/ObjectMixer/LightMixer/Content
+
Paths=../../../Engine/Plugins/Editor/ObjectMixer/ObjectMixer/Content
+
Paths=../../../Engine/Plugins/Editor/SpeedTreeImporter/Content
+
Paths=../../../Engine/Plugins/Enterprise/DatasmithContent/Content
+
Paths=../../../Engine/Plugins/Enterprise/GLTFExporter/Content
+
Paths=../../../Engine/Plugins/Experimental/ChaosCaching/Content
+
Paths=../../../Engine/Plugins/Experimental/ChaosClothEditor/Content
+
Paths=../../../Engine/Plugins/Experimental/ChaosNiagara/Content
+
Paths=../../../Engine/Plugins/Experimental/ChaosSolverPlugin/Content
+
Paths=../../../Engine/Plugins/Experimental/CommonUI/Content
+
Paths=../../../Engine/Plugins/Experimental/Dataflow/Content
+
Paths=../../../Engine/Plugins/Experimental/FullBodyIK/Content
+
Paths=../../../Engine/Plugins/Experimental/GeometryCollectionPlugin/Content
+
Paths=../../../Engine/Plugins/Experimental/GeometryFlow/Content
+
Paths=../../../Engine/Plugins/Experimental/ImpostorBaker/Content
+
Paths=../../../Engine/Plugins/Experimental/Landmass/Content
+
Paths=../../../Engine/Plugins/Experimental/MeshLODToolset/Content
+
Paths=../../../Engine/Plugins/Experimental/PythonScriptPlugin/Content
+
Paths=../../../Engine/Plugins/Experimental/StaticMeshEditorModeling/Content
+
Paths=../../../Engine/Plugins/Experimental/UVEditor/Content
+
Paths=../../../Engine/Plugins/Experimental/Volumetrics/Content
+
Paths=../../../Engine/Plugins/Experimental/Water/Content
+
Paths=../../../Engine/Plugins/FX/Niagara/Content
+
Paths=../../../Engine/Plugins/JsonBlueprintUtilities/Content
+
Paths=../../../Engine/Plugins/Media/MediaCompositing/Content
+
Paths=../../../Engine/Plugins/Media/MediaPlate/Content
+
Paths=../../../Engine/Plugins/MovieScene/SequencerScripting/Content
+
Paths=../../../Engine/Plugins/PivotTool/Content
+
Paths=../../../Engine/Plugins/PlacementTools/Content
+
Paths=../../../Engine/Plugins/Runtime/AudioSynesthesia/Content
+
Paths=../../../Engine/Plugins/Runtime/AudioWidgets/Content
+
Paths=../../../Engine/Plugins/Runtime/GeometryProcessing/Content
+
Paths=../../../Engine/Plugins/Runtime/Metasound/Content
+
Paths=../../../Engine/Plugins/Runtime/ResonanceAudio/Content
+
Paths=../../../Engine/Plugins/Runtime/SunPosition/Content
+
Paths=../../../Engine/Plugins/Runtime/Synthesis/Content
+
Paths=../../../Engine/Plugins/Runtime/WaveTable/Content
+
Paths=../../../Engine/Plugins/Runtime/WebBrowserWidget/Content
+
Paths=../../../Engine/Plugins/SkyCreatorPlugin/Content
+
Paths=../../../Engine/Plugins/VirtualProduction/CameraCalibrationCore/Content
+
Paths=../../../Engine/Plugins/VirtualProduction/LiveLinkCamera/Content
+
Paths=../../../Engine/Plugins/VirtualProduction/Takes/Content
+
Paths=../../../Engine/Plugins/Web/HttpBlueprint/Content
+
Paths=../../../Pal/Plugins/DLSS/Content
+
Paths=../../../Pal/Plugins/EffectsChecker/Content
+
Paths=../../../Pal/Plugins/HoudiniEngine/Content
+
Paths=../../../Pal/Plugins/PPSkyCreatorPlugin/Content
+
Paths=../../../Pal/Plugins/PocketpairUser/Content
+
Paths=../../../Pal/Plugins/SpreadSheetToCsv/Content
+
Paths=../../../Pal/Plugins/Wwise/Content
+
+
[/script/onlinesubsystemutils.ipnetdriver]
+
LanServerMaxTickRate=60
+
NetServerMaxTickRate=60
+
+
[/script/engine.player]
+
ConfiguredInternetSpeed=104857600
+
ConfiguredLanSpeed=104857600
+
+
[/script/socketsubsystemepic.epicnetdriver]
+
MaxClientRate=104857600
+
MaxInternetClientRate=104857600
+
+
[/script/engine.engine]
+
bSmoothFrameRate=true
+
SmoothedFrameRateRange=(LowerBound=(Type=Inclusive,Value=30.000000),UpperBound=(Type=Exclusive,Value=60.000000))
+
bUseFixedFrameRate=false
+
FixedFrameRate=60
+
MinDesiredFrameRate=30
+
NetClientTicksPerSecond=60
+196
modules/games/palworld/default.nix
···
···
+
{ lib, config, pkgs, ... } @ args:
+
+
with lib;
+
let
+
isEnabled = config.modules.games.enable && config.modules.games.palworld.enable;
+
baseCfg = config.modules.games;
+
cfg = config.modules.games.palworld;
+
port = toString cfg.port;
+
+
name = "palworld-server";
+
scripts = (import ../lib/scripts.nix) args;
+
+
generateSettings = 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 toString optVal;
+
in "${optName}=${encodedVal}")
+
value;
+
in
+
builtins.toFile name ''
+
[/Script/Pal.PalGameWorldSettings]
+
OptionSettings=(${concatStringsSep "," optionSettings})
+
'';
+
+
baseSettings = {
+
ServerName = "Unnamed Server";
+
AllowConnectPlatform = "Steam";
+
CoopPlayerMaxNum = cfg.maxPlayers;
+
bIsUseBackupSaveData = true;
+
RCONEnabled = false;
+
RESTAPIEnabled = false;
+
};
+
in
+
{
+
options.modules.games.palworld = {
+
enable = mkOption {
+
default = false;
+
description = "Whether to enable Palworld Dedicated Server.";
+
type = types.bool;
+
};
+
+
package = mkOption {
+
type = types.package;
+
default = pkgs.palworld-server;
+
};
+
+
public = mkOption {
+
type = types.bool;
+
default = false;
+
description = "Whether to enable Community Server mode";
+
};
+
+
datadir = mkOption {
+
type = types.path;
+
default = "${baseCfg.datadir}/palworld";
+
};
+
+
ip = mkOption {
+
type = types.nullOr types.str;
+
default = "0.0.0.0";
+
};
+
+
port = mkOption {
+
type = types.port;
+
default = 8211;
+
};
+
+
threads = mkOption {
+
type = types.int;
+
default = 5;
+
};
+
+
maxPlayers = mkOption {
+
type = types.int;
+
default = 6;
+
};
+
+
settings = mkOption {
+
type = types.attrs;
+
default = { };
+
};
+
};
+
+
config = mkIf isEnabled {
+
age.secrets."palworld-passwd.raw" = {
+
file = ./encrypt/palworld-passwd.age;
+
group = "${baseCfg.group}";
+
owner = "${baseCfg.user}";
+
mode = "770";
+
};
+
+
modules.router.nftables.capturePorts = [ cfg.port ];
+
networking.firewall.allowedUDPPorts = [ cfg.port ];
+
+
systemd.tmpfiles.rules = [
+
"d ${cfg.datadir} 0755 ${baseCfg.user} ${baseCfg.group} - -"
+
];
+
+
systemd.sockets."${name}" = {
+
wantedBy = [ "sockets.target" ];
+
partOf = [ "${name}.service" ];
+
listenDatagrams = [ "0.0.0.0:${port}" ];
+
socketConfig = {
+
SocketUser = "${baseCfg.user}";
+
SocketGroup = "${baseCfg.group}";
+
};
+
};
+
+
systemd.services."${name}" = let
+
dirs = {
+
Pal = "${cfg.package}/Pal";
+
Engine = "${cfg.package}/Engine";
+
};
+
+
files = let
+
settings = baseSettings // cfg.settings // {
+
ServerPassword = "@SERVER_PASSWORD@";
+
};
+
in {
+
"Pal/Binaries/Linux/steamclient.so" = "${pkgs.steamworks-sdk-redist}/lib/steamclient.so";
+
"Pal/Saved/Config/LinuxServer/PalWorldSettings.ini" = generateSettings "PalWorldSettings.ini" settings;
+
"Pal/Saved/Config/LinuxServer/Engine.ini" = ./Engine.ini;
+
};
+
+
script = let
+
args = [
+
"Pal"
+
"-port=${port}"
+
"-publicport=${port}"
+
"-useperfthreads"
+
"-NoAsyncLoadingThread"
+
"-UseMultithreadForDS"
+
"-players=${toString cfg.maxPlayers}"
+
"-NumberOfWorkerThreadsServer=${toString cfg.threads}"
+
]
+
++ optionals (cfg.ip != null) [ "-publicip=${cfg.ip}" ]
+
++ optionals cfg.public [ "-publiclobby" ];
+
bin = getExe (pkgs.mkSteamWrapper "${cfg.datadir}/Pal/Binaries/Linux/PalServer-Linux-Shipping");
+
forceBind = "${getExe pkgs.force-bind} -m '0.0.0.0:${port}=sd=0'";
+
in "${forceBind} ${bin} ${concatStringsSep " " args}";
+
in {
+
after = [ "network-online.target" ];
+
wants = [ "network-online.target" ];
+
path = with pkgs; [ xdg-user-dirs util-linux ];
+
+
inherit script;
+
preStart = let
+
passwordFile = config.age.secrets."palworld-passwd.raw".path;
+
in ''
+
export SERVER_PASSWORD=$(cat "${passwordFile}")
+
${scripts.mkDirs name dirs}
+
${scripts.mkFiles name files}
+
'';
+
+
serviceConfig = {
+
Restart = "on-failure";
+
User = "${baseCfg.user}";
+
Group = "${baseCfg.group}";
+
WorkingDirectory = "${cfg.datadir}";
+
+
CPUWeight = 90;
+
CPUQuota = "${toString ((cfg.threads + 1) * 100)}%";
+
+
/*
+
PrivateDevices = true;
+
PrivateTmp = true;
+
PrivateUsers = true;
+
ProtectClock = true;
+
ProtectProc = "noaccess";
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
RestrictRealtime = true;
+
*/
+
+
# force-bind needs to stay unlocked and needs to be able to ptrace
+
LockPersonality = false;
+
CapabilityBoundingSet = [ "CAP_SYS_PTRACE" ];
+
+
# Palworld needs namespaces and system calls
+
RestrictNamespaces = false;
+
SystemCallFilter = [];
+
};
+
};
+
};
+
}
+5
modules/games/palworld/encrypt/palworld-passwd.age
···
···
+
age-encryption.org/v1
+
-> ssh-ed25519 QwbpPw QN+UCu72rNSGKgnZpCiAgMQGx7NgnCAvRH+7PHb01F0
+
VxjDtBg5oFfHLcvDEzJ+gU/MVaqJKhHPVkBxlA+TMkU
+
--- JQbohsuqLyCVe+muSKagiWVdfrk8XO2tIjGq/U4GtoM
+
����ϹX���E���߁�a������uɛ���������
-1
modules/router/kernel.nix
···
"kernel.sysrq" = 4;
"kernel.unprivileged_bpf_disabled" = true;
"kernel.perf_event_paranoid" = 3;
-
"kernel.yama.ptrace_scope" = 2;
"kernel.kexec_load_disabled" = true;
"net.core.bpf_jit_harden" = 2;
"dev.tty.ldisc_autoload" = false;
···
"kernel.sysrq" = 4;
"kernel.unprivileged_bpf_disabled" = true;
"kernel.perf_event_paranoid" = 3;
"kernel.kexec_load_disabled" = true;
"net.core.bpf_jit_harden" = 2;
"dev.tty.ldisc_autoload" = false;
+2
secrets.nix
···
"./modules/automation/certs/mqtt.key.age".publicKeys = keys;
"./modules/automation/certs/mqtt.crt.age".publicKeys = keys;
}
···
"./modules/automation/certs/mqtt.key.age".publicKeys = keys;
"./modules/automation/certs/mqtt.crt.age".publicKeys = keys;
+
+
"./modules/games/palworld/encrypt/palworld-passwd.age".publicKeys = keys;
}