{ config, lib, pkgs, ... }: let cfg = config.services.saunafs; settingsFormat = let listSep = " "; allowedTypes = with lib.types; [ bool int float str ]; valueToString = val: if lib.isList val then lib.concatStringsSep listSep (map (x: valueToString x) val) else if lib.isBool val then (if val then "1" else "0") else toString val; in { type = let valueType = lib.types.oneOf ( [ (lib.types.listOf valueType) ] ++ allowedTypes ) // { description = "Flat key-value file"; }; in lib.types.attrsOf valueType; generate = name: value: pkgs.writeText name ( lib.concatStringsSep "\n" (lib.mapAttrsToList (key: val: "${key} = ${valueToString val}") value) ); }; initTool = pkgs.writeShellScriptBin "sfsmaster-init" '' if [ ! -e ${cfg.master.settings.DATA_PATH}/metadata.sfs ]; then cp --update=none ${pkgs.saunafs}/var/lib/saunafs/metadata.sfs.empty ${cfg.master.settings.DATA_PATH}/metadata.sfs chmod +w ${cfg.master.settings.DATA_PATH}/metadata.sfs fi ''; # master config file masterCfg = settingsFormat.generate "sfsmaster.cfg" cfg.master.settings; # metalogger config file metaloggerCfg = settingsFormat.generate "sfsmetalogger.cfg" cfg.metalogger.settings; # chunkserver config file chunkserverCfg = settingsFormat.generate "sfschunkserver.cfg" cfg.chunkserver.settings; # generic template for all daemons systemdService = name: extraConfig: configFile: { wantedBy = [ "multi-user.target" ]; wants = [ "network-online.target" ]; after = [ "network.target" "network-online.target" ]; serviceConfig = { Type = "forking"; ExecStart = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} start"; ExecStop = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} stop"; ExecReload = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} reload"; } // extraConfig; }; in { ###### interface options = { services.saunafs = { masterHost = lib.mkOption { type = lib.types.str; default = null; description = "IP or hostname name of master host."; }; sfsUser = lib.mkOption { type = lib.types.str; default = "saunafs"; description = "Run daemons as user."; }; client.enable = lib.mkEnableOption "Saunafs client"; master = { enable = lib.mkOption { type = lib.types.bool; description = '' Enable Saunafs master daemon. You need to run `sfsmaster-init` on a freshly installed master server to initialize the `DATA_PATH` directory. ''; default = false; }; exports = lib.mkOption { type = with lib.types; listOf str; default = null; description = "Paths to exports file (see {manpage}`sfsexports.cfg(5)`)."; example = lib.literalExpression '' [ "* / rw,alldirs,admin,maproot=0:0" ]; ''; }; openFirewall = lib.mkOption { type = lib.types.bool; description = "Whether to automatically open the necessary ports in the firewall."; default = false; }; settings = lib.mkOption { type = lib.types.submodule { freeformType = settingsFormat.type; options.DATA_PATH = lib.mkOption { type = lib.types.str; default = "/var/lib/saunafs/master"; description = "Data storage directory."; }; }; description = "Contents of config file ({manpage}`sfsmaster.cfg(5)`)."; }; }; metalogger = { enable = lib.mkEnableOption "Saunafs metalogger daemon"; settings = lib.mkOption { type = lib.types.submodule { freeformType = settingsFormat.type; options.DATA_PATH = lib.mkOption { type = lib.types.str; default = "/var/lib/saunafs/metalogger"; description = "Data storage directory"; }; }; description = "Contents of metalogger config file (see {manpage}`sfsmetalogger.cfg(5)`)."; }; }; chunkserver = { enable = lib.mkEnableOption "Saunafs chunkserver daemon"; openFirewall = lib.mkOption { type = lib.types.bool; description = "Whether to automatically open the necessary ports in the firewall."; default = false; }; hdds = lib.mkOption { type = with lib.types; listOf str; default = null; example = lib.literalExpression '' [ "/mnt/hdd1" ]; ''; description = '' Mount points to be used by chunkserver for storage (see {manpage}`sfshdd.cfg(5)`). Note, that these mount points must writeable by the user defined by the saunafs user. ''; }; settings = lib.mkOption { type = lib.types.submodule { freeformType = settingsFormat.type; options.DATA_PATH = lib.mkOption { type = lib.types.str; default = "/var/lib/saunafs/chunkserver"; description = "Directory for chunck meta data"; }; }; description = "Contents of chunkserver config file (see {manpage}`sfschunkserver.cfg(5)`)."; }; }; }; }; ###### implementation config = lib.mkIf (cfg.client.enable || cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable) { warnings = [ (lib.mkIf (cfg.sfsUser == "root") "Running saunafs services as root is not recommended.") ]; # Service settings services.saunafs = { master.settings = lib.mkIf cfg.master.enable { WORKING_USER = cfg.sfsUser; EXPORTS_FILENAME = toString ( pkgs.writeText "sfsexports.cfg" (lib.concatStringsSep "\n" cfg.master.exports) ); }; metalogger.settings = lib.mkIf cfg.metalogger.enable { WORKING_USER = cfg.sfsUser; MASTER_HOST = cfg.masterHost; }; chunkserver.settings = lib.mkIf cfg.chunkserver.enable { WORKING_USER = cfg.sfsUser; MASTER_HOST = cfg.masterHost; HDD_CONF_FILENAME = toString ( pkgs.writeText "sfshdd.cfg" (lib.concatStringsSep "\n" cfg.chunkserver.hdds) ); }; }; # Create system user account for daemons users = lib.mkIf (cfg.sfsUser != "root" && (cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable)) { users."${cfg.sfsUser}" = { isSystemUser = true; description = "saunafs daemon user"; group = "saunafs"; }; groups."${cfg.sfsUser}" = { }; }; environment.systemPackages = (lib.optional cfg.client.enable pkgs.saunafs) ++ (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 = lib.optional cfg.master.enable "d ${cfg.master.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -" ++ lib.optional cfg.metalogger.enable "d ${cfg.metalogger.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -" ++ lib.optional cfg.chunkserver.enable "d ${cfg.chunkserver.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -"; # Service definitions systemd.services.sfs-master = lib.mkIf cfg.master.enable ( systemdService "master" { TimeoutStartSec = 1800; TimeoutStopSec = 1800; Restart = "no"; } masterCfg ); systemd.services.sfs-metalogger = lib.mkIf cfg.metalogger.enable ( systemdService "metalogger" { Restart = "on-abort"; } metaloggerCfg ); systemd.services.sfs-chunkserver = lib.mkIf cfg.chunkserver.enable ( systemdService "chunkserver" { Restart = "on-abort"; } chunkserverCfg ); }; }