···
+
/*https://hg.sr.ht/~dermetfan/seaweedfs-nixos/browse/seaweedfs.nix?rev=tip*/
+
{ config, lib, pkgs, ... }:
+
cfg = config.modules.seaweedfs;
+
clusterModule = cluster: {
+
default = pkgs.seaweedfs;
+
type = with types; nullOr (submodule {
+
cert = mkOption { type = path; };
+
key = mkOption { type = path; };
+
type = with types; nullOr str;
+
type = with types; attrsOf (submodule (masterModule cluster.config));
+
description = "SeaweedFS masters";
+
type = with types; attrsOf (submodule (volumeModule cluster.config));
+
description = "SeaweedFS volumes";
+
type = with types; attrsOf (submodule (filerModule cluster.config));
+
description = "SeaweedFS filers";
+
type = with types; attrsOf (submodule (webdavModule cluster.config));
+
description = "SeaweedFS WebDAV servers";
+
type = with types; attrsOf (submodule instanceModule);
+
description = "SeaweedFS instances";
+
mapAttrs' (name: master: nameValuePair
+
inherit (master) cluster configs;
+
"-port=${toString port}"
+
"-volumeSizeLimitMB=${toString volumeSizeLimitMB}"
+
optional (cpuprofile != "") "-cpuprofile=${cpuprofile}" ++
+
optional (defaultReplication != null) ("-defaultReplication=${defaultReplication.code}") ++
+
optional disableHttp "-disableHttp" ++
+
optional (garbageThreshold != "") "-garbageThreshold=${garbageThreshold}" ++
+
optional (ip != "") "-ip=${ip}" ++
+
optional (master."ip.bind" != "") "-ip.bind=${master."ip.bind"}" ++
+
optional (mdir != "") "-mdir=${mdir}" ++
+
optional (memprofile != "") "-memprofile=${memprofile}" ++
+
optional metrics.enable "-metrics.address=${metrics.address.text}" ++
+
optional (metrics.intervalSeconds != null) "-metrics.intervalSeconds=${toString metrics.intervalSeconds}" ++
+
optional (peers != []) ("-peers=" + (concatStringsSep "," (map (peer: peer.text) peers))) ++
+
optional resumeState "-resumeState" ++
+
optional volumePreallocate "-volumePreallocate" ++
+
optional (whiteList != []) ("-whiteList=" + (concatStringsSep "," whiteList));
+
) cluster.config.masters //
+
mapAttrs' (name: volume: nameValuePair
+
inherit (volume) cluster configs;
+
"-port=${toString port}"
+
"-dir=${concatStringsSep "," dir}"
+
"-fileSizeLimitMB=${toString fileSizeLimitMB}"
+
"-idleTimeout=${toString idleTimeout}"
+
"-minFreeSpacePercent=${toString minFreeSpacePercent}"
+
"-preStopSeconds=${toString preStopSeconds}"
+
optional (compactionMBps != null) ("-compactionMBps=${compactionMBps}") ++
+
optional (cpuprofile != "") "-cpuprofile=${cpuprofile}" ++
+
optional (dataCenter != "") "-dataCenter=${dataCenter}" ++
+
optional volume."images.fix.orientation" "-images.fix.orientation" ++
+
optional (ip != "") "-ip=${ip}" ++
+
optional (volume."ip.bind" != "") "-ip.bind=${volume."ip.bind"}" ++
+
optional (max != []) "-max=${concatStringsSep "," (map toString max)}" ++
+
optional (memprofile != "") "-memprofile=${memprofile}" ++
+
optional (metricsPort != null) "-metricsPort=${toString metricsPort}" ++
+
optional (mserver != []) ("-mserver=" + (concatStringsSep "," (map (mserver: mserver.text) mserver))) ++
+
optional (volume."port.public" != null) "-port.public=${toString volume."port.public"}" ++
+
optional pprof "-pprof" ++
+
optional (publicUrl != "") "-publicUrl=${publicUrl}" ++
+
optional (rack != "") "-rack=${rack}" ++
+
optional (!volume."read.redirect") "-read.redirect=false" ++
+
optional (whiteList != []) ("-whiteList=" + (concatStringsSep "," whiteList));
+
systemdService.preStart = "mkdir -p ${concatStringsSep " " volume.dir}";
+
) cluster.config.volumes //
+
mapAttrs' (name: filer: nameValuePair
+
inherit (filer) cluster configs;
+
"-port=${toString port}"
+
"-dirListLimit=${toString dirListLimit}"
+
"-maxMB=${toString maxMB}"
+
optional (collection != "") "-collection=${collection}" ++
+
optional (dataCenter != "") "-dataCenter=${dataCenter}" ++
+
optional (defaultReplicaPlacement != null) ("-defaultReplicaPlacement=${defaultReplicaPlacement.code}") ++
+
optional disableDirListing "-disableDirListing" ++
+
optional disableHttp "-disableHttp" ++
+
optional encryptVolumeData "-encryptVolumeData" ++
+
optional (ip != "") "-ip=${ip}" ++
+
optional (filer."ip.bind" != "") "-ip.bind=${filer."ip.bind"}" ++
+
optional (master != []) ("-master=" + (concatStringsSep "," (map (master: master.text) master))) ++
+
optional (metricsPort != null) "-metricsPort=${toString metricsPort}" ++
+
optional (peers != []) ("-peers=" + (concatStringsSep "," (map (peer: peer.text) peers))) ++
+
optional (filer."port.readonly" != null) "-port.readonly=${toString filer."port.readonly"}" ++
+
optional (rack != "") "-rack=${rack}" ++
+
"-s3.port=${toString filer.s3.port}"
+
optional (s3.enable && s3."cert.file" != "") "-s3.cert.file=${s3."cert.file"}" ++
+
optional (s3.enable && s3."key.file" != "") "-s3.key.file=${s3."key.file"}" ++
+
optional (s3.enable && s3.config != "") "-s3.config=${s3.config}" ++
+
optional (s3.enable && s3.domainName != []) "-s3.domainName=${concatStringsSep "," s3.domainName}";
+
systemdService.preStart = let
+
conf = filer.configs.filer.leveldb2 or {};
+
in optionalString (conf ? "dir") "mkdir -p ${conf.dir}";
+
) cluster.config.filers //
+
mapAttrs' (name: webdav: nameValuePair
+
inherit (webdav) cluster;
+
"-port=${toString port}"
+
"-cacheCapacityMB=${toString cacheCapacityMB}"
+
optional (collection != "") "-collection=${collection}" ++
+
optional (cacheDir != "") "-cacheDir=${cacheDir}";
+
) cluster.config.webdavs;
+
commonModule = cluster: common: {
+
type = types.submodule clusterModule;
+
openFirewall = mkEnableOption "open the firewall";
+
config = { inherit cluster; };
+
masterModule = cluster: master: {
+
imports = [ (commonModule cluster) ];
+
type = with types; attrsOf attrs;
+
default.master.maintenance = {
+
ec.encode -fullPercent=95 -quietFor=1h
+
cpuprofile = mkOption {
+
defaultReplication = mkOption {
+
type = types.submodule replicationModule;
+
disableHttp = mkEnableOption "disable HTTP requests, gRPC only";
+
garbageThreshold = mkOption {
+
default = config.networking.hostName;
+
memprofile = mkOption {
+
enable = mkEnableOption "Prometheus";
+
type = types.submodule ipPortModule;
+
intervalSeconds = mkOption {
+
type = types.ints.unsigned;
+
default = mapAttrsIpPort master.config.cluster.masters;
+
resumeState = mkEnableOption "resume previous state on master server";
+
volumePreallocate = mkEnableOption "preallocate disk space for volumes";
+
volumeSizeLimitMB = mkOption {
+
type = types.ints.unsigned;
+
type = with types; listOf str;
+
volumeModule = cluster: volume: {
+
imports = [ (commonModule cluster) ];
+
type = with types; attrsOf attrs;
+
compactionMBps = mkOption {
+
type = with types; nullOr ints.unsigned;
+
cpuprofile = mkOption {
+
dataCenter = mkOption {
+
type = with types; listOf str;
+
default = [ "/var/lib/seaweedfs/${cluster._module.args.name}/volume-${volume.config._module.args.name}" ];
+
fileSizeLimitMB = mkOption {
+
type = types.ints.unsigned;
+
idleTimeout = mkOption{
+
type = types.ints.unsigned;
+
"images.fix.orientation" = mkEnableOption "adjustment of jpg orientation when uploading";
+
default = config.networking.hostName;
+
type = with types; listOf ints.unsigned;
+
memprofile = mkOption {
+
metricsPort = mkOption {
+
type = with types; nullOr port;
+
minFreeSpacePercent = mkOption {
+
type = types.ints.unsigned;
+
default = mapAttrsIpPort volume.config.cluster.masters;
+
"port.public" = mkOption {
+
type = with types; nullOr port;
+
pprof = mkEnableOption "pprof http handlers. precludes -memprofile and -cpuprofile";
+
preStopSeconds = mkOption {
+
"read.redirect" = mkOption {
+
type = with types; listOf str;
+
filerModule = cluster: filer: {
+
imports = [ (commonModule cluster) ];
+
type = with types; attrsOf attrs;
+
default.filer.leveldb2 = {
+
dir = "/var/lib/seaweedfs/${cluster._module.args.name}/filer-${filer.config._module.args.name}/filerldb2";
+
collection = mkOption {
+
dataCenter = mkOption {
+
defaultReplicaPlacement = mkOption {
+
type = with types; nullOr (submodule replicationModule);
+
dirListLimit = mkOption {
+
type = types.ints.unsigned;
+
disableDirListing = mkEnableOption "turn off directory listing";
+
disableHttp = mkEnableOption "disable http request, only gRpc operations are allowed";
+
encryptVolumeData = mkEnableOption "encrypt data on volume servers";
+
default = config.networking.hostName;
+
default = mapAttrsIpPort filer.config.cluster.masters;
+
type = types.ints.unsigned;
+
metricsPort = mkOption {
+
type = with types; nullOr port;
+
default = mapAttrsIpPort filer.config.cluster.filers;
+
"port.readonly" = mkOption {
+
type = with types; nullOr port;
+
enable = mkEnableOption "whether to start S3 gateway";
+
"cert.file" = mkOption {
+
domainName = mkOption {
+
type = with types; listOf str;
+
"key.file" = mkOption {
+
webdavModule = cluster: webdav: {
+
imports = [ (commonModule cluster) ];
+
cacheCapacityMB = mkOption {
+
collection = mkOption {
+
type = types.submodule ipPortModule;
+
instanceModule = instance: {
+
type = types.submodule clusterModule;
+
type = with types; listOf str;
+
type = with types; listOf str;
+
type = with types; attrsOf attrs;
+
default = instance.config.cluster.package;
+
systemdService = mkOption {
+
logArgs = [ "-logtostderr" ];
+
systemdService.path = optional (instance.config.command == "mount") pkgs.fuse;
+
replicationModule = replication: {
+
dataCenter = mkOption {
+
type = types.ints.between 0 9;
+
type = types.ints.between 0 9;
+
type = types.ints.between 0 9;
+
default = with replication.config; "${toString dataCenter}${toString rack}${toString server}";
+
peersType = with types; listOf (submodule ipPortModule);
+
ipPortModule = ipPort: {
+
default = with ipPort.config; "${ip}:${toString port}";
+
mapAttrsIpPort = attrs: mapAttrsToList (name: value: { inherit (value) ip port; }) attrs;
+
toTOML = with generators; toINI {
+
mkKeyValue = mkKeyValueDefault {
+
else mkValueStringDefault {} v;
+
flattenAttrs = separator: attrs: let
+
{ name = "a-m1"; value = {}; }
+
{ name = "a-m2"; value = {}; }
+
{ name = "b-m1"; value = {}; }
+
step1 = mapAttrs (outerName: outerValues:
+
mapAttrsToList (innerName: innerValues: nameValuePair
+
"${outerName}${separator}${innerName}"
+
{ name = "a-m1"; value = {}; }
+
{ name = "a-m2"; value = {}; }
+
{ name = "b-m1"; value = {}; }
+
step2 = mapAttrsToList (name: value: value) step1;
+
{ name = "a-m1"; value = {}; }
+
{ name = "a-m2"; value = {}; }
+
{ name = "b-m1"; value = {}; }
+
builtins.listToAttrs step3;
+
options.modules.seaweedfs = {
+
type = with types; attrsOf (submodule clusterModule);
+
description = "SeaweedFS clusters";
+
systemd.services = mapAttrs'
+
(name: instance: nameValuePair "seaweedfs-${name}" instance)
+
mapAttrs (clusterName: cluster:
+
mapAttrs (instanceName: instance: with instance; recursiveUpdate systemdService rec {
+
description = "SeaweedFS ${clusterName} ${instanceName}";
+
wants = [ "network.target" ];
+
wantedBy = [ "multi-user.target" ];
+
preStart = with serviceConfig; ''
+
let securityFile = config.environment.etc."seaweedfs/${clusterName}/security.toml";
+
in optionalString securityFile.enable "ln -s /etc/${securityFile.target} ${WorkingDirectory}/"
+
# TODO replace find usage with statically known condition
+
find -L /etc/${ConfigurationDirectory} -type f -exec ln -s '{}' ${WorkingDirectory}/ \;
+
${optionalString (systemdService ? preStart) systemdService.preStart}
+
ExecStart = "${package}/bin/weed ${concatStringsSep " " logArgs} ${command} ${concatStringsSep " " args}";
+
Restart = "on-failure";
+
ConfigurationDirectory = "seaweedfs/${clusterName}/${instanceName}";
+
RuntimeDirectory = ConfigurationDirectory;
+
RuntimeDirectoryPreserve = "restart";
+
WorkingDirectory = "/run/${RuntimeDirectory}";
+
(mapAttrs' (name: cluster:
+
let file = "seaweedfs/${name}/security.toml";
+
in nameValuePair file {
+
enable = config.environment.etc.${file}.text != "";
+
text = with cluster.security.grpc; toTOML (
+
(if ca == null then {} else { grpc.ca = ca; }) //
+
(if master == null then {} else { "grpc.master" = { inherit (master) cert key; }; }) //
+
(if volume == null then {} else { "grpc.volume" = { inherit (volume) cert key; }; }) //
+
(if filer == null then {} else { "grpc.filer" = { inherit (filer) cert key; }; }) //
+
(if client == null then {} else { "grpc.client" = { inherit (client) cert key; }; }) //
+
(if msgBroker == null then {} else { "grpc.msg_broker" = { inherit (msgBroker) cert key; }; })
+
(name: config: nameValuePair
+
"seaweedfs/${name}.toml"
+
{ text = toTOML config; }
+
mapAttrs (clusterName: cluster:
+
(instanceName: instance: instance.configs)
+
networking.firewall.allowedTCPPorts = let
+
modulesToPorts = extraPorts: mapAttrsToList (name: module:
+
optionals openFirewall (
+
[ port (port + 10000) ] ++
+
(filter (p: p != null) (extraPorts module))
+
in flatten (mapAttrsToList (clusterName: cluster:
+
(volume: with volume; [ metricsPort volume."port.public" ])
+
(filer: with filer; [ metricsPort filer."port.readonly" s3.port])