569 lines
20 KiB
Nix
569 lines
20 KiB
Nix
{ ... }:
|
|
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}: let
|
|
cfg = config.hectic.services."project-zomboid";
|
|
serverProperties = cfg.serverProperties // {
|
|
DefaultPort = cfg.port;
|
|
UDPPort = cfg.udpPort;
|
|
WorkshopItems = lib.concatStringsSep ";" cfg.workshopItems;
|
|
Mods = lib.concatStringsSep ";" cfg.mods;
|
|
};
|
|
configLines = lib.mapAttrsToList (
|
|
name: value:
|
|
"${name}=${if builtins.isBool value then lib.boolToString value else toString value}"
|
|
) serverProperties;
|
|
sandboxValueType = lib.types.oneOf [
|
|
lib.types.bool
|
|
lib.types.int
|
|
lib.types.float
|
|
lib.types.str
|
|
(lib.types.attrsOf sandboxValueType)
|
|
];
|
|
luaValue = value:
|
|
if builtins.isBool value then
|
|
lib.boolToString value
|
|
else if builtins.isInt value || builtins.isFloat value then
|
|
toString value
|
|
else if builtins.isAttrs value then
|
|
"{ ${lib.concatStringsSep " " (lib.mapAttrsToList (name: child: "[${luaValue name}] = ${luaValue child},") value)} }"
|
|
else
|
|
"\"${lib.replaceStrings [ "\\" "\"" "\n" "\r" ] [ "\\\\" "\\\"" "\\n" "\\r" ] value}\"";
|
|
sandboxConfigLines = lib.mapAttrsToList (
|
|
name: value: "[${luaValue name}] = ${luaValue value},"
|
|
) cfg.sandboxProperties;
|
|
zomboidDir = "${cfg.dataDir}/Zomboid";
|
|
adminPasswordFile = "${cfg.dataDir}/admin-password";
|
|
rconPasswordFile = cfg.rcon.passwordFile;
|
|
backupCfg = cfg.backup;
|
|
s3CredentialsFile = if backupCfg.s3.credentialsFile == null then "" else backupCfg.s3.credentialsFile;
|
|
s3Bucket = if backupCfg.s3.bucket == null then "" else backupCfg.s3.bucket;
|
|
s3Endpoint = if backupCfg.s3.endpoint == null then "" else backupCfg.s3.endpoint;
|
|
s3Region = if backupCfg.s3.region == null then "" else backupCfg.s3.region;
|
|
saveDir = "${zomboidDir}/Saves/Multiplayer/${cfg.serverName}";
|
|
serverConfigDir = "${zomboidDir}/Server";
|
|
backupScript = pkgs.writeShellScript "project-zomboid-backup" ''
|
|
set -eu
|
|
|
|
staging_dir=${lib.escapeShellArg backupCfg.stagingDir}
|
|
archive_dir=${lib.escapeShellArg backupCfg.archiveDir}
|
|
lock_file="$archive_dir/.backup.lock"
|
|
|
|
${pkgs.coreutils}/bin/install -d -m 0700 \
|
|
"$staging_dir/Zomboid/Saves/Multiplayer/${cfg.serverName}" \
|
|
"$staging_dir/Zomboid/Server" \
|
|
"$archive_dir"
|
|
|
|
exec 9>"$lock_file"
|
|
if ! ${pkgs.util-linux}/bin/flock -n 9; then
|
|
${pkgs.coreutils}/bin/printf '%s\n' 'Project Zomboid backup already running; skipping.' >&2
|
|
exit 0
|
|
fi
|
|
|
|
${lib.optionalString cfg.rcon.enable ''
|
|
rcon_password="$(${pkgs.coreutils}/bin/cat ${lib.escapeShellArg rconPasswordFile})"
|
|
if [ -z "$rcon_password" ]; then
|
|
${pkgs.coreutils}/bin/printf '%s\n' 'Project Zomboid RCON password file is empty.' >&2
|
|
exit 1
|
|
fi
|
|
${pkgs.rcon}/bin/rcon \
|
|
--host 127.0.0.1 \
|
|
--port ${toString cfg.rcon.port} \
|
|
--password "$rcon_password" \
|
|
save
|
|
${pkgs.coreutils}/bin/sleep ${toString backupCfg.saveWaitSeconds}
|
|
''}
|
|
|
|
sync_staging() {
|
|
${pkgs.rsync}/bin/rsync -a --delete \
|
|
${lib.escapeShellArg "${saveDir}/"} \
|
|
"$staging_dir/Zomboid/Saves/Multiplayer/${cfg.serverName}/"
|
|
${pkgs.rsync}/bin/rsync -a --delete --delete-excluded \
|
|
--include=${lib.escapeShellArg "/${cfg.serverName}_SandboxVars.lua"} \
|
|
--include=${lib.escapeShellArg "/${cfg.serverName}_spawnpoints.lua"} \
|
|
--include=${lib.escapeShellArg "/${cfg.serverName}_spawnregions.lua"} \
|
|
--exclude='*' \
|
|
${lib.escapeShellArg "${serverConfigDir}/"} \
|
|
"$staging_dir/Zomboid/Server/"
|
|
}
|
|
|
|
# Second pass narrows, but cannot eliminate, live-save inconsistency.
|
|
sync_staging
|
|
${pkgs.coreutils}/bin/sleep 5
|
|
sync_staging
|
|
|
|
timestamp="$(${pkgs.coreutils}/bin/date -u +%Y%m%dT%H%M%SZ)"
|
|
archive_name="project-zomboid-${cfg.serverName}-$timestamp.tar.zst"
|
|
archive_tmp="$archive_dir/.$archive_name.tmp"
|
|
archive="$archive_dir/$archive_name"
|
|
trap '${pkgs.coreutils}/bin/rm -f "$archive_tmp"' EXIT
|
|
${pkgs.gnutar}/bin/tar \
|
|
--use-compress-program=${lib.escapeShellArg "${pkgs.zstd}/bin/zstd -T0"} \
|
|
-C "$staging_dir" -cf "$archive_tmp" Zomboid
|
|
${pkgs.coreutils}/bin/chmod 0600 "$archive_tmp"
|
|
${pkgs.coreutils}/bin/mv "$archive_tmp" "$archive"
|
|
trap - EXIT
|
|
|
|
${pkgs.findutils}/bin/find "$archive_dir" -maxdepth 1 -type f \
|
|
-name ${lib.escapeShellArg "project-zomboid-${cfg.serverName}-*.tar.zst"} \
|
|
-mmin +${toString (backupCfg.retentionDays * 1440)} -delete
|
|
|
|
${lib.optionalString backupCfg.s3.enable ''
|
|
if [ -z "''${AWS_ACCESS_KEY_ID:-}" ] || [ -z "''${AWS_SECRET_ACCESS_KEY:-}" ]; then
|
|
${pkgs.coreutils}/bin/printf '%s\n' \
|
|
'AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY missing from Project Zomboid S3 credentials file.' >&2
|
|
exit 1
|
|
fi
|
|
s3_bucket=${lib.escapeShellArg s3Bucket}
|
|
s3_prefix=${lib.escapeShellArg backupCfg.s3.prefix}
|
|
s3_key="''${s3_prefix:+$s3_prefix/}$archive_name"
|
|
${pkgs.awscli2}/bin/aws s3 cp "$archive" \
|
|
"s3://$s3_bucket/$s3_key" \
|
|
--endpoint-url ${lib.escapeShellArg s3Endpoint} \
|
|
--region ${lib.escapeShellArg s3Region} \
|
|
--cli-connect-timeout 30 \
|
|
--cli-read-timeout 300 \
|
|
--only-show-errors
|
|
|
|
remote_prefix="$s3_prefix"
|
|
if [ -n "$remote_prefix" ]; then
|
|
remote_prefix="$remote_prefix/"
|
|
fi
|
|
archive_prefix=${lib.escapeShellArg "project-zomboid-${cfg.serverName}-"}
|
|
remote_list="$staging_dir/.remote-objects.json"
|
|
remote_delete_dir="$staging_dir/.remote-delete"
|
|
${pkgs.awscli2}/bin/aws s3api list-objects-v2 \
|
|
--bucket "$s3_bucket" \
|
|
--prefix "$remote_prefix" \
|
|
--endpoint-url ${lib.escapeShellArg s3Endpoint} \
|
|
--region ${lib.escapeShellArg s3Region} \
|
|
--output json > "$remote_list"
|
|
${pkgs.python3}/bin/python3 - "$remote_list" "$remote_delete_dir" \
|
|
"$(( $(${pkgs.coreutils}/bin/date +%s) - ${toString (backupCfg.s3.remoteRetentionDays * 86400)} ))" \
|
|
"$remote_prefix$archive_prefix" <<'PY'
|
|
import datetime
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
objects_path, delete_dir, cutoff, key_prefix = sys.argv[1:]
|
|
cutoff = int(cutoff)
|
|
archive_pattern = re.compile(
|
|
re.escape(key_prefix) + r"\d{8}T\d{6}Z\.tar\.zst\Z"
|
|
)
|
|
with open(objects_path, encoding="utf-8") as stream:
|
|
objects = json.load(stream).get("Contents", [])
|
|
|
|
old_keys = []
|
|
for item in objects:
|
|
key = item.get("Key", "")
|
|
if not archive_pattern.fullmatch(key):
|
|
continue
|
|
modified = datetime.datetime.fromisoformat(
|
|
item["LastModified"].replace("Z", "+00:00")
|
|
)
|
|
if int(modified.timestamp()) < cutoff:
|
|
old_keys.append(key)
|
|
|
|
os.makedirs(delete_dir, exist_ok=True)
|
|
for batch_number in range(0, len(old_keys), 1000):
|
|
batch = old_keys[batch_number:batch_number + 1000]
|
|
manifest_path = os.path.join(
|
|
delete_dir, f"batch-{batch_number // 1000:04d}.json"
|
|
)
|
|
with open(manifest_path, "w", encoding="utf-8") as stream:
|
|
json.dump(
|
|
{"Objects": [{"Key": key} for key in batch], "Quiet": True},
|
|
stream,
|
|
)
|
|
PY
|
|
for remote_manifest in "$remote_delete_dir"/*.json; do
|
|
[ -f "$remote_manifest" ] || continue
|
|
${pkgs.awscli2}/bin/aws s3api delete-objects \
|
|
--bucket "$s3_bucket" \
|
|
--delete "file://$remote_manifest" \
|
|
--endpoint-url ${lib.escapeShellArg s3Endpoint} \
|
|
--region ${lib.escapeShellArg s3Region} \
|
|
--only-show-errors
|
|
done
|
|
${pkgs.coreutils}/bin/rm -rf "$remote_list" "$remote_delete_dir"
|
|
''}
|
|
'';
|
|
startScript = pkgs.writeShellScript "project-zomboid-start" ''
|
|
admin_password=$(${pkgs.coreutils}/bin/cat ${lib.escapeShellArg adminPasswordFile})
|
|
exec ${pkgs.steam-run}/bin/steam-run \
|
|
${lib.escapeShellArg "${cfg.installDir}/start-server.sh"} \
|
|
-servername ${lib.escapeShellArg cfg.serverName} \
|
|
-adminpassword "$admin_password"
|
|
'';
|
|
in {
|
|
options.hectic.services."project-zomboid" = {
|
|
enable = lib.mkEnableOption "Project Zomboid dedicated server";
|
|
|
|
serverName = lib.mkOption {
|
|
type = lib.types.strMatching "[A-Za-z0-9._-]+";
|
|
default = "servertest";
|
|
description = "Name used for Project Zomboid server and save files.";
|
|
};
|
|
|
|
dataDir = lib.mkOption {
|
|
type = lib.types.path;
|
|
default = "/var/lib/project-zomboid";
|
|
description = "Writable state directory for the server.";
|
|
};
|
|
|
|
installDir = lib.mkOption {
|
|
type = lib.types.path;
|
|
default = "/var/lib/project-zomboid/server";
|
|
description = "Directory where SteamCMD installs the dedicated server.";
|
|
};
|
|
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 16261;
|
|
description = "Project Zomboid UDP discovery port.";
|
|
};
|
|
|
|
udpPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 16262;
|
|
description = "Project Zomboid UDP game port.";
|
|
};
|
|
|
|
branch = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Steam beta branch, for example legacy41.";
|
|
};
|
|
|
|
memory = lib.mkOption {
|
|
type = lib.types.strMatching "[0-9]+[mMgG]";
|
|
default = "3g";
|
|
description = "Maximum Java heap for the server, for example 3g.";
|
|
};
|
|
|
|
workshopItems = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Steam Workshop item IDs, downloaded and enabled by the server.";
|
|
};
|
|
|
|
mods = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Project Zomboid mod loading IDs from mod.info.";
|
|
};
|
|
|
|
serverProperties = lib.mkOption {
|
|
type = lib.types.attrsOf (
|
|
lib.types.oneOf [
|
|
lib.types.bool
|
|
lib.types.int
|
|
lib.types.str
|
|
]
|
|
);
|
|
default = { };
|
|
description = "Additional or overriding values for the server INI file.";
|
|
};
|
|
|
|
serverPropertiesFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Runtime file with additional INI values, suitable for secrets.";
|
|
};
|
|
|
|
sandboxProperties = lib.mkOption {
|
|
type = lib.types.attrsOf sandboxValueType;
|
|
default = { };
|
|
description = "Values for the Project Zomboid SandboxVars.lua file.";
|
|
};
|
|
|
|
openFirewall = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Open the Project Zomboid UDP ports in the firewall.";
|
|
};
|
|
|
|
rcon = {
|
|
enable = lib.mkEnableOption "local RCON for Project Zomboid automation";
|
|
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 27015;
|
|
description = "RCON TCP port; not opened in the firewall by this module.";
|
|
};
|
|
|
|
passwordFile = lib.mkOption {
|
|
type = lib.types.path;
|
|
default = "${cfg.dataDir}/rcon-password";
|
|
description = "Runtime file containing the generated RCON password.";
|
|
};
|
|
};
|
|
|
|
backup = {
|
|
enable = lib.mkEnableOption "no-stop Project Zomboid backups";
|
|
|
|
onCalendar = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "*:0/30";
|
|
description = "systemd calendar expression controlling backup frequency.";
|
|
};
|
|
|
|
stagingDir = lib.mkOption {
|
|
type = lib.types.path;
|
|
default = "${cfg.dataDir}/backups/staging";
|
|
description = "Local directory containing the two-pass rsync staging tree.";
|
|
};
|
|
|
|
archiveDir = lib.mkOption {
|
|
type = lib.types.path;
|
|
default = "${cfg.dataDir}/backups/archive";
|
|
description = "Local directory containing timestamped tar.zst archives.";
|
|
};
|
|
|
|
retentionDays = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 14;
|
|
description = "Delete local archives older than this many days.";
|
|
};
|
|
|
|
saveWaitSeconds = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 10;
|
|
description = "Seconds to wait after the RCON save command before rsync.";
|
|
};
|
|
|
|
s3 = {
|
|
enable = lib.mkEnableOption "uploading Project Zomboid backups to S3-compatible storage";
|
|
|
|
credentialsFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Runtime env file containing AWS_ACCESS_KEY_ID and
|
|
AWS_SECRET_ACCESS_KEY. Required when S3 upload is enabled.
|
|
'';
|
|
};
|
|
|
|
bucket = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "S3 bucket receiving backup archives.";
|
|
};
|
|
|
|
endpoint = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "S3-compatible endpoint URL.";
|
|
};
|
|
|
|
region = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "S3 region passed to awscli2.";
|
|
};
|
|
|
|
prefix = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "project-zomboid";
|
|
description = "Optional object key prefix within the S3 bucket.";
|
|
};
|
|
|
|
remoteRetentionDays = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 14;
|
|
description = "Delete uploaded archives older than this many days.";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = !cfg.rcon.enable || (
|
|
lib.hasPrefix "/" cfg.rcon.passwordFile
|
|
&& !lib.hasPrefix "/nix/store/" cfg.rcon.passwordFile
|
|
);
|
|
message = "hectic.services.project-zomboid.rcon.passwordFile must be a runtime path outside /nix/store.";
|
|
}
|
|
{
|
|
assertion = !backupCfg.s3.enable || backupCfg.enable;
|
|
message = "hectic.services.project-zomboid.backup must be enabled before S3 upload.";
|
|
}
|
|
{
|
|
assertion = !backupCfg.s3.enable || backupCfg.s3.credentialsFile != null;
|
|
message = "hectic.services.project-zomboid.backup.s3.credentialsFile is required when S3 upload is enabled.";
|
|
}
|
|
{
|
|
assertion = !backupCfg.s3.enable || backupCfg.s3.bucket != null;
|
|
message = "hectic.services.project-zomboid.backup.s3.bucket is required when S3 upload is enabled.";
|
|
}
|
|
{
|
|
assertion = !backupCfg.s3.enable || backupCfg.s3.endpoint != null;
|
|
message = "hectic.services.project-zomboid.backup.s3.endpoint is required when S3 upload is enabled.";
|
|
}
|
|
{
|
|
assertion = !backupCfg.s3.enable || backupCfg.s3.region != null;
|
|
message = "hectic.services.project-zomboid.backup.s3.region is required when S3 upload is enabled.";
|
|
}
|
|
{
|
|
assertion =
|
|
!backupCfg.s3.enable
|
|
|| backupCfg.s3.credentialsFile == null
|
|
|| (
|
|
lib.hasPrefix "/" backupCfg.s3.credentialsFile
|
|
&& !lib.hasPrefix "/nix/store/" backupCfg.s3.credentialsFile
|
|
);
|
|
message = "hectic.services.project-zomboid.backup.s3.credentialsFile must be a runtime path outside /nix/store.";
|
|
}
|
|
{
|
|
assertion =
|
|
!backupCfg.s3.enable
|
|
|| backupCfg.s3.endpoint == null
|
|
|| lib.hasPrefix "https://" backupCfg.s3.endpoint;
|
|
message = "hectic.services.project-zomboid.backup.s3.endpoint must use HTTPS.";
|
|
}
|
|
];
|
|
|
|
users.groups.project-zomboid = { };
|
|
users.users.project-zomboid = {
|
|
isSystemUser = true;
|
|
group = "project-zomboid";
|
|
home = cfg.dataDir;
|
|
createHome = true;
|
|
};
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d ${cfg.dataDir} 0750 project-zomboid project-zomboid - -"
|
|
"d ${cfg.installDir} 0750 project-zomboid project-zomboid - -"
|
|
] ++ lib.optionals backupCfg.enable [
|
|
"d ${cfg.dataDir}/backups 0700 project-zomboid project-zomboid - -"
|
|
"Z ${cfg.dataDir}/backups 0700 project-zomboid project-zomboid - -"
|
|
"d ${backupCfg.stagingDir} 0700 project-zomboid project-zomboid - -"
|
|
"d ${backupCfg.archiveDir} 0700 project-zomboid project-zomboid - -"
|
|
];
|
|
|
|
systemd.services.project-zomboid = {
|
|
description = "Project Zomboid dedicated server";
|
|
after = [ "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
preStart = ''
|
|
${pkgs.coreutils}/bin/install -d -m 0750 \
|
|
${lib.escapeShellArg cfg.installDir}
|
|
if [ ! -s ${lib.escapeShellArg adminPasswordFile} ]; then
|
|
umask 077
|
|
${pkgs.openssl}/bin/openssl rand -base64 32 > ${lib.escapeShellArg adminPasswordFile}
|
|
fi
|
|
${lib.optionalString cfg.rcon.enable ''
|
|
if [ ! -s ${lib.escapeShellArg rconPasswordFile} ]; then
|
|
umask 077
|
|
${pkgs.openssl}/bin/openssl rand -hex 32 > ${lib.escapeShellArg rconPasswordFile}
|
|
else
|
|
rcon_password=$(${pkgs.coreutils}/bin/cat ${lib.escapeShellArg rconPasswordFile})
|
|
case "$rcon_password" in
|
|
*[!0123456789abcdefABCDEF]*)
|
|
umask 077
|
|
${pkgs.openssl}/bin/openssl rand -hex 32 > ${lib.escapeShellArg rconPasswordFile}
|
|
;;
|
|
esac
|
|
fi
|
|
${pkgs.coreutils}/bin/chown project-zomboid:project-zomboid ${lib.escapeShellArg rconPasswordFile}
|
|
${pkgs.coreutils}/bin/chmod 0600 ${lib.escapeShellArg rconPasswordFile}
|
|
''}
|
|
${pkgs.steamcmd}/bin/steamcmd \
|
|
+force_install_dir ${lib.escapeShellArg cfg.installDir} \
|
|
+login anonymous \
|
|
+app_update 380870 ${lib.optionalString (cfg.branch != null) "-beta ${lib.escapeShellArg cfg.branch}"} validate \
|
|
+quit
|
|
${pkgs.gnused}/bin/sed -i -E \
|
|
's/"-Xmx[0-9]+[mMgG]"/"-Xmx${cfg.memory}"/' \
|
|
${lib.escapeShellArg "${cfg.installDir}/ProjectZomboid64.json"}
|
|
${pkgs.coreutils}/bin/install -d -m 0750 \
|
|
${lib.escapeShellArg "${zomboidDir}/Server"}
|
|
{
|
|
${lib.concatMapStringsSep "\n " (line:
|
|
"${pkgs.coreutils}/bin/printf '%s\\n' ${lib.escapeShellArg line};"
|
|
) configLines}
|
|
${lib.optionalString (cfg.serverPropertiesFile != null)
|
|
"${pkgs.coreutils}/bin/cat ${lib.escapeShellArg cfg.serverPropertiesFile};"}
|
|
${lib.optionalString cfg.rcon.enable ''
|
|
${pkgs.coreutils}/bin/printf '%s\n' ${lib.escapeShellArg "RCONPort=${toString cfg.rcon.port}"};
|
|
${pkgs.coreutils}/bin/printf '%s' 'RCONPassword=';
|
|
${pkgs.coreutils}/bin/cat ${lib.escapeShellArg rconPasswordFile};
|
|
${pkgs.coreutils}/bin/printf '\n';
|
|
''}
|
|
} > ${lib.escapeShellArg "${zomboidDir}/Server/${cfg.serverName}.ini"}
|
|
${lib.optionalString (cfg.sandboxProperties != { }) ''
|
|
{
|
|
${pkgs.coreutils}/bin/printf '%s\n' 'SandboxVars = {';
|
|
${lib.concatMapStringsSep "\n " (line:
|
|
"${pkgs.coreutils}/bin/printf '%s\\n' ${lib.escapeShellArg line};"
|
|
) sandboxConfigLines}
|
|
${pkgs.coreutils}/bin/printf '%s\n' '};';
|
|
} > ${lib.escapeShellArg "${zomboidDir}/Server/${cfg.serverName}_SandboxVars.lua"}
|
|
''}
|
|
${lib.optionalString (cfg.sandboxProperties == { }) ''
|
|
${pkgs.coreutils}/bin/rm -f \
|
|
${lib.escapeShellArg "${zomboidDir}/Server/${cfg.serverName}_SandboxVars.lua"}
|
|
''}
|
|
'';
|
|
|
|
serviceConfig = {
|
|
User = "project-zomboid";
|
|
Group = "project-zomboid";
|
|
WorkingDirectory = cfg.dataDir;
|
|
Environment = [
|
|
"HOME=${cfg.dataDir}"
|
|
"SteamAppId=108600"
|
|
];
|
|
ExecStart = startScript;
|
|
Restart = "on-failure";
|
|
RestartSec = 5;
|
|
TimeoutStartSec = "15min";
|
|
TimeoutStopSec = 30;
|
|
UMask = "0077";
|
|
};
|
|
};
|
|
|
|
systemd.services.project-zomboid-backup = lib.mkIf backupCfg.enable {
|
|
description = "No-stop Project Zomboid backup";
|
|
after = [ "project-zomboid.service" ];
|
|
unitConfig.ConditionPathExists = [
|
|
saveDir
|
|
serverConfigDir
|
|
];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = "project-zomboid";
|
|
Group = "project-zomboid";
|
|
ExecStart = backupScript;
|
|
TimeoutStartSec = "30min";
|
|
UMask = "0077";
|
|
} // lib.optionalAttrs backupCfg.s3.enable {
|
|
EnvironmentFile = s3CredentialsFile;
|
|
};
|
|
};
|
|
|
|
systemd.timers.project-zomboid-backup = lib.mkIf backupCfg.enable {
|
|
description = "Run Project Zomboid backup";
|
|
wantedBy = [ "timers.target" ];
|
|
timerConfig = {
|
|
OnCalendar = backupCfg.onCalendar;
|
|
Persistent = true;
|
|
};
|
|
};
|
|
|
|
networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [
|
|
cfg.port
|
|
cfg.udpPort
|
|
];
|
|
};
|
|
}
|