grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / tasks / filesystems / btrfs.nix
blob7329a798133790114be4d5eba2d74e1354ac4202
1 { config, lib, pkgs, utils, ... }:
3 let
4   inherit (lib)
5     mkEnableOption
6     mkOption
7     types
8     mkMerge
9     mkIf
10     optionals
11     mkDefault
12     nameValuePair
13     listToAttrs
14     filterAttrs
15     mapAttrsToList
16     foldl';
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;
29   options = {
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;
37         example = [ "/" ];
38         description = ''
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.
44         '';
45       };
47       interval = mkOption {
48         default = "monthly";
49         type = types.str;
50         example = "weekly";
51         description = ''
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)`).
55           See
56           {manpage}`systemd.time(7)`
57           for more information on the syntax.
58         '';
59       };
61     };
62   };
64   config = mkMerge [
65     (mkIf enableBtrfs {
66       system.fsPackages = [ pkgs.btrfs-progs ];
67     })
69     (mkIf inInitrd {
70       boot.initrd.kernelModules = [ "btrfs" ];
71       boot.initrd.availableKernelModules =
72         [ "crc32c" ]
73         ++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [
74           # Needed for mounting filesystems with new checksums
75           "xxhash_generic"
76           "blake2b_generic"
77           "sha256_generic" # Should be baked into our kernel, just to be sure
78         ];
80       boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable)
81       ''
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
85       '';
87       boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable)
88       ''
89         $out/bin/btrfs --version
90       '';
92       boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable)
93       ''
94         btrfs device scan
95       '';
97       boot.initrd.systemd.initrdBin = [ pkgs.btrfs-progs ];
98     })
100     (mkIf enableAutoScrub {
101       assertions = [
102         {
103           assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []);
104           message = ''
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'.
108           '';
109         }
110       ];
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 =
116       let
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 ]) [ ];
120       in
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
129       systemd.timers = let
130         scrubTimer = fs: let
131           fs' = utils.escapeSystemdPath fs;
132         in nameValuePair "btrfs-scrub-${fs'}" {
133           description = "regular btrfs scrub timer on ${fs}";
135           wantedBy = [ "timers.target" ];
136           timerConfig = {
137             OnCalendar = cfgScrub.interval;
138             AccuracySec = "1d";
139             Persistent = true;
140           };
141         };
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" ];
153           serviceConfig = {
154             # simple and not oneshot, otherwise ExecStop is not used
155             Type = "simple";
156             Nice = 19;
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}
162             '';
163           };
164         };
165       in listToAttrs (map scrubService cfgScrub.fileSystems);
166     })
167   ];