1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.moosefs;
9
10 mfsUser = if cfg.runAsUser then "moosefs" else "root";
11
12 settingsFormat =
13 let
14 listSep = " ";
15 allowedTypes = with lib.types; [
16 bool
17 int
18 float
19 str
20 ];
21 valueToString =
22 val:
23 if lib.isList val then
24 lib.concatStringsSep listSep (map (x: valueToString x) val)
25 else if lib.isBool val then
26 (if val then "1" else "0")
27 else
28 toString val;
29
30 in
31 {
32 type =
33 with lib.types;
34 let
35 valueType =
36 oneOf (
37 [
38 (listOf valueType)
39 ]
40 ++ allowedTypes
41 )
42 // {
43 description = "Flat key-value file";
44 };
45 in
46 attrsOf valueType;
47
48 generate =
49 name: value:
50 pkgs.writeText name (
51 lib.concatStringsSep "\n" (lib.mapAttrsToList (key: val: "${key} = ${valueToString val}") value)
52 );
53 };
54
55 # Manual initialization tool
56 initTool = pkgs.writeShellScriptBin "mfsmaster-init" ''
57 if [ ! -e ${cfg.master.settings.DATA_PATH}/metadata.mfs ]; then
58 cp ${pkgs.moosefs}/var/mfs/metadata.mfs.empty ${cfg.master.settings.DATA_PATH}
59 chmod +w ${cfg.master.settings.DATA_PATH}/metadata.mfs.empty
60 ${pkgs.moosefs}/bin/mfsmaster -a -c ${masterCfg} start
61 ${pkgs.moosefs}/bin/mfsmaster -c ${masterCfg} stop
62 rm ${cfg.master.settings.DATA_PATH}/metadata.mfs.empty
63 fi
64 '';
65
66 masterCfg = settingsFormat.generate "mfsmaster.cfg" cfg.master.settings;
67 metaloggerCfg = settingsFormat.generate "mfsmetalogger.cfg" cfg.metalogger.settings;
68 chunkserverCfg = settingsFormat.generate "mfschunkserver.cfg" cfg.chunkserver.settings;
69
70 systemdService = name: extraConfig: configFile: {
71 wantedBy = [ "multi-user.target" ];
72 wants = [ "network-online.target" ];
73 after = [
74 "network.target"
75 "network-online.target"
76 ];
77
78 serviceConfig = {
79 Type = "forking";
80 ExecStart = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} start";
81 ExecStop = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} stop";
82 ExecReload = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} reload";
83 PIDFile = "${cfg."${name}".settings.DATA_PATH}/.mfs${name}.lock";
84 } // extraConfig;
85 };
86
87in
88{
89 ###### interface
90 options = {
91 services.moosefs = {
92 masterHost = lib.mkOption {
93 type = lib.types.str;
94 default = null;
95 description = "IP or DNS name of the MooseFS master server.";
96 };
97
98 runAsUser = lib.mkOption {
99 type = lib.types.bool;
100 default = true;
101 example = true;
102 description = "Run daemons as moosefs user instead of root for better security.";
103 };
104
105 client.enable = lib.mkEnableOption "MooseFS client";
106
107 master = {
108 enable = lib.mkOption {
109 type = lib.types.bool;
110 description = ''
111 Enable MooseFS master daemon.
112 The master server coordinates all MooseFS operations and stores metadata.
113 '';
114 default = false;
115 };
116
117 autoInit = lib.mkOption {
118 type = lib.types.bool;
119 default = false;
120 description = "Whether to automatically initialize the master's metadata directory on first run. Use with caution.";
121 };
122
123 exports = lib.mkOption {
124 type = with lib.types; listOf str;
125 default = null;
126 description = "Export definitions for MooseFS (see mfsexports.cfg).";
127 example = [
128 "* / rw,alldirs,admin,maproot=0:0"
129 "* . rw"
130 ];
131 };
132
133 openFirewall = lib.mkOption {
134 type = lib.types.bool;
135 description = "Whether to automatically open required firewall ports for master service.";
136 default = false;
137 };
138
139 settings = lib.mkOption {
140 type = lib.types.submodule {
141 freeformType = settingsFormat.type;
142
143 options.DATA_PATH = lib.mkOption {
144 type = lib.types.str;
145 default = "/var/lib/mfs";
146 description = "Directory for storing master metadata.";
147 };
148 };
149 description = "Master configuration options (mfsmaster.cfg).";
150 };
151 };
152
153 metalogger = {
154 enable = lib.mkEnableOption "MooseFS metalogger daemon that maintains a backup copy of the master's metadata";
155
156 settings = lib.mkOption {
157 type = lib.types.submodule {
158 freeformType = settingsFormat.type;
159
160 options.DATA_PATH = lib.mkOption {
161 type = lib.types.str;
162 default = "/var/lib/mfs";
163 description = "Directory for storing metalogger data.";
164 };
165 };
166 description = "Metalogger configuration options (mfsmetalogger.cfg).";
167 };
168 };
169
170 chunkserver = {
171 enable = lib.mkEnableOption "MooseFS chunkserver daemon that stores file data";
172
173 openFirewall = lib.mkOption {
174 type = lib.types.bool;
175 description = "Whether to automatically open required firewall ports for chunkserver service.";
176 default = false;
177 };
178
179 hdds = lib.mkOption {
180 type = with lib.types; listOf str;
181 default = null;
182 description = "Mount points used by chunkserver for data storage (see mfshdd.cfg).";
183 example = [
184 "/mnt/hdd1"
185 "/mnt/hdd2"
186 ];
187 };
188
189 settings = lib.mkOption {
190 type = lib.types.submodule {
191 freeformType = settingsFormat.type;
192
193 options.DATA_PATH = lib.mkOption {
194 type = lib.types.str;
195 default = "/var/lib/mfs";
196 description = "Directory for lock files and other runtime data.";
197 };
198 };
199 description = "Chunkserver configuration options (mfschunkserver.cfg).";
200 };
201 };
202
203 cgiserver = {
204 enable = lib.mkEnableOption ''
205 MooseFS CGI server for web interface.
206 Warning: The CGI server interface should be properly secured from unauthorized access,
207 as it provides full control over your MooseFS installation.
208 '';
209
210 openFirewall = lib.mkOption {
211 type = lib.types.bool;
212 description = "Whether to automatically open the web interface port.";
213 default = false;
214 };
215
216 settings = lib.mkOption {
217 type = lib.types.submodule {
218 freeformType = settingsFormat.type;
219 options = {
220 BIND_HOST = lib.mkOption {
221 type = lib.types.str;
222 default = "0.0.0.0";
223 description = "IP address to bind CGI server to.";
224 };
225
226 PORT = lib.mkOption {
227 type = lib.types.port;
228 default = 9425;
229 description = "Port for CGI server to listen on.";
230 };
231 };
232 };
233 default = { };
234 description = "CGI server configuration options.";
235 };
236 };
237 };
238 };
239
240 ###### implementation
241 config =
242 lib.mkIf
243 (
244 cfg.client.enable
245 || cfg.master.enable
246 || cfg.metalogger.enable
247 || cfg.chunkserver.enable
248 || cfg.cgiserver.enable
249 )
250 {
251 warnings = [ (lib.mkIf (!cfg.runAsUser) "Running MooseFS services as root is not recommended.") ];
252
253 services.moosefs = {
254 master.settings = lib.mkIf cfg.master.enable (
255 lib.mkMerge [
256 {
257 WORKING_USER = mfsUser;
258 EXPORTS_FILENAME = toString (
259 pkgs.writeText "mfsexports.cfg" (lib.concatStringsSep "\n" cfg.master.exports)
260 );
261 }
262 (lib.mkIf cfg.cgiserver.enable {
263 MFSCGISERV = toString cfg.cgiserver.settings.PORT;
264 })
265 ]
266 );
267
268 metalogger.settings = lib.mkIf cfg.metalogger.enable {
269 WORKING_USER = mfsUser;
270 MASTER_HOST = cfg.masterHost;
271 };
272
273 chunkserver.settings = lib.mkIf cfg.chunkserver.enable {
274 WORKING_USER = mfsUser;
275 MASTER_HOST = cfg.masterHost;
276 HDD_CONF_FILENAME = toString (
277 pkgs.writeText "mfshdd.cfg" (lib.concatStringsSep "\n" cfg.chunkserver.hdds)
278 );
279 };
280 };
281
282 users =
283 lib.mkIf
284 (
285 cfg.runAsUser
286 && (cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable || cfg.cgiserver.enable)
287 )
288 {
289 users.moosefs = {
290 isSystemUser = true;
291 description = "MooseFS daemon user";
292 group = "moosefs";
293 };
294 groups.moosefs = { };
295 };
296
297 environment.systemPackages =
298 (lib.optional cfg.client.enable pkgs.moosefs) ++ (lib.optional cfg.master.enable initTool);
299
300 networking.firewall.allowedTCPPorts = lib.mkMerge [
301 (lib.optionals cfg.master.openFirewall [
302 9419
303 9420
304 9421
305 ])
306 (lib.optional cfg.chunkserver.openFirewall 9422)
307 (lib.optional (cfg.cgiserver.enable && cfg.cgiserver.openFirewall) cfg.cgiserver.settings.PORT)
308 ];
309
310 systemd.tmpfiles.rules =
311 [
312 # Master directories
313 (lib.optionalString cfg.master.enable "d ${cfg.master.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser} -")
314
315 # Metalogger directories
316 (lib.optionalString cfg.metalogger.enable "d ${cfg.metalogger.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser} -")
317
318 # Chunkserver directories
319 (lib.optionalString cfg.chunkserver.enable "d ${cfg.chunkserver.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser} -")
320 ]
321 ++ lib.optionals (cfg.chunkserver.enable && cfg.chunkserver.hdds != null) (
322 map (dir: "d ${dir} 0755 ${mfsUser} ${mfsUser} -") cfg.chunkserver.hdds
323 );
324
325 systemd.services = lib.mkMerge [
326 (lib.mkIf cfg.master.enable {
327 mfs-master = (
328 lib.mkMerge [
329 (systemdService "master" {
330 TimeoutStartSec = 1800;
331 TimeoutStopSec = 1800;
332 Restart = "on-failure";
333 User = mfsUser;
334 } masterCfg)
335 {
336 preStart = lib.mkIf cfg.master.autoInit "${initTool}/bin/mfsmaster-init";
337 }
338 ]
339 );
340 })
341
342 (lib.mkIf cfg.metalogger.enable {
343 mfs-metalogger = systemdService "metalogger" {
344 Restart = "on-abnormal";
345 User = mfsUser;
346 } metaloggerCfg;
347 })
348
349 (lib.mkIf cfg.chunkserver.enable {
350 mfs-chunkserver = systemdService "chunkserver" {
351 Restart = "on-abnormal";
352 User = mfsUser;
353 } chunkserverCfg;
354 })
355
356 (lib.mkIf cfg.cgiserver.enable {
357 mfs-cgiserv = {
358 description = "MooseFS CGI Server";
359 wantedBy = [ "multi-user.target" ];
360 after = [ "mfs-master.service" ];
361
362 serviceConfig = {
363 Type = "simple";
364 ExecStart = "${pkgs.moosefs}/bin/mfscgiserv -D /var/lib/mfs -f start";
365 ExecStop = "${pkgs.moosefs}/bin/mfscgiserv -D /var/lib/mfs stop";
366 Restart = "on-failure";
367 RestartSec = "30s";
368 User = mfsUser;
369 Group = mfsUser;
370 WorkingDirectory = "/var/lib/mfs";
371 };
372 };
373 })
374 ];
375 };
376}