From 8bd9ff166e0f45cea5e4ec9f3386788ca6b6ddda Mon Sep 17 00:00:00 2001 From: alexion Date: Thu, 10 Sep 2026 10:16:27 -0400 Subject: [PATCH] feat(nixos): run Valheim in a guest --- configs/nixos/guests/valheim.nix | 116 ++++++++++++++++++ configs/nixos/hosts/pikachu/default.nix | 28 +++-- configs/nixos/modules/default.nix | 8 ++ .../nixos/modules/services/games/valheim.nix | 17 ++- configs/nixos/modules/services/steamcmd.nix | 6 +- configs/nixos/secrets/shared.yaml | 6 +- 6 files changed, 158 insertions(+), 23 deletions(-) create mode 100644 configs/nixos/guests/valheim.nix diff --git a/configs/nixos/guests/valheim.nix b/configs/nixos/guests/valheim.nix new file mode 100644 index 0000000..6c38233 --- /dev/null +++ b/configs/nixos/guests/valheim.nix @@ -0,0 +1,116 @@ +{ + config, + lib, + ... +}: +let + cfg = config.guests.valheim; + steamDir = "/data/steamcmd"; + installDir = "/data/server"; + stateDir = "/data/state"; +in +{ + options.guests.valheim = { + steamPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/steamcmd"; + description = "Host path bind-mounted as SteamCMD's metadata directory."; + }; + + installPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/valheim/server"; + description = "Host path bind-mounted as Valheim's server install directory."; + }; + + statePath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/valheim/state"; + description = "Host path bind-mounted as Valheim's world state directory."; + }; + + serverName = lib.mkOption { + type = lib.types.str; + default = "valheim"; + description = "Name shown for the Valheim server."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 2456; + description = "Base UDP port for the Valheim server."; + }; + + passwordSecret = lib.mkOption { + type = lib.types.str; + default = "services/game-servers/password"; + description = "SOPS secret name containing the Valheim server password."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.steamPath != null; + message = "guests.valheim.steamPath must point at the host directory that stores SteamCMD metadata."; + } + { + assertion = cfg.installPath != null; + message = "guests.valheim.installPath must point at the host directory that stores Valheim server files."; + } + { + assertion = cfg.statePath != null; + message = "guests.valheim.statePath must point at the host directory that stores Valheim world state."; + } + ]; + + guests.valheim = { + interior.modules.services.games.valheim = { + enable = true; + inherit (cfg) serverName port; + inherit steamDir installDir stateDir; + passwordFile = "/run/secrets/${cfg.passwordSecret}"; + }; + + mounts = { + ${steamDir} = { + directory = cfg.steamPath; + owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd; + group = "storage"; + mode = "0770"; + }; + ${installDir} = { + directory = cfg.installPath; + owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd; + group = "storage"; + mode = "0770"; + }; + ${stateDir} = { + directory = cfg.statePath; + owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd; + group = "storage"; + mode = "0770"; + }; + ${"/run/secrets/${cfg.passwordSecret}"} = { + file = config.sops.secrets.${cfg.passwordSecret}.path; + readOnly = true; + }; + }; + + limits = { + memory = lib.mkDefault "4G"; + cpu = lib.mkDefault "400%"; + tasksMax = lib.mkDefault 1024; + }; + }; + + sops.secrets.${cfg.passwordSecret} = { + owner = "root"; + group = "storage"; + mode = "0440"; + }; + }; +} diff --git a/configs/nixos/hosts/pikachu/default.nix b/configs/nixos/hosts/pikachu/default.nix index 703c7b3..d5854e3 100644 --- a/configs/nixos/hosts/pikachu/default.nix +++ b/configs/nixos/hosts/pikachu/default.nix @@ -65,21 +65,25 @@ }; registry.address.pikachu.self = "10.23.10.102"; - registry.route.pikachu.self = { - domain = "valheim.alexion.dev"; - udp.ports = [ - config.modules.services.games.valheim.port - (config.modules.services.games.valheim.port + 1) - ]; - }; - modules.services.games.valheim = { + guests.valheim = { enable = true; - steamDir = "/pikachu/data/steamcmd"; - installDir = "/pikachu/data/servers/valheim/main"; - stateDir = "/pikachu/media/games/servers/valheim/main"; + 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"; - password = "foobarbaz"; + reverseProxy = { + enable = true; + route = { + domain = "valheim.alexion.dev"; + udp.ports = [ + config.guests.valheim.port + (config.guests.valheim.port + 1) + ]; + }; + }; }; guests.actualbudget = { diff --git a/configs/nixos/modules/default.nix b/configs/nixos/modules/default.nix index be827cf..f46e319 100644 --- a/configs/nixos/modules/default.nix +++ b/configs/nixos/modules/default.nix @@ -42,6 +42,10 @@ in users.groups.storage.gid = 10000; + security.sudo.extraConfig = '' + Defaults env_keep += "EDITOR VISUAL" + ''; + time.timeZone = "America/New_York"; i18n.defaultLocale = "en_GB.UTF-8"; @@ -87,6 +91,10 @@ in update = "sudo nixos-rebuild switch --flake .#${config.networking.hostName}"; cleanup = "sudo nix-collect-garbage -d"; + sops-host = { + command = "sudo --preserve-env=EDITOR,VISUAL env SOPS_AGE_KEY_FILE=/var/lib/sops-nix/key.txt nix shell nixpkgs#sops nixpkgs#neovim -c sops"; + abbreviation = false; + }; }; modules.shell.fish.enable = lib.mkDefault true; diff --git a/configs/nixos/modules/services/games/valheim.nix b/configs/nixos/modules/services/games/valheim.nix index 50f0dbc..1be092d 100644 --- a/configs/nixos/modules/services/games/valheim.nix +++ b/configs/nixos/modules/services/games/valheim.nix @@ -1,6 +1,7 @@ { config, lib, + pkgs, ... }: { @@ -37,9 +38,9 @@ description = "Base UDP port for the Valheim server."; }; - password = lib.mkOption { - type = lib.types.str; - description = "Valheim server password."; + passwordFile = lib.mkOption { + type = lib.types.path; + description = "Runtime file containing the Valheim server password."; }; }; @@ -47,6 +48,12 @@ config = lib.mkIf config.modules.services.games.valheim.enable ( let service = config.modules.services.games.valheim; + startScript = pkgs.writeShellScript "valheim-start" '' + set -eu + + password="$(${pkgs.coreutils}/bin/tr -d '\n' < ${lib.escapeShellArg service.passwordFile})" + exec ./valheim_server.x86_64 "$@" -password "$password" + ''; in { modules.services.steamcmd = { @@ -54,7 +61,7 @@ inherit (service) steamDir installDir stateDir; serviceName = "valheim"; appId = 896660; - executable = "./valheim_server.x86_64"; + executable = "${startScript}"; arguments = [ "-nographics" "-batchmode" @@ -66,8 +73,6 @@ service.serverName "-savedir" service.stateDir - "-password" - service.password "-public" "0" ]; diff --git a/configs/nixos/modules/services/steamcmd.nix b/configs/nixos/modules/services/steamcmd.nix index 3172af3..22a67ab 100644 --- a/configs/nixos/modules/services/steamcmd.nix +++ b/configs/nixos/modules/services/steamcmd.nix @@ -113,11 +113,11 @@ systemd.tmpfiles.rules = [ "d ${service.steamDir} 0770 ${user} ${group} - -" - "z ${service.steamDir} 0770 ${user} ${group} - -" + "Z ${service.steamDir} 0770 ${user} ${group} - -" "d ${service.installDir} 0770 ${user} ${group} - -" - "z ${service.installDir} 0770 ${user} ${group} - -" + "Z ${service.installDir} 0770 ${user} ${group} - -" "d ${service.stateDir} 0770 ${user} ${group} - -" - "z ${service.stateDir} 0770 ${user} ${group} - -" + "Z ${service.stateDir} 0770 ${user} ${group} - -" ]; systemd.services = { diff --git a/configs/nixos/secrets/shared.yaml b/configs/nixos/secrets/shared.yaml index 301ad8d..b58e483 100644 --- a/configs/nixos/secrets/shared.yaml +++ b/configs/nixos/secrets/shared.yaml @@ -4,6 +4,8 @@ services: users: alexion: password: ENC[AES256_GCM,data:SSyo2l6tCjgjxIuCEREIxU+inw2WHtJJR0/IEKUqefFfuAdA5Nf+lH/jfPw=,iv:9225w1Yur3KL3dBr1ct1aPvDdVJ94O8q9qAUiRrj8Nc=,tag:qZgx4XieVq4BcxkTZc0KGQ==,type:str] + game-servers: + password: ENC[AES256_GCM,data:Wtq03MLRnc6/,iv:HhbiP8ZcltVGbCNDdP/gH5mCZXhVCTFv1DGzfrrgQG8=,tag:srsqcBQSD/HU8zFsuFimJQ==,type:str] sops: age: - enc: | @@ -42,7 +44,7 @@ sops: 28uWUpKzzgErq1aEbKNgh4cU5TH2RlAHjtzRC3ARUu9hlW6lnB4pSw== -----END AGE ENCRYPTED FILE----- recipient: age1wf5s0n0tgt6ld2ysgu9dc67mj8ylwecgl4utzg7hqwy3kut9zyms7aglmh - lastmodified: "2026-08-17T00:30:50Z" - mac: ENC[AES256_GCM,data:bNOsakxhrlO1quwLGA8inruHZ6tFpUo6cKMlV10jkJ75GOFzl5jzflGIuPfyncJCtIaZRBA2tS17TpGEEJozmbB2xQkeu67NmzOpEdoJzZWs/bY0FslbpGf8aGAd15BjOYNyMMNFavt0anRgEnsYXTDCflDCHLg1wvAvwtgRGYs=,iv:kfpzzWn3BHkSTQ8qpcD9lYn4wnZ/3nj/41SR25vUahE=,tag:8D1LSEBJIQcwYK9ETomwIw==,type:str] + lastmodified: "2026-09-10T13:40:57Z" + mac: ENC[AES256_GCM,data:3nu+ho3TNDnGE55LIaHqJxlMs1hWVK2wwiJiBIDH0jop9Wo/AgaUXzwXY1MwMXJHhGrkcjhwWWJdEgxX88NNFTc59b4kLtRqE3bfNXoQ3y3giyDMFyVE9YCNd4jyl+pLhpVSZA/+TKEIBpdZRwaXHrYSaLtysg/OdzXing0l3SM=,iv:C/vZ6X1HikuBn+pWTlPIfCfg5mtW2i4P6XLwN1VwF6k=,tag:UQl10fueTwCtXuirP5+C/g==,type:str] unencrypted_suffix: _unencrypted version: 3.13.3