at 24.11-pre 5.3 kB view raw
1{ config, lib, pkgs, utils, ... }: 2 3with lib; 4 5let 6 7 inInitrd = config.boot.initrd.supportedFilesystems.btrfs or false; 8 inSystem = config.boot.supportedFilesystems.btrfs or false; 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 regularly call {command}`btrfs scrub` 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 ({manpage}`btrfs-scrub(8)`). 43 See 44 {manpage}`systemd.time(7)` 45 for more information on the syntax. 46 ''; 47 }; 48 49 }; 50 }; 51 52 config = mkMerge [ 53 (mkIf enableBtrfs { 54 system.fsPackages = [ pkgs.btrfs-progs ]; 55 }) 56 57 (mkIf inInitrd { 58 boot.initrd.kernelModules = [ "btrfs" ]; 59 boot.initrd.availableKernelModules = 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 boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) 69 '' 70 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs 71 ln -sv btrfs $out/bin/btrfsck 72 ln -sv btrfsck $out/bin/fsck.btrfs 73 ''; 74 75 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) 76 '' 77 $out/bin/btrfs --version 78 ''; 79 80 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) 81 '' 82 btrfs device scan 83 ''; 84 85 boot.initrd.systemd.initrdBin = [ pkgs.btrfs-progs ]; 86 }) 87 88 (mkIf enableAutoScrub { 89 assertions = [ 90 { 91 assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []); 92 message = '' 93 If 'services.btrfs.autoScrub' is enabled, you need to have at least one 94 btrfs file system mounted via 'fileSystems' or specify a list manually 95 in 'services.btrfs.autoScrub.fileSystems'. 96 ''; 97 } 98 ]; 99 100 # This will yield duplicated units if the user mounts a filesystem multiple times 101 # or additionally mounts subvolumes, but going the other way around via devices would 102 # yield duplicated units when a filesystem spans multiple devices. 103 # This way around seems like the more sensible default. 104 services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint) 105 (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems)); 106 107 # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service 108 # template units due to problems enabling the parameterized units, 109 # so settled with many units and templating via nix for now. 110 # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544 111 systemd.timers = let 112 scrubTimer = fs: let 113 fs' = utils.escapeSystemdPath fs; 114 in nameValuePair "btrfs-scrub-${fs'}" { 115 description = "regular btrfs scrub timer on ${fs}"; 116 117 wantedBy = [ "timers.target" ]; 118 timerConfig = { 119 OnCalendar = cfgScrub.interval; 120 AccuracySec = "1d"; 121 Persistent = true; 122 }; 123 }; 124 in listToAttrs (map scrubTimer cfgScrub.fileSystems); 125 126 systemd.services = let 127 scrubService = fs: let 128 fs' = utils.escapeSystemdPath fs; 129 in nameValuePair "btrfs-scrub-${fs'}" { 130 description = "btrfs scrub on ${fs}"; 131 # scrub prevents suspend2ram or proper shutdown 132 conflicts = [ "shutdown.target" "sleep.target" ]; 133 before = [ "shutdown.target" "sleep.target" ]; 134 135 serviceConfig = { 136 # simple and not oneshot, otherwise ExecStop is not used 137 Type = "simple"; 138 Nice = 19; 139 IOSchedulingClass = "idle"; 140 ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}"; 141 # if the service is stopped before scrub end, cancel it 142 ExecStop = pkgs.writeShellScript "btrfs-scrub-maybe-cancel" '' 143 (${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs} 144 ''; 145 }; 146 }; 147 in listToAttrs (map scrubService cfgScrub.fileSystems); 148 }) 149 ]; 150}