1 { config, lib, pkgs, ... }:
3 inherit (lib) mkEnableOption mkIf mkOption mkMerge literalExpression;
4 inherit (lib) mapAttrsToList filterAttrs unique recursiveUpdate types;
6 mkValueStringArmagetron = with lib; v:
7 if isInt v then toString v
8 else if isFloat v then toString v
9 else if isString v then v
10 else if true == v then "1"
11 else if false == v then "0"
12 else if null == v then ""
13 else throw "unsupported type: ${builtins.typeOf v}: ${(lib.generators.toPretty {} v)}";
15 settingsFormat = pkgs.formats.keyValue {
16 mkKeyValue = lib.generators.mkKeyValueDefault
18 mkValueString = mkValueStringArmagetron;
20 listsAsDuplicateKeys = true;
23 cfg = config.services.armagetronad;
24 enabledServers = lib.filterAttrs (n: v: v.enable) cfg.servers;
25 nameToId = serverName: "armagetronad-${serverName}";
26 getStateDirectory = serverName: "armagetronad/${serverName}";
27 getServerRoot = serverName: "/var/lib/${getStateDirectory serverName}";
31 services.armagetronad = {
33 description = "Armagetron server definitions.";
35 type = types.attrsOf (types.submodule {
37 enable = mkEnableOption "armagetronad";
39 package = lib.mkPackageOption pkgs "armagetronad-dedicated" {
41 pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated
44 Ensure that you use a derivation which contains the path `bin/armagetronad-dedicated`.
51 description = "Host to listen on. Used for SERVER_IP.";
57 description = "Port to listen on. Used for SERVER_PORT.";
61 type = types.nullOr types.str;
63 description = "DNS address to use for this server. Optional.";
66 openFirewall = mkOption {
69 description = "Set to true to open the configured UDP port for Armagetron Advanced.";
74 description = "The name of this server.";
78 type = settingsFormat.type;
81 Armagetron Advanced server rules configuration. Refer to:
82 <https://wiki.armagetronad.org/index.php?title=Console_Commands>
83 or `armagetronad-dedicated --doc` for a list.
85 This attrset is used to populate `settings_custom.cfg`; see:
86 <https://wiki.armagetronad.org/index.php/Configuration_Files>
88 example = literalExpression ''
95 roundSettings = mkOption {
96 type = settingsFormat.type;
99 Armagetron Advanced server per-round configuration. Refer to:
100 <https://wiki.armagetronad.org/index.php?title=Console_Commands>
101 or `armagetronad-dedicated --doc` for a list.
103 This attrset is used to populate `everytime.cfg`; see:
104 <https://wiki.armagetronad.org/index.php/Configuration_Files>
106 example = literalExpression ''
111 "iD Tech High Rubber rul3z!! Happy New Year 2008!!1"
122 config = mkIf (enabledServers != { }) {
123 systemd.tmpfiles.settings = mkMerge (mapAttrsToList
124 (serverName: serverCfg:
126 serverId = nameToId serverName;
127 serverRoot = getServerRoot serverName;
130 SERVER_IP = serverCfg.host;
131 SERVER_PORT = serverCfg.port;
132 SERVER_NAME = serverCfg.name;
133 } // (lib.optionalAttrs (serverCfg.dns != null) { SERVER_DNS = serverCfg.dns; })
135 customSettings = serverCfg.settings;
136 everytimeSettings = serverCfg.roundSettings;
138 serverInfoCfg = settingsFormat.generate "server_info.${serverName}.cfg" serverInfo;
139 customSettingsCfg = settingsFormat.generate "settings_custom.${serverName}.cfg" customSettings;
140 everytimeSettingsCfg = settingsFormat.generate "everytime.${serverName}.cfg" everytimeSettings;
143 "10-armagetronad-${serverId}" = {
144 "${serverRoot}/data" = {
151 "${serverRoot}/settings" = {
158 "${serverRoot}/var" = {
165 "${serverRoot}/resource" = {
172 "${serverRoot}/input" = {
179 "${serverRoot}/settings/server_info.cfg" = {
181 argument = "${serverInfoCfg}";
184 "${serverRoot}/settings/settings_custom.cfg" = {
186 argument = "${customSettingsCfg}";
189 "${serverRoot}/settings/everytime.cfg" = {
191 argument = "${everytimeSettingsCfg}";
200 systemd.services = mkMerge (mapAttrsToList
201 (serverName: serverCfg:
203 serverId = nameToId serverName;
206 "armagetronad-${serverName}" = {
207 description = "Armagetron Advanced Dedicated Server for ${serverName}";
208 wants = [ "basic.target" ];
209 after = [ "basic.target" "network.target" "multi-user.target" ];
210 wantedBy = [ "multi-user.target" ];
213 serverRoot = getServerRoot serverName;
217 StateDirectory = getStateDirectory serverName;
218 ExecStart = "${lib.getExe serverCfg.package} --daemon --input ${serverRoot}/input --userdatadir ${serverRoot}/data --userconfigdir ${serverRoot}/settings --vardir ${serverRoot}/var --autoresourcedir ${serverRoot}/resource";
219 Restart = "on-failure";
220 CapabilityBoundingSet = "";
221 LockPersonality = true;
222 NoNewPrivileges = true;
223 PrivateDevices = true;
227 ProtectControlGroups = true;
229 ProtectHostname = true;
230 ProtectKernelLogs = true;
231 ProtectKernelModules = true;
232 ProtectKernelTunables = true;
233 ProtectProc = "invisible";
234 ProtectSystem = "strict";
235 RestrictNamespaces = true;
236 RestrictSUIDSGID = true;
245 networking.firewall.allowedUDPPorts =
246 unique (mapAttrsToList (serverName: serverCfg: serverCfg.port) (filterAttrs (serverName: serverCfg: serverCfg.openFirewall) enabledServers));
248 users.users = mkMerge (mapAttrsToList
249 (serverName: serverCfg:
251 ${nameToId serverName} = {
252 group = nameToId serverName;
253 description = "Armagetron Advanced dedicated user for server ${serverName}";
260 users.groups = mkMerge (mapAttrsToList
261 (serverName: serverCfg:
263 ${nameToId serverName} = { };