1 { config, lib, pkgs, ... }:
2 let cfg = config.services.snapraid;
6 # Should have never been on the top-level.
7 (lib.mkRenamedOptionModule [ "snapraid" ] [ "services" "snapraid" ])
10 options.services.snapraid = with lib.types; {
11 enable = lib.mkEnableOption "SnapRAID";
12 dataDisks = lib.mkOption {
19 description = "SnapRAID data disks.";
22 parityFiles = lib.mkOption {
25 "/mnt/diskp/snapraid.parity"
26 "/mnt/diskq/snapraid.2-parity"
27 "/mnt/diskr/snapraid.3-parity"
28 "/mnt/disks/snapraid.4-parity"
29 "/mnt/diskt/snapraid.5-parity"
30 "/mnt/disku/snapraid.6-parity"
32 description = "SnapRAID parity files.";
35 contentFiles = lib.mkOption {
38 "/var/snapraid.content"
39 "/mnt/disk1/snapraid.content"
40 "/mnt/disk2/snapraid.content"
42 description = "SnapRAID content list files.";
45 exclude = lib.mkOption {
47 example = [ "*.unrecoverable" "/tmp/" "/lost+found/" ];
48 description = "SnapRAID exclude directives.";
51 touchBeforeSync = lib.mkOption {
55 "Whether {command}`snapraid touch` should be run before {command}`snapraid sync`.";
58 sync.interval = lib.mkOption {
61 description = "How often to run {command}`snapraid sync`.";
65 interval = lib.mkOption {
66 default = "Mon *-*-* 02:00:00";
68 description = "How often to run {command}`snapraid scrub`.";
75 "Percent of the array that should be checked by {command}`snapraid scrub`.";
78 olderThan = lib.mkOption {
82 "Number of days since data was last scrubbed before it can be scrubbed again.";
86 extraConfig = lib.mkOption {
95 description = "Extra config options for SnapRAID.";
102 nParity = builtins.length cfg.parityFiles;
103 mkPrepend = pre: s: pre + s;
105 lib.mkIf cfg.enable {
108 assertion = nParity <= 6;
109 message = "You can have no more than six SnapRAID parity files.";
112 assertion = builtins.length cfg.contentFiles >= nParity + 1;
114 "There must be at least one SnapRAID content file for each SnapRAID parity file plus one.";
119 systemPackages = with pkgs; [ snapraid ];
121 etc."snapraid.conf" = {
124 prependData = mkPrepend "data ";
125 prependContent = mkPrepend "content ";
126 prependExclude = mkPrepend "exclude ";
128 lib.concatStringsSep "\n"
130 ((lib.mapAttrsToList (name: value: name + " " + value)) dataDisks)
131 ++ lib.zipListsWith (a: b: a + b)
132 ([ "parity " ] ++ map (i: toString i + "-parity ") (lib.range 2 6))
133 parityFiles ++ map prependContent contentFiles
134 ++ map prependExclude exclude) + "\n" + extraConfig;
138 systemd.services = with cfg; {
140 description = "Scrub the SnapRAID array";
141 startAt = scrub.interval;
144 ExecStart = "${pkgs.snapraid}/bin/snapraid scrub -p ${
146 } -o ${toString scrub.olderThan}";
148 IOSchedulingPriority = 7;
149 CPUSchedulingPolicy = "batch";
151 LockPersonality = true;
152 MemoryDenyWriteExecute = true;
153 NoNewPrivileges = true;
154 PrivateDevices = true;
157 ProtectControlGroups = true;
158 ProtectHostname = true;
159 ProtectKernelLogs = true;
160 ProtectKernelModules = true;
161 ProtectKernelTunables = true;
162 RestrictAddressFamilies = "none";
163 RestrictNamespaces = true;
164 RestrictRealtime = true;
165 RestrictSUIDSGID = true;
166 SystemCallArchitectures = "native";
167 SystemCallFilter = "@system-service";
168 SystemCallErrorNumber = "EPERM";
169 CapabilityBoundingSet = "CAP_DAC_OVERRIDE";
171 ProtectSystem = "strict";
172 ProtectHome = "read-only";
174 # scrub requires access to directories containing content files
175 # to remove them if they are stale
177 contentDirs = map dirOf contentFiles;
180 lib.attrValues dataDisks ++ contentDirs
183 unitConfig.After = "snapraid-sync.service";
186 description = "Synchronize the state of the SnapRAID array";
187 startAt = sync.interval;
190 ExecStart = "${pkgs.snapraid}/bin/snapraid sync";
192 IOSchedulingPriority = 7;
193 CPUSchedulingPolicy = "batch";
195 LockPersonality = true;
196 MemoryDenyWriteExecute = true;
197 NoNewPrivileges = true;
200 ProtectControlGroups = true;
201 ProtectHostname = true;
202 ProtectKernelLogs = true;
203 ProtectKernelModules = true;
204 ProtectKernelTunables = true;
205 RestrictAddressFamilies = "none";
206 RestrictNamespaces = true;
207 RestrictRealtime = true;
208 RestrictSUIDSGID = true;
209 SystemCallArchitectures = "native";
210 SystemCallFilter = "@system-service";
211 SystemCallErrorNumber = "EPERM";
212 CapabilityBoundingSet = "CAP_DAC_OVERRIDE" +
213 lib.optionalString cfg.touchBeforeSync " CAP_FOWNER";
215 ProtectSystem = "strict";
216 ProtectHome = "read-only";
218 # sync requires access to directories containing content files
219 # to remove them if they are stale
221 contentDirs = map dirOf contentFiles;
222 # Multiple "split" parity files can be specified in a single
223 # "parityFile", separated by a comma.
224 # https://www.snapraid.it/manual#7.1
225 splitParityFiles = map (s: lib.splitString "," s) parityFiles;
228 lib.attrValues dataDisks ++ splitParityFiles ++ contentDirs
230 } // lib.optionalAttrs touchBeforeSync {
231 ExecStartPre = "${pkgs.snapraid}/bin/snapraid touch";