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