173 lines
5.3 KiB
Markdown
173 lines
5.3 KiB
Markdown
# 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:
|
|
|
|
```sh
|
|
cd configs/nixos
|
|
sops-host secrets/shared.yaml
|
|
```
|
|
|
|
## Validation commands
|
|
|
|
Evaluate the host:
|
|
|
|
```sh
|
|
nix eval .#nixosConfigurations.<host>.config.system.build.toplevel.drvPath --raw
|
|
```
|
|
|
|
Deploy the host:
|
|
|
|
```sh
|
|
deploy-to <host>
|
|
```
|
|
|
|
Check the host secret:
|
|
|
|
```sh
|
|
sudo test -s /run/secrets/services/game-servers/password && echo secret-ok
|
|
```
|
|
|
|
Check the guest:
|
|
|
|
```sh
|
|
sudo systemctl status container@<guest>.service --no-pager
|
|
```
|
|
|
|
Check the guest secret:
|
|
|
|
```sh
|
|
sudo nixos-container run <guest> -- test -s /run/secrets/services/game-servers/password
|
|
```
|
|
|
|
Check the game service:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
sudo nixos-container run <guest> -- ss -lunp | grep -E '<ports>|<game>'
|
|
```
|
|
|
|
Check reverse-proxy UDP listeners:
|
|
|
|
```sh
|
|
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.
|