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 137 Previously the default behaviour was to listen on all interfaces. 138 138 </para> 139 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> 140 148 </itemizedlist> 141 149 142 150 </section>
+105 -8
nixos/modules/tasks/filesystems/btrfs.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { config, lib, pkgs, utils, ... }: 2 2 3 3 with lib; 4 4 5 5 let 6 6 7 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; 8 14 9 15 in 10 16 11 17 { 12 - config = mkIf (any (fs: fs == "btrfs") config.boot.supportedFilesystems) { 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 + }; 13 50 14 - system.fsPackages = [ pkgs.btrfs-progs ]; 51 + }; 52 + }; 15 53 16 - boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ]; 54 + config = mkMerge [ 55 + (mkIf enableBtrfs { 56 + system.fsPackages = [ pkgs.btrfs-progs ]; 17 57 18 - boot.initrd.extraUtilsCommands = mkIf inInitrd 58 + boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ]; 59 + 60 + boot.initrd.extraUtilsCommands = mkIf inInitrd 19 61 '' 20 62 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs 21 63 ln -sv btrfs $out/bin/btrfsck 22 64 ln -sv btrfsck $out/bin/fsck.btrfs 23 65 ''; 24 66 25 - boot.initrd.extraUtilsCommandsTest = mkIf inInitrd 67 + boot.initrd.extraUtilsCommandsTest = mkIf inInitrd 26 68 '' 27 69 $out/bin/btrfs --version 28 70 ''; 29 71 30 - boot.initrd.postDeviceCommands = mkIf inInitrd 72 + boot.initrd.postDeviceCommands = mkIf inInitrd 31 73 '' 32 74 btrfs device scan 33 75 ''; 34 - }; 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 + ]; 35 132 }