Files
dev/docs/00 INBOX/ai/steamcmd servers.md

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

  1. Add the server to the host with modules.services.steamcmd.
  2. Set the Steam app ID.
  3. Set the SteamCMD directory.
  4. Set the server install directory.
  5. Set the server state directory.
  6. Set the server executable path.
  7. Set the game arguments.
  8. Set the documented game ports.
  9. Evaluate the host configuration.
  10. Deploy the host.
  11. Start the service on the host.
  12. Read the service logs.
  13. Wait for the game-specific ready log.
  14. 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

  1. Create configs/nixos/modules/services/games/<game>.nix.
  2. Define options under modules.services.games.<game>.
  3. Expose only deployment-specific values.
  4. Keep host paths in the host configuration.
  5. Keep secret selection in the host configuration.
  6. Keep network placement outside the game module.
  7. Move the Steam app ID into the game module.
  8. Move the executable path into the game module.
  9. Move game arguments into the game module.
  10. Move documented ports into the game module.
  11. Wire the game module to modules.services.steamcmd.
  12. Replace the direct host modules.services.steamcmd config.
  13. Enable modules.services.games.<game> on the host.
  14. Stage the new module file.
  15. Evaluate the host configuration.
  16. Deploy the host.
  17. Validate the service again on the host.

The game module owns game behavior. The host owns deployment values.

Stage 3: Add UDP ingress

  1. Read the game's official server port documentation.
  2. Add the UDP ports to registry.route.<host>.<name>.udp.ports.
  3. Keep registry.route.<host>.<name>.domain set.
  4. Point registry.address.<host>.<name> at the backend address.
  5. Evaluate the reverse-proxy host configuration.
  6. Deploy the reverse-proxy host.
  7. Verify nginx listens on the UDP ports.
  8. Configure public DNS outside Nix.
  9. Configure LAN DNS outside Nix.
  10. Configure router forwarding outside Nix.
  11. Test the public route from the game client.
  12. 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

  1. Create configs/nixos/guests/<game>.nix.
  2. Add the guest module to src/dotfiles/guests.nix.
  3. Wrap modules.services.games.<game> inside the guest.
  4. Bind-mount the host SteamCMD directory into the guest.
  5. Bind-mount the host install directory into the guest.
  6. Bind-mount the host state directory into the guest.
  7. Use guest-local paths for the game module.
  8. Add guests.<name> to the host configuration.
  9. Set the guest VLAN.
  10. Set the guest address.
  11. Enable reverse proxying when the server needs ingress.
  12. Put the UDP route under guests.<name>.reverseProxy.route.
  13. Evaluate the host configuration.
  14. Deploy the host.
  15. Validate the container service.
  16. Validate the game service inside the guest.
  17. Validate reverse-proxy UDP listeners.
  18. 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

  1. Use SOPS for game server passwords.
  2. Use services/game-servers/password for a shared game-server password.
  3. Add a per-game key only when the game needs a different password.
  4. Decrypt the secret on the host.
  5. Bind-mount the decrypted secret into the guest.
  6. Pass the password file to the game module.
  7. Read the password file at service start.
  8. 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

  1. Watch the first server start closely.
  2. Expect some games to initialize state on first start.
  3. Wait for the game-specific ready log before client testing.
  4. Check SteamCMD logs when updates stall.
  5. Check directory ownership when SteamCMD reports write failures.

The SteamCMD module repairs ownership recursively for managed directories.