1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 cfg = config.services.beesd;
10
11 logLevels = {
12 emerg = 0;
13 alert = 1;
14 crit = 2;
15 err = 3;
16 warning = 4;
17 notice = 5;
18 info = 6;
19 debug = 7;
20 };
21
22 fsOptions = with lib.types; {
23 options.spec = lib.mkOption {
24 type = str;
25 description = ''
26 Description of how to identify the filesystem to be duplicated by this
27 instance of bees. Note that deduplication crosses subvolumes; one must
28 not configure multiple instances for subvolumes of the same filesystem
29 (or block devices which are part of the same filesystem), but only for
30 completely independent btrfs filesystems.
31
32 This must be in a format usable by findmnt; that could be a key=value
33 pair, or a bare path to a mount point.
34 Using bare paths will allow systemd to start the beesd service only
35 after mounting the associated path.
36 '';
37 example = "LABEL=MyBulkDataDrive";
38 };
39 options.hashTableSizeMB = lib.mkOption {
40 type = lib.types.addCheck lib.types.int (n: lib.mod n 16 == 0);
41 default = 1024; # 1GB; default from upstream beesd script
42 description = ''
43 Hash table size in MB; must be a multiple of 16.
44
45 A larger ratio of index size to storage size means smaller blocks of
46 duplicate content are recognized.
47
48 If you have 1TB of data, a 4GB hash table (which is to say, a value of
49 4096) will permit 4KB extents (the smallest possible size) to be
50 recognized, whereas a value of 1024 -- creating a 1GB hash table --
51 will recognize only aligned duplicate blocks of 16KB.
52 '';
53 };
54 options.verbosity = lib.mkOption {
55 type = lib.types.enum (lib.attrNames logLevels ++ lib.attrValues logLevels);
56 apply = v: if lib.isString v then logLevels.${v} else v;
57 default = "info";
58 description = "Log verbosity (syslog keyword/level).";
59 };
60 options.workDir = lib.mkOption {
61 type = str;
62 default = ".beeshome";
63 description = ''
64 Name (relative to the root of the filesystem) of the subvolume where
65 the hash table will be stored.
66 '';
67 };
68 options.extraOptions = lib.mkOption {
69 type = listOf str;
70 default = [ ];
71 description = ''
72 Extra command-line options passed to the daemon. See upstream bees documentation.
73 '';
74 example = lib.literalExpression ''
75 [ "--thread-count" "4" ]
76 '';
77 };
78 };
79
80in
81{
82
83 options.services.beesd = {
84 filesystems = lib.mkOption {
85 type = with lib.types; attrsOf (submodule fsOptions);
86 description = "BTRFS filesystems to run block-level deduplication on.";
87 default = { };
88 example = lib.literalExpression ''
89 {
90 root = {
91 spec = "LABEL=root";
92 hashTableSizeMB = 2048;
93 verbosity = "crit";
94 extraOptions = [ "--loadavg-target" "5.0" ];
95 };
96 }
97 '';
98 };
99 };
100 config = lib.mkIf (cfg.filesystems != { }) {
101 systemd.packages = [ pkgs.bees ];
102 systemd.services = lib.mapAttrs' (
103 name: fs:
104 lib.nameValuePair "beesd@${name}" {
105 overrideStrategy = "asDropin";
106 serviceConfig = {
107 ExecStart =
108 let
109 configOpts = [
110 fs.spec
111 "verbosity=${toString fs.verbosity}"
112 "idxSizeMB=${toString fs.hashTableSizeMB}"
113 "workDir=${fs.workDir}"
114 ];
115 configOptsStr = lib.escapeShellArgs configOpts;
116 in
117 [
118 ""
119 "${pkgs.bees}/bin/bees-service-wrapper run ${configOptsStr} -- --no-timestamps ${lib.escapeShellArgs fs.extraOptions}"
120 ];
121 SyslogIdentifier = "beesd"; # would otherwise be "bees-service-wrapper"
122 };
123 unitConfig.RequiresMountsFor = lib.mkIf (lib.hasPrefix "/" fs.spec) fs.spec;
124 wantedBy = [ "multi-user.target" ];
125 }
126 ) cfg.filesystems;
127 };
128}