···
1
+
/*https://hg.sr.ht/~dermetfan/seaweedfs-nixos/browse/seaweedfs.nix?rev=tip*/
3
+
{ config, lib, pkgs, ... }:
8
+
cfg = config.modules.seaweedfs;
10
+
clusterModule = cluster: {
12
+
package = mkOption {
13
+
type = types.package;
14
+
default = pkgs.seaweedfs;
19
+
type = with types; nullOr (submodule {
21
+
cert = mkOption { type = path; };
22
+
key = mkOption { type = path; };
29
+
type = with types; nullOr str;
40
+
masters = mkOption {
41
+
type = with types; attrsOf (submodule (masterModule cluster.config));
43
+
description = "SeaweedFS masters";
46
+
volumes = mkOption {
47
+
type = with types; attrsOf (submodule (volumeModule cluster.config));
49
+
description = "SeaweedFS volumes";
53
+
type = with types; attrsOf (submodule (filerModule cluster.config));
55
+
description = "SeaweedFS filers";
58
+
webdavs = mkOption {
59
+
type = with types; attrsOf (submodule (webdavModule cluster.config));
61
+
description = "SeaweedFS WebDAV servers";
64
+
instances = mkOption {
65
+
type = with types; attrsOf (submodule instanceModule);
66
+
description = "SeaweedFS instances";
68
+
mapAttrs' (name: master: nameValuePair
71
+
inherit (master) cluster configs;
77
+
"-port=${toString port}"
78
+
"-volumeSizeLimitMB=${toString volumeSizeLimitMB}"
80
+
optional (cpuprofile != "") "-cpuprofile=${cpuprofile}" ++
81
+
optional (defaultReplication != null) ("-defaultReplication=${defaultReplication.code}") ++
82
+
optional disableHttp "-disableHttp" ++
83
+
optional (garbageThreshold != "") "-garbageThreshold=${garbageThreshold}" ++
84
+
optional (ip != "") "-ip=${ip}" ++
85
+
optional (master."ip.bind" != "") "-ip.bind=${master."ip.bind"}" ++
86
+
optional (mdir != "") "-mdir=${mdir}" ++
87
+
optional (memprofile != "") "-memprofile=${memprofile}" ++
88
+
optional metrics.enable "-metrics.address=${metrics.address.text}" ++
89
+
optional (metrics.intervalSeconds != null) "-metrics.intervalSeconds=${toString metrics.intervalSeconds}" ++
90
+
optional (peers != []) ("-peers=" + (concatStringsSep "," (map (peer: peer.text) peers))) ++
91
+
optional resumeState "-resumeState" ++
92
+
optional volumePreallocate "-volumePreallocate" ++
93
+
optional (whiteList != []) ("-whiteList=" + (concatStringsSep "," whiteList));
95
+
) cluster.config.masters //
96
+
mapAttrs' (name: volume: nameValuePair
99
+
inherit (volume) cluster configs;
101
+
command = "volume";
103
+
args = with volume;
105
+
"-port=${toString port}"
106
+
"-dir=${concatStringsSep "," dir}"
107
+
"-fileSizeLimitMB=${toString fileSizeLimitMB}"
108
+
"-idleTimeout=${toString idleTimeout}"
110
+
"-minFreeSpacePercent=${toString minFreeSpacePercent}"
111
+
"-preStopSeconds=${toString preStopSeconds}"
113
+
optional (compactionMBps != null) ("-compactionMBps=${compactionMBps}") ++
114
+
optional (cpuprofile != "") "-cpuprofile=${cpuprofile}" ++
115
+
optional (dataCenter != "") "-dataCenter=${dataCenter}" ++
116
+
optional volume."images.fix.orientation" "-images.fix.orientation" ++
117
+
optional (ip != "") "-ip=${ip}" ++
118
+
optional (volume."ip.bind" != "") "-ip.bind=${volume."ip.bind"}" ++
119
+
optional (max != []) "-max=${concatStringsSep "," (map toString max)}" ++
120
+
optional (memprofile != "") "-memprofile=${memprofile}" ++
121
+
optional (metricsPort != null) "-metricsPort=${toString metricsPort}" ++
122
+
optional (mserver != []) ("-mserver=" + (concatStringsSep "," (map (mserver: mserver.text) mserver))) ++
123
+
optional (volume."port.public" != null) "-port.public=${toString volume."port.public"}" ++
124
+
optional pprof "-pprof" ++
125
+
optional (publicUrl != "") "-publicUrl=${publicUrl}" ++
126
+
optional (rack != "") "-rack=${rack}" ++
127
+
optional (!volume."read.redirect") "-read.redirect=false" ++
128
+
optional (whiteList != []) ("-whiteList=" + (concatStringsSep "," whiteList));
130
+
systemdService.preStart = "mkdir -p ${concatStringsSep " " volume.dir}";
132
+
) cluster.config.volumes //
133
+
mapAttrs' (name: filer: nameValuePair
136
+
inherit (filer) cluster configs;
142
+
"-port=${toString port}"
143
+
"-dirListLimit=${toString dirListLimit}"
144
+
"-maxMB=${toString maxMB}"
146
+
optional (collection != "") "-collection=${collection}" ++
147
+
optional (dataCenter != "") "-dataCenter=${dataCenter}" ++
148
+
optional (defaultReplicaPlacement != null) ("-defaultReplicaPlacement=${defaultReplicaPlacement.code}") ++
149
+
optional disableDirListing "-disableDirListing" ++
150
+
optional disableHttp "-disableHttp" ++
151
+
optional encryptVolumeData "-encryptVolumeData" ++
152
+
optional (ip != "") "-ip=${ip}" ++
153
+
optional (filer."ip.bind" != "") "-ip.bind=${filer."ip.bind"}" ++
154
+
optional (master != []) ("-master=" + (concatStringsSep "," (map (master: master.text) master))) ++
155
+
optional (metricsPort != null) "-metricsPort=${toString metricsPort}" ++
156
+
optional (peers != []) ("-peers=" + (concatStringsSep "," (map (peer: peer.text) peers))) ++
157
+
optional (filer."port.readonly" != null) "-port.readonly=${toString filer."port.readonly"}" ++
158
+
optional (rack != "") "-rack=${rack}" ++
159
+
optionals s3.enable [
161
+
"-s3.port=${toString filer.s3.port}"
163
+
optional (s3.enable && s3."cert.file" != "") "-s3.cert.file=${s3."cert.file"}" ++
164
+
optional (s3.enable && s3."key.file" != "") "-s3.key.file=${s3."key.file"}" ++
165
+
optional (s3.enable && s3.config != "") "-s3.config=${s3.config}" ++
166
+
optional (s3.enable && s3.domainName != []) "-s3.domainName=${concatStringsSep "," s3.domainName}";
168
+
systemdService.preStart = let
169
+
conf = filer.configs.filer.leveldb2 or {};
170
+
in optionalString (conf ? "dir") "mkdir -p ${conf.dir}";
172
+
) cluster.config.filers //
173
+
mapAttrs' (name: webdav: nameValuePair
176
+
inherit (webdav) cluster;
178
+
command = "webdav";
180
+
args = with webdav;
182
+
"-port=${toString port}"
183
+
"-filer=${filer.text}"
184
+
"-cacheCapacityMB=${toString cacheCapacityMB}"
186
+
optional (collection != "") "-collection=${collection}" ++
187
+
optional (cacheDir != "") "-cacheDir=${cacheDir}";
189
+
) cluster.config.webdavs;
194
+
commonModule = cluster: common: {
196
+
cluster = mkOption {
197
+
type = types.submodule clusterModule;
201
+
openFirewall = mkEnableOption "open the firewall";
204
+
config = { inherit cluster; };
207
+
masterModule = cluster: master: {
208
+
imports = [ (commonModule cluster) ];
211
+
configs = mkOption {
212
+
type = with types; attrsOf attrs;
213
+
default.master.maintenance = {
215
+
ec.encode -fullPercent=95 -quietFor=1h
218
+
volume.balance -force
219
+
volume.fix.replication
221
+
sleep_minutes = 17;
225
+
cpuprofile = mkOption {
230
+
defaultReplication = mkOption {
231
+
type = types.submodule replicationModule;
235
+
disableHttp = mkEnableOption "disable HTTP requests, gRPC only";
237
+
garbageThreshold = mkOption {
244
+
default = config.networking.hostName;
247
+
"ip.bind" = mkOption {
249
+
default = "0.0.0.0";
257
+
memprofile = mkOption {
263
+
enable = mkEnableOption "Prometheus";
265
+
address = mkOption {
266
+
type = types.submodule ipPortModule;
270
+
intervalSeconds = mkOption {
271
+
type = types.ints.unsigned;
278
+
default = mapAttrsIpPort master.config.cluster.masters;
286
+
resumeState = mkEnableOption "resume previous state on master server";
288
+
volumePreallocate = mkEnableOption "preallocate disk space for volumes";
290
+
volumeSizeLimitMB = mkOption {
291
+
type = types.ints.unsigned;
295
+
whiteList = mkOption {
296
+
type = with types; listOf str;
302
+
volumeModule = cluster: volume: {
303
+
imports = [ (commonModule cluster) ];
306
+
configs = mkOption {
307
+
type = with types; attrsOf attrs;
311
+
compactionMBps = mkOption {
312
+
type = with types; nullOr ints.unsigned;
316
+
cpuprofile = mkOption {
321
+
dataCenter = mkOption {
327
+
type = with types; listOf str;
328
+
default = [ "/var/lib/seaweedfs/${cluster._module.args.name}/volume-${volume.config._module.args.name}" ];
331
+
fileSizeLimitMB = mkOption {
332
+
type = types.ints.unsigned;
336
+
idleTimeout = mkOption{
337
+
type = types.ints.unsigned;
341
+
"images.fix.orientation" = mkEnableOption "adjustment of jpg orientation when uploading";
344
+
type = types.enum [
350
+
default = "memory";
355
+
default = config.networking.hostName;
358
+
"ip.bind" = mkOption {
360
+
default = "0.0.0.0";
364
+
type = with types; listOf ints.unsigned;
368
+
memprofile = mkOption {
373
+
metricsPort = mkOption {
374
+
type = with types; nullOr port;
378
+
minFreeSpacePercent = mkOption {
379
+
type = types.ints.unsigned;
383
+
mserver = mkOption {
385
+
default = mapAttrsIpPort volume.config.cluster.masters;
393
+
"port.public" = mkOption {
394
+
type = with types; nullOr port;
398
+
pprof = mkEnableOption "pprof http handlers. precludes -memprofile and -cpuprofile";
400
+
preStopSeconds = mkOption {
405
+
publicUrl = mkOption {
415
+
"read.redirect" = mkOption {
420
+
whiteList = mkOption {
421
+
type = with types; listOf str;
427
+
filerModule = cluster: filer: {
428
+
imports = [ (commonModule cluster) ];
431
+
configs = mkOption {
432
+
type = with types; attrsOf attrs;
433
+
default.filer.leveldb2 = {
435
+
dir = "/var/lib/seaweedfs/${cluster._module.args.name}/filer-${filer.config._module.args.name}/filerldb2";
439
+
collection = mkOption {
444
+
dataCenter = mkOption {
449
+
defaultReplicaPlacement = mkOption {
450
+
type = with types; nullOr (submodule replicationModule);
454
+
dirListLimit = mkOption {
455
+
type = types.ints.unsigned;
459
+
disableDirListing = mkEnableOption "turn off directory listing";
461
+
disableHttp = mkEnableOption "disable http request, only gRpc operations are allowed";
463
+
encryptVolumeData = mkEnableOption "encrypt data on volume servers";
467
+
default = config.networking.hostName;
470
+
"ip.bind" = mkOption {
472
+
default = "0.0.0.0";
475
+
master = mkOption {
477
+
default = mapAttrsIpPort filer.config.cluster.masters;
481
+
type = types.ints.unsigned;
485
+
metricsPort = mkOption {
486
+
type = with types; nullOr port;
492
+
default = mapAttrsIpPort filer.config.cluster.filers;
500
+
"port.readonly" = mkOption {
501
+
type = with types; nullOr port;
511
+
enable = mkEnableOption "whether to start S3 gateway";
513
+
"cert.file" = mkOption {
518
+
config = mkOption {
523
+
domainName = mkOption {
524
+
type = with types; listOf str;
528
+
"key.file" = mkOption {
541
+
webdavModule = cluster: webdav: {
542
+
imports = [ (commonModule cluster) ];
545
+
cacheCapacityMB = mkOption {
550
+
cacheDir = mkOption {
555
+
collection = mkOption {
561
+
type = types.submodule ipPortModule;
575
+
instanceModule = instance: {
577
+
cluster = mkOption {
578
+
type = types.submodule clusterModule;
582
+
command = mkOption {
583
+
type = types.enum [
598
+
logArgs = mkOption {
599
+
type = with types; listOf str;
604
+
type = with types; listOf str;
608
+
configs = mkOption {
609
+
type = with types; attrsOf attrs;
613
+
package = mkOption {
614
+
type = types.package;
615
+
default = instance.config.cluster.package;
618
+
systemdService = mkOption {
619
+
type = types.attrs;
625
+
logArgs = [ "-logtostderr" ];
627
+
systemdService.path = optional (instance.config.command == "mount") pkgs.fuse;
631
+
replicationModule = replication: {
633
+
dataCenter = mkOption {
634
+
type = types.ints.between 0 9;
639
+
type = types.ints.between 0 9;
643
+
server = mkOption {
644
+
type = types.ints.between 0 9;
652
+
default = with replication.config; "${toString dataCenter}${toString rack}${toString server}";
657
+
peersType = with types; listOf (submodule ipPortModule);
659
+
ipPortModule = ipPort: {
673
+
default = with ipPort.config; "${ip}:${toString port}";
678
+
mapAttrsIpPort = attrs: mapAttrsToList (name: value: { inherit (value) ip port; }) attrs;
680
+
toTOML = with generators; toINI {
681
+
mkKeyValue = mkKeyValueDefault {
688
+
${removeSuffix "\n" v}
693
+
else mkValueStringDefault {} v;
697
+
flattenAttrs = separator: attrs: let
713
+
{ name = "a-m1"; value = {}; }
714
+
{ name = "a-m2"; value = {}; }
717
+
{ name = "b-m1"; value = {}; }
721
+
step1 = mapAttrs (outerName: outerValues:
722
+
mapAttrsToList (innerName: innerValues: nameValuePair
723
+
"${outerName}${separator}${innerName}"
731
+
{ name = "a-m1"; value = {}; }
732
+
{ name = "a-m2"; value = {}; }
735
+
{ name = "b-m1"; value = {}; }
739
+
step2 = mapAttrsToList (name: value: value) step1;
743
+
{ name = "a-m1"; value = {}; }
744
+
{ name = "a-m2"; value = {}; }
745
+
{ name = "b-m1"; value = {}; }
748
+
step3 = flatten step2;
757
+
builtins.listToAttrs step3;
759
+
options.modules.seaweedfs = {
760
+
clusters = mkOption {
761
+
type = with types; attrsOf (submodule clusterModule);
763
+
description = "SeaweedFS clusters";
768
+
systemd.services = mapAttrs'
769
+
(name: instance: nameValuePair "seaweedfs-${name}" instance)
770
+
(flattenAttrs "-" (
771
+
mapAttrs (clusterName: cluster:
772
+
mapAttrs (instanceName: instance: with instance; recursiveUpdate systemdService rec {
773
+
description = "SeaweedFS ${clusterName} ${instanceName}";
774
+
wants = [ "network.target" ];
776
+
wantedBy = [ "multi-user.target" ];
777
+
preStart = with serviceConfig; ''
779
+
let securityFile = config.environment.etc."seaweedfs/${clusterName}/security.toml";
780
+
in optionalString securityFile.enable "ln -s /etc/${securityFile.target} ${WorkingDirectory}/"
783
+
# TODO replace find usage with statically known condition
784
+
find -L /etc/${ConfigurationDirectory} -type f -exec ln -s '{}' ${WorkingDirectory}/ \;
786
+
${optionalString (systemdService ? preStart) systemdService.preStart}
788
+
serviceConfig = rec {
789
+
ExecStart = "${package}/bin/weed ${concatStringsSep " " logArgs} ${command} ${concatStringsSep " " args}";
790
+
Restart = "on-failure";
792
+
ConfigurationDirectory = "seaweedfs/${clusterName}/${instanceName}";
793
+
RuntimeDirectory = ConfigurationDirectory;
794
+
RuntimeDirectoryPreserve = "restart";
795
+
WorkingDirectory = "/run/${RuntimeDirectory}";
797
+
}) cluster.instances
802
+
(mapAttrs' (name: cluster:
803
+
let file = "seaweedfs/${name}/security.toml";
804
+
in nameValuePair file {
805
+
enable = config.environment.etc.${file}.text != "";
806
+
text = with cluster.security.grpc; toTOML (
807
+
(if ca == null then {} else { grpc.ca = ca; }) //
808
+
(if master == null then {} else { "grpc.master" = { inherit (master) cert key; }; }) //
809
+
(if volume == null then {} else { "grpc.volume" = { inherit (volume) cert key; }; }) //
810
+
(if filer == null then {} else { "grpc.filer" = { inherit (filer) cert key; }; }) //
811
+
(if client == null then {} else { "grpc.client" = { inherit (client) cert key; }; }) //
812
+
(if msgBroker == null then {} else { "grpc.msg_broker" = { inherit (msgBroker) cert key; }; })
817
+
(name: config: nameValuePair
818
+
"seaweedfs/${name}.toml"
819
+
{ text = toTOML config; }
821
+
(flattenAttrs "/" (
822
+
mapAttrs (clusterName: cluster:
825
+
(instanceName: instance: instance.configs)
832
+
networking.firewall.allowedTCPPorts = let
833
+
modulesToPorts = extraPorts: mapAttrsToList (name: module:
835
+
optionals openFirewall (
836
+
[ port (port + 10000) ] ++
837
+
(filter (p: p != null) (extraPorts module))
840
+
in flatten (mapAttrsToList (clusterName: cluster:
846
+
(volume: with volume; [ metricsPort volume."port.public" ])
850
+
(filer: with filer; [ metricsPort filer."port.readonly" s3.port])