at master 6.4 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8 9let 10 inherit (lib) 11 mkEnableOption 12 mkOption 13 types 14 mkMerge 15 mkIf 16 optionals 17 mkDefault 18 nameValuePair 19 listToAttrs 20 filterAttrs 21 mapAttrsToList 22 foldl' 23 ; 24 25 inInitrd = config.boot.initrd.supportedFilesystems.btrfs or false; 26 inSystem = config.boot.supportedFilesystems.btrfs or false; 27 28 cfgScrub = config.services.btrfs.autoScrub; 29 30 enableAutoScrub = cfgScrub.enable; 31 enableBtrfs = inInitrd || inSystem || enableAutoScrub; 32 33in 34 35{ 36 options = { 37 # One could also do regular btrfs balances, but that shouldn't be necessary 38 # during normal usage and as long as the filesystems aren't filled near capacity 39 services.btrfs.autoScrub = { 40 enable = mkEnableOption "regular btrfs scrub"; 41 42 fileSystems = mkOption { 43 type = types.listOf types.path; 44 example = [ "/" ]; 45 description = '' 46 List of paths to btrfs filesystems to regularly call {command}`btrfs scrub` on. 47 Defaults to all mount points with btrfs filesystems. 48 Note that if you have filesystems that span multiple devices (e.g. RAID), you should 49 take care to use the same device for any given mount point and let btrfs take care 50 of automatically mounting the rest, in order to avoid scrubbing the same data multiple times. 51 ''; 52 }; 53 54 interval = mkOption { 55 default = "monthly"; 56 type = types.str; 57 example = "weekly"; 58 description = '' 59 Systemd calendar expression for when to scrub btrfs filesystems. 60 The recommended period is a month but could be less 61 ({manpage}`btrfs-scrub(8)`). 62 See 63 {manpage}`systemd.time(7)` 64 for more information on the syntax. 65 ''; 66 }; 67 68 }; 69 }; 70 71 config = mkMerge [ 72 (mkIf enableBtrfs { 73 system.fsPackages = [ pkgs.btrfs-progs ]; 74 }) 75 76 (mkIf inInitrd { 77 boot.initrd.kernelModules = [ "btrfs" ]; 78 boot.initrd.availableKernelModules = [ 79 "crc32c" 80 ] 81 ++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [ 82 # Needed for mounting filesystems with new checksums 83 "xxhash_generic" 84 "blake2b_generic" 85 86 # `sha256` is always available, whereas `sha256_generic` is not available from 6.17 onwards 87 "sha256" # Should be baked into our kernel, just to be sure 88 ]; 89 90 boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' 91 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs 92 ln -sv btrfs $out/bin/btrfsck 93 ln -sv btrfsck $out/bin/fsck.btrfs 94 ''; 95 96 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' 97 $out/bin/btrfs --version 98 ''; 99 100 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) '' 101 btrfs device scan 102 ''; 103 104 boot.initrd.systemd.initrdBin = [ pkgs.btrfs-progs ]; 105 }) 106 107 (mkIf enableAutoScrub { 108 assertions = [ 109 { 110 assertion = cfgScrub.enable -> (cfgScrub.fileSystems != [ ]); 111 message = '' 112 If 'services.btrfs.autoScrub' is enabled, you need to have at least one 113 btrfs file system mounted via 'fileSystems' or specify a list manually 114 in 'services.btrfs.autoScrub.fileSystems'. 115 ''; 116 } 117 ]; 118 119 # This will remove duplicated units from either having a filesystem mounted multiple 120 # time, or additionally mounted subvolumes, as well as having a filesystem span 121 # multiple devices (provided the same device is used to mount said filesystem). 122 services.btrfs.autoScrub.fileSystems = 123 let 124 isDeviceInList = list: device: builtins.filter (e: e.device == device) list != [ ]; 125 126 uniqueDeviceList = foldl' (acc: e: if isDeviceInList acc e.device then acc else acc ++ [ e ]) [ ]; 127 in 128 mkDefault ( 129 map (e: e.mountPoint) ( 130 uniqueDeviceList ( 131 mapAttrsToList (name: fs: { 132 mountPoint = fs.mountPoint; 133 device = fs.device; 134 }) (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems) 135 ) 136 ) 137 ); 138 139 # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service 140 # template units due to problems enabling the parameterized units, 141 # so settled with many units and templating via nix for now. 142 # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544 143 systemd.timers = 144 let 145 scrubTimer = 146 fs: 147 let 148 fs' = utils.escapeSystemdPath fs; 149 in 150 nameValuePair "btrfs-scrub-${fs'}" { 151 description = "regular btrfs scrub timer on ${fs}"; 152 153 wantedBy = [ "timers.target" ]; 154 timerConfig = { 155 OnCalendar = cfgScrub.interval; 156 AccuracySec = "1d"; 157 Persistent = true; 158 }; 159 }; 160 in 161 listToAttrs (map scrubTimer cfgScrub.fileSystems); 162 163 systemd.services = 164 let 165 scrubService = 166 fs: 167 let 168 fs' = utils.escapeSystemdPath fs; 169 in 170 nameValuePair "btrfs-scrub-${fs'}" { 171 description = "btrfs scrub on ${fs}"; 172 documentation = [ "man:btrfs-scrub(8)" ]; 173 # scrub prevents suspend2ram or proper shutdown 174 conflicts = [ 175 "shutdown.target" 176 "sleep.target" 177 ]; 178 before = [ 179 "shutdown.target" 180 "sleep.target" 181 ]; 182 183 serviceConfig = { 184 # simple and not oneshot, otherwise ExecStop is not used 185 Type = "simple"; 186 Nice = 19; 187 IOSchedulingClass = "idle"; 188 ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}"; 189 # if the service is stopped before scrub end, cancel it 190 ExecStop = pkgs.writeShellScript "btrfs-scrub-maybe-cancel" '' 191 (${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs} 192 ''; 193 }; 194 }; 195 in 196 listToAttrs (map scrubService cfgScrub.fileSystems); 197 }) 198 ]; 199}