at 17.09-beta 6.5 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.factorio; 7 factorio = pkgs.factorio-headless; 8 name = "Factorio"; 9 stateDir = "/var/lib/factorio"; 10 mkSavePath = name: "${stateDir}/saves/${name}.zip"; 11 configFile = pkgs.writeText "factorio.conf" '' 12 use-system-read-write-data-directories=true 13 [path] 14 read-data=${factorio}/share/factorio/data 15 write-data=${stateDir} 16 ''; 17 serverSettings = { 18 name = cfg.game-name; 19 description = cfg.description; 20 visibility = { 21 public = cfg.public; 22 lan = cfg.lan; 23 }; 24 username = cfg.username; 25 password = cfg.password; 26 token = cfg.token; 27 game_password = cfg.game-password; 28 require_user_verification = true; 29 max_upload_in_kilobytes_per_second = 0; 30 minimum_latency_in_ticks = 0; 31 ignore_player_limit_for_returning_players = false; 32 allow_commands = "admins-only"; 33 autosave_interval = cfg.autosave-interval; 34 autosave_slots = 5; 35 afk_autokick_interval = 0; 36 auto_pause = true; 37 only_admins_can_pause_the_game = true; 38 autosave_only_on_server = true; 39 admins = []; 40 }; 41 serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings)); 42 modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods; 43in 44{ 45 options = { 46 services.factorio = { 47 enable = mkEnableOption name; 48 port = mkOption { 49 type = types.int; 50 default = 34197; 51 description = '' 52 The port to which the service should bind. 53 54 This option will also open up the UDP port in the firewall configuration. 55 ''; 56 }; 57 saveName = mkOption { 58 type = types.string; 59 default = "default"; 60 description = '' 61 The name of the savegame that will be used by the server. 62 63 When not present in ${stateDir}/saves, a new map with default 64 settings will be generated before starting the service. 65 ''; 66 }; 67 # TODO Add more individual settings as nixos-options? 68 # TODO XXX The server tries to copy a newly created config file over the old one 69 # on shutdown, but fails, because it's in the nix store. When is this needed? 70 # Can an admin set options in-game and expect to have them persisted? 71 configFile = mkOption { 72 type = types.path; 73 default = configFile; 74 defaultText = "configFile"; 75 description = '' 76 The server's configuration file. 77 78 The default file generated by this module contains lines essential to 79 the server's operation. Use its contents as a basis for any 80 customizations. 81 ''; 82 }; 83 mods = mkOption { 84 type = types.listOf types.package; 85 default = []; 86 description = '' 87 Mods the server should install and activate. 88 89 The derivations in this list must "build" the mod by simply copying 90 the .zip, named correctly, into the output directory. Eventually, 91 there will be a way to pull in the most up-to-date list of 92 derivations via nixos-channel. Until then, this is for experts only. 93 ''; 94 }; 95 game-name = mkOption { 96 type = types.nullOr types.string; 97 default = "Factorio Game"; 98 description = '' 99 Name of the game as it will appear in the game listing. 100 ''; 101 }; 102 description = mkOption { 103 type = types.nullOr types.string; 104 default = ""; 105 description = '' 106 Description of the game that will appear in the listing. 107 ''; 108 }; 109 public = mkOption { 110 type = types.bool; 111 default = false; 112 description = '' 113 Game will be published on the official Factorio matching server. 114 ''; 115 }; 116 lan = mkOption { 117 type = types.bool; 118 default = false; 119 description = '' 120 Game will be broadcast on LAN. 121 ''; 122 }; 123 username = mkOption { 124 type = types.nullOr types.string; 125 default = null; 126 description = '' 127 Your factorio.com login credentials. Required for games with visibility public. 128 ''; 129 }; 130 password = mkOption { 131 type = types.nullOr types.string; 132 default = null; 133 description = '' 134 Your factorio.com login credentials. Required for games with visibility public. 135 ''; 136 }; 137 token = mkOption { 138 type = types.nullOr types.string; 139 default = null; 140 description = '' 141 Authentication token. May be used instead of 'password' above. 142 ''; 143 }; 144 game-password = mkOption { 145 type = types.nullOr types.string; 146 default = null; 147 description = '' 148 Game password. 149 ''; 150 }; 151 autosave-interval = mkOption { 152 type = types.nullOr types.int; 153 default = null; 154 example = 10; 155 description = '' 156 Autosave interval in minutes. 157 ''; 158 }; 159 }; 160 }; 161 162 config = mkIf cfg.enable { 163 users = { 164 users.factorio = { 165 uid = config.ids.uids.factorio; 166 description = "Factorio server user"; 167 group = "factorio"; 168 home = stateDir; 169 createHome = true; 170 }; 171 172 groups.factorio = { 173 gid = config.ids.gids.factorio; 174 }; 175 }; 176 177 systemd.services.factorio = { 178 description = "Factorio headless server"; 179 wantedBy = [ "multi-user.target" ]; 180 after = [ "network.target" ]; 181 182 preStart = toString [ 183 "test -e ${stateDir}/saves/${cfg.saveName}.zip" 184 "||" 185 "${factorio}/bin/factorio" 186 "--config=${cfg.configFile}" 187 "--create=${mkSavePath cfg.saveName}" 188 (optionalString (cfg.mods != []) "--mod-directory=${modDir}") 189 ]; 190 191 serviceConfig = { 192 User = "factorio"; 193 Group = "factorio"; 194 Restart = "always"; 195 KillSignal = "SIGINT"; 196 WorkingDirectory = stateDir; 197 PrivateTmp = true; 198 UMask = "0007"; 199 ExecStart = toString [ 200 "${factorio}/bin/factorio" 201 "--config=${cfg.configFile}" 202 "--port=${toString cfg.port}" 203 "--start-server=${mkSavePath cfg.saveName}" 204 "--server-settings=${serverSettingsFile}" 205 (optionalString (cfg.mods != []) "--mod-directory=${modDir}") 206 ]; 207 }; 208 }; 209 210 networking.firewall.allowedUDPPorts = [ cfg.port ]; 211 }; 212}