···137137 Previously the default behaviour was to listen on all interfaces.
138138 </para>
139139 </listitem>
140140+ <listitem>
141141+ <para>
142142+ <literal>services.btrfs.autoScrub</literal> has been added, to
143143+ periodically check btrfs filesystems for data corruption.
144144+ If there's a correct copy available, it will automatically repair
145145+ corrupted blocks.
146146+ </para>
147147+ </listitem>
140148</itemizedlist>
141149142150</section>
+105-8
nixos/modules/tasks/filesystems/btrfs.nix
···11-{ config, lib, pkgs, ... }:
11+{ config, lib, pkgs, utils, ... }:
2233with lib;
4455let
6677 inInitrd = any (fs: fs == "btrfs") config.boot.initrd.supportedFilesystems;
88+ inSystem = any (fs: fs == "btrfs") config.boot.supportedFilesystems;
99+1010+ cfgScrub = config.services.btrfs.autoScrub;
1111+1212+ enableAutoScrub = cfgScrub.enable;
1313+ enableBtrfs = inInitrd || inSystem || enableAutoScrub;
814915in
10161117{
1212- config = mkIf (any (fs: fs == "btrfs") config.boot.supportedFilesystems) {
1818+ options = {
1919+ # One could also do regular btrfs balances, but that shouldn't be necessary
2020+ # during normal usage and as long as the filesystems aren't filled near capacity
2121+ services.btrfs.autoScrub = {
2222+ enable = mkEnableOption "Enable regular btrfs scrub";
2323+2424+ fileSystems = mkOption {
2525+ type = types.listOf types.path;
2626+ example = [ "/" ];
2727+ description = ''
2828+ List of paths to btrfs filesystems to regularily call <command>btrfs scrub</command> on.
2929+ Defaults to all mount points with btrfs filesystems.
3030+ If you mount a filesystem multiple times or additionally mount subvolumes,
3131+ you need to manually specify this list to avoid scrubbing multiple times.
3232+ '';
3333+ };
3434+3535+ interval = mkOption {
3636+ default = "monthly";
3737+ type = types.str;
3838+ example = "weekly";
3939+ description = ''
4040+ Systemd calendar expression for when to scrub btrfs filesystems.
4141+ The recommended period is a month but could be less
4242+ (<citerefentry><refentrytitle>btrfs-scrub</refentrytitle>
4343+ <manvolnum>8</manvolnum></citerefentry>).
4444+ See
4545+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
4646+ <manvolnum>7</manvolnum></citerefentry>
4747+ for more information on the syntax.
4848+ '';
4949+ };
13501414- system.fsPackages = [ pkgs.btrfs-progs ];
5151+ };
5252+ };
15531616- boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ];
5454+ config = mkMerge [
5555+ (mkIf enableBtrfs {
5656+ system.fsPackages = [ pkgs.btrfs-progs ];
17571818- boot.initrd.extraUtilsCommands = mkIf inInitrd
5858+ boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ];
5959+6060+ boot.initrd.extraUtilsCommands = mkIf inInitrd
1961 ''
2062 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
2163 ln -sv btrfs $out/bin/btrfsck
2264 ln -sv btrfsck $out/bin/fsck.btrfs
2365 '';
24662525- boot.initrd.extraUtilsCommandsTest = mkIf inInitrd
6767+ boot.initrd.extraUtilsCommandsTest = mkIf inInitrd
2668 ''
2769 $out/bin/btrfs --version
2870 '';
29713030- boot.initrd.postDeviceCommands = mkIf inInitrd
7272+ boot.initrd.postDeviceCommands = mkIf inInitrd
3173 ''
3274 btrfs device scan
3375 '';
3434- };
7676+ })
7777+7878+ (mkIf enableAutoScrub {
7979+ assertions = [
8080+ {
8181+ assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []);
8282+ message = ''
8383+ If 'services.btrfs.autoScrub' is enabled, you need to have at least one
8484+ btrfs file system mounted via 'fileSystems' or specify a list manually
8585+ in 'services.btrfs.autoScrub.fileSystems'.
8686+ '';
8787+ }
8888+ ];
8989+9090+ # This will yield duplicated units if the user mounts a filesystem multiple times
9191+ # or additionally mounts subvolumes, but going the other way around via devices would
9292+ # yield duplicated units when a filesystem spans multiple devices.
9393+ # This way around seems like the more sensible default.
9494+ services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint)
9595+ (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems));
9696+9797+ # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service
9898+ # template units due to problems enabling the parameterized units,
9999+ # so settled with many units and templating via nix for now.
100100+ # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544
101101+ systemd.timers = let
102102+ scrubTimer = fs: let
103103+ fs' = utils.escapeSystemdPath fs;
104104+ in nameValuePair "btrfs-scrub-${fs'}" {
105105+ description = "regular btrfs scrub timer on ${fs}";
106106+107107+ wantedBy = [ "timers.target" ];
108108+ timerConfig = {
109109+ OnCalendar = cfgScrub.interval;
110110+ AccuracySec = "1d";
111111+ Persistent = true;
112112+ };
113113+ };
114114+ in listToAttrs (map scrubTimer cfgScrub.fileSystems);
115115+116116+ systemd.services = let
117117+ scrubService = fs: let
118118+ fs' = utils.escapeSystemdPath fs;
119119+ in nameValuePair "btrfs-scrub-${fs'}" {
120120+ description = "btrfs scrub on ${fs}";
121121+122122+ serviceConfig = {
123123+ Type = "oneshot";
124124+ Nice = 19;
125125+ IOSchedulingClass = "idle";
126126+ ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}";
127127+ };
128128+ };
129129+ in listToAttrs (map scrubService cfgScrub.fileSystems);
130130+ })
131131+ ];
35132}