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}