Files
dotfiles/platforms/nixos/modules/services/valheim.nix
T

250 lines
6.8 KiB
Nix

{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.valheim;
startScript = pkgs.writeShellScriptBin "valheim-server-start" ''
set -euo pipefail
CONFIG_DIR="${cfg.dataDir}/.config/unity3d/IronGate/Valheim"
mkdir -p "$CONFIG_DIR"
${lib.optionalString (cfg.adminList != [ ]) ''
cat << 'EOF' > "$CONFIG_DIR/adminlist.txt"
// List of Steam IDs for adminlist ONE per line
${lib.concatStringsSep "\n" cfg.adminList}
EOF
''}
${lib.optionalString (cfg.permittedList != [ ]) ''
cat << 'EOF' > "$CONFIG_DIR/permittedlist.txt"
// List of Steam IDs for permittedlist ONE per line
${lib.concatStringsSep "\n" cfg.permittedList}
EOF
''}
${lib.optionalString (cfg.bannedList != [ ]) ''
cat << 'EOF' > "$CONFIG_DIR/bannedlist.txt"
// List of Steam IDs for bannedlist ONE per line
${lib.concatStringsSep "\n" cfg.bannedList}
EOF
''}
${
if cfg.passwordFile != null then
''
if [ ! -f "${cfg.passwordFile}" ]; then
echo "Valheim password file '${cfg.passwordFile}' not found!" >&2
exit 1
fi
PASS="$(tr -d '\r\n' < '${cfg.passwordFile}')"
''
else
''
PASS="${cfg.password}"
''
}
if [ ''${#PASS} -lt 5 ]; then
echo "Valheim server password must be at least 5 characters long!" >&2
exit 1
fi
ARGS=(
"-name" "${cfg.serverName}"
"-port" "${toString cfg.port}"
"-world" "${cfg.worldName}"
"-password" "$PASS"
"-public" "${if cfg.public then "1" else "0"}"
)
${lib.optionalString cfg.crossplay ''ARGS+=("-crossplay")''}
${lib.optionalString (cfg.saveDir != null) ''ARGS+=("-savedir" "${cfg.saveDir}")''}
${lib.concatMapStringsSep "\n" (arg: ''ARGS+=("${arg}")'') cfg.extraArgs}
exec ${cfg.package}/bin/valheim-server "''${ARGS[@]}"
'';
in
{
options.services.valheim = {
enable = lib.mkEnableOption "Valheim dedicated server";
package = lib.mkOption {
type = lib.types.package;
default = pkgs.valheim-server;
defaultText = lib.literalExpression "pkgs.valheim-server";
description = "The valheim-server package to use.";
};
serverName = lib.mkOption {
type = lib.types.str;
default = "Valheim Server";
example = "Some cozy server";
description = "The name listed in the server browser.";
};
worldName = lib.mkOption {
type = lib.types.str;
default = "Dedicated";
example = "Midgard";
description = ''
The name of the world file to use, without the extension.
If the world does not exist, the server will generate a world from a random seed.
'';
};
port = lib.mkOption {
type = lib.types.port;
default = 2456;
description = ''
The port on which to listen for incoming connections.
Ports (port + 1) and (port + 2) are used for Steam queries and routing.
'';
};
crossplay = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to enable cross-platform play via PlayFab backend.";
};
public = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to list the server in the public server browser.";
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to open UDP ports in the firewall.";
};
password = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
The server password in plain text.
Prefer `passwordFile` to avoid storing passwords in the world-readable Nix store.
'';
};
passwordFile = lib.mkOption {
type = lib.types.nullOr (lib.types.either lib.types.str lib.types.path);
default = null;
description = "Path to file containing the server password.";
};
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/valheim";
description = "Directory for valheim server data and state.";
};
saveDir = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Directory where world saves are placed (-savedir). Defaults to Unity config dir in dataDir.";
};
adminList = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "72057602627862526" ];
description = "List of Steam IDs to add to adminlist.txt.";
};
permittedList = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "72057602627862526" ];
description = "List of Steam IDs to add to permittedlist.txt.";
};
bannedList = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "72057602627862526" ];
description = "List of Steam IDs to add to bannedlist.txt.";
};
bepinexMods = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = "BepInEx mods to install.";
};
bepinexConfigs = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
description = "Config files for BepInEx mods.";
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra command line arguments to pass to valheim-server.";
};
};
config = lib.mkIf cfg.enable {
users.users.valheim = {
isSystemUser = true;
group = "valheim";
home = cfg.dataDir;
createHome = true;
};
users.groups.valheim = { };
systemd.services.valheim = {
description = "Valheim dedicated server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = with pkgs; [ coreutils ];
serviceConfig = {
Type = "exec";
User = "valheim";
Group = "valheim";
StateDirectory = "valheim";
WorkingDirectory = cfg.dataDir;
Restart = "on-failure";
RestartSec = "10s";
ExecStart = "${startScript}/bin/valheim-server-start";
};
};
networking.firewall = lib.mkIf cfg.openFirewall {
allowedUDPPorts = [
cfg.port
(cfg.port + 1)
(cfg.port + 2)
];
};
assertions = [
{
assertion = cfg.serverName != "";
message = "services.valheim.serverName must not be empty.";
}
{
assertion = cfg.worldName != "";
message = "services.valheim.worldName must not be empty.";
}
{
assertion = (cfg.password != null && cfg.password != "") || cfg.passwordFile != null;
message = "services.valheim requires either services.valheim.password or services.valheim.passwordFile to be set.";
}
];
};
}