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