at master 7.5 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.freeciv; 9 inherit (config.users) groups; 10 rootDir = "/run/freeciv"; 11 argsFormat = { 12 type = 13 with lib.types; 14 let 15 valueType = 16 nullOr (oneOf [ 17 bool 18 int 19 float 20 str 21 (listOf valueType) 22 ]) 23 // { 24 description = "freeciv-server params"; 25 }; 26 in 27 valueType; 28 generate = 29 name: value: 30 let 31 mkParam = 32 k: v: 33 if v == null then 34 [ ] 35 else if lib.isBool v then 36 lib.optional v ("--" + k) 37 else 38 [ 39 ("--" + k) 40 v 41 ]; 42 mkParams = k: v: map (mkParam k) (if lib.isList v then v else [ v ]); 43 in 44 lib.escapeShellArgs (lib.concatLists (lib.concatLists (lib.mapAttrsToList mkParams value))); 45 }; 46in 47{ 48 options = { 49 services.freeciv = { 50 enable = lib.mkEnableOption ''freeciv''; 51 settings = lib.mkOption { 52 description = '' 53 Parameters of freeciv-server. 54 ''; 55 default = { }; 56 type = lib.types.submodule { 57 freeformType = argsFormat.type; 58 options.Announce = lib.mkOption { 59 type = lib.types.enum [ 60 "IPv4" 61 "IPv6" 62 "none" 63 ]; 64 default = "none"; 65 description = "Announce game in LAN using given protocol."; 66 }; 67 options.auth = lib.mkEnableOption "server authentication"; 68 options.Database = lib.mkOption { 69 type = lib.types.nullOr lib.types.str; 70 apply = pkgs.writeText "auth.conf"; 71 default = '' 72 [fcdb] 73 backend="sqlite" 74 database="/var/lib/freeciv/auth.sqlite" 75 ''; 76 description = "Enable database connection with given configuration."; 77 }; 78 options.debug = lib.mkOption { 79 type = lib.types.ints.between 0 3; 80 default = 0; 81 description = "Set debug log level."; 82 }; 83 options.exit-on-end = lib.mkEnableOption "exit instead of restarting when a game ends"; 84 options.Guests = lib.mkEnableOption "guests to login if auth is enabled"; 85 options.Newusers = lib.mkEnableOption "new users to login if auth is enabled"; 86 options.port = lib.mkOption { 87 type = lib.types.port; 88 default = 5556; 89 description = "Listen for clients on given port"; 90 }; 91 options.quitidle = lib.mkOption { 92 type = lib.types.nullOr lib.types.int; 93 default = null; 94 description = "Quit if no players for given time in seconds."; 95 }; 96 options.read = lib.mkOption { 97 type = lib.types.lines; 98 apply = v: pkgs.writeTextDir "read.serv" v + "/read"; 99 default = '' 100 /fcdb lua sqlite_createdb() 101 ''; 102 description = "Startup script."; 103 }; 104 options.saves = lib.mkOption { 105 type = lib.types.nullOr lib.types.str; 106 default = "/var/lib/freeciv/saves/"; 107 description = '' 108 Save games to given directory, 109 a sub-directory named after the starting date of the service 110 will me inserted to preserve older saves. 111 ''; 112 }; 113 }; 114 }; 115 openFirewall = lib.mkEnableOption "opening the firewall for the port listening for clients"; 116 }; 117 }; 118 config = lib.mkIf cfg.enable { 119 users.groups.freeciv = { }; 120 # Use with: 121 # journalctl -u freeciv.service -f -o cat & 122 # cat >/run/freeciv.stdin 123 # load saves/2020-11-14_05-22-27/freeciv-T0005-Y-3750-interrupted.sav.bz2 124 systemd.sockets.freeciv = { 125 wantedBy = [ "sockets.target" ]; 126 socketConfig = { 127 ListenFIFO = "/run/freeciv.stdin"; 128 SocketGroup = groups.freeciv.name; 129 SocketMode = "660"; 130 RemoveOnStop = true; 131 }; 132 }; 133 systemd.services.freeciv = { 134 description = "Freeciv Service"; 135 after = [ "network.target" ]; 136 wantedBy = [ "multi-user.target" ]; 137 environment.HOME = "/var/lib/freeciv"; 138 serviceConfig = { 139 Restart = "on-failure"; 140 RestartSec = "5s"; 141 StandardInput = "fd:freeciv.socket"; 142 StandardOutput = "journal"; 143 StandardError = "journal"; 144 ExecStart = pkgs.writeShellScript "freeciv-server" ( 145 '' 146 set -eux 147 savedir=$(date +%Y-%m-%d_%H-%M-%S) 148 '' 149 + "${pkgs.freeciv}/bin/freeciv-server" 150 + " " 151 + lib.optionalString (cfg.settings.saves != null) ( 152 lib.concatStringsSep " " [ 153 "--saves" 154 "${lib.escapeShellArg cfg.settings.saves}/$savedir" 155 ] 156 ) 157 + " " 158 + argsFormat.generate "freeciv-server" (cfg.settings // { saves = null; }) 159 ); 160 DynamicUser = true; 161 # Create rootDir in the host's mount namespace. 162 RuntimeDirectory = [ (baseNameOf rootDir) ]; 163 RuntimeDirectoryMode = "755"; 164 StateDirectory = [ "freeciv" ]; 165 WorkingDirectory = "/var/lib/freeciv"; 166 # Avoid mounting rootDir in the own rootDir of ExecStart='s mount namespace. 167 InaccessiblePaths = [ "-+${rootDir}" ]; 168 # This is for BindPaths= and BindReadOnlyPaths= 169 # to allow traversal of directories they create in RootDirectory=. 170 UMask = "0066"; 171 RootDirectory = rootDir; 172 RootDirectoryStartOnly = true; 173 MountAPIVFS = true; 174 BindReadOnlyPaths = [ 175 builtins.storeDir 176 "/etc" 177 "/run" 178 ]; 179 # The following options are only for optimizing: 180 # systemd-analyze security freeciv 181 AmbientCapabilities = ""; 182 CapabilityBoundingSet = ""; 183 # ProtectClock= adds DeviceAllow=char-rtc r 184 DeviceAllow = ""; 185 LockPersonality = true; 186 MemoryDenyWriteExecute = true; 187 NoNewPrivileges = true; 188 PrivateDevices = true; 189 PrivateMounts = true; 190 PrivateNetwork = lib.mkDefault false; 191 PrivateTmp = true; 192 PrivateUsers = true; 193 ProtectClock = true; 194 ProtectControlGroups = true; 195 ProtectHome = true; 196 ProtectHostname = true; 197 ProtectKernelLogs = true; 198 ProtectKernelModules = true; 199 ProtectKernelTunables = true; 200 ProtectSystem = "strict"; 201 RemoveIPC = true; 202 RestrictAddressFamilies = [ 203 "AF_INET" 204 "AF_INET6" 205 ]; 206 RestrictNamespaces = true; 207 RestrictRealtime = true; 208 RestrictSUIDSGID = true; 209 SystemCallFilter = [ 210 "@system-service" 211 # Groups in @system-service which do not contain a syscall listed by: 212 # perf stat -x, 2>perf.log -e 'syscalls:sys_enter_*' freeciv-server 213 # in tests, and seem likely not necessary for freeciv-server. 214 "~@aio" 215 "~@chown" 216 "~@ipc" 217 "~@keyring" 218 "~@memlock" 219 "~@resources" 220 "~@setuid" 221 "~@sync" 222 "~@timer" 223 ]; 224 SystemCallArchitectures = "native"; 225 SystemCallErrorNumber = "EPERM"; 226 }; 227 }; 228 networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.settings.port ]; }; 229 }; 230 meta.maintainers = with lib.maintainers; [ julm ]; 231}