fix(nixos): address Nix review findings

Render Palworld secrets through its documented settings file and remove stale RCON configuration.

Harden generated commands, centralize shared modules, and codify the reviewed Nix conventions.
This commit is contained in:
2026-10-13 21:43:50 -04:00
parent 8462b3a5a8
commit 66efae302f
13 changed files with 215 additions and 94 deletions

View File

@@ -62,18 +62,6 @@
description = "Start the Palworld server after the guest reaches network readiness.";
};
rcon = {
enable = lib.mkEnableOption "Palworld RCON" // {
default = true;
};
port = lib.mkOption {
type = lib.types.port;
default = 25575;
description = "TCP port for Palworld RCON.";
};
};
passwordFile = lib.mkOption {
type = lib.types.path;
description = "Runtime file containing the Palworld server password.";
@@ -89,65 +77,120 @@
let
service = config.modules.services.games.palworld;
savedDir = "${service.installDir}/Pal/Saved";
defaultSettingsFile = "${service.installDir}/DefaultPalWorldSettings.ini";
settingsFile = "${savedDir}/Config/LinuxServer/PalWorldSettings.ini";
renderSettingsScript = pkgs.writeText "render-palworld-settings.py" ''
import json
import os
from pathlib import Path
import re
import sys
import tempfile
default_path, settings_path, server_name, password_path, admin_password_path = sys.argv[1:]
default_path = Path(default_path)
settings_path = Path(settings_path)
source_path = settings_path if settings_path.exists() else default_path
text = source_path.read_text(encoding="utf-8")
def read_secret(path):
return Path(path).read_text(encoding="utf-8").rstrip("\r\n")
def set_string_setting(text, name, value):
assignment = f"{name}={json.dumps(value, ensure_ascii=False)}"
pattern = re.compile(rf'(?<![A-Za-z0-9_]){re.escape(name)}="(?:[^"\\]|\\.)*"')
text, replacements = pattern.subn(lambda _match: assignment, text, count=1)
if replacements:
return text
marker = "OptionSettings=("
if marker not in text:
raise ValueError(f"{source_path} has no OptionSettings declaration")
return text.replace(marker, f"{marker}{assignment},", 1)
settings = {
"ServerName": server_name,
"ServerPassword": read_secret(password_path),
"AdminPassword": read_secret(admin_password_path),
}
for name, value in settings.items():
text = set_string_setting(text, name, value)
settings_path.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary_name = tempfile.mkstemp(
prefix=f".{settings_path.name}.",
dir=settings_path.parent,
)
temporary_path = Path(temporary_name)
try:
os.fchmod(descriptor, 0o600)
with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as settings_file:
settings_file.write(text)
os.replace(temporary_path, settings_path)
finally:
temporary_path.unlink(missing_ok=True)
'';
startScript = pkgs.writeShellScript "palworld-start" ''
set -eu
${pkgs.coreutils}/bin/install -d \
${lib.getExe' pkgs.coreutils "install"} -d \
-m 0770 \
-- \
${lib.escapeShellArg service.stateDir} \
${lib.escapeShellArg (builtins.dirOf savedDir)}
if [ -L ${lib.escapeShellArg savedDir} ]; then
current_target="$(${pkgs.coreutils}/bin/readlink ${lib.escapeShellArg savedDir})"
current_target="$(${lib.getExe' pkgs.coreutils "readlink"} -- ${lib.escapeShellArg savedDir})"
if [ "$current_target" != ${lib.escapeShellArg service.stateDir} ]; then
${pkgs.coreutils}/bin/rm ${lib.escapeShellArg savedDir}
${lib.getExe' pkgs.coreutils "rm"} -- ${lib.escapeShellArg savedDir}
fi
fi
if [ -d ${lib.escapeShellArg savedDir} ] && [ ! -L ${lib.escapeShellArg savedDir} ]; then
if ${pkgs.findutils}/bin/find ${lib.escapeShellArg service.stateDir} -mindepth 1 -maxdepth 1 | ${pkgs.gnugrep}/bin/grep -q .; then
${pkgs.coreutils}/bin/rm -rf ${lib.escapeShellArg savedDir}
if ${lib.getExe' pkgs.findutils "find"} -- ${lib.escapeShellArg service.stateDir} -mindepth 1 -maxdepth 1 | ${lib.getExe pkgs.gnugrep} -q .; then
${lib.getExe' pkgs.coreutils "rm"} -rf -- ${lib.escapeShellArg savedDir}
else
${pkgs.coreutils}/bin/cp -a ${lib.escapeShellArg savedDir}/. ${lib.escapeShellArg service.stateDir}/
${pkgs.coreutils}/bin/rm -rf ${lib.escapeShellArg savedDir}
${lib.getExe' pkgs.coreutils "cp"} -a -- ${lib.escapeShellArg savedDir}/. ${lib.escapeShellArg service.stateDir}/
${lib.getExe' pkgs.coreutils "rm"} -rf -- ${lib.escapeShellArg savedDir}
fi
fi
if [ ! -e ${lib.escapeShellArg savedDir} ]; then
${pkgs.coreutils}/bin/ln -s ${lib.escapeShellArg service.stateDir} ${lib.escapeShellArg savedDir}
${lib.getExe' pkgs.coreutils "ln"} -s -- ${lib.escapeShellArg service.stateDir} ${lib.escapeShellArg savedDir}
fi
password="$(${pkgs.coreutils}/bin/tr -d '\n' < ${lib.escapeShellArg service.passwordFile})"
admin_password="$(${pkgs.coreutils}/bin/tr -d '\n' < ${lib.escapeShellArg service.adminPasswordFile})"
${lib.getExe pkgs.python3} \
${renderSettingsScript} \
${lib.escapeShellArg defaultSettingsFile} \
${lib.escapeShellArg settingsFile} \
${lib.escapeShellArg service.serverName} \
${lib.escapeShellArg service.passwordFile} \
${lib.escapeShellArg service.adminPasswordFile}
exec ./PalServer.sh \
"$@" \
"-serverpassword=$password" \
"-adminpassword=$admin_password"
exec ./PalServer.sh "$@"
'';
in
{
modules.services.steamcmd = {
enable = true;
inherit (service) steamDir installDir stateDir;
serviceName = "palworld-${lib.toLower service.serverName}";
serviceName = "palworld";
appId = 2394010;
inherit (service) autoStart;
executable = "${startScript}";
arguments = [
"-useperfthreads"
"-NoAsyncLoadingThread"
"-UseMultithreadForDS"
"-port=${toString service.port}"
"-publicport=${toString service.port}"
"-servername=${service.serverName}"
"-players=${toString service.maxPlayers}"
]
++ lib.optional service.publicLobby "-publiclobby"
++ lib.optionals (service.publicIP != null) [ "-publicip=${service.publicIP}" ]
++ lib.optional service.rcon.enable "-rcon";
++ lib.optionals service.publicLobby (
[
"-publiclobby"
"-publicport=${toString service.port}"
]
++ lib.optionals (service.publicIP != null) [ "-publicip=${service.publicIP}" ]
);
udpPorts = [ service.port ];
tcpPorts = [ service.port ];
};
}
);