1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.deliantra-server;
7 serverPort = 13327;
8in {
9 options.services.deliantra-server = {
10 enable = mkOption {
11 type = types.bool;
12 default = false;
13 description = lib.mdDoc ''
14 If enabled, the Deliantra game server will be started at boot.
15 '';
16 };
17
18 package = mkOption {
19 type = types.package;
20 default = pkgs.deliantra-server;
21 defaultText = literalExpression "pkgs.deliantra-server";
22 description = lib.mdDoc ''
23 The package to use for the Deliantra server (and map/arch data, if you
24 don't change dataDir).
25 '';
26 };
27
28 dataDir = mkOption {
29 type = types.str;
30 default = "${pkgs.deliantra-data}";
31 defaultText = literalExpression ''"''${pkgs.deliantra-data}"'';
32 description = lib.mdDoc ''
33 Where to store readonly data (maps, archetypes, sprites, etc).
34 Note that if you plan to use the live map editor (rather than editing
35 the maps offline and then nixos-rebuilding), THIS MUST BE WRITEABLE --
36 copy the deliantra-data someplace writeable (say,
37 /var/lib/deliantra/data) and update this option accordingly.
38 '';
39 };
40
41 stateDir = mkOption {
42 type = types.str;
43 default = "/var/lib/deliantra";
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 Contents of the server configuration files. These will be appended to
66 the example configurations the server comes with and overwrite any
67 default settings defined therein.
68
69 The example here is not comprehensive. See the files in
70 /etc/deliantra-server after enabling this module for full documentation.
71 '';
72 example = literalExpression ''
73 {
74 dm_file = '''
75 admin:secret_password:localhost
76 alice:xyzzy:*
77 ''';
78 motd = "Welcome to Deliantra!";
79 settings = '''
80 # Settings for game mechanics.
81 stat_loss_on_death true
82 armor_max_enchant 7
83 ''';
84 config = '''
85 # Settings for the server daemon.
86 hiscore_url https://deliantra.example.net/scores/
87 max_map_reset 86400
88 ''';
89 }
90 '';
91 default = {
92 motd = "";
93 };
94 };
95 };
96
97 config = mkIf cfg.enable {
98 users.users.deliantra = {
99 description = "Deliantra server daemon user";
100 home = cfg.stateDir;
101 createHome = false;
102 isSystemUser = true;
103 group = "deliantra";
104 };
105 users.groups.deliantra = {};
106
107 # Merge the cfg.configFiles setting with the default files shipped with
108 # Deliantra.
109 # For most files this consists of reading
110 # ${deliantra}/etc/deliantra-server/${name} and appending the user setting
111 # to it.
112 environment.etc = lib.attrsets.mapAttrs'
113 (name: value: lib.attrsets.nameValuePair "deliantra-server/${name}" {
114 mode = "0644";
115 text =
116 # Deliantra doesn't come with a motd file, but respects it if present
117 # in /etc.
118 (optionalString (name != "motd")
119 (fileContents "${cfg.package}/etc/deliantra-server/${name}"))
120 + "\n${value}";
121 }) ({
122 motd = "";
123 settings = "";
124 config = "";
125 dm_file = "";
126 } // cfg.configFiles);
127
128 systemd.services.deliantra-server = {
129 description = "Deliantra Server Daemon";
130 wantedBy = [ "multi-user.target" ];
131 after = [ "network.target" ];
132
133 environment = {
134 DELIANTRA_DATADIR="${cfg.dataDir}";
135 DELIANTRA_LOCALDIR="${cfg.stateDir}";
136 DELIANTRA_CONFDIR="/etc/deliantra-server";
137 };
138
139 serviceConfig = mkMerge [
140 {
141 ExecStart = "${cfg.package}/bin/deliantra-server";
142 Restart = "always";
143 User = "deliantra";
144 Group = "deliantra";
145 WorkingDirectory = cfg.stateDir;
146 }
147 (mkIf (cfg.stateDir == "/var/lib/deliantra") {
148 StateDirectory = "deliantra";
149 })
150 ];
151
152 # The deliantra server needs access to a bunch of files at runtime that
153 # are not created automatically at server startup; they're meant to be
154 # installed in $PREFIX/var/deliantra-server by `make install`. And those
155 # files need to be writeable, so we can't just point at the ones in the
156 # nix store. Instead we take the approach of copying them out of the store
157 # on first run. If `bookarch` already exists, we assume the rest of the
158 # files do as well, and copy nothing -- otherwise we risk ovewriting
159 # server state information every time the server is upgraded.
160 preStart = ''
161 if [ ! -e "${cfg.stateDir}"/bookarch ]; then
162 ${pkgs.rsync}/bin/rsync -a --chmod=u=rwX,go=rX \
163 "${cfg.package}/var/deliantra-server/" "${cfg.stateDir}/"
164 fi
165 '';
166 };
167
168 networking.firewall = mkIf cfg.openFirewall {
169 allowedTCPPorts = [ serverPort ];
170 };
171 };
172}