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