## Creates all of the system's ZFS pools that are »createDuringInstallation«, plus their datasets. function create-zpools { # 1: mnt local poolName ; for poolName in "@{!config.setup.zfs.pools[@]}" ; do if [[ ! @{config.setup.zfs.pools!catAttrSets.createDuringInstallation[$poolName]} ]] ; then continue ; fi create-zpool "$1" "$poolName" done } ## Imports all of the system's ZFS pools that are »createDuringInstallation« and not imported yet. function import-zpools { # 1: mnt, 2: skipImported local mnt=$1 ; local skipImported=${2:-} local poolName ; for poolName in "@{!config.setup.zfs.pools[@]}" ; do if [[ ! @{config.setup.zfs.pools!catAttrSets.createDuringInstallation[$poolName]} ]] ; then continue ; fi if @{native.zfs}/bin/zfs get -o value -H name "$poolName" &>/dev/null && [[ $skipImported ]] ; then continue ; fi @{native.zfs}/bin/zpool import -f -N -R "$mnt" "$poolName" && prepend_trap "@{native.zfs}/bin/zpool export '$poolName'" EXIT || return : | @{native.zfs}/bin/zfs load-key -r "$poolName" || true ensure-datasets "$mnt" '^'"$poolName"'($|[/])' || return done } declare-command create-zpool mnt poolName << 'EOD' Creates a single of the system's ZFS pools, and its datasets. Can be called manually to create pools that were added to the configuration, or to create those declared with »createDuringInstallation = false«. Expects the backing device(-partition)s to exist as declared for the pool. EOD declare-flag install-system,create-zpool zpool-force "" "When creating ZFS storage pools, pass the »-f« (force) option. This may be required when installing to disks that are currently part of a pool, or ZFS refuses do reuse them." function create-zpool { local beLoud=/dev/null ; if [[ ${args[debug]:-} ]] ; then beLoud=/dev/stdout ; fi local beSilent=/dev/stderr ; if [[ ${args[quiet]:-} ]] ; then beSilent=/dev/null ; fi local mnt=$1 ; local poolName=$2 eval 'local -A pool='"@{config.setup.zfs.pools[$poolName]}" eval 'local -a vdevs='"${pool[vdevArgs]}" eval 'local -A poolProps='"${pool[props]}" eval 'local -A dataset='"@{config.setup.zfs.datasets[${pool[name]}]}" eval 'local -A dataProps='"${dataset[props]}" local dummy ; get-zfs-crypt-props "${dataset[name]}" dataProps dummy dummy local -a zpoolCreate=( ) ; keySrc=/dev/null if [[ ${dataProps[keyformat]:-} == ephemeral ]] ; then dataProps[encryption]=aes-256-gcm ; dataProps[keyformat]=hex ; dataProps[keylocation]=file:///dev/stdin ; keySrc=/dev/urandom fi local name ; for name in "${!poolProps[@]}" ; do zpoolCreate+=( -o "${name}=${poolProps[$name]}" ) ; done local name ; for name in "${!dataProps[@]}" ; do zpoolCreate+=( -O "${name}=${dataProps[$name]}" ) ; done local index ; for index in "${!vdevs[@]}" ; do part=${vdevs[$index]} ; if [[ $part =~ ^(mirror|raidz[123]?|draid[123]?.*|spare|log|dedup|special|cache)$ ]] ; then continue ; fi if [[ @{config.boot.initrd.luks.devices!catAttrSets.device[$part]:-} ]] ; then vdevs[$index]=/dev/mapper/$part else part=/dev/disk/by-partlabel/$part ; vdevs[$index]=$part if ! is-partition-on-disks "$part" "${blockDevs[@]}" ; then echo "Partition alias $part used by zpool ${pool[name]} does not point at one of the target disks ${blockDevs[@]}" 1>&2 ; \return 1 ; fi @{native.util-linux}/bin/wipefs --all "$part" >$beLoud 2>$beSilent || return # else mkfs might refuse to replace any previous filesystems fi done @{native.kmod}/bin/modprobe zfs || true { <$keySrc tr -dc 0-9a-f || true ; } | head -c 64 | ( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zpool create ${args[zpool-force]:+-f} "${zpoolCreate[@]}" -R "$mnt" "${pool[name]}" "${vdevs[@]}" ) || return prepend_trap "@{native.zfs}/bin/zpool export '$poolName'" EXIT || return if [[ $keySrc == /dev/urandom ]] ; then @{native.zfs}/bin/zfs unload-key "$poolName" &>/dev/null ; fi ensure-datasets $mnt '^'"$poolName"'($|[/])' || return if [[ ${args[debug]:-} ]] ; then @{native.zfs}/bin/zfs list -o name,canmount,mounted,mountpoint,keystatus,encryptionroot -r "$poolName" ; fi } declare-command ensure-datasets mnt filterExp? << 'EOD' Ensures that the system's datasets exist and have the defined properties (but not that they don't have properties that aren't defined). The pool(s) must exist, be imported with root prefix »$mnt«, and (if datasets are to be created or encryption roots to be inherited) the system's keystore must be open (see »mount-keystore-luks«) or the keys must be loaded. »keystatus« and »mounted« of existing datasets should remain unchanged by this function, newly crated datasets will not be mounted but have their keys loaded. EOD function ensure-datasets { if (( @{#config.setup.zfs.datasets[@]} == 0 )) ; then \return ; fi local mnt=$1 ; while [[ "$mnt" == */ ]] ; do mnt=${mnt:0:(-1)} ; done # (remove any tailing slashes) local filterExp=${2:-'^'} local zfs=@{native.zfs}/bin/zfs local name ; while IFS= read -u3 -r -d $'\0' name ; do if [[ ! $name =~ $filterExp ]] ; then : "Skipping dataset »$name« since it does not match »$filterExp«" ; continue ; fi eval 'local -A dataset='"@{config.setup.zfs.datasets[$name]}" eval 'local -A props='"${dataset[props]}" local explicitKeylocation=${props[keylocation]:-} cryptKey cryptRoot get-zfs-crypt-props "${dataset[name]}" props cryptKey cryptRoot if $zfs get -o value -H name "${dataset[name]}" &>/dev/null ; then # dataset exists: check its properties if [[ ${props[mountpoint]:-} ]] ; then # don't set the current mount point again (no-op), cuz that fails if the dataset is mounted # (The behavior inside a (nixos-enter) chroot is quite odd: ZFS ignores the chroot when printing the mountpoint, but heeds it when setting it. So this test fails (if the CHROOT_DIR is not set), but the set operation still sets the correct value.) local current=$($zfs get -o value -H mountpoint "${dataset[name]}") ; current=${current/${CHROOT_DIR:-}$mnt/} if [[ ${props[mountpoint]} == "${current:-/}" ]] ; then unset props[mountpoint] ; fi fi if [[ ${props[keyformat]:-} == ephemeral ]] ; then cryptRoot= ; unset props[keyformat] ; props[keylocation]=file:///dev/null fi if [[ $explicitKeylocation ]] ; then props[keylocation]=$explicitKeylocation ; fi unset props[encryption] ; unset props[keyformat] # can't change these anyway function ensure-props { # 1: datasetName local datasetName=$1 local propNames=$( IFS=, ; echo "${!props[*]}" ) local propValues=$( IFS=$'\n' ; echo "${props[*]}" ) if [[ $propValues != "$( $zfs get -o value -H "$propNames" "$datasetName" )" ]] ; then local -a zfsSet=( ) ; local propName ; for propName in "${!props[@]}" ; do zfsSet+=( "${propName}=${props[$propName]}" ) ; done ( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zfs set "${zfsSet[@]}" "$datasetName" ) || return fi if [[ $cryptRoot && $( $zfs get -o value -H encryptionroot "$datasetName" ) != "$cryptRoot" ]] ; then ( # inherit key from parent (which the parent would also already have done if necessary) if [[ $( $zfs get -o value -H keystatus "$cryptRoot" ) != available ]] ; then $zfs load-key -L file://"$cryptKey" "$cryptRoot" || exit ; trap "$zfs unload-key $cryptRoot || true" EXIT fi if [[ $( $zfs get -o value -H keystatus "$datasetName" ) != available ]] ; then $zfs load-key -L file://"$cryptKey" "$datasetName" || exit # will unload with cryptRoot fi ( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zfs change-key -i "$datasetName" ) || exit ) || return ; fi } ensure-props "${dataset[name]}" || return if [[ ${dataset[recursiveProps]:-} ]] ; then if [[ ${props[mountpoint]:-} != none ]] ; then unset props[mountpoint] ; fi while IFS= read -u3 -r name ; do ensure-props "$name" || return done 3< <( $zfs list -H -o name -r "${dataset[name]}" | LC_ALL=C sort | tail -n +2 ) fi else ( # create dataset if [[ ${props[keyformat]:-} == ephemeral ]] ; then props[encryption]=aes-256-gcm ; props[keyformat]=hex ; props[keylocation]=file:///dev/stdin ; explicitKeylocation=file:///dev/null declare -a zfsCreate=( ) ; for name in "${!props[@]}" ; do zfsCreate+=( -o "${name}=${props[$name]}" ) ; done {