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 }
85 // extraConfig;
86 };
87
88in
89{
90 ###### interface
91 options = {
92 services.moosefs = {
93 masterHost = lib.mkOption {
94 type = lib.types.str;
95 default = null;
96 description = "IP or DNS name of the MooseFS master server.";
97 };
98
99 runAsUser = lib.mkOption {
100 type = lib.types.bool;
101 default = true;
102 example = true;
103 description = "Run daemons as moosefs user instead of root for better security.";
104 };
105
106 client.enable = lib.mkEnableOption "MooseFS client";
107
108 master = {
109 enable = lib.mkOption {
110 type = lib.types.bool;
111 description = ''
112 Enable MooseFS master daemon.
113 The master server coordinates all MooseFS operations and stores metadata.
114 '';
115 default = false;
116 };
117
118 autoInit = lib.mkOption {
119 type = lib.types.bool;
120 default = false;
121 description = "Whether to automatically initialize the master's metadata directory on first run. Use with caution.";
122 };
123
124 exports = lib.mkOption {
125 type = with lib.types; listOf str;
126 default = null;
127 description = "Export definitions for MooseFS (see mfsexports.cfg).";
128 example = [
129 "* / rw,alldirs,admin,maproot=0:0"
130 "* . rw"
131 ];
132 };
133
134 openFirewall = lib.mkOption {
135 type = lib.types.bool;
136 description = "Whether to automatically open required firewall ports for master service.";
137 default = false;
138 };
139
140 settings = lib.mkOption {
141 type = lib.types.submodule {
142 freeformType = settingsFormat.type;
143
144 options.DATA_PATH = lib.mkOption {
145 type = lib.types.str;
146 default = "/var/lib/mfs";
147 description = "Directory for storing master metadata.";
148 };
149 };
150 description = "Master configuration options (mfsmaster.cfg).";
151 };
152 };
153
154 metalogger = {
155 enable = lib.mkEnableOption "MooseFS metalogger daemon that maintains a backup copy of the master's metadata";
156
157 settings = lib.mkOption {
158 type = lib.types.submodule {
159 freeformType = settingsFormat.type;
160
161 options.DATA_PATH = lib.mkOption {
162 type = lib.types.str;
163 default = "/var/lib/mfs";
164 description = "Directory for storing metalogger data.";
165 };
166 };
167 description = "Metalogger configuration options (mfsmetalogger.cfg).";
168 };
169 };
170
171 chunkserver = {
172 enable = lib.mkEnableOption "MooseFS chunkserver daemon that stores file data";
173
174 openFirewall = lib.mkOption {
175 type = lib.types.bool;
176 description = "Whether to automatically open required firewall ports for chunkserver service.";
177 default = false;
178 };
179
180 hdds = lib.mkOption {
181 type = with lib.types; listOf str;
182 default = null;
183 description = "Mount points used by chunkserver for data storage (see mfshdd.cfg).";
184 example = [
185 "/mnt/hdd1"
186 "/mnt/hdd2"
187 ];
188 };
189
190 settings = lib.mkOption {
191 type = lib.types.submodule {
192 freeformType = settingsFormat.type;
193
194 options.DATA_PATH = lib.mkOption {
195 type = lib.types.str;
196 default = "/var/lib/mfs";
197 description = "Directory for lock files and other runtime data.";
198 };
199 };
200 description = "Chunkserver configuration options (mfschunkserver.cfg).";
201 };
202 };
203
204 cgiserver = {
205 enable = lib.mkEnableOption ''
206 MooseFS CGI server for web interface.
207 Warning: The CGI server interface should be properly secured from unauthorized access,
208 as it provides full control over your MooseFS installation.
209 '';
210
211 openFirewall = lib.mkOption {
212 type = lib.types.bool;
213 description = "Whether to automatically open the web interface port.";
214 default = false;
215 };
216
217 settings = lib.mkOption {
218 type = lib.types.submodule {
219 freeformType = settingsFormat.type;
220 options = {
221 BIND_HOST = lib.mkOption {
222 type = lib.types.str;
223 default = "0.0.0.0";
224 description = "IP address to bind CGI server to.";
225 };
226
227 PORT = lib.mkOption {
228 type = lib.types.port;
229 default = 9425;
230 description = "Port for CGI server to listen on.";
231 };
232 };
233 };
234 default = { };
235 description = "CGI server configuration options.";
236 };
237 };
238 };
239 };
240
241 ###### implementation
242 config =
243 lib.mkIf
244 (
245 cfg.client.enable
246 || cfg.master.enable
247 || cfg.metalogger.enable
248 || cfg.chunkserver.enable
249 || cfg.cgiserver.enable
250 )
251 {
252 warnings = [ (lib.mkIf (!cfg.runAsUser) "Running MooseFS services as root is not recommended.") ];
253
254 services.moosefs = {
255 master.settings = lib.mkIf cfg.master.enable (
256 lib.mkMerge [
257 {
258 WORKING_USER = mfsUser;
259 EXPORTS_FILENAME = toString (
260 pkgs.writeText "mfsexports.cfg" (lib.concatStringsSep "\n" cfg.master.exports)
261 );
262 }
263 (lib.mkIf cfg.cgiserver.enable {
264 MFSCGISERV = toString cfg.cgiserver.settings.PORT;
265 })
266 ]
267 );
268
269 metalogger.settings = lib.mkIf cfg.metalogger.enable {
270 WORKING_USER = mfsUser;
271 MASTER_HOST = cfg.masterHost;
272 };
273
274 chunkserver.settings = lib.mkIf cfg.chunkserver.enable {
275 WORKING_USER = mfsUser;
276 MASTER_HOST = cfg.masterHost;
277 HDD_CONF_FILENAME = toString (
278 pkgs.writeText "mfshdd.cfg" (lib.concatStringsSep "\n" cfg.chunkserver.hdds)
279 );
280 };
281 };
282
283 users =
284 lib.mkIf
285 (
286 cfg.runAsUser
287 && (cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable || cfg.cgiserver.enable)
288 )
289 {
290 users.moosefs = {
291 isSystemUser = true;
292 description = "MooseFS daemon user";
293 group = "moosefs";
294 };
295 groups.moosefs = { };
296 };
297
298 environment.systemPackages =
299 (lib.optional cfg.client.enable pkgs.moosefs) ++ (lib.optional cfg.master.enable initTool);
300
301 networking.firewall.allowedTCPPorts = lib.mkMerge [
302 (lib.optionals cfg.master.openFirewall [
303 9419
304 9420
305 9421
306 ])
307 (lib.optional cfg.chunkserver.openFirewall 9422)
308 (lib.optional (cfg.cgiserver.enable && cfg.cgiserver.openFirewall) cfg.cgiserver.settings.PORT)
309 ];
310
311 systemd.tmpfiles.rules = [
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}