at 23.11-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 (lib.mdDoc "regular btrfs scrub"); 23 24 fileSystems = mkOption { 25 type = types.listOf types.path; 26 example = [ "/" ]; 27 description = lib.mdDoc '' 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 = lib.mdDoc '' 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 boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" ]; 57 boot.initrd.availableKernelModules = mkIf inInitrd ( 58 [ "crc32c" ] 59 ++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [ 60 # Needed for mounting filesystems with new checksums 61 "xxhash_generic" 62 "blake2b_generic" 63 "sha256_generic" # Should be baked into our kernel, just to be sure 64 ] 65 ); 66 67 boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) 68 '' 69 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs 70 ln -sv btrfs $out/bin/btrfsck 71 ln -sv btrfsck $out/bin/fsck.btrfs 72 ''; 73 74 boot.initrd.extraUtilsCommandsTest = mkIf (inInitrd && !config.boot.initrd.systemd.enable) 75 '' 76 $out/bin/btrfs --version 77 ''; 78 79 boot.initrd.postDeviceCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) 80 '' 81 btrfs device scan 82 ''; 83 }) 84 85 (mkIf enableAutoScrub { 86 assertions = [ 87 { 88 assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []); 89 message = '' 90 If 'services.btrfs.autoScrub' is enabled, you need to have at least one 91 btrfs file system mounted via 'fileSystems' or specify a list manually 92 in 'services.btrfs.autoScrub.fileSystems'. 93 ''; 94 } 95 ]; 96 97 # This will yield duplicated units if the user mounts a filesystem multiple times 98 # or additionally mounts subvolumes, but going the other way around via devices would 99 # yield duplicated units when a filesystem spans multiple devices. 100 # This way around seems like the more sensible default. 101 services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint) 102 (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems)); 103 104 # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service 105 # template units due to problems enabling the parameterized units, 106 # so settled with many units and templating via nix for now. 107 # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544 108 systemd.timers = let 109 scrubTimer = fs: let 110 fs' = utils.escapeSystemdPath fs; 111 in nameValuePair "btrfs-scrub-${fs'}" { 112 description = "regular btrfs scrub timer on ${fs}"; 113 114 wantedBy = [ "timers.target" ]; 115 timerConfig = { 116 OnCalendar = cfgScrub.interval; 117 AccuracySec = "1d"; 118 Persistent = true; 119 }; 120 }; 121 in listToAttrs (map scrubTimer cfgScrub.fileSystems); 122 123 systemd.services = let 124 scrubService = fs: let 125 fs' = utils.escapeSystemdPath fs; 126 in nameValuePair "btrfs-scrub-${fs'}" { 127 description = "btrfs scrub on ${fs}"; 128 # scrub prevents suspend2ram or proper shutdown 129 conflicts = [ "shutdown.target" "sleep.target" ]; 130 before = [ "shutdown.target" "sleep.target" ]; 131 132 serviceConfig = { 133 # simple and not oneshot, otherwise ExecStop is not used 134 Type = "simple"; 135 Nice = 19; 136 IOSchedulingClass = "idle"; 137 ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}"; 138 # if the service is stopped before scrub end, cancel it 139 ExecStop = pkgs.writeShellScript "btrfs-scrub-maybe-cancel" '' 140 (${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs} 141 ''; 142 }; 143 }; 144 in listToAttrs (map scrubService cfgScrub.fileSystems); 145 }) 146 ]; 147}