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}