at master 8.7 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.services.saunafs; 10 11 settingsFormat = 12 let 13 listSep = " "; 14 allowedTypes = with lib.types; [ 15 bool 16 int 17 float 18 str 19 ]; 20 valueToString = 21 val: 22 if lib.isList val then 23 lib.concatStringsSep listSep (map (x: valueToString x) val) 24 else if lib.isBool val then 25 (if val then "1" else "0") 26 else 27 toString val; 28 29 in 30 { 31 type = 32 let 33 valueType = 34 lib.types.oneOf ( 35 [ 36 (lib.types.listOf valueType) 37 ] 38 ++ allowedTypes 39 ) 40 // { 41 description = "Flat key-value file"; 42 }; 43 in 44 lib.types.attrsOf valueType; 45 46 generate = 47 name: value: 48 pkgs.writeText name ( 49 lib.concatStringsSep "\n" (lib.mapAttrsToList (key: val: "${key} = ${valueToString val}") value) 50 ); 51 }; 52 53 initTool = pkgs.writeShellScriptBin "sfsmaster-init" '' 54 if [ ! -e ${cfg.master.settings.DATA_PATH}/metadata.sfs ]; then 55 cp --update=none ${pkgs.saunafs}/var/lib/saunafs/metadata.sfs.empty ${cfg.master.settings.DATA_PATH}/metadata.sfs 56 chmod +w ${cfg.master.settings.DATA_PATH}/metadata.sfs 57 fi 58 ''; 59 60 # master config file 61 masterCfg = settingsFormat.generate "sfsmaster.cfg" cfg.master.settings; 62 63 # metalogger config file 64 metaloggerCfg = settingsFormat.generate "sfsmetalogger.cfg" cfg.metalogger.settings; 65 66 # chunkserver config file 67 chunkserverCfg = settingsFormat.generate "sfschunkserver.cfg" cfg.chunkserver.settings; 68 69 # generic template for all daemons 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.saunafs}/bin/sfs${name} -c ${configFile} start"; 81 ExecStop = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} stop"; 82 ExecReload = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} reload"; 83 } 84 // extraConfig; 85 }; 86 87in 88{ 89 ###### interface 90 91 options = { 92 services.saunafs = { 93 masterHost = lib.mkOption { 94 type = lib.types.str; 95 default = null; 96 description = "IP or hostname name of master host."; 97 }; 98 99 sfsUser = lib.mkOption { 100 type = lib.types.str; 101 default = "saunafs"; 102 description = "Run daemons as user."; 103 }; 104 105 client.enable = lib.mkEnableOption "Saunafs client"; 106 107 master = { 108 enable = lib.mkOption { 109 type = lib.types.bool; 110 description = '' 111 Enable Saunafs master daemon. 112 113 You need to run `sfsmaster-init` on a freshly installed master server to 114 initialize the `DATA_PATH` directory. 115 ''; 116 default = false; 117 }; 118 119 exports = lib.mkOption { 120 type = with lib.types; listOf str; 121 default = null; 122 description = "Paths to exports file (see {manpage}`sfsexports.cfg(5)`)."; 123 example = lib.literalExpression '' 124 [ "* / rw,alldirs,admin,maproot=0:0" ]; 125 ''; 126 }; 127 128 openFirewall = lib.mkOption { 129 type = lib.types.bool; 130 description = "Whether to automatically open the necessary ports in the firewall."; 131 default = false; 132 }; 133 134 settings = lib.mkOption { 135 type = lib.types.submodule { 136 freeformType = settingsFormat.type; 137 138 options.DATA_PATH = lib.mkOption { 139 type = lib.types.str; 140 default = "/var/lib/saunafs/master"; 141 description = "Data storage directory."; 142 }; 143 }; 144 145 description = "Contents of config file ({manpage}`sfsmaster.cfg(5)`)."; 146 }; 147 }; 148 149 metalogger = { 150 enable = lib.mkEnableOption "Saunafs metalogger daemon"; 151 152 settings = lib.mkOption { 153 type = lib.types.submodule { 154 freeformType = settingsFormat.type; 155 156 options.DATA_PATH = lib.mkOption { 157 type = lib.types.str; 158 default = "/var/lib/saunafs/metalogger"; 159 description = "Data storage directory"; 160 }; 161 }; 162 163 description = "Contents of metalogger config file (see {manpage}`sfsmetalogger.cfg(5)`)."; 164 }; 165 }; 166 167 chunkserver = { 168 enable = lib.mkEnableOption "Saunafs chunkserver daemon"; 169 170 openFirewall = lib.mkOption { 171 type = lib.types.bool; 172 description = "Whether to automatically open the necessary ports in the firewall."; 173 default = false; 174 }; 175 176 hdds = lib.mkOption { 177 type = with lib.types; listOf str; 178 default = null; 179 180 example = lib.literalExpression '' 181 [ "/mnt/hdd1" ]; 182 ''; 183 184 description = '' 185 Mount points to be used by chunkserver for storage (see {manpage}`sfshdd.cfg(5)`). 186 187 Note, that these mount points must writeable by the user defined by the saunafs user. 188 ''; 189 }; 190 191 settings = lib.mkOption { 192 type = lib.types.submodule { 193 freeformType = settingsFormat.type; 194 195 options.DATA_PATH = lib.mkOption { 196 type = lib.types.str; 197 default = "/var/lib/saunafs/chunkserver"; 198 description = "Directory for chunck meta data"; 199 }; 200 }; 201 202 description = "Contents of chunkserver config file (see {manpage}`sfschunkserver.cfg(5)`)."; 203 }; 204 }; 205 }; 206 }; 207 208 ###### implementation 209 210 config = 211 lib.mkIf (cfg.client.enable || cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable) 212 { 213 214 warnings = [ 215 (lib.mkIf (cfg.sfsUser == "root") "Running saunafs services as root is not recommended.") 216 ]; 217 218 # Service settings 219 services.saunafs = { 220 master.settings = lib.mkIf cfg.master.enable { 221 WORKING_USER = cfg.sfsUser; 222 EXPORTS_FILENAME = toString ( 223 pkgs.writeText "sfsexports.cfg" (lib.concatStringsSep "\n" cfg.master.exports) 224 ); 225 }; 226 227 metalogger.settings = lib.mkIf cfg.metalogger.enable { 228 WORKING_USER = cfg.sfsUser; 229 MASTER_HOST = cfg.masterHost; 230 }; 231 232 chunkserver.settings = lib.mkIf cfg.chunkserver.enable { 233 WORKING_USER = cfg.sfsUser; 234 MASTER_HOST = cfg.masterHost; 235 HDD_CONF_FILENAME = toString ( 236 pkgs.writeText "sfshdd.cfg" (lib.concatStringsSep "\n" cfg.chunkserver.hdds) 237 ); 238 }; 239 }; 240 241 # Create system user account for daemons 242 users = 243 lib.mkIf 244 (cfg.sfsUser != "root" && (cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable)) 245 { 246 users."${cfg.sfsUser}" = { 247 isSystemUser = true; 248 description = "saunafs daemon user"; 249 group = "saunafs"; 250 }; 251 groups."${cfg.sfsUser}" = { }; 252 }; 253 254 environment.systemPackages = 255 (lib.optional cfg.client.enable pkgs.saunafs) ++ (lib.optional cfg.master.enable initTool); 256 257 networking.firewall.allowedTCPPorts = 258 (lib.optionals cfg.master.openFirewall [ 259 9419 260 9420 261 9421 262 ]) 263 ++ (lib.optional cfg.chunkserver.openFirewall 9422); 264 265 # Ensure storage directories exist 266 systemd.tmpfiles.rules = 267 lib.optional cfg.master.enable "d ${cfg.master.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -" 268 ++ lib.optional cfg.metalogger.enable "d ${cfg.metalogger.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -" 269 ++ lib.optional cfg.chunkserver.enable "d ${cfg.chunkserver.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -"; 270 271 # Service definitions 272 systemd.services.sfs-master = lib.mkIf cfg.master.enable ( 273 systemdService "master" { 274 TimeoutStartSec = 1800; 275 TimeoutStopSec = 1800; 276 Restart = "no"; 277 } masterCfg 278 ); 279 280 systemd.services.sfs-metalogger = lib.mkIf cfg.metalogger.enable ( 281 systemdService "metalogger" { Restart = "on-abort"; } metaloggerCfg 282 ); 283 284 systemd.services.sfs-chunkserver = lib.mkIf cfg.chunkserver.enable ( 285 systemdService "chunkserver" { Restart = "on-abort"; } chunkserverCfg 286 ); 287 }; 288}