at master 9.4 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 inherit (lib) 9 mkEnableOption 10 mkIf 11 mkOption 12 mkMerge 13 literalExpression 14 ; 15 inherit (lib) 16 mapAttrsToList 17 filterAttrs 18 unique 19 recursiveUpdate 20 types 21 ; 22 23 mkValueStringArmagetron = 24 with lib; 25 v: 26 if isInt v then 27 toString v 28 else if isFloat v then 29 toString v 30 else if isString v then 31 v 32 else if true == v then 33 "1" 34 else if false == v then 35 "0" 36 else if null == v then 37 "" 38 else 39 throw "unsupported type: ${builtins.typeOf v}: ${(lib.generators.toPretty { } v)}"; 40 41 settingsFormat = pkgs.formats.keyValue { 42 mkKeyValue = lib.generators.mkKeyValueDefault { 43 mkValueString = mkValueStringArmagetron; 44 } " "; 45 listsAsDuplicateKeys = true; 46 }; 47 48 cfg = config.services.armagetronad; 49 enabledServers = lib.filterAttrs (n: v: v.enable) cfg.servers; 50 nameToId = serverName: "armagetronad-${serverName}"; 51 getStateDirectory = serverName: "armagetronad/${serverName}"; 52 getServerRoot = serverName: "/var/lib/${getStateDirectory serverName}"; 53in 54{ 55 options = { 56 services.armagetronad = { 57 servers = mkOption { 58 description = "Armagetron server definitions."; 59 default = { }; 60 type = types.attrsOf ( 61 types.submodule { 62 options = { 63 enable = mkEnableOption "armagetronad"; 64 65 package = lib.mkPackageOption pkgs "armagetronad-dedicated" { 66 example = '' 67 pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated 68 ''; 69 extraDescription = '' 70 Ensure that you use a derivation which contains the path `bin/armagetronad-dedicated`. 71 ''; 72 }; 73 74 host = mkOption { 75 type = types.str; 76 default = "0.0.0.0"; 77 description = "Host to listen on. Used for SERVER_IP."; 78 }; 79 80 port = mkOption { 81 type = types.port; 82 default = 4534; 83 description = "Port to listen on. Used for SERVER_PORT."; 84 }; 85 86 dns = mkOption { 87 type = types.nullOr types.str; 88 default = null; 89 description = "DNS address to use for this server. Optional."; 90 }; 91 92 openFirewall = mkOption { 93 type = types.bool; 94 default = true; 95 description = "Set to true to open the configured UDP port for Armagetron Advanced."; 96 }; 97 98 name = mkOption { 99 type = types.str; 100 description = "The name of this server."; 101 }; 102 103 settings = mkOption { 104 type = settingsFormat.type; 105 default = { }; 106 description = '' 107 Armagetron Advanced server rules configuration. Refer to: 108 <https://wiki.armagetronad.org/index.php?title=Console_Commands> 109 or `armagetronad-dedicated --doc` for a list. 110 111 This attrset is used to populate `settings_custom.cfg`; see: 112 <https://wiki.armagetronad.org/index.php/Configuration_Files> 113 ''; 114 example = literalExpression '' 115 { 116 CYCLE_RUBBER = 40; 117 } 118 ''; 119 }; 120 121 roundSettings = mkOption { 122 type = settingsFormat.type; 123 default = { }; 124 description = '' 125 Armagetron Advanced server per-round configuration. Refer to: 126 <https://wiki.armagetronad.org/index.php?title=Console_Commands> 127 or `armagetronad-dedicated --doc` for a list. 128 129 This attrset is used to populate `everytime.cfg`; see: 130 <https://wiki.armagetronad.org/index.php/Configuration_Files> 131 ''; 132 example = literalExpression '' 133 { 134 SAY = [ 135 "Hosted on NixOS" 136 "https://nixos.org" 137 "iD Tech High Rubber rul3z!! Happy New Year 2008!!1" 138 ]; 139 } 140 ''; 141 }; 142 }; 143 } 144 ); 145 }; 146 }; 147 }; 148 149 config = mkIf (enabledServers != { }) { 150 systemd.tmpfiles.settings = mkMerge ( 151 mapAttrsToList ( 152 serverName: serverCfg: 153 let 154 serverId = nameToId serverName; 155 serverRoot = getServerRoot serverName; 156 serverInfo = ( 157 { 158 SERVER_IP = serverCfg.host; 159 SERVER_PORT = serverCfg.port; 160 SERVER_NAME = serverCfg.name; 161 } 162 // (lib.optionalAttrs (serverCfg.dns != null) { SERVER_DNS = serverCfg.dns; }) 163 ); 164 customSettings = serverCfg.settings; 165 everytimeSettings = serverCfg.roundSettings; 166 167 serverInfoCfg = settingsFormat.generate "server_info.${serverName}.cfg" serverInfo; 168 customSettingsCfg = settingsFormat.generate "settings_custom.${serverName}.cfg" customSettings; 169 everytimeSettingsCfg = settingsFormat.generate "everytime.${serverName}.cfg" everytimeSettings; 170 in 171 { 172 "10-armagetronad-${serverId}" = { 173 "${serverRoot}/data" = { 174 d = { 175 group = serverId; 176 user = serverId; 177 mode = "0750"; 178 }; 179 }; 180 "${serverRoot}/settings" = { 181 d = { 182 group = serverId; 183 user = serverId; 184 mode = "0750"; 185 }; 186 }; 187 "${serverRoot}/var" = { 188 d = { 189 group = serverId; 190 user = serverId; 191 mode = "0750"; 192 }; 193 }; 194 "${serverRoot}/resource" = { 195 d = { 196 group = serverId; 197 user = serverId; 198 mode = "0750"; 199 }; 200 }; 201 "${serverRoot}/input" = { 202 "f+" = { 203 group = serverId; 204 user = serverId; 205 mode = "0640"; 206 }; 207 }; 208 "${serverRoot}/settings/server_info.cfg" = { 209 "L+" = { 210 argument = "${serverInfoCfg}"; 211 }; 212 }; 213 "${serverRoot}/settings/settings_custom.cfg" = { 214 "L+" = { 215 argument = "${customSettingsCfg}"; 216 }; 217 }; 218 "${serverRoot}/settings/everytime.cfg" = { 219 "L+" = { 220 argument = "${everytimeSettingsCfg}"; 221 }; 222 }; 223 }; 224 } 225 ) enabledServers 226 ); 227 228 systemd.services = mkMerge ( 229 mapAttrsToList ( 230 serverName: serverCfg: 231 let 232 serverId = nameToId serverName; 233 in 234 { 235 "armagetronad-${serverName}" = { 236 description = "Armagetron Advanced Dedicated Server for ${serverName}"; 237 wants = [ "basic.target" ]; 238 after = [ 239 "basic.target" 240 "network.target" 241 "multi-user.target" 242 ]; 243 wantedBy = [ "multi-user.target" ]; 244 serviceConfig = 245 let 246 serverRoot = getServerRoot serverName; 247 in 248 { 249 Type = "simple"; 250 StateDirectory = getStateDirectory serverName; 251 ExecStart = "${lib.getExe serverCfg.package} --daemon --input ${serverRoot}/input --userdatadir ${serverRoot}/data --userconfigdir ${serverRoot}/settings --vardir ${serverRoot}/var --autoresourcedir ${serverRoot}/resource"; 252 Restart = "on-failure"; 253 CapabilityBoundingSet = ""; 254 LockPersonality = true; 255 NoNewPrivileges = true; 256 PrivateDevices = true; 257 PrivateTmp = true; 258 PrivateUsers = true; 259 ProtectClock = true; 260 ProtectControlGroups = true; 261 ProtectHome = true; 262 ProtectHostname = true; 263 ProtectKernelLogs = true; 264 ProtectKernelModules = true; 265 ProtectKernelTunables = true; 266 ProtectProc = "invisible"; 267 ProtectSystem = "strict"; 268 RestrictNamespaces = true; 269 RestrictSUIDSGID = true; 270 User = serverId; 271 Group = serverId; 272 }; 273 }; 274 } 275 ) enabledServers 276 ); 277 278 networking.firewall.allowedUDPPorts = unique ( 279 mapAttrsToList (serverName: serverCfg: serverCfg.port) ( 280 filterAttrs (serverName: serverCfg: serverCfg.openFirewall) enabledServers 281 ) 282 ); 283 284 users.users = mkMerge ( 285 mapAttrsToList (serverName: serverCfg: { 286 ${nameToId serverName} = { 287 group = nameToId serverName; 288 description = "Armagetron Advanced dedicated user for server ${serverName}"; 289 isSystemUser = true; 290 }; 291 }) enabledServers 292 ); 293 294 users.groups = mkMerge ( 295 mapAttrsToList (serverName: serverCfg: { 296 ${nameToId serverName} = { }; 297 }) enabledServers 298 ); 299 }; 300}