diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 0afeefa3b86d..29aa70fd6165 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -168,6 +168,13 @@
services.baget.
+
+
+ moosefs, fault
+ tolerant petabyte distributed file system. Available as
+ moosefs.
+
+
prosody-filer,
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index 7813d4abd3ee..c4ace1366f2f 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -51,6 +51,9 @@ In addition to numerous new and upgraded packages, this release has the followin
- [BaGet](https://loic-sharma.github.io/BaGet/), a lightweight NuGet and symbol server. Available at [services.baget](#opt-services.baget.enable).
+- [moosefs](https://moosefs.com), fault tolerant petabyte distributed file system.
+ Available as [moosefs](#opt-services.moosefs).
+
- [prosody-filer](https://github.com/ThomasLeister/prosody-filer), a server for handling XMPP HTTP Upload requests. Available at [services.prosody-filer](#opt-services.prosody-filer.enable).
- [ethercalc](https://github.com/audreyt/ethercalc), an online collaborative
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 9b95a97e1c7b..28724d1e85d3 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -682,6 +682,7 @@
./services/network-filesystems/litestream/default.nix
./services/network-filesystems/netatalk.nix
./services/network-filesystems/nfsd.nix
+ ./services/network-filesystems/moosefs.nix
./services/network-filesystems/openafs/client.nix
./services/network-filesystems/openafs/server.nix
./services/network-filesystems/orangefs/server.nix
diff --git a/nixos/modules/services/network-filesystems/moosefs.nix b/nixos/modules/services/network-filesystems/moosefs.nix
new file mode 100644
index 000000000000..88b2ada37e75
--- /dev/null
+++ b/nixos/modules/services/network-filesystems/moosefs.nix
@@ -0,0 +1,249 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.moosefs;
+
+ mfsUser = if cfg.runAsUser then "moosefs" else "root";
+
+ settingsFormat = let
+ listSep = " ";
+ allowedTypes = with types; [ bool int float str ];
+ valueToString = val:
+ if isList val then concatStringsSep listSep (map (x: valueToString x) val)
+ else if isBool val then (if val then "1" else "0")
+ else toString val;
+
+ in {
+ type = with types; let
+ valueType = oneOf ([
+ (listOf valueType)
+ ] ++ allowedTypes) // {
+ description = "Flat key-value file";
+ };
+ in attrsOf valueType;
+
+ generate = name: value:
+ pkgs.writeText name ( lib.concatStringsSep "\n" (
+ lib.mapAttrsToList (key: val: "${key} = ${valueToString val}") value ));
+ };
+
+
+ initTool = pkgs.writeShellScriptBin "mfsmaster-init" ''
+ if [ ! -e ${cfg.master.settings.DATA_PATH}/metadata.mfs ]; then
+ cp ${pkgs.moosefs}/var/mfs/metadata.mfs.empty ${cfg.master.settings.DATA_PATH}
+ chmod +w ${cfg.master.settings.DATA_PATH}/metadata.mfs.empty
+ ${pkgs.moosefs}/bin/mfsmaster -a -c ${masterCfg} start
+ ${pkgs.moosefs}/bin/mfsmaster -c ${masterCfg} stop
+ rm ${cfg.master.settings.DATA_PATH}/metadata.mfs.empty
+ fi
+ '';
+
+ # master config file
+ masterCfg = settingsFormat.generate
+ "mfsmaster.cfg" cfg.master.settings;
+
+ # metalogger config file
+ metaloggerCfg = settingsFormat.generate
+ "mfsmetalogger.cfg" cfg.metalogger.settings;
+
+ # chunkserver config file
+ chunkserverCfg = settingsFormat.generate
+ "mfschunkserver.cfg" cfg.chunkserver.settings;
+
+ # generic template for all deamons
+ systemdService = name: extraConfig: configFile: {
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ];
+ after = [ "network.target" "network-online.target" ];
+
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} start";
+ ExecStop = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} stop";
+ ExecReload = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} reload";
+ PIDFile = "${cfg."${name}".settings.DATA_PATH}/.mfs${name}.lock";
+ } // extraConfig;
+ };
+
+in {
+ ###### interface
+
+ options = {
+ services.moosefs = {
+ masterHost = mkOption {
+ type = types.str;
+ default = null;
+ description = "IP or DNS name of master host.";
+ };
+
+ runAsUser = mkOption {
+ type = types.bool;
+ default = true;
+ example = true;
+ description = "Run daemons as user moosefs instead of root.";
+ };
+
+ client.enable = mkEnableOption "Moosefs client.";
+
+ master = {
+ enable = mkOption {
+ type = types.bool;
+ description = ''
+ Enable Moosefs master daemon.
+
+ You need to run mfsmaster-init on a freshly installed master server to
+ initialize the DATA_PATH direcory.
+ '';
+ default = false;
+ };
+
+ exports = mkOption {
+ type = with types; listOf str;
+ default = null;
+ description = "Paths to export (see mfsexports.cfg).";
+ example = [
+ "* / rw,alldirs,admin,maproot=0:0"
+ "* . rw"
+ ];
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ description = "Whether to automatically open the necessary ports in the firewall.";
+ default = false;
+ };
+
+ settings = mkOption {
+ type = types.submodule {
+ freeformType = settingsFormat.type;
+
+ options.DATA_PATH = mkOption {
+ type = types.str;
+ default = "/var/lib/mfs";
+ description = "Data storage directory.";
+ };
+ };
+
+ description = "Contents of config file (mfsmaster.cfg).";
+ };
+ };
+
+ metalogger = {
+ enable = mkEnableOption "Moosefs metalogger daemon.";
+
+ settings = mkOption {
+ type = types.submodule {
+ freeformType = settingsFormat.type;
+
+ options.DATA_PATH = mkOption {
+ type = types.str;
+ default = "/var/lib/mfs";
+ description = "Data storage directory";
+ };
+ };
+
+ description = "Contents of metalogger config file (mfsmetalogger.cfg).";
+ };
+ };
+
+ chunkserver = {
+ enable = mkEnableOption "Moosefs chunkserver daemon.";
+
+ openFirewall = mkOption {
+ type = types.bool;
+ description = "Whether to automatically open the necessary ports in the firewall.";
+ default = false;
+ };
+
+ hdds = mkOption {
+ type = with types; listOf str;
+ default = null;
+ description = "Mount points to be used by chunkserver for storage (see mfshdd.cfg).";
+ example = [ "/mnt/hdd1" ];
+ };
+
+ settings = mkOption {
+ type = types.submodule {
+ freeformType = settingsFormat.type;
+
+ options.DATA_PATH = mkOption {
+ type = types.str;
+ default = "/var/lib/mfs";
+ description = "Directory for lock file.";
+ };
+ };
+
+ description = "Contents of chunkserver config file (mfschunkserver.cfg).";
+ };
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf ( cfg.client.enable || cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable ) {
+
+ warnings = [ ( mkIf (!cfg.runAsUser) "Running moosefs services as root is not recommended.") ];
+
+ # Service settings
+ services.moosefs = {
+ master.settings = mkIf cfg.master.enable {
+ WORKING_USER = mfsUser;
+ EXPORTS_FILENAME = toString ( pkgs.writeText "mfsexports.cfg"
+ (concatStringsSep "\n" cfg.master.exports));
+ };
+
+ metalogger.settings = mkIf cfg.metalogger.enable {
+ WORKING_USER = mfsUser;
+ MASTER_HOST = cfg.masterHost;
+ };
+
+ chunkserver.settings = mkIf cfg.chunkserver.enable {
+ WORKING_USER = mfsUser;
+ MASTER_HOST = cfg.masterHost;
+ HDD_CONF_FILENAME = toString ( pkgs.writeText "mfshdd.cfg"
+ (concatStringsSep "\n" cfg.chunkserver.hdds));
+ };
+ };
+
+ # Create system user account for daemons
+ users = mkIf ( cfg.runAsUser && ( cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable ) ) {
+ users.moosefs = {
+ isSystemUser = true;
+ description = "moosefs daemon user";
+ group = "moosefs";
+ };
+ groups.moosefs = {};
+ };
+
+ environment.systemPackages =
+ (lib.optional cfg.client.enable pkgs.moosefs) ++
+ (lib.optional cfg.master.enable initTool);
+
+ networking.firewall.allowedTCPPorts =
+ (lib.optionals cfg.master.openFirewall [ 9419 9420 9421 ]) ++
+ (lib.optional cfg.chunkserver.openFirewall 9422);
+
+ # Ensure storage directories exist
+ systemd.tmpfiles.rules =
+ optional cfg.master.enable "d ${cfg.master.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}"
+ ++ optional cfg.metalogger.enable "d ${cfg.metalogger.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}"
+ ++ optional cfg.chunkserver.enable "d ${cfg.chunkserver.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}";
+
+ # Service definitions
+ systemd.services.mfs-master = mkIf cfg.master.enable
+ ( systemdService "master" {
+ TimeoutStartSec = 1800;
+ TimeoutStopSec = 1800;
+ Restart = "no";
+ } masterCfg );
+
+ systemd.services.mfs-metalogger = mkIf cfg.metalogger.enable
+ ( systemdService "metalogger" { Restart = "on-abnormal"; } metaloggerCfg );
+
+ systemd.services.mfs-chunkserver = mkIf cfg.chunkserver.enable
+ ( systemdService "chunkserver" { Restart = "on-abnormal"; } chunkserverCfg );
+ };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 1077dfab27c8..c01a28f04f89 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -305,6 +305,7 @@ in
moodle = handleTest ./moodle.nix {};
morty = handleTest ./morty.nix {};
mosquitto = handleTest ./mosquitto.nix {};
+ moosefs = handleTest ./moosefs.nix {};
mpd = handleTest ./mpd.nix {};
mpv = handleTest ./mpv.nix {};
mumble = handleTest ./mumble.nix {};
diff --git a/nixos/tests/moosefs.nix b/nixos/tests/moosefs.nix
new file mode 100644
index 000000000000..0dc08748b828
--- /dev/null
+++ b/nixos/tests/moosefs.nix
@@ -0,0 +1,89 @@
+import ./make-test-python.nix ({ pkgs, ... } :
+
+let
+ master = { pkgs, ... } : {
+ # data base is stored in memory
+ # server crashes with default memory size
+ virtualisation.memorySize = 1024;
+
+ services.moosefs.master = {
+ enable = true;
+ openFirewall = true;
+ exports = [
+ "* / rw,alldirs,admin,maproot=0:0"
+ "* . rw"
+ ];
+ };
+ };
+
+ chunkserver = { pkgs, ... } : {
+ virtualisation.emptyDiskImages = [ 4096 ];
+ boot.initrd.postDeviceCommands = ''
+ ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb
+ '';
+
+ fileSystems = pkgs.lib.mkVMOverride {
+ "/data" = {
+ device = "/dev/disk/by-label/data";
+ fsType = "ext4";
+ };
+ };
+
+ services.moosefs = {
+ masterHost = "master";
+ chunkserver = {
+ openFirewall = true;
+ enable = true;
+ hdds = [ "~/data" ];
+ };
+ };
+ };
+
+ metalogger = { pkgs, ... } : {
+ services.moosefs = {
+ masterHost = "master";
+ metalogger.enable = true;
+ };
+ };
+
+ client = { pkgs, ... } : {
+ services.moosefs.client.enable = true;
+ };
+
+in {
+ name = "moosefs";
+
+ nodes= {
+ inherit master;
+ inherit metalogger;
+ chunkserver1 = chunkserver;
+ chunkserver2 = chunkserver;
+ client1 = client;
+ client2 = client;
+ };
+
+ testScript = ''
+ # prepare master server
+ master.start()
+ master.wait_for_unit("multi-user.target")
+ master.succeed("mfsmaster-init")
+ master.succeed("systemctl restart mfs-master")
+ master.wait_for_unit("mfs-master.service")
+
+ metalogger.wait_for_unit("mfs-metalogger.service")
+
+ for chunkserver in [chunkserver1, chunkserver2]:
+ chunkserver.wait_for_unit("multi-user.target")
+ chunkserver.succeed("chown moosefs:moosefs /data")
+ chunkserver.succeed("systemctl restart mfs-chunkserver")
+ chunkserver.wait_for_unit("mfs-chunkserver.service")
+
+ for client in [client1, client2]:
+ client.wait_for_unit("multi-user.target")
+ client.succeed("mkdir /moosefs")
+ client.succeed("mount -t moosefs master:/ /moosefs")
+
+ client1.succeed("echo test > /moosefs/file")
+ client2.succeed("grep test /moosefs/file")
+ '';
+})