From fc2159335287be671f508886594425ec4aa61647 Mon Sep 17 00:00:00 2001 From: Florian Jacob Date: Fri, 8 Dec 2017 22:46:10 +0100 Subject: nixos/btrfs: add services.btrfs.autoScrub for automatic regular scrubbing of mounted btrfs filesystems, similar to what's already there for zfs. --- nixos/modules/tasks/filesystems/btrfs.nix | 113 +++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 8 deletions(-) (limited to 'nixos/modules/tasks/filesystems') diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix index 8cfa1b6921d3..1384873b6631 100644 --- a/nixos/modules/tasks/filesystems/btrfs.nix +++ b/nixos/modules/tasks/filesystems/btrfs.nix @@ -1,35 +1,132 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, utils, ... }: with lib; let inInitrd = any (fs: fs == "btrfs") config.boot.initrd.supportedFilesystems; + inSystem = any (fs: fs == "btrfs") config.boot.supportedFilesystems; + + cfgScrub = config.services.btrfs.autoScrub; + + enableAutoScrub = cfgScrub.enable; + enableBtrfs = inInitrd || inSystem || enableAutoScrub; in { - config = mkIf (any (fs: fs == "btrfs") config.boot.supportedFilesystems) { + options = { + # One could also do regular btrfs balances, but that shouldn't be necessary + # during normal usage and as long as the filesystems aren't filled near capacity + services.btrfs.autoScrub = { + enable = mkEnableOption "Enable regular btrfs scrub"; - system.fsPackages = [ pkgs.btrfs-progs ]; + fileSystems = mkOption { + type = types.listOf types.path; + example = [ "/" ]; + description = '' + List of paths to btrfs filesystems to regularily call btrfs scrub on. + Defaults to all mount points with btrfs filesystems. + If you mount a filesystem multiple times or additionally mount subvolumes, + you need to manually specify this list to avoid scrubbing multiple times. + ''; + }; - boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ]; + interval = mkOption { + default = "monthly"; + type = types.str; + example = "weekly"; + description = '' + Systemd calendar expression for when to scrub btrfs filesystems. + The recommended period is a month but could be less + (btrfs-scrub + 8). + See + systemd.time + 7 + for more information on the syntax. + ''; + }; + + }; + }; - boot.initrd.extraUtilsCommands = mkIf inInitrd + config = mkMerge [ + (mkIf enableBtrfs { + system.fsPackages = [ pkgs.btrfs-progs ]; + + boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" "crc32c" ]; + + boot.initrd.extraUtilsCommands = mkIf inInitrd '' copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs ln -sv btrfs $out/bin/btrfsck ln -sv btrfsck $out/bin/fsck.btrfs ''; - boot.initrd.extraUtilsCommandsTest = mkIf inInitrd + boot.initrd.extraUtilsCommandsTest = mkIf inInitrd '' $out/bin/btrfs --version ''; - boot.initrd.postDeviceCommands = mkIf inInitrd + boot.initrd.postDeviceCommands = mkIf inInitrd '' btrfs device scan ''; - }; + }) + + (mkIf enableAutoScrub { + assertions = [ + { + assertion = cfgScrub.enable -> (cfgScrub.fileSystems != []); + message = '' + If 'services.btrfs.autoScrub' is enabled, you need to have at least one + btrfs file system mounted via 'fileSystems' or specify a list manually + in 'services.btrfs.autoScrub.fileSystems'. + ''; + } + ]; + + # This will yield duplicated units if the user mounts a filesystem multiple times + # or additionally mounts subvolumes, but going the other way around via devices would + # yield duplicated units when a filesystem spans multiple devices. + # This way around seems like the more sensible default. + services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint) + (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems)); + + # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service + # template units due to problems enabling the parameterized units, + # so settled with many units and templating via nix for now. + # https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544 + systemd.timers = let + scrubTimer = fs: let + fs' = utils.escapeSystemdPath fs; + in nameValuePair "btrfs-scrub-${fs'}" { + description = "regular btrfs scrub timer on ${fs}"; + + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = cfgScrub.interval; + AccuracySec = "1d"; + Persistent = true; + }; + }; + in listToAttrs (map scrubTimer cfgScrub.fileSystems); + + systemd.services = let + scrubService = fs: let + fs' = utils.escapeSystemdPath fs; + in nameValuePair "btrfs-scrub-${fs'}" { + description = "btrfs scrub on ${fs}"; + + serviceConfig = { + Type = "oneshot"; + Nice = 19; + IOSchedulingClass = "idle"; + ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}"; + }; + }; + in listToAttrs (map scrubService cfgScrub.fileSystems); + }) + ]; } -- cgit 1.4.1