···137 Previously the default behaviour was to listen on all interfaces.
138 </para>
139 </listitem>
00000000140</itemizedlist>
141142</section>
···137 Previously the default behaviour was to listen on all interfaces.
138 </para>
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>
148</itemizedlist>
149150</section>
···1+{ config, lib, pkgs, utils, ... }:
23with lib;
45let
67 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;
1415in
1617{
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+ };
5051+ };
52+ };
5354+ config = mkMerge [
55+ (mkIf enableBtrfs {
56+ system.fsPackages = [ pkgs.btrfs-progs ];
5758+ boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ];
59+60+ boot.initrd.extraUtilsCommands = mkIf inInitrd
61 ''
62 copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
63 ln -sv btrfs $out/bin/btrfsck
64 ln -sv btrfsck $out/bin/fsck.btrfs
65 '';
6667+ boot.initrd.extraUtilsCommandsTest = mkIf inInitrd
68 ''
69 $out/bin/btrfs --version
70 '';
7172+ boot.initrd.postDeviceCommands = mkIf inInitrd
73 ''
74 btrfs device scan
75 '';
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+ ];
132}