# SteamCMD servers This note records the workflow used to add the Valheim server. The workflow starts with the lowest useful layer. Each later step moves game knowledge into a narrower module. ## Stage 1: Run one server with `steamcmd` Start by proving the generic SteamCMD runner works on a host. Configure `modules.services.steamcmd` directly in the host file. ```nix modules.services.steamcmd = { enable = true; steamDir = "/pikachu/data/steamcmd"; serviceName = "valheim"; appId = 896660; installDir = "/pikachu/data/servers/valheim/main"; stateDir = "/pikachu/media/games/servers/valheim/main"; executable = "./valheim_server.x86_64"; arguments = [ "-nographics" "-batchmode" "-name" "main" "-port" "2456" "-world" "main" "-savedir" config.modules.services.steamcmd.stateDir "-password" "foobarbaz" "-public" "0" ]; udpPorts = [ 2456 2457 ]; }; ``` This layer owns SteamCMD update behavior and systemd wiring. It also owns directory setup for SteamCMD metadata, server files, and state. Do not pin the game server version in Nix. SteamCMD mutates the installed server files. ## Stage 2: Add a game service module After direct SteamCMD deployment works, create a game module. Use `configs/nixos/modules/services/games/.nix`. The game module owns game-specific details: - Steam app ID - server executable - required command-line flags - documented UDP ports - private or public listing policy The host keeps deployment-specific values: - host paths - server name - secret choice - guest placement, when a guest exists For Valheim, the host config became: ```nix modules.services.games.valheim = { enable = true; steamDir = "/pikachu/data/steamcmd"; installDir = "/pikachu/data/servers/valheim/main"; stateDir = "/pikachu/media/games/servers/valheim/main"; serverName = "main"; password = "foobarbaz"; }; ``` The Valheim module translated that into `modules.services.steamcmd`. It generated the Valheim argument list and UDP ports. ## Stage 3: Add UDP ingress Valheim uses UDP. HTTP virtual hosts do not route Valheim traffic. Extend `registry.route` with UDP ports: ```nix registry.route.pikachu.valheim = { domain = "valheim.alexion.dev"; udp.ports = [ 2456 2457 ]; }; ``` Keep `domain` mandatory. Nix does not own public DNS today. The route domain still 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 ports. ```nginx server { listen 2456 udp; proxy_pass 10.23.20.197:2456; } ``` Configure public DNS outside Nix: ```text valheim.alexion.dev -> public IP ``` Configure LAN DNS outside Nix: ```text valheim.alexion.dev -> reverse-proxy guest IP ``` Configure router forwarding outside Nix: ```text 2456/udp -> reverse-proxy guest:2456 2457/udp -> reverse-proxy guest:2457 ``` ## Stage 4: Move the server into a guest After the host-level game module works, add a guest module. Use `configs/nixos/guests/.nix`. The guest module wraps `modules.services.games.`. It also maps host paths into guest paths. For Valheim, the host config became: ```nix guests.valheim = { enable = true; vlan = 20; address = "10.23.20.197"; steamPath = "/pikachu/data/steamcmd"; installPath = "/pikachu/data/servers/valheim/main"; statePath = "/pikachu/media/games/servers/valheim/main"; serverName = "main"; reverseProxy = { enable = true; route = { domain = "valheim.alexion.dev"; udp.ports = [ config.guests.valheim.port (config.guests.valheim.port + 1) ]; }; }; }; ``` The generic guest machinery publishes registry entries. ```nix guests.valheim.address = "10.23.20.197"; ``` becomes: ```nix registry.address.pikachu.valheim = "10.23.20.197"; ``` The guest route becomes: ```nix registry.route.pikachu.valheim = config.guests.valheim.reverseProxy.route; ``` ## Secrets Use SOPS for the game server password. Use one shared secret for game servers: ```yaml services: game-servers: password: ``` The Valheim guest defaults to: ```nix passwordSecret = "services/game-servers/password"; ``` The host decrypts the secret. The guest module bind-mounts the decrypted file into the guest. The Valheim module reads the password file at runtime. Edit shared secrets with the host key: ```sh cd configs/nixos sops-host secrets/shared.yaml ``` ## Validation Evaluate the host: ```sh nix eval .#nixosConfigurations.pikachu.config.system.build.toplevel.drvPath --raw ``` Deploy the host: ```sh deploy-to pikachu ``` Check the host secret: ```sh sudo test -s /run/secrets/services/game-servers/password && echo secret-ok ``` Check the Valheim guest: ```sh sudo systemctl status container@valheim.service --no-pager ``` Check the guest secret: ```sh sudo nixos-container run valheim -- test -s /run/secrets/services/game-servers/password ``` Check the Valheim service: ```sh sudo nixos-container run valheim -- systemctl status valheim.service --no-pager sudo nixos-container run valheim -- journalctl -u valheim.service -n 200 --no-pager ``` Wait for these log lines: ```text Game server connected Opened Steam server ``` Check Valheim UDP listeners: ```sh sudo nixos-container run valheim -- ss -lunp | grep -E '2456|2457|valheim' ``` Check reverse-proxy UDP listeners: ```sh sudo nixos-container run reverse-proxy -- ss -lunp | grep -E '2456|2457|nginx' ``` Test the client through LAN DNS or public DNS: ```text valheim.alexion.dev:2456 ``` Test direct guest connectivity when ingress fails: ```text 10.23.20.197:2456 ``` ## Operational notes A first Valheim start can spend time generating a world. The service accepts connections after `Opened Steam server` appears. SteamCMD can leave old ownership on migrated server files. The SteamCMD module uses recursive tmpfiles repair for its managed directories.