feat(nixos): add Palworld Tracion guest
This commit was merged in pull request #39.
This commit is contained in:
146
configs/nixos/guests/palworld.nix
Normal file
146
configs/nixos/guests/palworld.nix
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.guests.palworld;
|
||||||
|
steamDir = "/data/steamcmd";
|
||||||
|
installDir = "/data/server";
|
||||||
|
stateDir = "/data/state";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.guests.palworld = {
|
||||||
|
steamPath = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "/srv/steamcmd";
|
||||||
|
description = "Host path bind-mounted as SteamCMD's metadata directory.";
|
||||||
|
};
|
||||||
|
|
||||||
|
installPath = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "/srv/palworld/server";
|
||||||
|
description = "Host path bind-mounted as Palworld's server install directory.";
|
||||||
|
};
|
||||||
|
|
||||||
|
statePath = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "/srv/palworld/state";
|
||||||
|
description = "Host path bind-mounted as Palworld's durable state directory.";
|
||||||
|
};
|
||||||
|
|
||||||
|
serverName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "palworld";
|
||||||
|
description = "Name shown for the Palworld server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 8211;
|
||||||
|
description = "UDP port for the Palworld server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
publicIP = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Public IP address advertised by the Palworld server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordSecret = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "services/game-servers/password";
|
||||||
|
description = "SOPS secret name containing the Palworld server password.";
|
||||||
|
};
|
||||||
|
|
||||||
|
adminPasswordSecret = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "services/game-servers/password";
|
||||||
|
description = "SOPS secret name containing the Palworld admin password.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.steamPath != null;
|
||||||
|
message = "guests.palworld.steamPath must point at the host directory that stores SteamCMD metadata.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = cfg.installPath != null;
|
||||||
|
message = "guests.palworld.installPath must point at the host directory that stores Palworld server files.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = cfg.statePath != null;
|
||||||
|
message = "guests.palworld.statePath must point at the host directory that stores Palworld durable state.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
guests.palworld = {
|
||||||
|
interior.modules.services.games.palworld = {
|
||||||
|
enable = true;
|
||||||
|
inherit (cfg) serverName port publicIP;
|
||||||
|
inherit steamDir installDir stateDir;
|
||||||
|
passwordFile = "/run/secrets/${cfg.passwordSecret}";
|
||||||
|
adminPasswordFile = "/run/secrets/${cfg.adminPasswordSecret}";
|
||||||
|
};
|
||||||
|
|
||||||
|
mounts =
|
||||||
|
{
|
||||||
|
${steamDir} = {
|
||||||
|
directory = cfg.steamPath;
|
||||||
|
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||||
|
group = "storage";
|
||||||
|
mode = "0770";
|
||||||
|
};
|
||||||
|
${installDir} = {
|
||||||
|
directory = cfg.installPath;
|
||||||
|
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||||
|
group = "storage";
|
||||||
|
mode = "0770";
|
||||||
|
};
|
||||||
|
${stateDir} = {
|
||||||
|
directory = cfg.statePath;
|
||||||
|
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||||
|
group = "storage";
|
||||||
|
mode = "0770";
|
||||||
|
};
|
||||||
|
${"/run/secrets/${cfg.passwordSecret}"} = {
|
||||||
|
file = config.sops.secrets.${cfg.passwordSecret}.path;
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (cfg.adminPasswordSecret != cfg.passwordSecret) {
|
||||||
|
${"/run/secrets/${cfg.adminPasswordSecret}"} = {
|
||||||
|
file = config.sops.secrets.${cfg.adminPasswordSecret}.path;
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
limits = {
|
||||||
|
memory = lib.mkDefault "8G";
|
||||||
|
cpu = lib.mkDefault "400%";
|
||||||
|
tasksMax = lib.mkDefault 2048;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets =
|
||||||
|
{
|
||||||
|
${cfg.passwordSecret} = {
|
||||||
|
owner = "root";
|
||||||
|
group = "storage";
|
||||||
|
mode = "0440";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (cfg.adminPasswordSecret != cfg.passwordSecret) {
|
||||||
|
${cfg.adminPasswordSecret} = {
|
||||||
|
owner = "root";
|
||||||
|
group = "storage";
|
||||||
|
mode = "0440";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -86,6 +86,25 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
guests.palworld = {
|
||||||
|
enable = true;
|
||||||
|
vlan = 20;
|
||||||
|
address = "10.23.20.250";
|
||||||
|
steamPath = "/pikachu/data/steamcmd";
|
||||||
|
installPath = "/pikachu/data/servers/palworld/tracion";
|
||||||
|
statePath = "/pikachu/media/games/servers/palworld/tracion";
|
||||||
|
serverName = "Tracion";
|
||||||
|
adminPasswordSecret = "services/game-servers/admin/password";
|
||||||
|
publicIP = config.guests.palworld.address;
|
||||||
|
reverseProxy = {
|
||||||
|
enable = true;
|
||||||
|
route = {
|
||||||
|
domain = "palworld.alexion.dev";
|
||||||
|
udp.ports = [ config.guests.palworld.port ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
guests.actualbudget = {
|
guests.actualbudget = {
|
||||||
enable = true;
|
enable = true;
|
||||||
vlan = 20;
|
vlan = 20;
|
||||||
|
|||||||
148
configs/nixos/modules/services/games/palworld.nix
Normal file
148
configs/nixos/modules/services/games/palworld.nix
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
options.modules.services.games.palworld = {
|
||||||
|
enable = lib.mkEnableOption "Palworld dedicated server";
|
||||||
|
|
||||||
|
steamDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "/data/steamcmd";
|
||||||
|
description = "Directory where SteamCMD stores metadata and cache files.";
|
||||||
|
};
|
||||||
|
|
||||||
|
installDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "/data/servers/palworld/tracion";
|
||||||
|
description = "Directory where SteamCMD installs the Palworld server files.";
|
||||||
|
};
|
||||||
|
|
||||||
|
stateDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "/data/state/palworld/tracion";
|
||||||
|
description = "Directory where Palworld stores durable server state.";
|
||||||
|
};
|
||||||
|
|
||||||
|
serverName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "palworld";
|
||||||
|
description = "Name shown for the Palworld server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 8211;
|
||||||
|
description = "UDP port for the Palworld server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
publicIP = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Public IP address advertised by the Palworld server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
maxPlayers = lib.mkOption {
|
||||||
|
type = lib.types.ints.between 1 32;
|
||||||
|
default = 32;
|
||||||
|
description = "Maximum player count for the Palworld server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
publicLobby = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Advertise the server in Palworld's public lobby.";
|
||||||
|
};
|
||||||
|
|
||||||
|
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.";
|
||||||
|
};
|
||||||
|
|
||||||
|
adminPasswordFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = "Runtime file containing the Palworld admin password.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf config.modules.services.games.palworld.enable (
|
||||||
|
let
|
||||||
|
service = config.modules.services.games.palworld;
|
||||||
|
savedDir = "${service.installDir}/Pal/Saved";
|
||||||
|
startScript = pkgs.writeShellScript "palworld-start" ''
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/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})"
|
||||||
|
if [ "$current_target" != ${lib.escapeShellArg service.stateDir} ]; then
|
||||||
|
${pkgs.coreutils}/bin/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}
|
||||||
|
else
|
||||||
|
${pkgs.coreutils}/bin/cp -a ${lib.escapeShellArg savedDir}/. ${lib.escapeShellArg service.stateDir}/
|
||||||
|
${pkgs.coreutils}/bin/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}
|
||||||
|
fi
|
||||||
|
|
||||||
|
password="$(${pkgs.coreutils}/bin/tr -d '\n' < ${lib.escapeShellArg service.passwordFile})"
|
||||||
|
admin_password="$(${pkgs.coreutils}/bin/tr -d '\n' < ${lib.escapeShellArg service.adminPasswordFile})"
|
||||||
|
|
||||||
|
exec ./PalServer.sh \
|
||||||
|
"$@" \
|
||||||
|
"-serverpassword=$password" \
|
||||||
|
"-adminpassword=$admin_password"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
modules.services.steamcmd = {
|
||||||
|
enable = true;
|
||||||
|
inherit (service) steamDir installDir stateDir;
|
||||||
|
serviceName = "palworld-${lib.toLower service.serverName}";
|
||||||
|
appId = 2394010;
|
||||||
|
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";
|
||||||
|
udpPorts = [ service.port ];
|
||||||
|
tcpPorts = [ service.port ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -59,6 +59,12 @@
|
|||||||
default = [ ];
|
default = [ ];
|
||||||
description = "UDP ports opened for the game service.";
|
description = "UDP ports opened for the game service.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tcpPorts = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.port;
|
||||||
|
default = [ ];
|
||||||
|
description = "TCP ports opened for the game service.";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkMerge [
|
config = lib.mkMerge [
|
||||||
@@ -159,6 +165,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
networking.firewall.allowedUDPPorts = service.udpPorts;
|
networking.firewall.allowedUDPPorts = service.udpPorts;
|
||||||
|
networking.firewall.allowedTCPPorts = service.tcpPorts;
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -18,13 +18,6 @@ or begin
|
|||||||
return 2
|
return 2
|
||||||
end
|
end
|
||||||
|
|
||||||
set untracked_lines (git status --porcelain --untracked-files=normal | string match -r '^\?\? ')
|
|
||||||
if test (count $untracked_lines) -gt 0
|
|
||||||
echo "deploy-to: refusing to deploy untracked files" >&2
|
|
||||||
echo "deploy-to: git add new files that Nix must see, or remove them" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
git fetch origin
|
git fetch origin
|
||||||
or return $status
|
or return $status
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ services:
|
|||||||
password: ENC[AES256_GCM,data:SSyo2l6tCjgjxIuCEREIxU+inw2WHtJJR0/IEKUqefFfuAdA5Nf+lH/jfPw=,iv:9225w1Yur3KL3dBr1ct1aPvDdVJ94O8q9qAUiRrj8Nc=,tag:qZgx4XieVq4BcxkTZc0KGQ==,type:str]
|
password: ENC[AES256_GCM,data:SSyo2l6tCjgjxIuCEREIxU+inw2WHtJJR0/IEKUqefFfuAdA5Nf+lH/jfPw=,iv:9225w1Yur3KL3dBr1ct1aPvDdVJ94O8q9qAUiRrj8Nc=,tag:qZgx4XieVq4BcxkTZc0KGQ==,type:str]
|
||||||
game-servers:
|
game-servers:
|
||||||
password: ENC[AES256_GCM,data:Wtq03MLRnc6/,iv:HhbiP8ZcltVGbCNDdP/gH5mCZXhVCTFv1DGzfrrgQG8=,tag:srsqcBQSD/HU8zFsuFimJQ==,type:str]
|
password: ENC[AES256_GCM,data:Wtq03MLRnc6/,iv:HhbiP8ZcltVGbCNDdP/gH5mCZXhVCTFv1DGzfrrgQG8=,tag:srsqcBQSD/HU8zFsuFimJQ==,type:str]
|
||||||
|
admin:
|
||||||
|
password: ENC[AES256_GCM,data:52jwSCfV,iv:MQf1VOHnGBf2yF+9SXk04FniAjghSaZe2Gn/xvAXU/0=,tag:SnrJFlHwOaM6vsNLBc84nw==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
@@ -44,7 +46,7 @@ sops:
|
|||||||
28uWUpKzzgErq1aEbKNgh4cU5TH2RlAHjtzRC3ARUu9hlW6lnB4pSw==
|
28uWUpKzzgErq1aEbKNgh4cU5TH2RlAHjtzRC3ARUu9hlW6lnB4pSw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1wf5s0n0tgt6ld2ysgu9dc67mj8ylwecgl4utzg7hqwy3kut9zyms7aglmh
|
recipient: age1wf5s0n0tgt6ld2ysgu9dc67mj8ylwecgl4utzg7hqwy3kut9zyms7aglmh
|
||||||
lastmodified: "2026-09-10T13:40:57Z"
|
lastmodified: "2026-09-10T18:32:33Z"
|
||||||
mac: ENC[AES256_GCM,data:3nu+ho3TNDnGE55LIaHqJxlMs1hWVK2wwiJiBIDH0jop9Wo/AgaUXzwXY1MwMXJHhGrkcjhwWWJdEgxX88NNFTc59b4kLtRqE3bfNXoQ3y3giyDMFyVE9YCNd4jyl+pLhpVSZA/+TKEIBpdZRwaXHrYSaLtysg/OdzXing0l3SM=,iv:C/vZ6X1HikuBn+pWTlPIfCfg5mtW2i4P6XLwN1VwF6k=,tag:UQl10fueTwCtXuirP5+C/g==,type:str]
|
mac: ENC[AES256_GCM,data:g3xIylSvyn5Z0JBA8siFmMrZxoM3a3U5c+f9qFmb3zXDLF03/feCUlvG2jjB7P2N9phIGSIEOPk1dzZDBMwv6D2hK6MppupV+MIe9diIt0gGARIOJNphrxLDxMHtwcWg6Z20D6tbPl0ECtLI0K9GOtwvI0OXeyYzu5VgFaFlZHw=,iv:TAUVJRXFTKNVA0ORMD7x56WVGjG+zimj3iqEn7urh64=,tag:SeOOV9ZUY4x5Ns0A16F1uw==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.13.3
|
version: 3.13.3
|
||||||
|
|||||||
29
docs/.obsidian/workspace.json
vendored
29
docs/.obsidian/workspace.json
vendored
@@ -22,6 +22,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "72e6501450a46859",
|
||||||
|
"type": "tabs",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "06bc3d52246b600b",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "markdown",
|
||||||
|
"state": {
|
||||||
|
"file": "00 INBOX/ai/steamcmd servers.md",
|
||||||
|
"mode": "source",
|
||||||
|
"source": true
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "steamcmd servers"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "vertical"
|
"direction": "vertical"
|
||||||
@@ -233,13 +253,16 @@
|
|||||||
},
|
},
|
||||||
"active": "0c587e5452c16787",
|
"active": "0c587e5452c16787",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
|
"00 INBOX/how to setup a steamcmd server.md",
|
||||||
|
"00 INBOX/ai/steamcmd servers.md",
|
||||||
|
"03 wrk/ariadne.md",
|
||||||
|
"00 INBOX/ai/ariadne-cli-skeleton.md",
|
||||||
"00 INBOX/ai/ariadne-project-goals.md",
|
"00 INBOX/ai/ariadne-project-goals.md",
|
||||||
|
"TODO.md",
|
||||||
"00 INBOX/ai/dictionary-cli.md",
|
"00 INBOX/ai/dictionary-cli.md",
|
||||||
"00 INBOX/ai/dictionary-skill.md",
|
"00 INBOX/ai/dictionary-skill.md",
|
||||||
"03 wrk/falinks.md",
|
"03 wrk/falinks.md",
|
||||||
"03 wrk/ariadne.md",
|
|
||||||
"03 wrk/machoke.md",
|
"03 wrk/machoke.md",
|
||||||
"TODO.md",
|
|
||||||
"00 INBOX/home assistant agent integration.md",
|
"00 INBOX/home assistant agent integration.md",
|
||||||
"02 notes/falinks public surfaces.md",
|
"02 notes/falinks public surfaces.md",
|
||||||
"03 wrk/machoke.md~",
|
"03 wrk/machoke.md~",
|
||||||
@@ -261,8 +284,6 @@
|
|||||||
"00 INBOX/ai/hoopa design review findings.md",
|
"00 INBOX/ai/hoopa design review findings.md",
|
||||||
"00 INBOX/ai/hoopa follow up decisions.md",
|
"00 INBOX/ai/hoopa follow up decisions.md",
|
||||||
"01 sources/obsidian and ai were made for each other.md",
|
"01 sources/obsidian and ai were made for each other.md",
|
||||||
"99 meta/technical writing.md",
|
|
||||||
"99 meta/dictionary.md",
|
|
||||||
"99 meta/Untitled",
|
"99 meta/Untitled",
|
||||||
"03 wrk",
|
"03 wrk",
|
||||||
"02 notes",
|
"02 notes",
|
||||||
|
|||||||
125
docs/00 INBOX/how to setup a steamcmd server.md
Normal file
125
docs/00 INBOX/how to setup a steamcmd server.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
date-created: 2026-09-10 12:01
|
||||||
|
---
|
||||||
|
|
||||||
|
# How to set up a `steamcmd` server
|
||||||
|
|
||||||
|
This note explains how to add a SteamCMD-managed game server to the NixOS configuration.
|
||||||
|
|
||||||
|
Use these existing files as examples:
|
||||||
|
|
||||||
|
- `configs/nixos/modules/services/steamcmd.nix`
|
||||||
|
- `configs/nixos/modules/services/games/valheim.nix`
|
||||||
|
- `configs/nixos/guests/valheim.nix`
|
||||||
|
- `configs/nixos/hosts/pikachu/default.nix`
|
||||||
|
|
||||||
|
## Understand the layers
|
||||||
|
|
||||||
|
The shared `steamcmd` module installs and runs a dedicated server.
|
||||||
|
It owns the low-level systemd service, update service, user, directories, and firewall ports.
|
||||||
|
|
||||||
|
A game module maps game-specific options onto `modules.services.steamcmd`.
|
||||||
|
It hides Steam app IDs, executable names, launch arguments, and port calculations.
|
||||||
|
|
||||||
|
A guest module maps host-level storage, secrets, and resource limits onto the game module.
|
||||||
|
It keeps host paths outside the guest's interior configuration.
|
||||||
|
|
||||||
|
A host enables the guest and supplies network placement, storage paths, and routing.
|
||||||
|
|
||||||
|
## Create the game module
|
||||||
|
|
||||||
|
1. Create `configs/nixos/modules/services/games/<game>.nix`.
|
||||||
|
2. Add an option namespace at `modules.services.games.<game>`.
|
||||||
|
3. Add an `enable` option with `lib.mkEnableOption`.
|
||||||
|
4. Add options for game-specific settings.
|
||||||
|
5. Add options for `steamDir`, `installDir`, and `stateDir`.
|
||||||
|
6. Use `lib.mkIf config.modules.services.games.<game>.enable` for the module configuration.
|
||||||
|
7. Set `modules.services.steamcmd.enable = true`.
|
||||||
|
8. Pass `steamDir`, `installDir`, and `stateDir` to `modules.services.steamcmd`.
|
||||||
|
9. Set `serviceName` to the game service name.
|
||||||
|
10. Set `appId` to the Steam dedicated-server app ID.
|
||||||
|
11. Set `executable` to the server executable or a generated wrapper script.
|
||||||
|
12. Set `arguments` to the game launch arguments.
|
||||||
|
13. Set `udpPorts` to every UDP port the server needs.
|
||||||
|
14. Put secrets in runtime files instead of option values.
|
||||||
|
15. Read secret files from a wrapper script when the server needs secret values.
|
||||||
|
|
||||||
|
Use `configs/nixos/modules/services/games/valheim.nix` as the main pattern.
|
||||||
|
It wraps the executable because Valheim receives its password as a command-line argument.
|
||||||
|
|
||||||
|
## Create the guest module
|
||||||
|
|
||||||
|
1. Create `configs/nixos/guests/<game>.nix`.
|
||||||
|
2. Add an option namespace at `guests.<game>`.
|
||||||
|
3. Add host-path options for the SteamCMD metadata, install, and state directories.
|
||||||
|
4. Give host-path options a `null` default.
|
||||||
|
5. Add assertions that require each host path when the guest is enabled.
|
||||||
|
6. Define guest-internal paths in a `let` block.
|
||||||
|
7. Enable `guests.<game>.interior.modules.services.games.<game>`.
|
||||||
|
8. Pass the guest-internal paths to the game module.
|
||||||
|
9. Add mounts that bind host paths to the guest-internal paths.
|
||||||
|
10. Set directory mount `owner`, `group`, and `mode` together.
|
||||||
|
11. Mount secret files under `/run/secrets/...` when the game needs secrets.
|
||||||
|
12. Declare matching `sops.secrets` entries for those secret files.
|
||||||
|
13. Set conservative default resource limits under `guests.<game>.limits`.
|
||||||
|
|
||||||
|
Use `configs/nixos/guests/valheim.nix` as the main pattern.
|
||||||
|
It mounts the password secret into the guest and passes only the runtime path inward.
|
||||||
|
|
||||||
|
## Enable the guest on a host
|
||||||
|
|
||||||
|
1. Open the target host file under `configs/nixos/hosts/<host>/default.nix`.
|
||||||
|
2. Set `guests.<game>.enable = true`.
|
||||||
|
3. Set `vlan` and `address` for the guest.
|
||||||
|
4. Set host storage paths for SteamCMD metadata, server files, and state.
|
||||||
|
5. Set game options such as server name and port.
|
||||||
|
6. Enable reverse-proxy routing when the server needs external access.
|
||||||
|
7. Route every game UDP port exposed by the guest.
|
||||||
|
8. Add required secrets to the host's SOPS files.
|
||||||
|
|
||||||
|
Use the Valheim section in `configs/nixos/hosts/pikachu/default.nix` as the host-level pattern.
|
||||||
|
|
||||||
|
## Test the change
|
||||||
|
|
||||||
|
1. Stage new Nix files before evaluation.
|
||||||
|
Flake evaluation ignores untracked files.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ git add configs/nixos/modules/services/games/<game>.nix configs/nixos/guests/<game>.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build the target host configuration.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ nixos-rebuild build --flake .#<host>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Deploy the host when the build succeeds.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ sudo nixos-rebuild switch --flake .#<host>
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Enter the guest from the host.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ sudo machinectl shell <game>
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Check the update service inside the guest.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ systemctl status steamcmd-<game>-update.service
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Check the game service inside the guest.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ systemctl status <game>.service
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Inspect the logs when either service fails.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ journalctl -u steamcmd-<game>-update.service -u <game>.service
|
||||||
|
```
|
||||||
55
docs/TODO.md
55
docs/TODO.md
@@ -45,7 +45,7 @@
|
|||||||
- subagents
|
- subagents
|
||||||
- test-driven-development
|
- test-driven-development
|
||||||
|
|
||||||
## pikachu proxmox -> nixos
|
## pikachu + pichu proxmox -> nixos
|
||||||
|
|
||||||
- lidarr (requires raichu)
|
- lidarr (requires raichu)
|
||||||
- immich
|
- immich
|
||||||
@@ -57,16 +57,60 @@
|
|||||||
- adguard
|
- adguard
|
||||||
- homepage
|
- homepage
|
||||||
|
|
||||||
|
## raichu proxmox -> nixos
|
||||||
|
- dwn torrent (vpn)
|
||||||
|
- dwn usenet (vpn)
|
||||||
|
- dwn-torrent-novpn
|
||||||
|
- prowlarr
|
||||||
|
- flaresolverr
|
||||||
|
- radarr
|
||||||
|
- sonarr
|
||||||
|
- radarr-anime
|
||||||
|
- sonarr-anime
|
||||||
|
- configarr
|
||||||
|
- lidarr
|
||||||
|
- audiobookshelf
|
||||||
|
- portainer
|
||||||
|
- seerr
|
||||||
|
- plex
|
||||||
|
- pterodactyl
|
||||||
|
- palworld old
|
||||||
|
- project zomboid
|
||||||
|
- minecraft
|
||||||
|
- cobblemon
|
||||||
|
|
||||||
# learning
|
# learning
|
||||||
- https://www.youtube.com/@EeroAlvar/videos
|
- YouTube
|
||||||
|
- EeroAlvar
|
||||||
|
- https://www.youtube.com/watch?v=DzhLFiblPUk
|
||||||
|
- https://www.youtube.com/watch?v=hQGAwD9vkLo
|
||||||
|
- https://www.youtube.com/watch?v=hhtExS5UQBI
|
||||||
|
- https://www.youtube.com/watch?v=Y1xO9OsCBC4
|
||||||
|
- https://www.youtube.com/watch?v=DWWrLlM3gwQ
|
||||||
|
- https://www.youtube.com/watch?v=yHL-gmkzC-4
|
||||||
|
- https://www.youtube.com/watch?v=KRVYUkM16hE
|
||||||
|
- https://www.youtube.com/watch?v=RxdvzenLcl4
|
||||||
|
- https://www.youtube.com/watch?v=mdbpIpPSPwQ
|
||||||
|
- https://www.youtube.com/watch?v=GHsq0klC_4g
|
||||||
|
- https://www.youtube.com/watch?v=ciC6ffUqI8k
|
||||||
|
- https://www.youtube.com/watch?v=gYxZt9Qe0fk
|
||||||
|
- https://www.youtube.com/watch?v=0tqty8ltKDA
|
||||||
|
- https://www.youtube.com/watch?v=kzcI5F4tGiU
|
||||||
|
- https://www.youtube.com/watch?v=iKwPaB5TUdI
|
||||||
|
- https://www.youtube.com/watch?v=A-CtdQFRX64
|
||||||
|
|
||||||
# tech debt
|
# tech debt
|
||||||
|
|
||||||
|
- better remote deployment
|
||||||
|
|
||||||
## nix refactor
|
## nix refactor
|
||||||
```text
|
```text
|
||||||
|
nixos
|
||||||
├── guests
|
├── guests
|
||||||
│ ├── actualbudget.nix
|
│ ├── actualbudget.nix
|
||||||
│ ├── copyparty.nix
|
│ ├── copyparty.nix
|
||||||
│ └── reverse-proxy.nix
|
│ ├── reverse-proxy.nix
|
||||||
|
│ └── valheim.nix
|
||||||
├── hosts
|
├── hosts
|
||||||
│ ├── neogaia
|
│ ├── neogaia
|
||||||
│ │ ├── default.nix
|
│ │ ├── default.nix
|
||||||
@@ -120,9 +164,12 @@
|
|||||||
│ │ ├── vlans.nix
|
│ │ ├── vlans.nix
|
||||||
│ │ └── vpn-client.nix
|
│ │ └── vpn-client.nix
|
||||||
│ ├── services
|
│ ├── services
|
||||||
|
│ │ ├── games
|
||||||
|
│ │ │ └── valheim.nix
|
||||||
│ │ ├── actualbudget.nix
|
│ │ ├── actualbudget.nix
|
||||||
│ │ ├── copyparty.nix
|
│ │ ├── copyparty.nix
|
||||||
│ │ └── reverse-proxy.nix
|
│ │ ├── reverse-proxy.nix
|
||||||
|
│ │ └── steamcmd.nix
|
||||||
│ ├── shell
|
│ ├── shell
|
||||||
│ │ ├── direnv
|
│ │ ├── direnv
|
||||||
│ │ │ ├── default.nix
|
│ │ │ ├── default.nix
|
||||||
|
|||||||
Reference in New Issue
Block a user