docs: make SteamCMD guide procedural

This commit is contained in:
2026-09-10 11:57:31 -04:00
parent 16c8331379
commit 523655a9ec

View File

@@ -1,103 +1,107 @@
# SteamCMD servers # SteamCMD servers
This note describes the workflow for adding a server that SteamCMD installs. Use this workflow to add a server that SteamCMD installs.
Start with the generic runner on a host. Complete each stage before you start the next stage.
Then move game behavior into a game module.
Move deployment behavior into a guest module after validation.
## Stage 1: Run the server with `steamcmd` ## Stage 1: Prove the SteamCMD runner
Prove the generic SteamCMD runner before adding game-specific modules. 1. Add the server to the host with `modules.services.steamcmd`.
Configure `modules.services.steamcmd` directly in the host file. 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.
Use real values for the target game. Keep this stage small.
For the first Valheim deployment, the direct configuration set the Steam app ID, install path, state path, executable, command-line arguments, and UDP ports directly on `modules.services.steamcmd`. Use it only to validate SteamCMD, game arguments, ports, and state paths.
This layer owns shared SteamCMD behavior:
- service user
- directory creation
- SteamCMD install and update command
- systemd service wrapper
- firewall ports
Do not pin the game server version in Nix. Do not pin the game server version in Nix.
SteamCMD mutates the installed server files. SteamCMD mutates the installed server files.
## Stage 2: Add a game service module ## Stage 2: Create the game module
Create a game module after direct SteamCMD deployment works. 1. Create `configs/nixos/modules/services/games/<game>.nix`.
Use `configs/nixos/modules/services/games/<game>.nix`. 2. Add the module to `configs/nixos/modules/default.nix`.
3. Define options under `modules.services.games.<game>`.
4. Expose only deployment-specific values.
5. Keep host paths in the host configuration.
6. Keep secret selection in the host configuration.
7. Keep network placement outside the game module.
8. Move the Steam app ID into the game module.
9. Move the executable path into the game module.
10. Move game arguments into the game module.
11. Move documented ports into the game module.
12. Wire the game module to `modules.services.steamcmd`.
13. Replace the direct host `modules.services.steamcmd` config.
14. Enable `modules.services.games.<game>` on the host.
15. Evaluate the host configuration.
16. Deploy the host.
17. Validate the service again on the host.
The game module owns game-specific behavior: The game module owns game behavior.
The host owns deployment values.
- Steam app ID
- server executable
- command-line flags
- documented ports
- default listing policy
The host owns deployment-specific values:
- paths
- server name
- secret choice
- network placement
A host can run the game module directly during validation.
For Valheim, the host passed only deployment values to `modules.services.games.valheim`.
The game module translated those values into `modules.services.steamcmd` options.
## Stage 3: Add UDP ingress ## Stage 3: Add UDP ingress
Many game servers use UDP. 1. Read the game's official server port documentation.
HTTP virtual hosts do not route raw UDP traffic. 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.
Extend `registry.route` with the UDP ports for the public route. Use the route domain for identity only.
Keep `domain` mandatory. Nginx stream routing uses destination ports for UDP traffic.
Nix does not own public DNS today. It does not route UDP by hostname.
The route domain identifies the public route.
A future LAN DNS module can also consume it.
The reverse-proxy module joins `registry.route` with `registry.address`.
It renders nginx `stream` entries for UDP routes.
Configure public DNS outside Nix to point the route domain at the public IP.
Configure LAN DNS outside Nix to point the same route domain at the reverse-proxy guest IP.
Configure router forwarding outside Nix to forward the documented UDP ports to the reverse-proxy guest.
Use the game documentation to choose the port list.
For Valheim, the official guide says it uses the configured port and port plus one.
## Stage 4: Move the server into a guest ## Stage 4: Move the server into a guest
Add a guest module after host-level validation succeeds. 1. Create `configs/nixos/guests/<game>.nix`.
Use `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 module wraps `modules.services.games.<game>`. The guest machinery publishes `guests.<name>.address` to `registry.address.<host>.<name>`.
It maps host paths into guest paths. It publishes `guests.<name>.reverseProxy.route` to `registry.route.<host>.<name>`.
It also connects guest address and route options to the registry.
The host config then owns deployment wiring:
- VLAN
- guest address
- host paths for SteamCMD files, server files, and server state
- server name
- reverse-proxy route
The generic guest machinery publishes `guests.<name>.address` as `registry.address.<host>.<name>`.
It also publishes `guests.<name>.reverseProxy.route` as `registry.route.<host>.<name>` when reverse proxying is enabled.
## Secrets ## Secrets
Use SOPS for game server passwords. 1. Use SOPS for game server passwords.
Use one shared secret when servers can share a password. 2. Use `services/game-servers/password` for a shared game-server password.
The current shared key is `services/game-servers/password`. 3. Add a per-game key only when the game needs a different password.
4. Decrypt the secret on the host.
The host decrypts the secret. 5. Bind-mount the decrypted secret into the guest.
The guest module bind-mounts the decrypted file into the guest. 6. Pass the password file to the game module.
The game module reads the password file at runtime. 7. Read the password file at service start.
8. Do not put plaintext passwords in host configuration.
Edit shared secrets with the host key: Edit shared secrets with the host key:
@@ -106,9 +110,9 @@ cd configs/nixos
sops-host secrets/shared.yaml sops-host secrets/shared.yaml
``` ```
## Validation ## Validation commands
Evaluate the host before deployment: Evaluate the host:
```sh ```sh
nix eval .#nixosConfigurations.<host>.config.system.build.toplevel.drvPath --raw nix eval .#nixosConfigurations.<host>.config.system.build.toplevel.drvPath --raw
@@ -145,14 +149,6 @@ sudo nixos-container run <guest> -- systemctl status <game>.service --no-pager
sudo nixos-container run <guest> -- journalctl -u <game>.service -n 200 --no-pager sudo nixos-container run <guest> -- journalctl -u <game>.service -n 200 --no-pager
``` ```
Wait for the game-specific ready log.
For Valheim, wait for these lines:
```text
Game server connected
Opened Steam server
```
Check game UDP listeners: Check game UDP listeners:
```sh ```sh
@@ -165,13 +161,12 @@ Check reverse-proxy UDP listeners:
sudo nixos-container run reverse-proxy -- ss -lunp | grep -E '<ports>|nginx' sudo nixos-container run reverse-proxy -- ss -lunp | grep -E '<ports>|nginx'
``` ```
Test the client through LAN DNS or public DNS. ## Operational checks
Test direct guest connectivity when ingress fails.
## Operational notes 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.
A first server start can spend time preparing state. The SteamCMD module repairs ownership recursively for managed directories.
For Valheim, first start can generate a world.
SteamCMD can leave old ownership on migrated server files.
The SteamCMD module uses recursive tmpfiles repair for its managed directories.