diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 978d33e7585..5c5281b730f 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -334,6 +334,7 @@ ./services/games/minecraft-server.nix ./services/games/minetest-server.nix ./services/games/openarena.nix + ./services/games/teeworlds.nix ./services/games/terraria.nix ./services/hardware/acpid.nix ./services/hardware/actkbd.nix diff --git a/nixos/modules/services/games/teeworlds.nix b/nixos/modules/services/games/teeworlds.nix new file mode 100644 index 00000000000..babf989c98c --- /dev/null +++ b/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 teeworlds.cfg. See Teeworlds Documentation. + ''; + 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/nixos/tests/teeworlds.nix b/nixos/tests/teeworlds.nix new file mode 100644 index 00000000000..edf58896878 --- /dev/null +++ b/nixos/tests/teeworlds.nix @@ -0,0 +1,55 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +let + client = + { pkgs, ... }: + + { imports = [ ./common/x11.nix ]; + environment.systemPackages = [ pkgs.teeworlds ]; + }; + +in { + name = "teeworlds"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ hax404 ]; + }; + + nodes = + { server = + { services.teeworlds = { + enable = true; + openPorts = true; + }; + }; + + client1 = client; + client2 = client; + }; + + testScript = + '' + start_all() + + server.wait_for_unit("teeworlds.service") + server.wait_until_succeeds("ss --numeric --udp --listening | grep -q 8303") + + client1.wait_for_x() + client2.wait_for_x() + + client1.execute("teeworlds 'player_name Alice;connect server'&") + server.wait_until_succeeds( + 'journalctl -u teeworlds -e | grep --extended-regexp -q "team_join player=\'[0-9]:Alice"' + ) + + client2.execute("teeworlds 'player_name Bob;connect server'&") + server.wait_until_succeeds( + 'journalctl -u teeworlds -e | grep --extended-regexp -q "team_join player=\'[0-9]:Bob"' + ) + + server.sleep(10) # wait for a while to get a nice screenshot + + client1.screenshot("screen_client1") + client2.screenshot("screen_client2") + ''; + +}) diff --git a/pkgs/games/teeworlds/default.nix b/pkgs/games/teeworlds/default.nix index 3035c02e262..9ff50d533be 100644 --- a/pkgs/games/teeworlds/default.nix +++ b/pkgs/games/teeworlds/default.nix @@ -1,5 +1,6 @@ { fetchFromGitHub, stdenv, cmake, pkgconfig, python3, alsaLib , libX11, libGLU, SDL2, lua5_3, zlib, freetype, wavpack, icoutils +, nixosTests }: stdenv.mkDerivation rec { @@ -36,6 +37,8 @@ stdenv.mkDerivation rec { install -D $src/other/teeworlds.desktop $out/share/applications/teeworlds.desktop ''; + passthru.tests.teeworlds = nixosTests.teeworlds; + meta = { description = "Retro multiplayer shooter game";