1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let cfg = config.snapraid;
6in
7{
8 options.snapraid = with types; {
9 enable = mkEnableOption (lib.mdDoc "SnapRAID");
10 dataDisks = mkOption {
11 default = { };
12 example = {
13 d1 = "/mnt/disk1/";
14 d2 = "/mnt/disk2/";
15 d3 = "/mnt/disk3/";
16 };
17 description = lib.mdDoc "SnapRAID data disks.";
18 type = attrsOf str;
19 };
20 parityFiles = mkOption {
21 default = [ ];
22 example = [
23 "/mnt/diskp/snapraid.parity"
24 "/mnt/diskq/snapraid.2-parity"
25 "/mnt/diskr/snapraid.3-parity"
26 "/mnt/disks/snapraid.4-parity"
27 "/mnt/diskt/snapraid.5-parity"
28 "/mnt/disku/snapraid.6-parity"
29 ];
30 description = lib.mdDoc "SnapRAID parity files.";
31 type = listOf str;
32 };
33 contentFiles = mkOption {
34 default = [ ];
35 example = [
36 "/var/snapraid.content"
37 "/mnt/disk1/snapraid.content"
38 "/mnt/disk2/snapraid.content"
39 ];
40 description = lib.mdDoc "SnapRAID content list files.";
41 type = listOf str;
42 };
43 exclude = mkOption {
44 default = [ ];
45 example = [ "*.unrecoverable" "/tmp/" "/lost+found/" ];
46 description = lib.mdDoc "SnapRAID exclude directives.";
47 type = listOf str;
48 };
49 touchBeforeSync = mkOption {
50 default = true;
51 example = false;
52 description = lib.mdDoc
53 "Whether {command}`snapraid touch` should be run before {command}`snapraid sync`.";
54 type = bool;
55 };
56 sync.interval = mkOption {
57 default = "01:00";
58 example = "daily";
59 description = lib.mdDoc "How often to run {command}`snapraid sync`.";
60 type = str;
61 };
62 scrub = {
63 interval = mkOption {
64 default = "Mon *-*-* 02:00:00";
65 example = "weekly";
66 description = lib.mdDoc "How often to run {command}`snapraid scrub`.";
67 type = str;
68 };
69 plan = mkOption {
70 default = 8;
71 example = 5;
72 description = lib.mdDoc
73 "Percent of the array that should be checked by {command}`snapraid scrub`.";
74 type = int;
75 };
76 olderThan = mkOption {
77 default = 10;
78 example = 20;
79 description = lib.mdDoc
80 "Number of days since data was last scrubbed before it can be scrubbed again.";
81 type = int;
82 };
83 };
84 extraConfig = mkOption {
85 default = "";
86 example = ''
87 nohidden
88 blocksize 256
89 hashsize 16
90 autosave 500
91 pool /pool
92 '';
93 description = lib.mdDoc "Extra config options for SnapRAID.";
94 type = lines;
95 };
96 };
97
98 config =
99 let
100 nParity = builtins.length cfg.parityFiles;
101 mkPrepend = pre: s: pre + s;
102 in
103 mkIf cfg.enable {
104 assertions = [
105 {
106 assertion = nParity <= 6;
107 message = "You can have no more than six SnapRAID parity files.";
108 }
109 {
110 assertion = builtins.length cfg.contentFiles >= nParity + 1;
111 message =
112 "There must be at least one SnapRAID content file for each SnapRAID parity file plus one.";
113 }
114 ];
115
116 environment = {
117 systemPackages = with pkgs; [ snapraid ];
118
119 etc."snapraid.conf" = {
120 text = with cfg;
121 let
122 prependData = mkPrepend "data ";
123 prependContent = mkPrepend "content ";
124 prependExclude = mkPrepend "exclude ";
125 in
126 concatStringsSep "\n"
127 (map prependData
128 ((mapAttrsToList (name: value: name + " " + value)) dataDisks)
129 ++ zipListsWith (a: b: a + b)
130 ([ "parity " ] ++ map (i: toString i + "-parity ") (range 2 6))
131 parityFiles ++ map prependContent contentFiles
132 ++ map prependExclude exclude) + "\n" + extraConfig;
133 };
134 };
135
136 systemd.services = with cfg; {
137 snapraid-scrub = {
138 description = "Scrub the SnapRAID array";
139 startAt = scrub.interval;
140 serviceConfig = {
141 Type = "oneshot";
142 ExecStart = "${pkgs.snapraid}/bin/snapraid scrub -p ${
143 toString scrub.plan
144 } -o ${toString scrub.olderThan}";
145 Nice = 19;
146 IOSchedulingPriority = 7;
147 CPUSchedulingPolicy = "batch";
148
149 LockPersonality = true;
150 MemoryDenyWriteExecute = true;
151 NoNewPrivileges = true;
152 PrivateDevices = true;
153 PrivateTmp = true;
154 ProtectClock = true;
155 ProtectControlGroups = true;
156 ProtectHostname = true;
157 ProtectKernelLogs = true;
158 ProtectKernelModules = true;
159 ProtectKernelTunables = true;
160 RestrictAddressFamilies = "none";
161 RestrictNamespaces = true;
162 RestrictRealtime = true;
163 RestrictSUIDSGID = true;
164 SystemCallArchitectures = "native";
165 SystemCallFilter = "@system-service";
166 SystemCallErrorNumber = "EPERM";
167 CapabilityBoundingSet = "CAP_DAC_OVERRIDE";
168
169 ProtectSystem = "strict";
170 ProtectHome = "read-only";
171 ReadWritePaths =
172 # scrub requires access to directories containing content files
173 # to remove them if they are stale
174 let
175 contentDirs = map dirOf contentFiles;
176 in
177 unique (
178 attrValues dataDisks ++ contentDirs
179 );
180 };
181 unitConfig.After = "snapraid-sync.service";
182 };
183 snapraid-sync = {
184 description = "Synchronize the state of the SnapRAID array";
185 startAt = sync.interval;
186 serviceConfig = {
187 Type = "oneshot";
188 ExecStart = "${pkgs.snapraid}/bin/snapraid sync";
189 Nice = 19;
190 IOSchedulingPriority = 7;
191 CPUSchedulingPolicy = "batch";
192
193 LockPersonality = true;
194 MemoryDenyWriteExecute = true;
195 NoNewPrivileges = true;
196 PrivateTmp = true;
197 ProtectClock = true;
198 ProtectControlGroups = true;
199 ProtectHostname = true;
200 ProtectKernelLogs = true;
201 ProtectKernelModules = true;
202 ProtectKernelTunables = true;
203 RestrictAddressFamilies = "none";
204 RestrictNamespaces = true;
205 RestrictRealtime = true;
206 RestrictSUIDSGID = true;
207 SystemCallArchitectures = "native";
208 SystemCallFilter = "@system-service";
209 SystemCallErrorNumber = "EPERM";
210 CapabilityBoundingSet = "CAP_DAC_OVERRIDE" +
211 lib.optionalString cfg.touchBeforeSync " CAP_FOWNER";
212
213 ProtectSystem = "strict";
214 ProtectHome = "read-only";
215 ReadWritePaths =
216 # sync requires access to directories containing content files
217 # to remove them if they are stale
218 let
219 contentDirs = map dirOf contentFiles;
220 in
221 unique (
222 attrValues dataDisks ++ parityFiles ++ contentDirs
223 );
224 } // optionalAttrs touchBeforeSync {
225 ExecStartPre = "${pkgs.snapraid}/bin/snapraid touch";
226 };
227 };
228 };
229 };
230}