at 25.11-pre 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 ++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [ 81 # Needed for mounting filesystems with new checksums 82 "xxhash_generic" 83 "blake2b_generic" 84 "sha256_generic" # Should be baked into our kernel, just to be sure 85 ]; 86 87 boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' 88 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs 89 ln -sv btrfs $out/bin/btrfsck 90 ln -sv btrfsck $out/bin/fsck.btrfs 91 ''; 92 93 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' 94 $out/bin/btrfs --version 95 ''; 96 97 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) '' 98 btrfs device scan 99 ''; 100 101 boot.initrd.systemd.initrdBin = [ pkgs.btrfs-progs ]; 102 }) 103 104 (mkIf enableAutoScrub { 105 assertions = [ 106 { 107 assertion = cfgScrub.enable -> (cfgScrub.fileSystems != [ ]); 108 message = '' 109 If 'services.btrfs.autoScrub' is enabled, you need to have at least one 110 btrfs file system mounted via 'fileSystems' or specify a list manually 111 in 'services.btrfs.autoScrub.fileSystems'. 112 ''; 113 } 114 ]; 115 116 # This will remove duplicated units from either having a filesystem mounted multiple 117 # time, or additionally mounted subvolumes, as well as having a filesystem span 118 # multiple devices (provided the same device is used to mount said filesystem). 119 services.btrfs.autoScrub.fileSystems = 120 let 121 isDeviceInList = list: device: builtins.filter (e: e.device == device) list != [ ]; 122 123 uniqueDeviceList = foldl' (acc: e: if isDeviceInList acc e.device then acc else acc ++ [ e ]) [ ]; 124 in 125 mkDefault ( 126 map (e: e.mountPoint) ( 127 uniqueDeviceList ( 128 mapAttrsToList (name: fs: { 129 mountPoint = fs.mountPoint; 130 device = fs.device; 131 }) (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems) 132 ) 133 ) 134 ); 135 136 # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service 137 # template units due to problems enabling the parameterized units, 138 # so settled with many units and templating via nix for now. 139 # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544 140 systemd.timers = 141 let 142 scrubTimer = 143 fs: 144 let 145 fs' = utils.escapeSystemdPath fs; 146 in 147 nameValuePair "btrfs-scrub-${fs'}" { 148 description = "regular btrfs scrub timer on ${fs}"; 149 150 wantedBy = [ "timers.target" ]; 151 timerConfig = { 152 OnCalendar = cfgScrub.interval; 153 AccuracySec = "1d"; 154 Persistent = true; 155 }; 156 }; 157 in 158 listToAttrs (map scrubTimer cfgScrub.fileSystems); 159 160 systemd.services = 161 let 162 scrubService = 163 fs: 164 let 165 fs' = utils.escapeSystemdPath fs; 166 in 167 nameValuePair "btrfs-scrub-${fs'}" { 168 description = "btrfs scrub on ${fs}"; 169 documentation = [ "man:btrfs-scrub(8)" ]; 170 # scrub prevents suspend2ram or proper shutdown 171 conflicts = [ 172 "shutdown.target" 173 "sleep.target" 174 ]; 175 before = [ 176 "shutdown.target" 177 "sleep.target" 178 ]; 179 180 serviceConfig = { 181 # simple and not oneshot, otherwise ExecStop is not used 182 Type = "simple"; 183 Nice = 19; 184 IOSchedulingClass = "idle"; 185 ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}"; 186 # if the service is stopped before scrub end, cancel it 187 ExecStop = pkgs.writeShellScript "btrfs-scrub-maybe-cancel" '' 188 (${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs} 189 ''; 190 }; 191 }; 192 in 193 listToAttrs (map scrubService cfgScrub.fileSystems); 194 }) 195 ]; 196}