1 { config, lib, pkgs, utils, ... }:
18 inInitrd = config.boot.initrd.supportedFilesystems.btrfs or false;
19 inSystem = config.boot.supportedFilesystems.btrfs or false;
21 cfgScrub = config.services.btrfs.autoScrub;
23 enableAutoScrub = cfgScrub.enable;
24 enableBtrfs = inInitrd || inSystem || enableAutoScrub;
30 # One could also do regular btrfs balances, but that shouldn't be necessary
31 # during normal usage and as long as the filesystems aren't filled near capacity
32 services.btrfs.autoScrub = {
33 enable = mkEnableOption "regular btrfs scrub";
35 fileSystems = mkOption {
36 type = types.listOf types.path;
39 List of paths to btrfs filesystems to regularly call {command}`btrfs scrub` on.
40 Defaults to all mount points with btrfs filesystems.
41 Note that if you have filesystems that span multiple devices (e.g. RAID), you should
42 take care to use the same device for any given mount point and let btrfs take care
43 of automatically mounting the rest, in order to avoid scrubbing the same data multiple times.
52 Systemd calendar expression for when to scrub btrfs filesystems.
53 The recommended period is a month but could be less
54 ({manpage}`btrfs-scrub(8)`).
56 {manpage}`systemd.time(7)`
57 for more information on the syntax.
66 system.fsPackages = [ pkgs.btrfs-progs ];
70 boot.initrd.kernelModules = [ "btrfs" ];
71 boot.initrd.availableKernelModules =
73 ++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [
74 # Needed for mounting filesystems with new checksums
77 "sha256_generic" # Should be baked into our kernel, just to be sure
80 boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable)
82 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
83 ln -sv btrfs $out/bin/btrfsck
84 ln -sv btrfsck $out/bin/fsck.btrfs
87 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable)
89 $out/bin/btrfs --version
92 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable)
97 boot.initrd.systemd.initrdBin = [ pkgs.btrfs-progs ];
100 (mkIf enableAutoScrub {
103 assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []);
105 If 'services.btrfs.autoScrub' is enabled, you need to have at least one
106 btrfs file system mounted via 'fileSystems' or specify a list manually
107 in 'services.btrfs.autoScrub.fileSystems'.
112 # This will remove duplicated units from either having a filesystem mounted multiple
113 # time, or additionally mounted subvolumes, as well as having a filesystem span
114 # multiple devices (provided the same device is used to mount said filesystem).
115 services.btrfs.autoScrub.fileSystems =
117 isDeviceInList = list: device: builtins.filter (e: e.device == device) list != [ ];
119 uniqueDeviceList = foldl' (acc: e: if isDeviceInList acc e.device then acc else acc ++ [ e ]) [ ];
121 mkDefault (map (e: e.mountPoint)
122 (uniqueDeviceList (mapAttrsToList (name: fs: { mountPoint = fs.mountPoint; device = fs.device; })
123 (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems))));
125 # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service
126 # template units due to problems enabling the parameterized units,
127 # so settled with many units and templating via nix for now.
128 # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544
131 fs' = utils.escapeSystemdPath fs;
132 in nameValuePair "btrfs-scrub-${fs'}" {
133 description = "regular btrfs scrub timer on ${fs}";
135 wantedBy = [ "timers.target" ];
137 OnCalendar = cfgScrub.interval;
142 in listToAttrs (map scrubTimer cfgScrub.fileSystems);
144 systemd.services = let
145 scrubService = fs: let
146 fs' = utils.escapeSystemdPath fs;
147 in nameValuePair "btrfs-scrub-${fs'}" {
148 description = "btrfs scrub on ${fs}";
149 # scrub prevents suspend2ram or proper shutdown
150 conflicts = [ "shutdown.target" "sleep.target" ];
151 before = [ "shutdown.target" "sleep.target" ];
154 # simple and not oneshot, otherwise ExecStop is not used
157 IOSchedulingClass = "idle";
158 ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}";
159 # if the service is stopped before scrub end, cancel it
160 ExecStop = pkgs.writeShellScript "btrfs-scrub-maybe-cancel" ''
161 (${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs}
165 in listToAttrs (map scrubService cfgScrub.fileSystems);