1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.crossfire-server;
7 serverPort = 13327;
8in {
9 options.services.crossfire-server = {
10 enable = mkOption {
11 type = types.bool;
12 default = false;
13 description = lib.mdDoc ''
14 If enabled, the Crossfire game server will be started at boot.
15 '';
16 };
17
18 package = mkOption {
19 type = types.package;
20 default = pkgs.crossfire-server;
21 defaultText = literalExpression "pkgs.crossfire-server";
22 description = lib.mdDoc ''
23 The package to use for the Crossfire server (and map/arch data, if you
24 don't change dataDir).
25 '';
26 };
27
28 dataDir = mkOption {
29 type = types.str;
30 default = "${cfg.package}/share/crossfire";
31 defaultText = literalExpression ''"''${config.services.crossfire.package}/share/crossfire"'';
32 description = lib.mdDoc ''
33 Where to load readonly data from -- maps, archetypes, treasure tables,
34 and the like. If you plan to edit the data on the live server (rather
35 than overlaying the crossfire-maps and crossfire-arch packages and
36 nixos-rebuilding), point this somewhere read-write and copy the data
37 there before starting the server.
38 '';
39 };
40
41 stateDir = mkOption {
42 type = types.str;
43 default = "/var/lib/crossfire";
44 description = lib.mdDoc ''
45 Where to store runtime data (save files, persistent items, etc).
46
47 If left at the default, this will be automatically created on server
48 startup if it does not already exist. If changed, it is the admin's
49 responsibility to make sure that the directory exists and is writeable
50 by the `crossfire` user.
51 '';
52 };
53
54 openFirewall = mkOption {
55 type = types.bool;
56 default = false;
57 description = lib.mdDoc ''
58 Whether to open ports in the firewall for the server.
59 '';
60 };
61
62 configFiles = mkOption {
63 type = types.attrsOf types.str;
64 description = lib.mdDoc ''
65 Text to append to the corresponding configuration files. Note that the
66 files given in the example are *not* the complete set of files available
67 to customize; look in /etc/crossfire after enabling the server to see
68 the available files, and read the comments in each file for detailed
69 documentation on the format and what settings are available.
70
71 Note that the motd, rules, and news files, if configured here, will
72 overwrite the example files that come with the server, rather than being
73 appended to them as the other configuration files are.
74 '';
75 example = literalExpression ''
76 {
77 dm_file = '''
78 admin:secret_password:localhost
79 alice:xyzzy:*
80 ''';
81 ban_file = '''
82 # Bob is a jerk
83 bob@*
84 # So is everyone on 192.168.86.255/24
85 *@192.168.86.
86 ''';
87 metaserver2 = '''
88 metaserver2_notification on
89 localhostname crossfire.example.net
90 ''';
91 motd = "Welcome to CrossFire!";
92 news = "No news yet.";
93 rules = "Don't be a jerk.";
94 settings = '''
95 # be nicer to newbies and harsher to experienced players
96 balanced_stat_loss true
97 # don't let players pick up and use admin-created items
98 real_wiz false
99 ''';
100 }
101 '';
102 default = {};
103 };
104 };
105
106 config = mkIf cfg.enable {
107 users.users.crossfire = {
108 description = "Crossfire server daemon user";
109 home = cfg.stateDir;
110 createHome = false;
111 isSystemUser = true;
112 group = "crossfire";
113 };
114 users.groups.crossfire = {};
115
116 # Merge the cfg.configFiles setting with the default files shipped with
117 # Crossfire.
118 # For most files this consists of reading ${crossfire}/etc/crossfire/${name}
119 # and appending the user setting to it; the motd, news, and rules are handled
120 # specially, with user-provided values completely replacing the original.
121 environment.etc = lib.attrsets.mapAttrs'
122 (name: value: lib.attrsets.nameValuePair "crossfire/${name}" {
123 mode = "0644";
124 text =
125 (optionalString (!elem name ["motd" "news" "rules"])
126 (fileContents "${cfg.package}/etc/crossfire/${name}"))
127 + "\n${value}";
128 }) ({
129 ban_file = "";
130 dm_file = "";
131 exp_table = "";
132 forbid = "";
133 metaserver2 = "";
134 motd = fileContents "${cfg.package}/etc/crossfire/motd";
135 news = fileContents "${cfg.package}/etc/crossfire/news";
136 rules = fileContents "${cfg.package}/etc/crossfire/rules";
137 settings = "";
138 stat_bonus = "";
139 } // cfg.configFiles);
140
141 systemd.services.crossfire-server = {
142 description = "Crossfire Server Daemon";
143 wantedBy = [ "multi-user.target" ];
144 after = [ "network.target" ];
145
146 serviceConfig = mkMerge [
147 {
148 ExecStart = "${cfg.package}/bin/crossfire-server -conf /etc/crossfire -local '${cfg.stateDir}' -data '${cfg.dataDir}'";
149 Restart = "always";
150 User = "crossfire";
151 Group = "crossfire";
152 WorkingDirectory = cfg.stateDir;
153 }
154 (mkIf (cfg.stateDir == "/var/lib/crossfire") {
155 StateDirectory = "crossfire";
156 })
157 ];
158
159 # The crossfire server needs access to a bunch of files at runtime that
160 # are not created automatically at server startup; they're meant to be
161 # installed in $PREFIX/var/crossfire by `make install`. And those files
162 # need to be writeable, so we can't just point at the ones in the nix
163 # store. Instead we take the approach of copying them out of the store
164 # on first run. If `bookarch` already exists, we assume the rest of the
165 # files do as well, and copy nothing -- otherwise we risk ovewriting
166 # server state information every time the server is upgraded.
167 preStart = ''
168 if [ ! -e "${cfg.stateDir}"/bookarch ]; then
169 ${pkgs.rsync}/bin/rsync -a --chmod=u=rwX,go=rX \
170 "${cfg.package}/var/crossfire/" "${cfg.stateDir}/"
171 fi
172 '';
173 };
174
175 networking.firewall = mkIf cfg.openFirewall {
176 allowedTCPPorts = [ serverPort ];
177 };
178 };
179}