diff options
author | Mx Kookie <kookie@spacekookie.de> | 2020-10-31 19:35:09 +0100 |
---|---|---|
committer | Mx Kookie <kookie@spacekookie.de> | 2020-10-31 19:35:09 +0100 |
commit | c4625b175f8200f643fd6e11010932ea44c78433 (patch) | |
tree | bce3f89888c8ac3991fa5569a878a9eab6801ccc /infra/libkookie/nixpkgs/nixos/modules/services/games | |
parent | 49f735974dd103039ddc4cb576bb76555164a9e7 (diff) | |
parent | d661aa56a8843e991261510c1bb28fdc2f6975ae (diff) |
Add 'infra/libkookie/' from commit 'd661aa56a8843e991261510c1bb28fdc2f6975ae'
git-subtree-dir: infra/libkookie
git-subtree-mainline: 49f735974dd103039ddc4cb576bb76555164a9e7
git-subtree-split: d661aa56a8843e991261510c1bb28fdc2f6975ae
Diffstat (limited to 'infra/libkookie/nixpkgs/nixos/modules/services/games')
6 files changed, 913 insertions, 0 deletions
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/games/factorio.nix b/infra/libkookie/nixpkgs/nixos/modules/services/games/factorio.nix new file mode 100644 index 000000000000..4b2e1a3c07f0 --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/services/games/factorio.nix @@ -0,0 +1,242 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.factorio; + name = "Factorio"; + stateDir = "/var/lib/${cfg.stateDirName}"; + mkSavePath = name: "${stateDir}/saves/${name}.zip"; + configFile = pkgs.writeText "factorio.conf" '' + use-system-read-write-data-directories=true + [path] + read-data=${cfg.package}/share/factorio/data + write-data=${stateDir} + ''; + serverSettings = { + name = cfg.game-name; + description = cfg.description; + visibility = { + public = cfg.public; + lan = cfg.lan; + }; + username = cfg.username; + password = cfg.password; + token = cfg.token; + game_password = cfg.game-password; + require_user_verification = cfg.requireUserVerification; + max_upload_in_kilobytes_per_second = 0; + minimum_latency_in_ticks = 0; + ignore_player_limit_for_returning_players = false; + allow_commands = "admins-only"; + autosave_interval = cfg.autosave-interval; + autosave_slots = 5; + afk_autokick_interval = 0; + auto_pause = true; + only_admins_can_pause_the_game = true; + autosave_only_on_server = true; + admins = []; + } // cfg.extraSettings; + serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings)); + modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods; +in +{ + options = { + services.factorio = { + enable = mkEnableOption name; + port = mkOption { + type = types.int; + default = 34197; + description = '' + The port to which the service should bind. + + This option will also open up the UDP port in the firewall configuration. + ''; + }; + saveName = mkOption { + type = types.str; + default = "default"; + description = '' + The name of the savegame that will be used by the server. + + When not present in ${stateDir}/saves, a new map with default + settings will be generated before starting the service. + ''; + }; + # TODO Add more individual settings as nixos-options? + # TODO XXX The server tries to copy a newly created config file over the old one + # on shutdown, but fails, because it's in the nix store. When is this needed? + # Can an admin set options in-game and expect to have them persisted? + configFile = mkOption { + type = types.path; + default = configFile; + defaultText = "configFile"; + description = '' + The server's configuration file. + + The default file generated by this module contains lines essential to + the server's operation. Use its contents as a basis for any + customizations. + ''; + }; + stateDirName = mkOption { + type = types.str; + default = "factorio"; + description = '' + Name of the directory under /var/lib holding the server's data. + + The configuration and map will be stored here. + ''; + }; + mods = mkOption { + type = types.listOf types.package; + default = []; + description = '' + Mods the server should install and activate. + + The derivations in this list must "build" the mod by simply copying + the .zip, named correctly, into the output directory. Eventually, + there will be a way to pull in the most up-to-date list of + derivations via nixos-channel. Until then, this is for experts only. + ''; + }; + game-name = mkOption { + type = types.nullOr types.str; + default = "Factorio Game"; + description = '' + Name of the game as it will appear in the game listing. + ''; + }; + description = mkOption { + type = types.nullOr types.str; + default = ""; + description = '' + Description of the game that will appear in the listing. + ''; + }; + extraSettings = mkOption { + type = types.attrs; + default = {}; + example = { admins = [ "username" ];}; + description = '' + Extra game configuration that will go into server-settings.json + ''; + }; + public = mkOption { + type = types.bool; + default = false; + description = '' + Game will be published on the official Factorio matching server. + ''; + }; + lan = mkOption { + type = types.bool; + default = false; + description = '' + Game will be broadcast on LAN. + ''; + }; + username = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Your factorio.com login credentials. Required for games with visibility public. + ''; + }; + package = mkOption { + type = types.package; + default = pkgs.factorio-headless; + defaultText = "pkgs.factorio-headless"; + example = "pkgs.factorio-headless-experimental"; + description = '' + Factorio version to use. This defaults to the stable channel. + ''; + }; + password = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Your factorio.com login credentials. Required for games with visibility public. + ''; + }; + token = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Authentication token. May be used instead of 'password' above. + ''; + }; + game-password = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Game password. + ''; + }; + requireUserVerification = mkOption { + type = types.bool; + default = true; + description = '' + When set to true, the server will only allow clients that have a valid factorio.com account. + ''; + }; + autosave-interval = mkOption { + type = types.nullOr types.int; + default = null; + example = 10; + description = '' + Autosave interval in minutes. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.factorio = { + description = "Factorio headless server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + preStart = toString [ + "test -e ${stateDir}/saves/${cfg.saveName}.zip" + "||" + "${cfg.package}/bin/factorio" + "--config=${cfg.configFile}" + "--create=${mkSavePath cfg.saveName}" + (optionalString (cfg.mods != []) "--mod-directory=${modDir}") + ]; + + serviceConfig = { + Restart = "always"; + KillSignal = "SIGINT"; + DynamicUser = true; + StateDirectory = cfg.stateDirName; + UMask = "0007"; + ExecStart = toString [ + "${cfg.package}/bin/factorio" + "--config=${cfg.configFile}" + "--port=${toString cfg.port}" + "--start-server=${mkSavePath cfg.saveName}" + "--server-settings=${serverSettingsFile}" + (optionalString (cfg.mods != []) "--mod-directory=${modDir}") + ]; + + # Sandboxing + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ]; + RestrictRealtime = true; + RestrictNamespaces = true; + MemoryDenyWriteExecute = true; + }; + }; + + networking.firewall.allowedUDPPorts = [ cfg.port ]; + }; +} diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/games/minecraft-server.nix b/infra/libkookie/nixpkgs/nixos/modules/services/games/minecraft-server.nix new file mode 100644 index 000000000000..eb9288fca586 --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/services/games/minecraft-server.nix @@ -0,0 +1,234 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.minecraft-server; + + # We don't allow eula=false anyways + eulaFile = builtins.toFile "eula.txt" '' + # eula.txt managed by NixOS Configuration + eula=true + ''; + + whitelistFile = pkgs.writeText "whitelist.json" + (builtins.toJSON + (mapAttrsToList (n: v: { name = n; uuid = v; }) cfg.whitelist)); + + cfgToString = v: if builtins.isBool v then boolToString v else toString v; + + serverPropertiesFile = pkgs.writeText "server.properties" ('' + # server.properties managed by NixOS configuration + '' + concatStringsSep "\n" (mapAttrsToList + (n: v: "${n}=${cfgToString v}") cfg.serverProperties)); + + + # To be able to open the firewall, we need to read out port values in the + # server properties, but fall back to the defaults when those don't exist. + # These defaults are from https://minecraft.gamepedia.com/Server.properties#Java_Edition_3 + defaultServerPort = 25565; + + serverPort = cfg.serverProperties.server-port or defaultServerPort; + + rconPort = if cfg.serverProperties.enable-rcon or false + then cfg.serverProperties."rcon.port" or 25575 + else null; + + queryPort = if cfg.serverProperties.enable-query or false + then cfg.serverProperties."query.port" or 25565 + else null; + +in { + options = { + services.minecraft-server = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, start a Minecraft Server. The server + data will be loaded from and saved to + <option>services.minecraft-server.dataDir</option>. + ''; + }; + + declarative = mkOption { + type = types.bool; + default = false; + description = '' + Whether to use a declarative Minecraft server configuration. + Only if set to <literal>true</literal>, the options + <option>services.minecraft-server.whitelist</option> and + <option>services.minecraft-server.serverProperties</option> will be + applied. + ''; + }; + + eula = mkOption { + type = types.bool; + default = false; + description = '' + Whether you agree to + <link xlink:href="https://account.mojang.com/documents/minecraft_eula"> + Mojangs EULA</link>. This option must be set to + <literal>true</literal> to run Minecraft server. + ''; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/minecraft"; + description = '' + Directory to store Minecraft database and other state/data files. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Whether to open ports in the firewall for the server. + ''; + }; + + whitelist = mkOption { + type = let + minecraftUUID = types.strMatching + "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // { + description = "Minecraft UUID"; + }; + in types.attrsOf minecraftUUID; + default = {}; + description = '' + Whitelisted players, only has an effect when + <option>services.minecraft-server.declarative</option> is + <literal>true</literal> and the whitelist is enabled + via <option>services.minecraft-server.serverProperties</option> by + setting <literal>white-list</literal> to <literal>true</literal>. + This is a mapping from Minecraft usernames to UUIDs. + You can use <link xlink:href="https://mcuuid.net/"/> to get a + Minecraft UUID for a username. + ''; + example = literalExample '' + { + username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"; + }; + ''; + }; + + serverProperties = mkOption { + type = with types; attrsOf (oneOf [ bool int str ]); + default = {}; + example = literalExample '' + { + server-port = 43000; + difficulty = 3; + gamemode = 1; + max-players = 5; + motd = "NixOS Minecraft server!"; + white-list = true; + enable-rcon = true; + "rcon.password" = "hunter2"; + } + ''; + description = '' + Minecraft server properties for the server.properties file. Only has + an effect when <option>services.minecraft-server.declarative</option> + is set to <literal>true</literal>. See + <link xlink:href="https://minecraft.gamepedia.com/Server.properties#Java_Edition_3"/> + for documentation on these values. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.minecraft-server; + defaultText = "pkgs.minecraft-server"; + example = literalExample "pkgs.minecraft-server_1_12_2"; + description = "Version of minecraft-server to run."; + }; + + jvmOpts = mkOption { + type = types.separatedString " "; + default = "-Xmx2048M -Xms2048M"; + # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script + example = "-Xmx2048M -Xms4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing " + + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 " + + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10"; + description = "JVM options for the Minecraft server."; + }; + }; + }; + + config = mkIf cfg.enable { + + users.users.minecraft = { + description = "Minecraft server service user"; + home = cfg.dataDir; + createHome = true; + uid = config.ids.uids.minecraft; + }; + + systemd.services.minecraft-server = { + description = "Minecraft Server Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}"; + Restart = "always"; + User = "minecraft"; + WorkingDirectory = cfg.dataDir; + }; + + preStart = '' + ln -sf ${eulaFile} eula.txt + '' + (if cfg.declarative then '' + + if [ -e .declarative ]; then + + # Was declarative before, no need to back up anything + ln -sf ${whitelistFile} whitelist.json + cp -f ${serverPropertiesFile} server.properties + + else + + # Declarative for the first time, backup stateful files + ln -sb --suffix=.stateful ${whitelistFile} whitelist.json + cp -b --suffix=.stateful ${serverPropertiesFile} server.properties + + # server.properties must have write permissions, because every time + # the server starts it first parses the file and then regenerates it.. + chmod +w server.properties + echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \ + > .declarative + + fi + '' else '' + if [ -e .declarative ]; then + rm .declarative + fi + ''); + }; + + networking.firewall = mkIf cfg.openFirewall (if cfg.declarative then { + allowedUDPPorts = [ serverPort ]; + allowedTCPPorts = [ serverPort ] + ++ optional (queryPort != null) queryPort + ++ optional (rconPort != null) rconPort; + } else { + allowedUDPPorts = [ defaultServerPort ]; + allowedTCPPorts = [ defaultServerPort ]; + }); + + assertions = [ + { assertion = cfg.eula; + message = "You must agree to Mojangs EULA to run minecraft-server." + + " Read https://account.mojang.com/documents/minecraft_eula and" + + " set `services.minecraft-server.eula` to `true` if you agree."; + } + ]; + + }; +} diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/games/minetest-server.nix b/infra/libkookie/nixpkgs/nixos/modules/services/games/minetest-server.nix new file mode 100644 index 000000000000..f52079fc1ef6 --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/services/games/minetest-server.nix @@ -0,0 +1,107 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.minetest-server; + flag = val: name: if val != null then "--${name} ${val} " else ""; + flags = [ + (flag cfg.gameId "gameid") + (flag cfg.world "world") + (flag cfg.configPath "config") + (flag cfg.logPath "logfile") + (flag cfg.port "port") + ]; +in +{ + options = { + services.minetest-server = { + enable = mkOption { + type = types.bool; + default = false; + description = "If enabled, starts a Minetest Server."; + }; + + gameId = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Id of the game to use. To list available games run + `minetestserver --gameid list`. + + If only one game exists, this option can be null. + ''; + }; + + world = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Name of the world to use. To list available worlds run + `minetestserver --world list`. + + If only one world exists, this option can be null. + ''; + }; + + configPath = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to the config to use. + + If set to null, the config of the running user will be used: + `~/.minetest/minetest.conf`. + ''; + }; + + logPath = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to logfile for logging. + + If set to null, logging will be output to stdout which means + all output will be catched by systemd. + ''; + }; + + port = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + Port number to bind to. + + If set to null, the default 30000 will be used. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + users.users.minetest = { + description = "Minetest Server Service user"; + home = "/var/lib/minetest"; + createHome = true; + uid = config.ids.uids.minetest; + group = "minetest"; + }; + users.groups.minetest.gid = config.ids.gids.minetest; + + systemd.services.minetest-server = { + description = "Minetest Server Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig.Restart = "always"; + serviceConfig.User = "minetest"; + serviceConfig.Group = "minetest"; + + script = '' + cd /var/lib/minetest + + exec ${pkgs.minetest}/bin/minetest --server ${concatStrings flags} + ''; + }; + }; +} diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/games/openarena.nix b/infra/libkookie/nixpkgs/nixos/modules/services/games/openarena.nix new file mode 100644 index 000000000000..8c014d78809b --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/services/games/openarena.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.openarena; +in +{ + options = { + services.openarena = { + enable = mkEnableOption "OpenArena"; + + openPorts = mkOption { + type = types.bool; + default = false; + description = "Whether to open firewall ports for OpenArena"; + }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = []; + description = ''Extra flags to pass to <command>oa_ded</command>''; + example = [ + "+set dedicated 2" + "+set sv_hostname 'My NixOS OpenArena Server'" + # Load a map. Mandatory for clients to be able to connect. + "+map oa_dm1" + ]; + }; + }; + }; + + config = mkIf cfg.enable { + networking.firewall = mkIf cfg.openPorts { + allowedUDPPorts = [ 27960 ]; + }; + + systemd.services.openarena = { + description = "OpenArena"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + DynamicUser = true; + StateDirectory = "openarena"; + ExecStart = "${pkgs.openarena}/bin/oa_ded +set fs_basepath ${pkgs.openarena}/openarena-0.8.8 +set fs_homepath /var/lib/openarena ${concatStringsSep " " cfg.extraFlags}"; + Restart = "on-failure"; + + # Hardening + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + PrivateDevices = true; + }; + }; + }; +} diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/games/teeworlds.nix b/infra/libkookie/nixpkgs/nixos/modules/services/games/teeworlds.nix new file mode 100644 index 000000000000..babf989c98ca --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/services/games/teeworlds.nix @@ -0,0 +1,119 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.teeworlds; + register = cfg.register; + + teeworldsConf = pkgs.writeText "teeworlds.cfg" '' + sv_port ${toString cfg.port} + sv_register ${if cfg.register then "1" else "0"} + ${optionalString (cfg.name != null) "sv_name ${cfg.name}"} + ${optionalString (cfg.motd != null) "sv_motd ${cfg.motd}"} + ${optionalString (cfg.password != null) "password ${cfg.password}"} + ${optionalString (cfg.rconPassword != null) "sv_rcon_password ${cfg.rconPassword}"} + ${concatStringsSep "\n" cfg.extraOptions} + ''; + +in +{ + options = { + services.teeworlds = { + enable = mkEnableOption "Teeworlds Server"; + + openPorts = mkOption { + type = types.bool; + default = false; + description = "Whether to open firewall ports for Teeworlds"; + }; + + name = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Name of the server. Defaults to 'unnamed server'. + ''; + }; + + register = mkOption { + type = types.bool; + example = true; + default = false; + description = '' + Whether the server registers as public server in the global server list. This is disabled by default because of privacy. + ''; + }; + + motd = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Set the server message of the day text. + ''; + }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Password to connect to the server. + ''; + }; + + rconPassword = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Password to access the remote console. If not set, a randomly generated one is displayed in the server log. + ''; + }; + + port = mkOption { + type = types.int; + default = 8303; + description = '' + Port the server will listen on. + ''; + }; + + extraOptions = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Extra configuration lines for the <filename>teeworlds.cfg</filename>. See <link xlink:href="https://www.teeworlds.com/?page=docs&wiki=server_settings">Teeworlds Documentation</link>. + ''; + example = [ "sv_map dm1" "sv_gametype dm" ]; + }; + }; + }; + + config = mkIf cfg.enable { + networking.firewall = mkIf cfg.openPorts { + allowedUDPPorts = [ cfg.port ]; + }; + + systemd.services.teeworlds = { + description = "Teeworlds Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + DynamicUser = true; + ExecStart = "${pkgs.teeworlds}/bin/teeworlds_srv -f ${teeworldsConf}"; + + # Hardening + CapabilityBoundingSet = false; + PrivateDevices = true; + PrivateUsers = true; + ProtectHome = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + SystemCallArchitectures = "native"; + }; + }; + }; +} diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/games/terraria.nix b/infra/libkookie/nixpkgs/nixos/modules/services/games/terraria.nix new file mode 100644 index 000000000000..34c8ff137d6a --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/services/games/terraria.nix @@ -0,0 +1,155 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.terraria; + worldSizeMap = { small = 1; medium = 2; large = 3; }; + valFlag = name: val: optionalString (val != null) "-${name} \"${escape ["\\" "\""] (toString val)}\""; + boolFlag = name: val: optionalString val "-${name}"; + flags = [ + (valFlag "port" cfg.port) + (valFlag "maxPlayers" cfg.maxPlayers) + (valFlag "password" cfg.password) + (valFlag "motd" cfg.messageOfTheDay) + (valFlag "world" cfg.worldPath) + (valFlag "autocreate" (builtins.getAttr cfg.autoCreatedWorldSize worldSizeMap)) + (valFlag "banlist" cfg.banListPath) + (boolFlag "secure" cfg.secure) + (boolFlag "noupnp" cfg.noUPnP) + ]; + stopScript = pkgs.writeScript "terraria-stop" '' + #!${pkgs.runtimeShell} + + if ! [ -d "/proc/$1" ]; then + exit 0 + fi + + ${getBin pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/terraria.sock send-keys Enter exit Enter + ${getBin pkgs.coreutils}/bin/tail --pid="$1" -f /dev/null + ''; +in +{ + options = { + services.terraria = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, starts a Terraria server. The server can be connected to via <literal>tmux -S ${cfg.dataDir}/terraria.sock attach</literal> + for administration by users who are a part of the <literal>terraria</literal> group (use <literal>C-b d</literal> shortcut to detach again). + ''; + }; + + port = mkOption { + type = types.int; + default = 7777; + description = '' + Specifies the port to listen on. + ''; + }; + + maxPlayers = mkOption { + type = types.int; + default = 255; + description = '' + Sets the max number of players (between 1 and 255). + ''; + }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Sets the server password. Leave <literal>null</literal> for no password. + ''; + }; + + messageOfTheDay = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Set the server message of the day text. + ''; + }; + + worldPath = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + The path to the world file (<literal>.wld</literal>) which should be loaded. + If no world exists at this path, one will be created with the size + specified by <literal>autoCreatedWorldSize</literal>. + ''; + }; + + autoCreatedWorldSize = mkOption { + type = types.enum [ "small" "medium" "large" ]; + default = "medium"; + description = '' + Specifies the size of the auto-created world if <literal>worldPath</literal> does not + point to an existing world. + ''; + }; + + banListPath = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + The path to the ban list. + ''; + }; + + secure = mkOption { + type = types.bool; + default = false; + description = "Adds additional cheat protection to the server."; + }; + + noUPnP = mkOption { + type = types.bool; + default = false; + description = "Disables automatic Universal Plug and Play."; + }; + dataDir = mkOption { + type = types.str; + default = "/var/lib/terraria"; + example = "/srv/terraria"; + description = "Path to variable state data directory for terraria."; + }; + }; + }; + + config = mkIf cfg.enable { + users.users.terraria = { + description = "Terraria server service user"; + home = cfg.dataDir; + createHome = true; + uid = config.ids.uids.terraria; + }; + + users.groups.terraria = { + gid = config.ids.gids.terraria; + members = [ "terraria" ]; + }; + + systemd.services.terraria = { + description = "Terraria Server Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + User = "terraria"; + Type = "forking"; + GuessMainPID = true; + ExecStart = "${getBin pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/terraria.sock new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}"; + ExecStop = "${stopScript} $MAINPID"; + }; + + postStart = '' + ${pkgs.coreutils}/bin/chmod 660 ${cfg.dataDir}/terraria.sock + ${pkgs.coreutils}/bin/chgrp terraria ${cfg.dataDir}/terraria.sock + ''; + }; + }; +} |