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