docs: generalize SteamCMD server workflow

This commit is contained in:
2026-09-10 10:20:56 -04:00
parent 6328445976
commit ab4f13a385

View File

@@ -1,12 +1,13 @@
# SteamCMD servers # SteamCMD servers
This note records the workflow used to add the Valheim server. This note describes the workflow for adding a server that SteamCMD installs.
The workflow starts with the lowest useful layer. Start with the generic runner on a host.
Each later step moves game knowledge into a narrower module. Then move game behavior into a game module.
Move deployment behavior into a guest module after validation.
## Stage 1: Run one server with `steamcmd` ## Stage 1: Run the server with `steamcmd`
Start by proving the generic SteamCMD runner works on a host. Prove the generic SteamCMD runner before adding game-specific modules.
Configure `modules.services.steamcmd` directly in the host file. Configure `modules.services.steamcmd` directly in the host file.
```nix ```nix
@@ -41,33 +42,41 @@ modules.services.steamcmd = {
}; };
``` ```
This layer owns SteamCMD update behavior and systemd wiring. Use real values for the target game.
It also owns directory setup for SteamCMD metadata, server files, and state. The example shows Valheim because it was the first server added.
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: Add a game service module
After direct SteamCMD deployment works, create a game module. Create a game module after direct SteamCMD deployment works.
Use `configs/nixos/modules/services/games/<game>.nix`. Use `configs/nixos/modules/services/games/<game>.nix`.
The game module owns game-specific details: The game module owns game-specific behavior:
- Steam app ID - Steam app ID
- server executable - server executable
- required command-line flags - command-line flags
- documented UDP ports - documented ports
- private or public listing policy - default listing policy
The host keeps deployment-specific values: The host owns deployment-specific values:
- host paths - paths
- server name - server name
- secret choice - secret choice
- guest placement, when a guest exists - network placement
For Valheim, the host config became: A host can run the game module directly during validation:
```nix ```nix
modules.services.games.valheim = { modules.services.games.valheim = {
@@ -76,17 +85,17 @@ modules.services.games.valheim = {
installDir = "/pikachu/data/servers/valheim/main"; installDir = "/pikachu/data/servers/valheim/main";
stateDir = "/pikachu/media/games/servers/valheim/main"; stateDir = "/pikachu/media/games/servers/valheim/main";
serverName = "main"; serverName = "main";
password = "foobarbaz"; passwordFile = "/run/secrets/services/game-servers/password";
}; };
``` ```
The Valheim module translated that into `modules.services.steamcmd`. The game module translates its options into `modules.services.steamcmd`.
It generated the Valheim argument list and UDP ports. It passes the app ID, executable, arguments, and ports to the runner.
## Stage 3: Add UDP ingress ## Stage 3: Add UDP ingress
Valheim uses UDP. Many game servers use UDP.
HTTP virtual hosts do not route Valheim traffic. HTTP virtual hosts do not route raw UDP traffic.
Extend `registry.route` with UDP ports: Extend `registry.route` with UDP ports:
@@ -102,11 +111,11 @@ registry.route.pikachu.valheim = {
Keep `domain` mandatory. Keep `domain` mandatory.
Nix does not own public DNS today. Nix does not own public DNS today.
The route domain still identifies the public route. The route domain identifies the public route.
A future LAN DNS module can also consume it. A future LAN DNS module can also consume it.
The reverse-proxy module joins `registry.route` with `registry.address`. The reverse-proxy module joins `registry.route` with `registry.address`.
It renders nginx `stream` entries for UDP ports. It renders nginx `stream` entries for UDP routes.
```nginx ```nginx
server { server {
@@ -134,15 +143,19 @@ Configure router forwarding outside Nix:
2457/udp -> reverse-proxy guest:2457 2457/udp -> reverse-proxy guest:2457
``` ```
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
After the host-level game module works, add a guest module. Add a guest module after host-level validation succeeds.
Use `configs/nixos/guests/<game>.nix`. Use `configs/nixos/guests/<game>.nix`.
The guest module wraps `modules.services.games.<game>`. The guest module wraps `modules.services.games.<game>`.
It also maps host paths into guest paths. It maps host paths into guest paths.
It also connects guest address and route options to the registry.
For Valheim, the host config became: The host config then owns deployment wiring:
```nix ```nix
guests.valheim = { guests.valheim = {
@@ -186,8 +199,8 @@ registry.route.pikachu.valheim = config.guests.valheim.reverseProxy.route;
## Secrets ## Secrets
Use SOPS for the game server password. Use SOPS for game server passwords.
Use one shared secret for game servers: Use one shared secret when servers can share a password:
```yaml ```yaml
services: services:
@@ -203,7 +216,7 @@ passwordSecret = "services/game-servers/password";
The host decrypts the secret. The host decrypts the secret.
The guest module bind-mounts the decrypted file into the guest. The guest module bind-mounts the decrypted file into the guest.
The Valheim module reads the password file at runtime. The game module reads the password file at runtime.
Edit shared secrets with the host key: Edit shared secrets with the host key:
@@ -232,7 +245,7 @@ Check the host secret:
sudo test -s /run/secrets/services/game-servers/password && echo secret-ok sudo test -s /run/secrets/services/game-servers/password && echo secret-ok
``` ```
Check the Valheim guest: Check the guest:
```sh ```sh
sudo systemctl status container@valheim.service --no-pager sudo systemctl status container@valheim.service --no-pager
@@ -244,21 +257,22 @@ Check the guest secret:
sudo nixos-container run valheim -- test -s /run/secrets/services/game-servers/password sudo nixos-container run valheim -- test -s /run/secrets/services/game-servers/password
``` ```
Check the Valheim service: Check the game service:
```sh ```sh
sudo nixos-container run valheim -- systemctl status valheim.service --no-pager sudo nixos-container run valheim -- systemctl status valheim.service --no-pager
sudo nixos-container run valheim -- journalctl -u valheim.service -n 200 --no-pager sudo nixos-container run valheim -- journalctl -u valheim.service -n 200 --no-pager
``` ```
Wait for these log lines: Wait for the game-specific ready log.
For Valheim, wait for these lines:
```text ```text
Game server connected Game server connected
Opened Steam server Opened Steam server
``` ```
Check Valheim UDP listeners: Check game UDP listeners:
```sh ```sh
sudo nixos-container run valheim -- ss -lunp | grep -E '2456|2457|valheim' sudo nixos-container run valheim -- ss -lunp | grep -E '2456|2457|valheim'
@@ -284,8 +298,8 @@ Test direct guest connectivity when ingress fails:
## Operational notes ## Operational notes
A first Valheim start can spend time generating a world. A first server start can spend time preparing state.
The service accepts connections after `Opened Steam server` appears. For Valheim, first start can generate a world.
SteamCMD can leave old ownership on migrated server files. SteamCMD can leave old ownership on migrated server files.
The SteamCMD module uses recursive tmpfiles repair for its managed directories. The SteamCMD module uses recursive tmpfiles repair for its managed directories.