1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.moosefs;
7
8 mfsUser = if cfg.runAsUser then "moosefs" else "root";
9
10 settingsFormat = let
11 listSep = " ";
12 allowedTypes = with types; [ bool int float str ];
13 valueToString = val:
14 if isList val then concatStringsSep listSep (map (x: valueToString x) val)
15 else if isBool val then (if val then "1" else "0")
16 else toString val;
17
18 in {
19 type = with types; let
20 valueType = oneOf ([
21 (listOf valueType)
22 ] ++ allowedTypes) // {
23 description = "Flat key-value file";
24 };
25 in attrsOf valueType;
26
27 generate = name: value:
28 pkgs.writeText name ( lib.concatStringsSep "\n" (
29 lib.mapAttrsToList (key: val: "${key} = ${valueToString val}") value ));
30 };
31
32
33 initTool = pkgs.writeShellScriptBin "mfsmaster-init" ''
34 if [ ! -e ${cfg.master.settings.DATA_PATH}/metadata.mfs ]; then
35 cp ${pkgs.moosefs}/var/mfs/metadata.mfs.empty ${cfg.master.settings.DATA_PATH}
36 chmod +w ${cfg.master.settings.DATA_PATH}/metadata.mfs.empty
37 ${pkgs.moosefs}/bin/mfsmaster -a -c ${masterCfg} start
38 ${pkgs.moosefs}/bin/mfsmaster -c ${masterCfg} stop
39 rm ${cfg.master.settings.DATA_PATH}/metadata.mfs.empty
40 fi
41 '';
42
43 # master config file
44 masterCfg = settingsFormat.generate
45 "mfsmaster.cfg" cfg.master.settings;
46
47 # metalogger config file
48 metaloggerCfg = settingsFormat.generate
49 "mfsmetalogger.cfg" cfg.metalogger.settings;
50
51 # chunkserver config file
52 chunkserverCfg = settingsFormat.generate
53 "mfschunkserver.cfg" cfg.chunkserver.settings;
54
55 # generic template for all daemons
56 systemdService = name: extraConfig: configFile: {
57 wantedBy = [ "multi-user.target" ];
58 wants = [ "network-online.target" ];
59 after = [ "network.target" "network-online.target" ];
60
61 serviceConfig = {
62 Type = "forking";
63 ExecStart = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} start";
64 ExecStop = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} stop";
65 ExecReload = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} reload";
66 PIDFile = "${cfg."${name}".settings.DATA_PATH}/.mfs${name}.lock";
67 } // extraConfig;
68 };
69
70in {
71 ###### interface
72
73 options = {
74 services.moosefs = {
75 masterHost = mkOption {
76 type = types.str;
77 default = null;
78 description = lib.mdDoc "IP or DNS name of master host.";
79 };
80
81 runAsUser = mkOption {
82 type = types.bool;
83 default = true;
84 example = true;
85 description = lib.mdDoc "Run daemons as user moosefs instead of root.";
86 };
87
88 client.enable = mkEnableOption (lib.mdDoc "Moosefs client");
89
90 master = {
91 enable = mkOption {
92 type = types.bool;
93 description = lib.mdDoc ''
94 Enable Moosefs master daemon.
95
96 You need to run `mfsmaster-init` on a freshly installed master server to
97 initialize the `DATA_PATH` directory.
98 '';
99 default = false;
100 };
101
102 exports = mkOption {
103 type = with types; listOf str;
104 default = null;
105 description = lib.mdDoc "Paths to export (see mfsexports.cfg).";
106 example = [
107 "* / rw,alldirs,admin,maproot=0:0"
108 "* . rw"
109 ];
110 };
111
112 openFirewall = mkOption {
113 type = types.bool;
114 description = lib.mdDoc "Whether to automatically open the necessary ports in the firewall.";
115 default = false;
116 };
117
118 settings = mkOption {
119 type = types.submodule {
120 freeformType = settingsFormat.type;
121
122 options.DATA_PATH = mkOption {
123 type = types.str;
124 default = "/var/lib/mfs";
125 description = lib.mdDoc "Data storage directory.";
126 };
127 };
128
129 description = lib.mdDoc "Contents of config file (mfsmaster.cfg).";
130 };
131 };
132
133 metalogger = {
134 enable = mkEnableOption (lib.mdDoc "Moosefs metalogger daemon");
135
136 settings = mkOption {
137 type = types.submodule {
138 freeformType = settingsFormat.type;
139
140 options.DATA_PATH = mkOption {
141 type = types.str;
142 default = "/var/lib/mfs";
143 description = lib.mdDoc "Data storage directory";
144 };
145 };
146
147 description = lib.mdDoc "Contents of metalogger config file (mfsmetalogger.cfg).";
148 };
149 };
150
151 chunkserver = {
152 enable = mkEnableOption (lib.mdDoc "Moosefs chunkserver daemon");
153
154 openFirewall = mkOption {
155 type = types.bool;
156 description = lib.mdDoc "Whether to automatically open the necessary ports in the firewall.";
157 default = false;
158 };
159
160 hdds = mkOption {
161 type = with types; listOf str;
162 default = null;
163 description = lib.mdDoc "Mount points to be used by chunkserver for storage (see mfshdd.cfg).";
164 example = [ "/mnt/hdd1" ];
165 };
166
167 settings = mkOption {
168 type = types.submodule {
169 freeformType = settingsFormat.type;
170
171 options.DATA_PATH = mkOption {
172 type = types.str;
173 default = "/var/lib/mfs";
174 description = lib.mdDoc "Directory for lock file.";
175 };
176 };
177
178 description = lib.mdDoc "Contents of chunkserver config file (mfschunkserver.cfg).";
179 };
180 };
181 };
182 };
183
184 ###### implementation
185
186 config = mkIf ( cfg.client.enable || cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable ) {
187
188 warnings = [ ( mkIf (!cfg.runAsUser) "Running moosefs services as root is not recommended.") ];
189
190 # Service settings
191 services.moosefs = {
192 master.settings = mkIf cfg.master.enable {
193 WORKING_USER = mfsUser;
194 EXPORTS_FILENAME = toString ( pkgs.writeText "mfsexports.cfg"
195 (concatStringsSep "\n" cfg.master.exports));
196 };
197
198 metalogger.settings = mkIf cfg.metalogger.enable {
199 WORKING_USER = mfsUser;
200 MASTER_HOST = cfg.masterHost;
201 };
202
203 chunkserver.settings = mkIf cfg.chunkserver.enable {
204 WORKING_USER = mfsUser;
205 MASTER_HOST = cfg.masterHost;
206 HDD_CONF_FILENAME = toString ( pkgs.writeText "mfshdd.cfg"
207 (concatStringsSep "\n" cfg.chunkserver.hdds));
208 };
209 };
210
211 # Create system user account for daemons
212 users = mkIf ( cfg.runAsUser && ( cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable ) ) {
213 users.moosefs = {
214 isSystemUser = true;
215 description = "moosefs daemon user";
216 group = "moosefs";
217 };
218 groups.moosefs = {};
219 };
220
221 environment.systemPackages =
222 (lib.optional cfg.client.enable pkgs.moosefs) ++
223 (lib.optional cfg.master.enable initTool);
224
225 networking.firewall.allowedTCPPorts =
226 (lib.optionals cfg.master.openFirewall [ 9419 9420 9421 ]) ++
227 (lib.optional cfg.chunkserver.openFirewall 9422);
228
229 # Ensure storage directories exist
230 systemd.tmpfiles.rules =
231 optional cfg.master.enable "d ${cfg.master.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}"
232 ++ optional cfg.metalogger.enable "d ${cfg.metalogger.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}"
233 ++ optional cfg.chunkserver.enable "d ${cfg.chunkserver.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}";
234
235 # Service definitions
236 systemd.services.mfs-master = mkIf cfg.master.enable
237 ( systemdService "master" {
238 TimeoutStartSec = 1800;
239 TimeoutStopSec = 1800;
240 Restart = "no";
241 } masterCfg );
242
243 systemd.services.mfs-metalogger = mkIf cfg.metalogger.enable
244 ( systemdService "metalogger" { Restart = "on-abnormal"; } metaloggerCfg );
245
246 systemd.services.mfs-chunkserver = mkIf cfg.chunkserver.enable
247 ( systemdService "chunkserver" { Restart = "on-abnormal"; } chunkserverCfg );
248 };
249}