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