From e01eda6edd426904679d78c1803ce8ce57666487 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 7 Aug 2023 11:20:09 +1200 Subject: nixos/teeworlds: add more configuration options, revise - add 'package' option - add 'game' and 'server' attrset - reduce repetition by using functions bool and optionalSetting - add default value for cfg.name - revise some option descriptions --- nixos/modules/services/games/teeworlds.nix | 310 +++++++++++++++++++++++++++-- 1 file changed, 298 insertions(+), 12 deletions(-) (limited to 'nixos/modules/services/games') diff --git a/nixos/modules/services/games/teeworlds.nix b/nixos/modules/services/games/teeworlds.nix index bd0df1ffca578..04b611fb3cb14 100644 --- a/nixos/modules/services/games/teeworlds.nix +++ b/nixos/modules/services/games/teeworlds.nix @@ -6,13 +6,86 @@ let cfg = config.services.teeworlds; register = cfg.register; + bool = b: if b != null && b then "1" else "0"; + optionalSetting = s: setting: optionalString (s != null) "${setting} ${s}"; + lookup = attrs: key: default: if attrs ? key then attrs."${key}" else default; + + inactivePenaltyOptions = { + "spectator" = "1"; + "spectator/kick" = "2"; + "kick" = "3"; + }; + skillLevelOptions = { + "casual" = "0"; + "normal" = "1"; + "competitive" = "2"; + }; + tournamentModeOptions = { + "disable" = "0"; + "enable" = "1"; + "restrictSpectators" = "2"; + }; + 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}"} + sv_register ${bool cfg.register} + sv_name ${cfg.name} + ${optionalSetting cfg.motd "sv_motd"} + ${optionalSetting cfg.password "password"} + ${optionalSetting cfg.rconPassword "sv_rcon_password"} + + ${optionalSetting cfg.server.bindAddr "bindaddr"} + ${optionalSetting cfg.server.hostName "sv_hostname"} + sv_high_bandwidth ${bool cfg.server.enableHighBandwidth} + sv_inactivekick ${lookup inactivePenaltyOptions cfg.server.inactivePenalty "spectator/kick"} + sv_inactivekick_spec ${bool cfg.server.kickInactiveSpectators} + sv_inactivekick_time ${toString cfg.server.inactiveTime} + sv_max_clients ${toString cfg.server.maxClients} + sv_max_clients_per_ip ${toString cfg.server.maxClientsPerIP} + sv_skill_level ${lookup skillLevelOptions cfg.server.skillLevel "normal"} + sv_spamprotection ${bool cfg.server.enableSpamProtection} + + sv_gametype ${cfg.game.gameType} + sv_map ${cfg.game.map} + sv_match_swap ${bool cfg.game.swapTeams} + sv_player_ready_mode ${bool cfg.game.enableReadyMode} + sv_player_slots ${toString cfg.game.playerSlots} + sv_powerups ${bool cfg.game.enablePowerups} + sv_scorelimit ${toString cfg.game.scoreLimit} + sv_strict_spectate_mode ${bool cfg.game.restrictSpectators} + sv_teamdamage ${bool cfg.game.enableTeamDamage} + sv_timelimit ${toString cfg.game.timeLimit} + sv_tournament_mode ${lookup tournamentModeOptions cfg.server.tournamentMode "disable"} + sv_vote_kick ${bool cfg.game.enableVoteKick} + sv_vote_kick_bantime ${toString cfg.game.voteKickBanTime} + sv_vote_kick_min ${toString cfg.game.voteKickMinimumPlayers} + + ${optionalSetting cfg.server.bindAddr "bindaddr"} + ${optionalSetting cfg.server.hostName "sv_hostname"} + sv_high_bandwidth ${bool cfg.server.enableHighBandwidth} + sv_inactivekick ${lookup inactivePenaltyOptions cfg.server.inactivePenalty "spectator/kick"} + sv_inactivekick_spec ${bool cfg.server.kickInactiveSpectators} + sv_inactivekick_time ${toString cfg.server.inactiveTime} + sv_max_clients ${toString cfg.server.maxClients} + sv_max_clients_per_ip ${toString cfg.server.maxClientsPerIP} + sv_skill_level ${lookup skillLevelOptions cfg.server.skillLevel "normal"} + sv_spamprotection ${bool cfg.server.enableSpamProtection} + + sv_gametype ${cfg.game.gameType} + sv_map ${cfg.game.map} + sv_match_swap ${bool cfg.game.swapTeams} + sv_player_ready_mode ${bool cfg.game.enableReadyMode} + sv_player_slots ${toString cfg.game.playerSlots} + sv_powerups ${bool cfg.game.enablePowerups} + sv_scorelimit ${toString cfg.game.scoreLimit} + sv_strict_spectate_mode ${bool cfg.game.restrictSpectators} + sv_teamdamage ${bool cfg.game.enableTeamDamage} + sv_timelimit ${toString cfg.game.timeLimit} + sv_tournament_mode ${lookup tournamentModeOptions cfg.server.tournamentMode "disable"} + sv_vote_kick ${bool cfg.game.enableVoteKick} + sv_vote_kick_bantime ${toString cfg.game.voteKickBanTime} + sv_vote_kick_min ${toString cfg.game.voteKickMinimumPlayers} + ${concatStringsSep "\n" cfg.extraOptions} ''; @@ -22,17 +95,19 @@ in services.teeworlds = { enable = mkEnableOption (lib.mdDoc "Teeworlds Server"); + package = mkPackageOptionMD pkgs "teeworlds-server" { }; + openPorts = mkOption { type = types.bool; default = false; - description = lib.mdDoc "Whether to open firewall ports for Teeworlds"; + description = lib.mdDoc "Whether to open firewall ports for Teeworlds."; }; name = mkOption { - type = types.nullOr types.str; - default = null; + type = types.str; + default = "unnamed server"; description = lib.mdDoc '' - Name of the server. Defaults to 'unnamed server'. + Name of the server. ''; }; @@ -41,7 +116,7 @@ in example = true; default = false; description = lib.mdDoc '' - Whether the server registers as public server in the global server list. This is disabled by default because of privacy. + Whether the server registers as a public server in the global server list. This is disabled by default for privacy reasons. ''; }; @@ -49,7 +124,7 @@ in type = types.nullOr types.str; default = null; description = lib.mdDoc '' - Set the server message of the day text. + The server's message of the day text. ''; }; @@ -85,6 +160,217 @@ in ''; example = [ "sv_map dm1" "sv_gametype dm" ]; }; + + server = { + bindAddr = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc '' + The address the server will bind to. + ''; + }; + + enableHighBandwidth = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to enable high bandwidth mode on LAN servers. This will double the amount of bandwidth required for running the server. + ''; + }; + + hostName = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc '' + Hostname for the server. + ''; + }; + + inactivePenalty = mkOption { + type = types.enum [ "spectator" "spectator/kick" "kick" ]; + example = "spectator"; + default = "spectator/kick"; + description = lib.mdDoc '' + Specify what to do when a client goes inactive (see [](#opt-services.teeworlds.server.inactiveTime)). + + - `spectator`: send the client into spectator mode + + - `spectator/kick`: send the client into a free spectator slot, otherwise kick the client + + - `kick`: kick the client + ''; + }; + + kickInactiveSpectators = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to kick inactive spectators. + ''; + }; + + inactiveTime = mkOption { + type = types.ints.unsigned; + default = 3; + description = lib.mdDoc '' + The amount of minutes a client has to idle before it is considered inactive. + ''; + }; + + maxClients = mkOption { + type = types.ints.unsigned; + default = 12; + description = lib.mdDoc '' + The maximum amount of clients that can be connected to the server at the same time. + ''; + }; + + maxClientsPerIP = mkOption { + type = types.ints.unsigned; + default = 12; + description = lib.mdDoc '' + The maximum amount of clients with the same IP address that can be connected to the server at the same time. + ''; + }; + + skillLevel = mkOption { + type = types.enum [ "casual" "normal" "competitive" ]; + default = "normal"; + description = lib.mdDoc '' + The skill level shown in the server browser. + ''; + }; + + enableSpamProtection = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether to enable chat spam protection. + ''; + }; + }; + + game = { + gameType = mkOption { + type = types.str; + example = "ctf"; + default = "dm"; + description = lib.mdDoc '' + The game type to use on the server. + + The default gametypes are `dm`, `tdm`, `ctf`, `lms`, and `lts`. + ''; + }; + + map = mkOption { + type = types.str; + example = "ctf5"; + default = "dm1"; + description = lib.mdDoc '' + The map to use on the server. + ''; + }; + + swapTeams = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether to swap teams each round. + ''; + }; + + enableReadyMode = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to enable "ready mode"; where players can pause/unpause the game + and start the game in warmup, using their ready state. + ''; + }; + + playerSlots = mkOption { + type = types.ints.unsigned; + default = 8; + description = lib.mdDoc '' + The amount of slots to reserve for players (as opposed to spectators). + ''; + }; + + enablePowerups = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether to allow powerups such as the ninja. + ''; + }; + + scoreLimit = mkOption { + type = types.ints.unsigned; + example = 400; + default = 20; + description = lib.mdDoc '' + The score limit needed to win a round. + ''; + }; + + restrictSpectators = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to restrict access to information such as health, ammo and armour in spectator mode. + ''; + }; + + enableTeamDamage = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to enable team damage; whether to allow team mates to inflict damage on one another. + ''; + }; + + timeLimit = mkOption { + type = types.ints.unsigned; + default = 0; + description = lib.mdDoc '' + Time limit of the game. In cases of equal points, there will be sudden death. + Setting this to 0 disables a time limit. + ''; + }; + + tournamentMode = mkOption { + type = types.enum [ "disable" "enable" "restrictSpectators" ]; + default = "disable"; + description = lib.mdDoc '' + Whether to enable tournament mode. In tournament mode, players join as spectators. + If this is set to `restrictSpectators`, tournament mode is enabled but spectator chat is restricted. + ''; + }; + + enableVoteKick = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether to enable voting to kick players. + ''; + }; + + voteKickBanTime = mkOption { + type = types.ints.unsigned; + default = 5; + description = lib.mdDoc '' + The amount of minutes that a player is banned for if they get kicked by a vote. + ''; + }; + + voteKickMinimumPlayers = mkOption { + type = types.ints.unsigned; + default = 5; + description = lib.mdDoc '' + The minimum amount of players required to start a kick vote. + ''; + }; + }; }; }; @@ -100,7 +386,7 @@ in serviceConfig = { DynamicUser = true; - ExecStart = "${pkgs.teeworlds-server}/bin/teeworlds_srv -f ${teeworldsConf}"; + ExecStart = "${cfg.package}/bin/teeworlds_srv -f ${teeworldsConf}"; # Hardening CapabilityBoundingSet = false; -- cgit 1.4.1