lol

nixos/btrfs: add services.btrfs.autoScrub for automatic regular scrubbing of mounted btrfs filesystems, similar to what's already there for zfs.

+113 -8
+8
nixos/doc/manual/release-notes/rl-1803.xml
··· 137 Previously the default behaviour was to listen on all interfaces. 138 </para> 139 </listitem> 140 </itemizedlist> 141 142 </section>
··· 137 Previously the default behaviour was to listen on all interfaces. 138 </para> 139 </listitem> 140 + <listitem> 141 + <para> 142 + <literal>services.btrfs.autoScrub</literal> has been added, to 143 + periodically check btrfs filesystems for data corruption. 144 + If there's a correct copy available, it will automatically repair 145 + corrupted blocks. 146 + </para> 147 + </listitem> 148 </itemizedlist> 149 150 </section>
+105 -8
nixos/modules/tasks/filesystems/btrfs.nix
··· 1 - { config, lib, pkgs, ... }: 2 3 with lib; 4 5 let 6 7 inInitrd = any (fs: fs == "btrfs") config.boot.initrd.supportedFilesystems; 8 9 in 10 11 { 12 - config = mkIf (any (fs: fs == "btrfs") config.boot.supportedFilesystems) { 13 14 - system.fsPackages = [ pkgs.btrfs-progs ]; 15 16 - boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ]; 17 18 - boot.initrd.extraUtilsCommands = mkIf inInitrd 19 '' 20 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs 21 ln -sv btrfs $out/bin/btrfsck 22 ln -sv btrfsck $out/bin/fsck.btrfs 23 ''; 24 25 - boot.initrd.extraUtilsCommandsTest = mkIf inInitrd 26 '' 27 $out/bin/btrfs --version 28 ''; 29 30 - boot.initrd.postDeviceCommands = mkIf inInitrd 31 '' 32 btrfs device scan 33 ''; 34 - }; 35 }
··· 1 + { config, lib, pkgs, utils, ... }: 2 3 with lib; 4 5 let 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 15 in 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 "Enable 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" "crc32c" ]; 59 + 60 + boot.initrd.extraUtilsCommands = mkIf inInitrd 61 '' 62 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs 63 ln -sv btrfs $out/bin/btrfsck 64 ln -sv btrfsck $out/bin/fsck.btrfs 65 ''; 66 67 + boot.initrd.extraUtilsCommandsTest = mkIf inInitrd 68 '' 69 $out/bin/btrfs --version 70 ''; 71 72 + boot.initrd.postDeviceCommands = mkIf inInitrd 73 '' 74 btrfs device scan 75 ''; 76 + }) 77 + 78 + (mkIf enableAutoScrub { 79 + assertions = [ 80 + { 81 + assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []); 82 + message = '' 83 + If 'services.btrfs.autoScrub' is enabled, you need to have at least one 84 + btrfs file system mounted via 'fileSystems' or specify a list manually 85 + in 'services.btrfs.autoScrub.fileSystems'. 86 + ''; 87 + } 88 + ]; 89 + 90 + # This will yield duplicated units if the user mounts a filesystem multiple times 91 + # or additionally mounts subvolumes, but going the other way around via devices would 92 + # yield duplicated units when a filesystem spans multiple devices. 93 + # This way around seems like the more sensible default. 94 + services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint) 95 + (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems)); 96 + 97 + # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service 98 + # template units due to problems enabling the parameterized units, 99 + # so settled with many units and templating via nix for now. 100 + # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544 101 + systemd.timers = let 102 + scrubTimer = fs: let 103 + fs' = utils.escapeSystemdPath fs; 104 + in nameValuePair "btrfs-scrub-${fs'}" { 105 + description = "regular btrfs scrub timer on ${fs}"; 106 + 107 + wantedBy = [ "timers.target" ]; 108 + timerConfig = { 109 + OnCalendar = cfgScrub.interval; 110 + AccuracySec = "1d"; 111 + Persistent = true; 112 + }; 113 + }; 114 + in listToAttrs (map scrubTimer cfgScrub.fileSystems); 115 + 116 + systemd.services = let 117 + scrubService = fs: let 118 + fs' = utils.escapeSystemdPath fs; 119 + in nameValuePair "btrfs-scrub-${fs'}" { 120 + description = "btrfs scrub on ${fs}"; 121 + 122 + serviceConfig = { 123 + Type = "oneshot"; 124 + Nice = 19; 125 + IOSchedulingClass = "idle"; 126 + ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}"; 127 + }; 128 + }; 129 + in listToAttrs (map scrubService cfgScrub.fileSystems); 130 + }) 131 + ]; 132 }