diff --git a/nixos/modules/services/network-filesystems/seaweedfs.nix b/nixos/modules/services/network-filesystems/seaweedfs.nix new file mode 100644 index 0000000000000..4523fa996145b --- /dev/null +++ b/nixos/modules/services/network-filesystems/seaweedfs.nix @@ -0,0 +1,524 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.seaweedfs; + baseDir = "/var/lib/seaweedfs"; +in +{ + options.services.seaweedfs = { + package = lib.mkOption { + type = lib.types.package; + default = pkgs.seaweedfs; + defaultText = lib.literalExpression "pkgs.seaweedfs"; + description = "The SeaweedFS package to use."; + }; + + master = { + enable = lib.mkEnableOption "SeaweedFS master server"; + + port = lib.mkOption { + type = lib.types.port; + default = 9333; + description = "Port for master server."; + }; + + ip = lib.mkOption { + type = lib.types.str; + description = "IP address to bind to."; + }; + + ipBind = lib.mkOption { + type = lib.types.str; + default = ""; + description = "IP address to bind to. If empty, defaults to same as ip."; + }; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "${baseDir}/master"; + description = "Data directory for master."; + }; + + peers = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "192.168.1.10:9333" + "192.168.1.11:9333" + ]; + description = "List of master peers for clustering."; + }; + + volumeSizeLimitMB = lib.mkOption { + type = lib.types.int; + default = 30000; + description = "Master stops directing writes to oversized volumes."; + }; + + defaultReplication = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Default replication type if not specified."; + }; + + electionTimeout = lib.mkOption { + type = lib.types.str; + default = "10s"; + description = "Election timeout of master servers."; + }; + + heartbeatInterval = lib.mkOption { + type = lib.types.str; + default = "300ms"; + description = "Heartbeat interval of master servers, randomly multiplied by [1, 1.25)."; + }; + + garbageThreshold = lib.mkOption { + type = lib.types.float; + default = 0.3; + description = "Threshold to vacuum and reclaim spaces."; + }; + + whiteList = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Ip addresses having write permission. No limit if empty."; + }; + + metricsPort = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = null; + description = "Prometheus metrics listen port."; + }; + + maxParallelVacuum = lib.mkOption { + type = lib.types.int; + default = 1; + description = "Maximum number of volumes to vacuum in parallel per volume server."; + }; + }; + + volume = { + enable = lib.mkEnableOption "SeaweedFS volume server"; + + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Port for volume server."; + }; + + ip = lib.mkOption { + type = lib.types.str; + description = "IP address to bind to."; + }; + + ipBind = lib.mkOption { + type = lib.types.str; + default = ""; + description = "IP address to bind to. If empty, defaults to same as ip."; + }; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "${baseDir}/volume"; + description = "Data directory for volume."; + }; + + master = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "192.168.1.10:9333" + "192.168.1.11:9333" + ]; + description = "List of master servers addresses."; + }; + + maxVolumes = lib.mkOption { + type = lib.types.int; + default = 8; + description = "Maximum numbers of volumes, count[,count]... If set to zero, the limit will be auto configured as free disk space divided by volume size."; + }; + + dataCenter = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Current volume server's data center name."; + }; + + rack = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Current volume server's rack name."; + }; + + disk = lib.mkOption { + type = lib.types.str; + default = ""; + description = "[hdd|ssd|] hard drive or solid state drive or any tag."; + }; + + idxDir = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Directory to store .idx files."; + }; + + index = lib.mkOption { + type = lib.types.enum [ + "memory" + "leveldb" + "leveldbMedium" + "leveldbLarge" + ]; + default = "memory"; + description = "Mode for memory~performance balance."; + }; + + readMode = lib.mkOption { + type = lib.types.enum [ + "local" + "proxy" + "redirect" + ]; + default = "proxy"; + description = "How to deal with non-local volume: not found|proxy to remote node|redirect volume location."; + }; + + minFreeSpace = lib.mkOption { + type = lib.types.str; + default = "1"; + description = "Min free disk space (value<=100 as percentage like 1, other as human readable bytes, like 10GiB)."; + }; + + fileSizeLimitMB = lib.mkOption { + type = lib.types.int; + default = 256; + description = "Limit file size to avoid out of memory."; + }; + + metricsPort = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = null; + description = "Prometheus metrics listen port."; + }; + + whiteList = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Ip addresses having write permission. No limit if empty."; + }; + }; + + filer = { + enable = lib.mkEnableOption "SeaweedFS filer server"; + + port = lib.mkOption { + type = lib.types.port; + default = 8888; + description = "Port for filer server."; + }; + + ip = lib.mkOption { + type = lib.types.str; + description = "IP address to bind to."; + }; + + ipBind = lib.mkOption { + type = lib.types.str; + default = ""; + description = "IP address to bind to. If empty, defaults to same as ip."; + }; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "${baseDir}/filer"; + description = "Data directory for filer."; + }; + + master = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "192.168.1.10:9333" + "192.168.1.11:9333" + ]; + description = "List of master servers addresses."; + }; + + maxMB = lib.mkOption { + type = lib.types.int; + default = 4; + description = "Split files larger than the limit."; + }; + + webdav = { + enable = lib.mkEnableOption "WebDAV support for filer"; + + port = lib.mkOption { + type = lib.types.port; + default = 7333; + description = "WebDAV server http listen port."; + }; + + path = lib.mkOption { + type = lib.types.str; + default = "/"; + description = "Use this remote path from filer server."; + }; + + collection = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Collection to create the files."; + }; + + cacheDir = lib.mkOption { + type = lib.types.str; + default = "${baseDir}/webdav-cache"; + description = "Local cache directory for file chunks."; + }; + + cacheCapacityMB = lib.mkOption { + type = lib.types.int; + default = 1000; + description = "Local cache capacity in MB."; + }; + + disk = lib.mkOption { + type = lib.types.str; + default = ""; + description = "[hdd|ssd|] hard drive or solid state drive or any tag."; + }; + + replication = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Replication strategy for the files."; + }; + }; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf (cfg.master.enable || cfg.volume.enable || cfg.filer.enable) { + users.users.seaweedfs = { + isSystemUser = true; + group = "seaweedfs"; + home = baseDir; + createHome = true; + }; + + users.groups.seaweedfs = { }; + + systemd.tmpfiles.rules = [ + "d ${baseDir} 0755 seaweedfs seaweedfs -" + "d ${cfg.master.dataDir} 0755 seaweedfs seaweedfs -" + "d ${cfg.volume.dataDir} 0755 seaweedfs seaweedfs -" + "d ${cfg.filer.dataDir} 0755 seaweedfs seaweedfs -" + "d ${cfg.filer.webdav.cacheDir} 0755 seaweedfs seaweedfs -" + ]; + }) + + (lib.mkIf cfg.master.enable { + systemd.services.seaweedfs-master = { + description = "SeaweedFS Master Server"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + mkdir -p ${cfg.master.dataDir} + chown -R seaweedfs:seaweedfs ${cfg.master.dataDir} + chmod -R 755 ${cfg.master.dataDir} + ''; + + serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/weed master \ + -port=${toString cfg.master.port} \ + -ip=${cfg.master.ip} \ + ${lib.optionalString (cfg.master.ipBind != "") "-ip.bind=${cfg.master.ipBind}"} \ + -mdir=${cfg.master.dataDir} \ + -volumeSizeLimitMB=${toString cfg.master.volumeSizeLimitMB} \ + ${ + lib.optionalString ( + cfg.master.defaultReplication != "" + ) "-defaultReplication=${cfg.master.defaultReplication}" + } \ + -electionTimeout=${cfg.master.electionTimeout} \ + -heartbeatInterval=${cfg.master.heartbeatInterval} \ + -garbageThreshold=${toString cfg.master.garbageThreshold} \ + ${ + lib.optionalString ( + cfg.master.metricsPort != null + ) "-metricsPort=${toString cfg.master.metricsPort}" + } \ + -maxParallelVacuumPerServer=${toString cfg.master.maxParallelVacuum} \ + ${ + lib.optionalString (cfg.master.peers != [ ]) "-peers=${lib.concatStringsSep "," cfg.master.peers}" + } \ + ${ + lib.optionalString ( + cfg.master.whiteList != [ ] + ) "-whiteList=${lib.concatStringsSep "," cfg.master.whiteList}" + } + ''; + User = "seaweedfs"; + Group = "seaweedfs"; + StateDirectory = "seaweedfs"; + RuntimeDirectory = "seaweedfs"; + Restart = "always"; + RestartSec = "10s"; + WorkingDirectory = "${cfg.master.dataDir}"; + LimitNOFILE = 65535; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + }; + }; + }) + + (lib.mkIf cfg.volume.enable { + systemd.services.seaweedfs-volume = { + description = "SeaweedFS Volume Server"; + after = [ + "network-online.target" + "seaweedfs-master.service" + ]; + wants = [ "network-online.target" ]; + requires = [ "seaweedfs-master.service" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + mkdir -p ${cfg.volume.dataDir} + chown -R seaweedfs:seaweedfs ${cfg.volume.dataDir} + chmod -R 755 ${cfg.volume.dataDir} + ${lib.optionalString (cfg.volume.idxDir != null) '' + mkdir -p ${cfg.volume.idxDir} + chown -R seaweedfs:seaweedfs ${cfg.volume.idxDir} + chmod -R 755 ${cfg.volume.idxDir} + ''} + ''; + + serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/weed volume \ + -port=${toString cfg.volume.port} \ + -ip=${cfg.volume.ip} \ + ${lib.optionalString (cfg.volume.ipBind != "") "-ip.bind=${cfg.volume.ipBind}"} \ + -dir=${cfg.volume.dataDir} \ + -mserver=${lib.concatStringsSep "," cfg.volume.master} \ + -max=${toString cfg.volume.maxVolumes} \ + ${lib.optionalString (cfg.volume.dataCenter != "") "-dataCenter=${cfg.volume.dataCenter}"} \ + ${lib.optionalString (cfg.volume.rack != "") "-rack=${cfg.volume.rack}"} \ + ${lib.optionalString (cfg.volume.disk != "") "-disk=${cfg.volume.disk}"} \ + ${lib.optionalString (cfg.volume.idxDir != null) "-dir.idx=${cfg.volume.idxDir}"} \ + -index=${cfg.volume.index} \ + -readMode=${cfg.volume.readMode} \ + -minFreeSpace=${cfg.volume.minFreeSpace} \ + -fileSizeLimitMB=${toString cfg.volume.fileSizeLimitMB} \ + ${ + lib.optionalString ( + cfg.volume.metricsPort != null + ) "-metricsPort=${toString cfg.volume.metricsPort}" + } \ + ${ + lib.optionalString ( + cfg.volume.whiteList != [ ] + ) "-whiteList=${lib.concatStringsSep "," cfg.volume.whiteList}" + } + ''; + User = "seaweedfs"; + Group = "seaweedfs"; + StateDirectory = "seaweedfs"; + RuntimeDirectory = "seaweedfs"; + Restart = "always"; + RestartSec = "10s"; + WorkingDirectory = "${cfg.volume.dataDir}"; + LimitNOFILE = 65535; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + }; + }; + }) + + (lib.mkIf cfg.filer.enable { + systemd.services.seaweedfs-filer = { + description = "SeaweedFS Filer Server"; + after = [ + "network-online.target" + "seaweedfs-master.service" + ]; + wants = [ "network-online.target" ]; + requires = [ "seaweedfs-master.service" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + mkdir -p ${cfg.filer.dataDir} + chown -R seaweedfs:seaweedfs ${cfg.filer.dataDir} + chmod -R 755 ${cfg.filer.dataDir} + ${lib.optionalString cfg.filer.webdav.enable '' + mkdir -p ${cfg.filer.webdav.cacheDir} + chown -R seaweedfs:seaweedfs ${cfg.filer.webdav.cacheDir} + chmod -R 755 ${cfg.filer.webdav.cacheDir} + ''} + ''; + + serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/weed filer \ + -port=${toString cfg.filer.port} \ + -ip=${cfg.filer.ip} \ + ${lib.optionalString (cfg.filer.ipBind != "") "-ip.bind=${cfg.filer.ipBind}"} \ + -master=${lib.concatStringsSep "," cfg.filer.master} \ + -defaultStoreDir=${cfg.filer.dataDir} \ + -maxMB=${toString cfg.filer.maxMB} \ + ${lib.optionalString cfg.filer.webdav.enable "-webdav"} \ + ${ + lib.optionalString ( + cfg.filer.webdav.enable && cfg.filer.webdav.port != null + ) "-webdav.port=${toString cfg.filer.webdav.port}" + } \ + ${ + lib.optionalString ( + cfg.filer.webdav.enable && cfg.filer.webdav.collection != "" + ) "-webdav.collection=${cfg.filer.webdav.collection}" + } \ + ${ + lib.optionalString ( + cfg.filer.webdav.enable && cfg.filer.webdav.cacheDir != null + ) "-webdav.cacheDir=${cfg.filer.webdav.cacheDir}" + } \ + ${ + lib.optionalString ( + cfg.filer.webdav.enable && cfg.filer.webdav.cacheCapacityMB != null + ) "-webdav.cacheCapacityMB=${toString cfg.filer.webdav.cacheCapacityMB}" + } \ + ${ + lib.optionalString ( + cfg.filer.webdav.enable && cfg.filer.webdav.disk != "" + ) "-webdav.disk=${cfg.filer.webdav.disk}" + } \ + ${ + lib.optionalString ( + cfg.filer.webdav.enable && cfg.filer.webdav.replication != "" + ) "-webdav.replication=${cfg.filer.webdav.replication}" + } + ''; + User = "seaweedfs"; + Group = "seaweedfs"; + StateDirectory = "seaweedfs"; + RuntimeDirectory = "seaweedfs"; + Restart = "always"; + RestartSec = "10s"; + WorkingDirectory = "${cfg.filer.dataDir}"; + LimitNOFILE = 65535; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + }; + }; + }) + ]; +}