···
1
+
{ config, lib, pkgs, ... }:
4
+
cfg = config.services.freeciv;
5
+
inherit (config.users) groups;
6
+
rootDir = "/run/freeciv";
8
+
type = with lib.types; let
9
+
valueType = nullOr (oneOf [
13
+
description = "freeciv-server params";
16
+
generate = name: value:
18
+
if v == null then []
19
+
else if isBool v then if v then [("--"+k)] else []
21
+
mkParams = k: v: map (mkParam k) (if isList v then v else [v]);
22
+
in escapeShellArgs (concatLists (concatLists (mapAttrsToList mkParams value)));
27
+
services.freeciv = {
28
+
enable = mkEnableOption ''freeciv'';
29
+
settings = mkOption {
31
+
Parameters of freeciv-server.
34
+
type = types.submodule {
35
+
freeformType = argsFormat.type;
36
+
options.Announce = mkOption {
37
+
type = types.enum ["IPv4" "IPv6" "none"];
39
+
description = "Announce game in LAN using given protocol.";
41
+
options.auth = mkEnableOption "server authentication";
42
+
options.Database = mkOption {
43
+
type = types.nullOr types.str;
44
+
apply = pkgs.writeText "auth.conf";
48
+
database="/var/lib/freeciv/auth.sqlite"
50
+
description = "Enable database connection with given configuration.";
52
+
options.debug = mkOption {
53
+
type = types.ints.between 0 3;
55
+
description = "Set debug log level.";
57
+
options.exit-on-end = mkEnableOption "exit instead of restarting when a game ends.";
58
+
options.Guests = mkEnableOption "guests to login if auth is enabled";
59
+
options.Newusers = mkEnableOption "new users to login if auth is enabled";
60
+
options.port = mkOption {
63
+
description = "Listen for clients on given port";
65
+
options.quitidle = mkOption {
66
+
type = types.nullOr types.int;
68
+
description = "Quit if no players for given time in seconds.";
70
+
options.read = mkOption {
72
+
apply = v: pkgs.writeTextDir "read.serv" v + "/read";
74
+
/fcdb lua sqlite_createdb()
76
+
description = "Startup script.";
78
+
options.saves = mkOption {
79
+
type = types.nullOr types.str;
80
+
default = "/var/lib/freeciv/saves/";
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.
89
+
openFirewall = mkEnableOption "opening the firewall for the port listening for clients";
92
+
config = mkIf cfg.enable {
93
+
users.groups.freeciv = {};
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" ];
101
+
ListenFIFO = "/run/freeciv.stdin";
102
+
SocketGroup = groups.freeciv.name;
103
+
SocketMode = "660";
104
+
RemoveOnStop = true;
107
+
systemd.services.freeciv = {
108
+
description = "Freeciv Service";
109
+
after = [ "network.target" ];
110
+
wantedBy = [ "multi-user.target" ];
111
+
environment.HOME = "/var/lib/freeciv";
113
+
Restart = "on-failure";
115
+
StandardInput = "fd:freeciv.socket";
116
+
StandardOutput = "journal";
117
+
StandardError = "journal";
118
+
ExecStart = pkgs.writeShellScript "freeciv-server" (''
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=.
136
+
RootDirectory = rootDir;
137
+
RootDirectoryStartOnly = true;
138
+
MountAPIVFS = true;
139
+
BindReadOnlyPaths = [
144
+
# The following options are only for optimizing:
145
+
# systemd-analyze security freeciv
146
+
AmbientCapabilities = "";
147
+
CapabilityBoundingSet = "";
148
+
# ProtectClock= adds DeviceAllow=char-rtc r
150
+
LockPersonality = true;
151
+
MemoryDenyWriteExecute = true;
152
+
NoNewPrivileges = true;
153
+
PrivateDevices = true;
154
+
PrivateMounts = true;
155
+
PrivateNetwork = mkDefault false;
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";
167
+
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
168
+
RestrictNamespaces = true;
169
+
RestrictRealtime = true;
170
+
RestrictSUIDSGID = true;
171
+
SystemCallFilter = [
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"
179
+
SystemCallArchitectures = "native";
180
+
SystemCallErrorNumber = "EPERM";
183
+
networking.firewall = mkIf cfg.openFirewall
184
+
{ allowedTCPPorts = [ cfg.settings.port ]; };
186
+
meta.maintainers = with lib.maintainers; [ julm ];