diff --git a/docs/00 INBOX/ai/steamcmd servers.md b/docs/00 INBOX/ai/steamcmd servers.md index b8b46d5..60f951f 100644 --- a/docs/00 INBOX/ai/steamcmd servers.md +++ b/docs/00 INBOX/ai/steamcmd servers.md @@ -1,71 +1,172 @@ # SteamCMD servers -Use `modules.services.steamcmd` as the low-level runner for one SteamCMD server. -Create a game module under `configs/nixos/modules/services/games/` for each game. +Use this workflow to add a server that SteamCMD installs. +Complete each stage before you start the next stage. -The game module owns game-specific defaults and command-line flags. -The host owns deployment-specific paths, names, secrets, and network placement. +## Stage 1: Prove the SteamCMD runner -## Create a game module +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/.nix`. 2. Define options under `modules.services.games.`. -3. Expose only values the host must choose. -4. Map those values to `modules.services.steamcmd`. -5. Set the Steam dedicated-server app ID in the game module. -6. Set the server executable in the game module. -7. Generate game-specific arguments in the game module. -8. Open the game's documented UDP ports in the game module. +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.` 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. -Keep `modules.services.steamcmd` focused on shared SteamCMD behavior. -Do not put game-specific flags or port rules there. +The game module owns game behavior. +The host owns deployment values. -## Configure a host +## Stage 3: Add UDP ingress -During validation, enable the game module directly on a host. +1. Read the game's official server port documentation. +2. Add the UDP ports to `registry.route...udp.ports`. +3. Keep `registry.route...domain` set. +4. Point `registry.address..` 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. -```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"; -}; +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/.nix`. +2. Add the guest module to `src/dotfiles/guests.nix`. +3. Wrap `modules.services.games.` 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.` 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..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..address` to `registry.address..`. +It publishes `guests..reverseProxy.route` to `registry.route..`. + +## 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 ``` -Use host paths for direct host deployment. -Use guest paths after the server moves into a guest. +## Validation commands -## Deploy and test +Evaluate the host: -1. Evaluate the host configuration. +```sh +nix eval .#nixosConfigurations..config.system.build.toplevel.drvPath --raw +``` - ```sh - nix eval .#nixosConfigurations..config.system.build.toplevel.drvPath --raw - ``` +Deploy the host: -2. Deploy the host. +```sh +deploy-to +``` - ```sh - deploy-to - ``` +Check the host secret: -3. Check the game service. +```sh +sudo test -s /run/secrets/services/game-servers/password && echo secret-ok +``` - ```sh - sudo systemctl status .service - sudo journalctl -u .service -n 200 --no-pager - ``` +Check the guest: -4. Check UDP listeners. +```sh +sudo systemctl status container@.service --no-pager +``` - ```sh - sudo ss -lunp | grep -E '||' - ``` +Check the guest secret: -5. Connect from the game client. +```sh +sudo nixos-container run -- test -s /run/secrets/services/game-servers/password +``` -The Valheim server is ready after the log contains `Game server connected`. -It can spend time generating a new world before it accepts connections. +Check the game service: + +```sh +sudo nixos-container run -- systemctl status .service --no-pager +sudo nixos-container run -- journalctl -u .service -n 200 --no-pager +``` + +Check game UDP listeners: + +```sh +sudo nixos-container run -- ss -lunp | grep -E '|' +``` + +Check reverse-proxy UDP listeners: + +```sh +sudo nixos-container run reverse-proxy -- ss -lunp | grep -E '|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.