diff --git a/nixos/modules/zfs-health.nix b/nixos/modules/zfs-health.nix new file mode 100644 index 0000000..82826ef --- /dev/null +++ b/nixos/modules/zfs-health.nix @@ -0,0 +1,159 @@ +{ + config, + pkgs, + lib, + ... +}: let + cfg = config.zfs.health; + pool = { + config, + name, + ... + }: { + options = { + scrub = lib.mkOption { + type = with lib.types; nullOr str; + }; + + trim = { + secure = lib.mkOption { + type = lib.types.bool; + default = false; + }; + + rate = lib.mkOption { + type = with lib.types; nullOr int; + default = null; + }; + + schedule = lib.mkOption { + type = with lib.types; nullOr str; + }; + }; + }; + }; + + zpool = lib.getExe' cfg.package "zpool"; + + trimPools = lib.filterAttrs (_: poolConfig: poolConfig.trim != null) cfg.pools; +in { + options.zfs.health = { + enable = lib.mkEnableOption "Enable ZFS health assurance module."; + + package = lib.mkOption { + type = lib.types.package; + default = config.boot.zfs.package; + }; + + pools = lib.mkOption { + type = with lib.types; attrsOf (submodule pool); + default = []; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services = lib.foldl (acc: attr: acc // attr) {} [ + (lib.flip lib.mapAttrs' trimPools + ( + pool: poolConfig: + lib.nameValuePair "zfs-health-trim-${pool}-resume" { + after = ["systemd-suspend.service"]; + wantedBy = ["suspend.target"]; + + restartIfChanged = false; + + script = '' + set -eo pipefail + + if -e /var/run/zfs-health-trim-${pool}-suspended ; then + systemctl start "zfs-healt-trim-${pool}.service" + rm /var/run/zfs-health-trim-${pool}-suspended + fi + ''; + + serviceConfig = { + Type = "oneshot"; + }; + } + )) + (lib.flip lib.mapAttrs' trimPools + ( + pool: poolConfig: + lib.nameValuePair "zfs-health-trim-${pool}-suspend" { + before = ["systemd-suspend.service"]; + wantedBy = ["suspend.target"]; + + restartIfChanged = false; + + script = '' + set -eo pipefail + + if systemctl status "zfs-healt-trim-${pool}.service" ; then + touch /var/run/zfs-health-trim-${pool}-suspended + fi + ''; + + serviceConfig = { + Type = "oneshot"; + }; + } + )) + (lib.flip lib.mapAttrs' trimPools + (pool: poolConfig: + lib.nameValuePair "zfs-health-trim-${pool}" { + after = ["multi-user.target"]; + + restartIfChanged = false; + + preStop = '' + set -eo pipefail + + if ${zpool} status ${pool} | grep -q trimming ; then + ${zpool} trim --suspend ${pool} + + if ! [ -z $MAINPID ] ; then + while kill -0 $MAINPID 2> /dev/null ; do + sleep 10 + done + fi + fi + ''; + + script = '' + if ${zpool} status ${pool} | grep -q trimming ; then + echo "Trim already in progress, polling for completion" + while ${zpool} status ${pool} | grep trimming >/dev/null ; do + sleep 60 + done + else + ${zpool} trim ${pool} \ + --wait \ + ${lib.optionalString (poolConfig.trim.rate != null) "--rate ${toString poolConfig.trim.rate}"} \ + ${lib.optionalString poolConfig.trim.secure "--secure"} + fi + + echo "Trim done!" + ${zpool} status ${pool} + ''; + + serviceConfig = { + Type = "oneshot"; + }; + })) + ]; + + systemd.timers = lib.pipe cfg.pools [ + (lib.filterAttrs (_: poolConfig: poolConfig.trim != null)) + (lib.mapAttrs' + (pool: poolConfig: + lib.nameValuePair "zfs-health-trim-${pool}" { + after = ["multi-user.target"]; + + timerConfig = { + OnCalendar = poolConfig.trim.schedule; + Persistent = true; + }; + })) + ]; + }; +} diff --git a/nixos/systems/omen/default.nix b/nixos/systems/omen/default.nix index 4c98202..a542705 100644 --- a/nixos/systems/omen/default.nix +++ b/nixos/systems/omen/default.nix @@ -59,6 +59,7 @@ in { inputs.uk3s-nix.nixosModules.ucontainersNetwork inputs.notnft.nixosModules.default inputs.self.nixosModules.notnft + inputs.self.nixosModules.zfsHealth inputs.impermenance.nixosModules.impermanence ]; diff --git a/nixos/systems/omen/filesystems.nix b/nixos/systems/omen/filesystems.nix index 0e72fc1..e2a9e32 100644 --- a/nixos/systems/omen/filesystems.nix +++ b/nixos/systems/omen/filesystems.nix @@ -114,4 +114,14 @@ in { options = nfsOptions; }; }; + + zfs.health = { + enable = true; + + pools."omen-ssd" = { + trim = { + schedule = "Sun *-*-* 03:00:00"; + }; + }; + }; }