at 22.05-pre 5.4 kB view raw
1{ config, lib, pkgs, utils, ... }: 2 3with lib; 4 5let 6 7 inInitrd = any (fs: fs == "btrfs") config.boot.initrd.supportedFilesystems; 8 inSystem = any (fs: fs == "btrfs") config.boot.supportedFilesystems; 9 10 cfgScrub = config.services.btrfs.autoScrub; 11 12 enableAutoScrub = cfgScrub.enable; 13 enableBtrfs = inInitrd || inSystem || enableAutoScrub; 14 15in 16 17{ 18 options = { 19 # One could also do regular btrfs balances, but that shouldn't be necessary 20 # during normal usage and as long as the filesystems aren't filled near capacity 21 services.btrfs.autoScrub = { 22 enable = mkEnableOption "regular btrfs scrub"; 23 24 fileSystems = mkOption { 25 type = types.listOf types.path; 26 example = [ "/" ]; 27 description = '' 28 List of paths to btrfs filesystems to regularily call <command>btrfs scrub</command> on. 29 Defaults to all mount points with btrfs filesystems. 30 If you mount a filesystem multiple times or additionally mount subvolumes, 31 you need to manually specify this list to avoid scrubbing multiple times. 32 ''; 33 }; 34 35 interval = mkOption { 36 default = "monthly"; 37 type = types.str; 38 example = "weekly"; 39 description = '' 40 Systemd calendar expression for when to scrub btrfs filesystems. 41 The recommended period is a month but could be less 42 (<citerefentry><refentrytitle>btrfs-scrub</refentrytitle> 43 <manvolnum>8</manvolnum></citerefentry>). 44 See 45 <citerefentry><refentrytitle>systemd.time</refentrytitle> 46 <manvolnum>7</manvolnum></citerefentry> 47 for more information on the syntax. 48 ''; 49 }; 50 51 }; 52 }; 53 54 config = mkMerge [ 55 (mkIf enableBtrfs { 56 system.fsPackages = [ pkgs.btrfs-progs ]; 57 58 boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" ]; 59 boot.initrd.availableKernelModules = mkIf inInitrd ( 60 [ "crc32c" ] 61 ++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [ 62 # Needed for mounting filesystems with new checksums 63 "xxhash_generic" 64 "blake2b_generic" 65 "sha256_generic" # Should be baked into our kernel, just to be sure 66 ] 67 ); 68 69 boot.initrd.extraUtilsCommands = mkIf inInitrd 70 '' 71 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs 72 ln -sv btrfs $out/bin/btrfsck 73 ln -sv btrfsck $out/bin/fsck.btrfs 74 ''; 75 76 boot.initrd.extraUtilsCommandsTest = mkIf inInitrd 77 '' 78 $out/bin/btrfs --version 79 ''; 80 81 boot.initrd.postDeviceCommands = mkIf inInitrd 82 '' 83 btrfs device scan 84 ''; 85 }) 86 87 (mkIf enableAutoScrub { 88 assertions = [ 89 { 90 assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []); 91 message = '' 92 If 'services.btrfs.autoScrub' is enabled, you need to have at least one 93 btrfs file system mounted via 'fileSystems' or specify a list manually 94 in 'services.btrfs.autoScrub.fileSystems'. 95 ''; 96 } 97 ]; 98 99 # This will yield duplicated units if the user mounts a filesystem multiple times 100 # or additionally mounts subvolumes, but going the other way around via devices would 101 # yield duplicated units when a filesystem spans multiple devices. 102 # This way around seems like the more sensible default. 103 services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint) 104 (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems)); 105 106 # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service 107 # template units due to problems enabling the parameterized units, 108 # so settled with many units and templating via nix for now. 109 # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544 110 systemd.timers = let 111 scrubTimer = fs: let 112 fs' = utils.escapeSystemdPath fs; 113 in nameValuePair "btrfs-scrub-${fs'}" { 114 description = "regular btrfs scrub timer on ${fs}"; 115 116 wantedBy = [ "timers.target" ]; 117 timerConfig = { 118 OnCalendar = cfgScrub.interval; 119 AccuracySec = "1d"; 120 Persistent = true; 121 }; 122 }; 123 in listToAttrs (map scrubTimer cfgScrub.fileSystems); 124 125 systemd.services = let 126 scrubService = fs: let 127 fs' = utils.escapeSystemdPath fs; 128 in nameValuePair "btrfs-scrub-${fs'}" { 129 description = "btrfs scrub on ${fs}"; 130 # scrub prevents suspend2ram or proper shutdown 131 conflicts = [ "shutdown.target" "sleep.target" ]; 132 before = [ "shutdown.target" "sleep.target" ]; 133 134 serviceConfig = { 135 # simple and not oneshot, otherwise ExecStop is not used 136 Type = "simple"; 137 Nice = 19; 138 IOSchedulingClass = "idle"; 139 ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}"; 140 # if the service is stopped before scrub end, cancel it 141 ExecStop = pkgs.writeShellScript "btrfs-scrub-maybe-cancel" '' 142 (${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs} 143 ''; 144 }; 145 }; 146 in listToAttrs (map scrubService cfgScrub.fileSystems); 147 }) 148 ]; 149}