diff options
Diffstat (limited to 'nixos/modules/tasks/filesystems/zfs.nix')
-rw-r--r-- | nixos/modules/tasks/filesystems/zfs.nix | 239 |
1 files changed, 172 insertions, 67 deletions
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index 1c4bbc16b49..d4b10e9ed09 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -1,11 +1,10 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, utils, ... }: # # todo: # - crontab for scrubs, etc # - zfs tunables -# - /etc/zfs/zpool.cache handling - +with utils; with lib; let @@ -22,15 +21,30 @@ let kernel = config.boot.kernelPackages; - splPkg = if cfgZfs.useGit then kernel.spl_git else kernel.spl; - zfsPkg = if cfgZfs.useGit then kernel.zfs_git else kernel.zfs; + splKernelPkg = if cfgZfs.useGit then kernel.spl_git else kernel.spl; + zfsKernelPkg = if cfgZfs.useGit then kernel.zfs_git else kernel.zfs; + zfsUserPkg = if cfgZfs.useGit then pkgs.zfs_git else pkgs.zfs; autosnapPkg = pkgs.zfstools.override { - zfs = zfsPkg; + zfs = zfsUserPkg; }; zfsAutoSnap = "${autosnapPkg}/bin/zfs-auto-snapshot"; + datasetToPool = x: elemAt (splitString "/" x) 0; + + fsToPool = fs: datasetToPool fs.device; + + zfsFilesystems = filter (x: x.fsType == "zfs") (attrValues config.fileSystems); + + isRoot = fs: fs.neededForBoot || elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ]; + + allPools = unique ((map fsToPool zfsFilesystems) ++ cfgZfs.extraPools); + + rootPools = unique (map fsToPool (filter isRoot zfsFilesystems)); + + dataPools = unique (filter (pool: !(elem pool rootPools)) allPools); + in { @@ -38,28 +52,73 @@ in ###### interface options = { - boot.spl.hostid = mkOption { - default = ""; - example = "0xdeadbeef"; - description = '' - ZFS uses a system's hostid to determine if a storage pool (zpool) is - native to this system, and should thus be imported automatically. - Unfortunately, this hostid can change under linux from boot to boot (by - changing network adapters, for instance). Specify a unique 32 bit hostid in - hex here for zfs to prevent getting a random hostid between boots and having to - manually import pools. - ''; - }; + boot.zfs = { + useGit = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Use the git version of the SPL and ZFS packages. + Note that these are unreleased versions, with less testing, and therefore + may be more unstable. + ''; + }; + + extraPools = mkOption { + type = types.listOf types.str; + default = []; + example = [ "tank" "data" ]; + description = '' + Name or GUID of extra ZFS pools that you wish to import during boot. + + Usually this is not necessary. Instead, you should set the mountpoint property + of ZFS filesystems to <literal>legacy</literal> and add the ZFS filesystems to + NixOS's <option>fileSystems</option> option, which makes NixOS automatically + import the associated pool. + + However, in some cases (e.g. if you have many filesystems) it may be preferable + to exclusively use ZFS commands to manage filesystems. If so, since NixOS/systemd + will not be managing those filesystems, you will need to specify the ZFS pool here + so that NixOS automatically imports it on every boot. + ''; + }; + + forceImportRoot = mkOption { + type = types.bool; + default = true; + example = false; + description = '' + Forcibly import the ZFS root pool(s) during early boot. + + This is enabled by default for backwards compatibility purposes, but it is highly + recommended to disable this option, as it bypasses some of the safeguards ZFS uses + to protect your ZFS pools. + + If you set this option to <literal>false</literal> and NixOS subsequently fails to + boot because it cannot import the root pool, you should boot with the + <literal>zfs_force=1</literal> option as a kernel parameter (e.g. by manually + editing the kernel params in grub during boot). You should only need to do this + once. + ''; + }; - boot.zfs.useGit = mkOption { - type = types.bool; - default = false; - example = true; - description = '' - Use the git version of the SPL and ZFS packages. - Note that these are unreleased versions, with less testing, and therefore - may be more unstable. - ''; + forceImportAll = mkOption { + type = types.bool; + default = true; + example = false; + description = '' + Forcibly import all ZFS pool(s). + + This is enabled by default for backwards compatibility purposes, but it is highly + recommended to disable this option, as it bypasses some of the safeguards ZFS uses + to protect your ZFS pools. + + If you set this option to <literal>false</literal> and NixOS subsequently fails to + import your non-root ZFS pool(s), you should manually import each pool with + "zpool import -f <pool-name>", and then reboot. You should only need to do + this once. + ''; + }; }; services.zfs.autoSnapshot = { @@ -124,67 +183,113 @@ in config = mkMerge [ (mkIf enableZfs { + assertions = [ + { + assertion = config.networking.hostId != null; + message = "ZFS requires config.networking.hostId to be set"; + } + { + assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot; + message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot"; + } + ]; + boot = { kernelModules = [ "spl" "zfs" ] ; - extraModulePackages = [ splPkg zfsPkg ]; - extraModprobeConfig = mkIf (cfgSpl.hostid != "") '' - options spl spl_hostid=${cfgSpl.hostid} - ''; + extraModulePackages = [ splKernelPkg zfsKernelPkg ]; }; boot.initrd = mkIf inInitrd { kernelModules = [ "spl" "zfs" ]; extraUtilsCommands = '' - cp -v ${zfsPkg}/sbin/zfs $out/bin - cp -v ${zfsPkg}/sbin/zdb $out/bin - cp -v ${zfsPkg}/sbin/zpool $out/bin - cp -pdv ${zfsPkg}/lib/lib*.so* $out/lib - cp -pdv ${pkgs.zlib}/lib/lib*.so* $out/lib + copy_bin_and_libs ${zfsUserPkg}/sbin/zfs + copy_bin_and_libs ${zfsUserPkg}/sbin/zdb + copy_bin_and_libs ${zfsUserPkg}/sbin/zpool ''; - postDeviceCommands = + extraUtilsCommandsTest = mkIf inInitrd '' - zpool import -f -a + $out/bin/zfs --help >/dev/null 2>&1 + $out/bin/zpool --help >/dev/null 2>&1 ''; + postDeviceCommands = concatStringsSep "\n" (['' + ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}" + + for o in $(cat /proc/cmdline); do + case $o in + zfs_force|zfs_force=1) + ZFS_FORCE="-f" + ;; + esac + done + ''] ++ (map (pool: '' + echo "importing root ZFS pool \"${pool}\"..." + zpool import -N $ZFS_FORCE "${pool}" + '') rootPools)); }; boot.loader.grub = mkIf inInitrd { zfsSupport = true; }; - systemd.services."zpool-import" = { - description = "Import zpools"; - after = [ "systemd-udev-settle.service" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${zfsPkg}/sbin/zpool import -f -a"; - }; - restartIfChanged = false; + environment.etc."zfs/zed.d".source = "${zfsUserPkg}/etc/zfs/zed.d/*"; + + system.fsPackages = [ zfsUserPkg ]; # XXX: needed? zfs doesn't have (need) a fsck + environment.systemPackages = [ zfsUserPkg ]; + services.udev.packages = [ zfsUserPkg ]; # to hook zvol naming, etc. + systemd.packages = [ zfsUserPkg ]; + + systemd.services = let + getPoolFilesystems = pool: + filter (x: x.fsType == "zfs" && (fsToPool x) == pool) (attrValues config.fileSystems); + + getPoolMounts = pool: + let + mountPoint = fs: escapeSystemdPath fs.mountPoint; + in + map (x: "${mountPoint x}.mount") (getPoolFilesystems pool); + + createImportService = pool: + nameValuePair "zfs-import-${pool}" { + description = "Import ZFS pool \"${pool}\""; + requires = [ "systemd-udev-settle.service" ]; + after = [ "systemd-udev-settle.service" "systemd-modules-load.service" ]; + wantedBy = (getPoolMounts pool) ++ [ "local-fs.target" ]; + before = (getPoolMounts pool) ++ [ "local-fs.target" ]; + unitConfig = { + DefaultDependencies = "no"; + }; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + zpool_cmd="${zfsUserPkg}/sbin/zpool" + ("$zpool_cmd" list "${pool}" >/dev/null) || "$zpool_cmd" import -N ${optionalString cfgZfs.forceImportAll "-f"} "${pool}" + ''; + }; + in listToAttrs (map createImportService dataPools) // { + "zfs-mount" = { after = [ "systemd-modules-load.service" ]; }; + "zfs-share" = { after = [ "systemd-modules-load.service" ]; }; + "zed" = { after = [ "systemd-modules-load.service" ]; }; }; - systemd.services."zfs-mount" = { - description = "Mount ZFS Volumes"; - after = [ "zpool-import.service" ]; - wantedBy = [ "local-fs.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${zfsPkg}/sbin/zfs mount -a"; - ExecStop = "${zfsPkg}/sbin/zfs umount -a"; - }; - restartIfChanged = false; - }; + systemd.targets."zfs-import" = + let + services = map (pool: "zfs-import-${pool}.service") dataPools; + in + { + requires = services; + after = services; + }; - system.fsPackages = [ zfsPkg ]; # XXX: needed? zfs doesn't have (need) a fsck - environment.systemPackages = [ zfsPkg ]; - services.udev.packages = [ zfsPkg ]; # to hook zvol naming, etc. + systemd.targets."zfs".wantedBy = [ "multi-user.target" ]; }) (mkIf enableAutoSnapshots { systemd.services."zfs-snapshot-frequent" = { description = "ZFS auto-snapshotting every 15 mins"; - after = [ "zpool-import.service" ]; + after = [ "zfs-import.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${zfsAutoSnap} frequent ${toString cfgSnapshots.frequent}"; @@ -195,7 +300,7 @@ in systemd.services."zfs-snapshot-hourly" = { description = "ZFS auto-snapshotting every hour"; - after = [ "zpool-import.service" ]; + after = [ "zfs-import.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${zfsAutoSnap} hourly ${toString cfgSnapshots.hourly}"; @@ -206,7 +311,7 @@ in systemd.services."zfs-snapshot-daily" = { description = "ZFS auto-snapshotting every day"; - after = [ "zpool-import.service" ]; + after = [ "zfs-import.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${zfsAutoSnap} daily ${toString cfgSnapshots.daily}"; @@ -217,7 +322,7 @@ in systemd.services."zfs-snapshot-weekly" = { description = "ZFS auto-snapshotting every week"; - after = [ "zpool-import.service" ]; + after = [ "zfs-import.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${zfsAutoSnap} weekly ${toString cfgSnapshots.weekly}"; @@ -228,7 +333,7 @@ in systemd.services."zfs-snapshot-monthly" = { description = "ZFS auto-snapshotting every month"; - after = [ "zpool-import.service" ]; + after = [ "zfs-import.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${zfsAutoSnap} monthly ${toString cfgSnapshots.monthly}"; |