# These functions have »pkgs.zfs« as undeclared dependency (so that they can alternatively use initramfs' »extraUtils«). ## Creates the system's ZFS pools and their datasets. function create-zpools { # 1: mnt local mnt=$1 ; local poolName ; for poolName in "@{!config.wip.fs.zfs.pools[@]}" ; do ( set -eu eval 'declare -A pool='"@{config.wip.fs.zfs.pools[$poolName]}" eval 'declare -a vdevs='"${pool[vdevArgs]}" eval 'declare -A poolProps='"${pool[props]}" eval 'declare -A dataset='"@{config.wip.fs.zfs.datasets[${pool[name]}]}" eval 'declare -A dataProps='"${dataset[props]}" get-zfs-crypt-props "${dataset[name]}" dataProps declare -a args=( ) for name in "${!poolProps[@]}" ; do args+=( -o "${name}=${poolProps[$name]}" ) ; done for name in "${!dataProps[@]}" ; do args+=( -O "${name}=${dataProps[$name]}" ) ; done 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[@]}" ; exit 1 ; fi fi done ( set -x ; zpool create "${args[@]}" -R "$mnt" "${pool[name]}" "${vdevs[@]}" ) ) && { prepend_trap "zpool export '$poolName'" EXIT } ; done && ensure-datasets $mnt } ## 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«). # »keystatus« and »mounted« of existing datasets should remain unchained, newly crated datasets will not be mounted but have their keys loaded. function ensure-datasets {( set -eu # 1: mnt, 2?: filterExp if (( @{#config.wip.fs.zfs.datasets[@]} == 0 )) ; then return ; fi mnt=$1 ; while [[ "$mnt" == */ ]] ; do mnt=${mnt:0:(-1)} ; done # (remove any tailing slashes) filterExp=${2:-'^'} tmpMnt=$(mktemp -d) ; trap "rmdir $tmpMnt" EXIT : 'Step-through is very verbose and breaks the loop, disabling it for this function' ; trap - debug printf '%s\0' "@{!config.wip.fs.zfs.datasets[@]}" | LC_ALL=C sort -z | while IFS= read -r -d $'\0' name ; do if [[ ! $name =~ $filterExp ]] ; then printf 'Skipping dataset »%s« since it does not match »%s«\n' "$name" "$filterExp" >&2 ; continue ; fi eval 'declare -A dataset='"@{config.wip.fs.zfs.datasets[$name]}" eval 'declare -A props='"${dataset[props]}" explicitKeylocation=${props[keylocation]:-} 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 current=$(zfs get -o value -H mountpoint "${dataset[name]}") ; current=${current/$mnt/} if [[ ${props[mountpoint]} == "${current:-/}" ]] ; then unset props[mountpoint] ; fi fi if [[ ${props[keyformat]:-} == ephemeral ]] ; then cryptRoot=${dataset[name]} ; unset props[keyformat] ; props[keylocation]=file:///dev/null fi unset props[encryption] ; unset props[keyformat] # can't change these anyway names=$(IFS=, ; echo "${!props[*]}") ; values=$(IFS=$'\n' ; echo "${props[*]}") if [[ $values != "$(zfs get -o value -H "$names" "${dataset[name]}")" ]] ; then ( declare -a args=( ) ; for name in "${!props[@]}" ; do args+=( "${name}=${props[$name]}" ) ; done ( set -x ; zfs set "${args[@]}" "${dataset[name]}" ) ) ; fi if [[ $cryptRoot && $(zfs get -o value -H encryptionroot "${dataset[name]}") != "$cryptRoot" ]] ; then ( # inherit key from parent (which the parent would also already have done if necessary) parent=$(dirname "${dataset[name]}") if [[ $(zfs get -o value -H keystatus "$parent") != available ]] ; then zfs load-key -L file://"$cryptKey" "$parent" ; trap "zfs unload-key $parent || true" EXIT fi if [[ $(zfs get -o value -H keystatus "${dataset[name]}") != available ]] ; then zfs load-key -L file://"$cryptKey" "${dataset[name]}" # will unload with parent fi ( set -x ; zfs change-key -i "${dataset[name]}" ) ) ; 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 args=( ) ; for name in "${!props[@]}" ; do args+=( -o "${name}=${props[$name]}" ) ; done