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