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