1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 CONTAINS_NEWLINE_RE = ".*\n.*";
9 # The following values are reserved as complete option values:
10 # { - start of a group.
11 # """ - start of a multi-line string.
12 RESERVED_VALUE_RE = "[[:space:]]*(\"\"\"|\\{)[[:space:]]*";
13 NEEDS_MULTILINE_RE = "${CONTAINS_NEWLINE_RE}|${RESERVED_VALUE_RE}";
14
15 # There is no way to encode """ on its own line in a Minetest config.
16 UNESCAPABLE_RE = ".*\n\"\"\"\n.*";
17
18 toConfMultiline =
19 name: value:
20 assert lib.assertMsg (
21 (builtins.match UNESCAPABLE_RE value) == null
22 ) ''""" can't be on its own line in a minetest config.'';
23 "${name} = \"\"\"\n${value}\n\"\"\"\n";
24
25 toConf =
26 values:
27 lib.concatStrings (
28 lib.mapAttrsToList (
29 name: value:
30 {
31 bool = "${name} = ${toString value}\n";
32 int = "${name} = ${toString value}\n";
33 null = "";
34 set = "${name} = {\n${toConf value}}\n";
35 string =
36 if (builtins.match NEEDS_MULTILINE_RE value) != null then
37 toConfMultiline name value
38 else
39 "${name} = ${value}\n";
40 }
41 .${builtins.typeOf value}
42 ) values
43 );
44
45 cfg = config.services.minetest-server;
46 flag =
47 val: name:
48 lib.optionals (val != null) [
49 "--${name}"
50 "${toString val}"
51 ];
52
53 flags = [
54 "--server"
55 ]
56 ++ (
57 if cfg.configPath != null then
58 [
59 "--config"
60 cfg.configPath
61 ]
62 else
63 [
64 "--config"
65 (builtins.toFile "minetest.conf" (toConf cfg.config))
66 ]
67 )
68 ++ (flag cfg.gameId "gameid")
69 ++ (flag cfg.world "world")
70 ++ (flag cfg.logPath "logfile")
71 ++ (flag cfg.port "port")
72 ++ cfg.extraArgs;
73in
74{
75 options = {
76 services.minetest-server = {
77 enable = lib.mkOption {
78 type = lib.types.bool;
79 default = false;
80 description = "If enabled, starts a Minetest Server.";
81 };
82
83 gameId = lib.mkOption {
84 type = lib.types.nullOr lib.types.str;
85 default = null;
86 description = ''
87 Id of the game to use. To list available games run
88 `minetestserver --gameid list`.
89
90 If only one game exists, this option can be null.
91 '';
92 };
93
94 world = lib.mkOption {
95 type = lib.types.nullOr lib.types.path;
96 default = null;
97 description = ''
98 Name of the world to use. To list available worlds run
99 `minetestserver --world list`.
100
101 If only one world exists, this option can be null.
102 '';
103 };
104
105 configPath = lib.mkOption {
106 type = lib.types.nullOr lib.types.path;
107 default = null;
108 description = ''
109 Path to the config to use.
110
111 If set to null, the config of the running user will be used:
112 `~/.minetest/minetest.conf`.
113 '';
114 };
115
116 config = lib.mkOption {
117 type = lib.types.attrsOf lib.types.anything;
118 default = { };
119 description = ''
120 Settings to add to the minetest config file.
121
122 This option is ignored if `configPath` is set.
123 '';
124 };
125
126 logPath = lib.mkOption {
127 type = lib.types.nullOr lib.types.path;
128 default = null;
129 description = ''
130 Path to logfile for logging.
131
132 If set to null, logging will be output to stdout which means
133 all output will be caught by systemd.
134 '';
135 };
136
137 port = lib.mkOption {
138 type = lib.types.nullOr lib.types.port;
139 default = null;
140 description = ''
141 Port number to bind to.
142
143 If set to null, the default 30000 will be used.
144 '';
145 };
146
147 extraArgs = lib.mkOption {
148 type = lib.types.listOf lib.types.str;
149 default = [ ];
150 description = ''
151 Additional command line flags to pass to the minetest executable.
152 '';
153 };
154 };
155 };
156
157 config = lib.mkIf cfg.enable {
158 users.users.minetest = {
159 description = "Minetest Server Service user";
160 home = "/var/lib/minetest";
161 createHome = true;
162 uid = config.ids.uids.minetest;
163 group = "minetest";
164 };
165 users.groups.minetest.gid = config.ids.gids.minetest;
166
167 systemd.services.minetest-server = {
168 description = "Minetest Server Service";
169 wantedBy = [ "multi-user.target" ];
170 after = [ "network.target" ];
171
172 serviceConfig.Restart = "always";
173 serviceConfig.User = "minetest";
174 serviceConfig.Group = "minetest";
175
176 script = ''
177 cd /var/lib/minetest
178
179 exec ${pkgs.minetest}/bin/minetest ${lib.escapeShellArgs flags}
180 '';
181 };
182 };
183}