5.3 KiB
SteamCMD servers
Use this workflow to add a server that SteamCMD installs. Complete each stage before you start the next stage.
Stage 1: Prove the SteamCMD runner
- Add the server to the host with
modules.services.steamcmd. - Set the Steam app ID.
- Set the SteamCMD directory.
- Set the server install directory.
- Set the server state directory.
- Set the server executable path.
- Set the game arguments.
- Set the documented game ports.
- Evaluate the host configuration.
- Deploy the host.
- Start the service on the host.
- Read the service logs.
- Wait for the game-specific ready log.
- Connect from the game client.
Keep this stage small. Use it only to validate SteamCMD, game arguments, ports, and state paths.
Do not pin the game server version in Nix. SteamCMD mutates the installed server files.
Stage 2: Create the game module
- Create
configs/nixos/modules/services/games/<game>.nix. - Define options under
modules.services.games.<game>. - Expose only deployment-specific values.
- Keep host paths in the host configuration.
- Keep secret selection in the host configuration.
- Keep network placement outside the game module.
- Move the Steam app ID into the game module.
- Move the executable path into the game module.
- Move game arguments into the game module.
- Move documented ports into the game module.
- Wire the game module to
modules.services.steamcmd. - Replace the direct host
modules.services.steamcmdconfig. - Enable
modules.services.games.<game>on the host. - Stage the new module file.
- Evaluate the host configuration.
- Deploy the host.
- Validate the service again on the host.
The game module owns game behavior. The host owns deployment values.
Stage 3: Add UDP ingress
- Read the game's official server port documentation.
- Add the UDP ports to
registry.route.<host>.<name>.udp.ports. - Keep
registry.route.<host>.<name>.domainset. - Point
registry.address.<host>.<name>at the backend address. - Evaluate the reverse-proxy host configuration.
- Deploy the reverse-proxy host.
- Verify nginx listens on the UDP ports.
- Configure public DNS outside Nix.
- Configure LAN DNS outside Nix.
- Configure router forwarding outside Nix.
- Test the public route from the game client.
- Test the backend address directly when ingress fails.
Use the route domain for identity only. Nginx stream routing uses destination ports for UDP traffic. It does not route UDP by hostname.
Stage 4: Move the server into a guest
- Create
configs/nixos/guests/<game>.nix. - Add the guest module to
src/dotfiles/guests.nix. - Wrap
modules.services.games.<game>inside the guest. - Bind-mount the host SteamCMD directory into the guest.
- Bind-mount the host install directory into the guest.
- Bind-mount the host state directory into the guest.
- Use guest-local paths for the game module.
- Add
guests.<name>to the host configuration. - Set the guest VLAN.
- Set the guest address.
- Enable reverse proxying when the server needs ingress.
- Put the UDP route under
guests.<name>.reverseProxy.route. - Evaluate the host configuration.
- Deploy the host.
- Validate the container service.
- Validate the game service inside the guest.
- Validate reverse-proxy UDP listeners.
- Connect from the game client.
The guest machinery publishes guests.<name>.address to registry.address.<host>.<name>.
It publishes guests.<name>.reverseProxy.route to registry.route.<host>.<name>.
Secrets
- Use SOPS for game server passwords.
- Use
services/game-servers/passwordfor a shared game-server password. - Add a per-game key only when the game needs a different password.
- Decrypt the secret on the host.
- Bind-mount the decrypted secret into the guest.
- Pass the password file to the game module.
- Read the password file at service start.
- Do not put plaintext passwords in host configuration.
Edit shared secrets with the host key:
cd configs/nixos
sops-host secrets/shared.yaml
Validation commands
Evaluate the host:
nix eval .#nixosConfigurations.<host>.config.system.build.toplevel.drvPath --raw
Deploy the host:
deploy-to <host>
Check the host secret:
sudo test -s /run/secrets/services/game-servers/password && echo secret-ok
Check the guest:
sudo systemctl status container@<guest>.service --no-pager
Check the guest secret:
sudo nixos-container run <guest> -- test -s /run/secrets/services/game-servers/password
Check the game service:
sudo nixos-container run <guest> -- systemctl status <game>.service --no-pager
sudo nixos-container run <guest> -- journalctl -u <game>.service -n 200 --no-pager
Check game UDP listeners:
sudo nixos-container run <guest> -- ss -lunp | grep -E '<ports>|<game>'
Check reverse-proxy UDP listeners:
sudo nixos-container run reverse-proxy -- ss -lunp | grep -E '<ports>|nginx'
Operational checks
- Watch the first server start closely.
- Expect some games to initialize state on first start.
- Wait for the game-specific ready log before client testing.
- Check SteamCMD logs when updates stall.
- Check directory ownership when SteamCMD reports write failures.
The SteamCMD module repairs ownership recursively for managed directories.