at v192 11 kB view raw
1{ config, lib, pkgs, utils, ... }: 2# 3# todo: 4# - crontab for scrubs, etc 5# - zfs tunables 6 7with utils; 8with lib; 9 10let 11 12 cfgSpl = config.boot.spl; 13 cfgZfs = config.boot.zfs; 14 cfgSnapshots = config.services.zfs.autoSnapshot; 15 16 inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems; 17 inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems; 18 19 enableAutoSnapshots = cfgSnapshots.enable; 20 enableZfs = inInitrd || inSystem || enableAutoSnapshots; 21 22 kernel = config.boot.kernelPackages; 23 24 splKernelPkg = if cfgZfs.useGit then kernel.spl_git else kernel.spl; 25 zfsKernelPkg = if cfgZfs.useGit then kernel.zfs_git else kernel.zfs; 26 zfsUserPkg = if cfgZfs.useGit then pkgs.zfs_git else pkgs.zfs; 27 28 autosnapPkg = pkgs.zfstools.override { 29 zfs = zfsUserPkg; 30 }; 31 32 zfsAutoSnap = "${autosnapPkg}/bin/zfs-auto-snapshot"; 33 34 datasetToPool = x: elemAt (splitString "/" x) 0; 35 36 fsToPool = fs: datasetToPool fs.device; 37 38 zfsFilesystems = filter (x: x.fsType == "zfs") (attrValues config.fileSystems); 39 40 isRoot = fs: fs.neededForBoot || elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ]; 41 42 allPools = unique ((map fsToPool zfsFilesystems) ++ cfgZfs.extraPools); 43 44 rootPools = unique (map fsToPool (filter isRoot zfsFilesystems)); 45 46 dataPools = unique (filter (pool: !(elem pool rootPools)) allPools); 47 48in 49 50{ 51 52 ###### interface 53 54 options = { 55 boot.zfs = { 56 useGit = mkOption { 57 type = types.bool; 58 default = false; 59 example = true; 60 description = '' 61 Use the git version of the SPL and ZFS packages. 62 Note that these are unreleased versions, with less testing, and therefore 63 may be more unstable. 64 ''; 65 }; 66 67 extraPools = mkOption { 68 type = types.listOf types.str; 69 default = []; 70 example = [ "tank" "data" ]; 71 description = '' 72 Name or GUID of extra ZFS pools that you wish to import during boot. 73 74 Usually this is not necessary. Instead, you should set the mountpoint property 75 of ZFS filesystems to <literal>legacy</literal> and add the ZFS filesystems to 76 NixOS's <option>fileSystems</option> option, which makes NixOS automatically 77 import the associated pool. 78 79 However, in some cases (e.g. if you have many filesystems) it may be preferable 80 to exclusively use ZFS commands to manage filesystems. If so, since NixOS/systemd 81 will not be managing those filesystems, you will need to specify the ZFS pool here 82 so that NixOS automatically imports it on every boot. 83 ''; 84 }; 85 86 forceImportRoot = mkOption { 87 type = types.bool; 88 default = true; 89 example = false; 90 description = '' 91 Forcibly import the ZFS root pool(s) during early boot. 92 93 This is enabled by default for backwards compatibility purposes, but it is highly 94 recommended to disable this option, as it bypasses some of the safeguards ZFS uses 95 to protect your ZFS pools. 96 97 If you set this option to <literal>false</literal> and NixOS subsequently fails to 98 boot because it cannot import the root pool, you should boot with the 99 <literal>zfs_force=1</literal> option as a kernel parameter (e.g. by manually 100 editing the kernel params in grub during boot). You should only need to do this 101 once. 102 ''; 103 }; 104 105 forceImportAll = mkOption { 106 type = types.bool; 107 default = true; 108 example = false; 109 description = '' 110 Forcibly import all ZFS pool(s). 111 112 This is enabled by default for backwards compatibility purposes, but it is highly 113 recommended to disable this option, as it bypasses some of the safeguards ZFS uses 114 to protect your ZFS pools. 115 116 If you set this option to <literal>false</literal> and NixOS subsequently fails to 117 import your non-root ZFS pool(s), you should manually import each pool with 118 "zpool import -f &lt;pool-name&gt;", and then reboot. You should only need to do 119 this once. 120 ''; 121 }; 122 }; 123 124 services.zfs.autoSnapshot = { 125 enable = mkOption { 126 default = false; 127 type = types.bool; 128 description = '' 129 Enable the (OpenSolaris-compatible) ZFS auto-snapshotting service. 130 Note that you must set the <literal>com.sun:auto-snapshot</literal> 131 property to <literal>true</literal> on all datasets which you wish 132 to auto-snapshot. 133 134 You can override a child dataset to use, or not use auto-snapshotting 135 by setting its flag with the given interval: 136 <literal>zfs set com.sun:auto-snapshot:weekly=false DATASET</literal> 137 ''; 138 }; 139 140 frequent = mkOption { 141 default = 4; 142 type = types.int; 143 description = '' 144 Number of frequent (15-minute) auto-snapshots that you wish to keep. 145 ''; 146 }; 147 148 hourly = mkOption { 149 default = 24; 150 type = types.int; 151 description = '' 152 Number of hourly auto-snapshots that you wish to keep. 153 ''; 154 }; 155 156 daily = mkOption { 157 default = 7; 158 type = types.int; 159 description = '' 160 Number of daily auto-snapshots that you wish to keep. 161 ''; 162 }; 163 164 weekly = mkOption { 165 default = 4; 166 type = types.int; 167 description = '' 168 Number of weekly auto-snapshots that you wish to keep. 169 ''; 170 }; 171 172 monthly = mkOption { 173 default = 12; 174 type = types.int; 175 description = '' 176 Number of monthly auto-snapshots that you wish to keep. 177 ''; 178 }; 179 }; 180 }; 181 182 ###### implementation 183 184 config = mkMerge [ 185 (mkIf enableZfs { 186 assertions = [ 187 { 188 assertion = config.networking.hostId != null; 189 message = "ZFS requires config.networking.hostId to be set"; 190 } 191 { 192 assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot; 193 message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot"; 194 } 195 ]; 196 197 boot = { 198 kernelModules = [ "spl" "zfs" ] ; 199 extraModulePackages = [ splKernelPkg zfsKernelPkg ]; 200 }; 201 202 boot.initrd = mkIf inInitrd { 203 kernelModules = [ "spl" "zfs" ]; 204 extraUtilsCommands = 205 '' 206 copy_bin_and_libs ${zfsUserPkg}/sbin/zfs 207 copy_bin_and_libs ${zfsUserPkg}/sbin/zdb 208 copy_bin_and_libs ${zfsUserPkg}/sbin/zpool 209 ''; 210 extraUtilsCommandsTest = mkIf inInitrd 211 '' 212 $out/bin/zfs --help >/dev/null 2>&1 213 $out/bin/zpool --help >/dev/null 2>&1 214 ''; 215 postDeviceCommands = concatStringsSep "\n" (['' 216 ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}" 217 218 for o in $(cat /proc/cmdline); do 219 case $o in 220 zfs_force|zfs_force=1) 221 ZFS_FORCE="-f" 222 ;; 223 esac 224 done 225 ''] ++ (map (pool: '' 226 echo "importing root ZFS pool \"${pool}\"..." 227 zpool import -N $ZFS_FORCE "${pool}" 228 '') rootPools)); 229 }; 230 231 boot.loader.grub = mkIf inInitrd { 232 zfsSupport = true; 233 }; 234 235 environment.etc."zfs/zed.d".source = "${zfsUserPkg}/etc/zfs/zed.d/*"; 236 237 system.fsPackages = [ zfsUserPkg ]; # XXX: needed? zfs doesn't have (need) a fsck 238 environment.systemPackages = [ zfsUserPkg ]; 239 services.udev.packages = [ zfsUserPkg ]; # to hook zvol naming, etc. 240 systemd.packages = [ zfsUserPkg ]; 241 242 systemd.services = let 243 getPoolFilesystems = pool: 244 filter (x: x.fsType == "zfs" && (fsToPool x) == pool) (attrValues config.fileSystems); 245 246 getPoolMounts = pool: 247 let 248 mountPoint = fs: escapeSystemdPath fs.mountPoint; 249 in 250 map (x: "${mountPoint x}.mount") (getPoolFilesystems pool); 251 252 createImportService = pool: 253 nameValuePair "zfs-import-${pool}" { 254 description = "Import ZFS pool \"${pool}\""; 255 requires = [ "systemd-udev-settle.service" ]; 256 after = [ "systemd-udev-settle.service" "systemd-modules-load.service" ]; 257 wantedBy = (getPoolMounts pool) ++ [ "local-fs.target" ]; 258 before = (getPoolMounts pool) ++ [ "local-fs.target" ]; 259 unitConfig = { 260 DefaultDependencies = "no"; 261 }; 262 serviceConfig = { 263 Type = "oneshot"; 264 RemainAfterExit = true; 265 }; 266 script = '' 267 zpool_cmd="${zfsUserPkg}/sbin/zpool" 268 ("$zpool_cmd" list "${pool}" >/dev/null) || "$zpool_cmd" import -N ${optionalString cfgZfs.forceImportAll "-f"} "${pool}" 269 ''; 270 }; 271 in listToAttrs (map createImportService dataPools) // { 272 "zfs-mount" = { after = [ "systemd-modules-load.service" ]; }; 273 "zfs-share" = { after = [ "systemd-modules-load.service" ]; }; 274 "zed" = { after = [ "systemd-modules-load.service" ]; }; 275 }; 276 277 systemd.targets."zfs-import" = 278 let 279 services = map (pool: "zfs-import-${pool}.service") dataPools; 280 in 281 { 282 requires = services; 283 after = services; 284 }; 285 286 systemd.targets."zfs".wantedBy = [ "multi-user.target" ]; 287 }) 288 289 (mkIf enableAutoSnapshots { 290 systemd.services."zfs-snapshot-frequent" = { 291 description = "ZFS auto-snapshotting every 15 mins"; 292 after = [ "zfs-import.target" ]; 293 serviceConfig = { 294 Type = "oneshot"; 295 ExecStart = "${zfsAutoSnap} frequent ${toString cfgSnapshots.frequent}"; 296 }; 297 restartIfChanged = false; 298 startAt = "*:15,30,45"; 299 }; 300 301 systemd.services."zfs-snapshot-hourly" = { 302 description = "ZFS auto-snapshotting every hour"; 303 after = [ "zfs-import.target" ]; 304 serviceConfig = { 305 Type = "oneshot"; 306 ExecStart = "${zfsAutoSnap} hourly ${toString cfgSnapshots.hourly}"; 307 }; 308 restartIfChanged = false; 309 startAt = "hourly"; 310 }; 311 312 systemd.services."zfs-snapshot-daily" = { 313 description = "ZFS auto-snapshotting every day"; 314 after = [ "zfs-import.target" ]; 315 serviceConfig = { 316 Type = "oneshot"; 317 ExecStart = "${zfsAutoSnap} daily ${toString cfgSnapshots.daily}"; 318 }; 319 restartIfChanged = false; 320 startAt = "daily"; 321 }; 322 323 systemd.services."zfs-snapshot-weekly" = { 324 description = "ZFS auto-snapshotting every week"; 325 after = [ "zfs-import.target" ]; 326 serviceConfig = { 327 Type = "oneshot"; 328 ExecStart = "${zfsAutoSnap} weekly ${toString cfgSnapshots.weekly}"; 329 }; 330 restartIfChanged = false; 331 startAt = "weekly"; 332 }; 333 334 systemd.services."zfs-snapshot-monthly" = { 335 description = "ZFS auto-snapshotting every month"; 336 after = [ "zfs-import.target" ]; 337 serviceConfig = { 338 Type = "oneshot"; 339 ExecStart = "${zfsAutoSnap} monthly ${toString cfgSnapshots.monthly}"; 340 }; 341 restartIfChanged = false; 342 startAt = "monthly"; 343 }; 344 }) 345 ]; 346}