improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2022-11-30 13:41:21 +01:00
## Creates all of the system's ZFS pools that are »createDuringInstallation«, plus their datasets.
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
function create-zpools { # 1: mnt
2023-06-16 02:14:51 +02:00
local poolName ; for poolName in "@{!config.setup.zfs.pools[@]}" ; do
if [ [ ! @{ config.setup.zfs.pools!catAttrSets.createDuringInstallation[ $poolName ] } ] ] ; then continue ; fi
2022-11-30 13:41:21 +01:00
create-zpool " $1 " " $poolName "
done
}
2023-08-14 18:59:47 +02:00
## 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
}
2023-06-16 02:14:51 +02:00
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
2023-08-14 18:59:47 +02:00
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."
2023-06-16 02:14:51 +02:00
function create-zpool {
2024-02-01 13:30:57 +01:00
local beLoud = /dev/null ; if [ [ ${ args [debug] :- } ] ] ; then beLoud = /dev/stdout ; fi
local beSilent = /dev/stderr ; if [ [ ${ args [quiet] :- } ] ] ; then beSilent = /dev/null ; fi
2023-01-29 15:55:56 +01:00
local mnt = $1 ; local poolName = $2
2023-06-16 02:14:51 +02:00
eval 'local -A pool=' " @{config.setup.zfs.pools[ $poolName ]} "
2023-01-29 15:55:56 +01:00
eval 'local -a vdevs=' " ${ pool [vdevArgs] } "
eval 'local -A poolProps=' " ${ pool [props] } "
2023-06-16 02:14:51 +02:00
eval 'local -A dataset=' " @{config.setup.zfs.datasets[ ${ pool [name] } ]} "
2023-01-29 15:55:56 +01:00
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 ; \r eturn 1 ; fi
2024-02-01 13:30:57 +01:00
@{ native.util-linux} /bin/wipefs --all " $part " >$beLoud 2>$beSilent || return # else mkfs might refuse to replace any previous filesystems
2022-11-30 13:41:21 +01:00
fi
2023-01-29 15:55:56 +01:00
done
@{ native.kmod} /bin/modprobe zfs || true
2023-08-12 17:20:06 +02:00
{ <$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
2023-06-16 02:14:51 +02:00
prepend_trap " @{native.zfs}/bin/zpool export ' $poolName ' " EXIT || return
2023-01-29 15:55:56 +01:00
if [ [ $keySrc = = /dev/urandom ] ] ; then @{ native.zfs} /bin/zfs unload-key " $poolName " & >/dev/null ; fi
2022-11-30 13:41:21 +01:00
ensure-datasets $mnt '^' " $poolName " '($|[/])' || return
2022-12-27 15:37:27 +01:00
if [ [ ${ args [debug] :- } ] ] ; then @{ native.zfs} /bin/zfs list -o name,canmount,mounted,mountpoint,keystatus,encryptionroot -r " $poolName " ; fi
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
}
2023-06-16 02:14:51 +02:00
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
2023-01-29 15:55:56 +01:00
local mnt = $1 ; while [ [ " $mnt " = = */ ] ] ; do mnt = ${ mnt : 0 : (-1) } ; done # (remove any tailing slashes)
local filterExp = ${ 2 :- '^' }
local zfs = @{ native.zfs} /bin/zfs
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2023-01-29 15:55:56 +01:00
local name ; while IFS = read -u3 -r -d $'\0' name ; do
2023-07-20 17:47:47 +02:00
if [ [ ! $name = ~ $filterExp ] ] ; then : " Skipping dataset » $name « since it does not match » $filterExp « " ; continue ; fi
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2023-06-16 02:14:51 +02:00
eval 'local -A dataset=' " @{config.setup.zfs.datasets[ $name ]} "
2023-01-29 15:55:56 +01:00
eval 'local -A props=' " ${ dataset [props] } "
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2023-01-29 15:55:56 +01:00
local explicitKeylocation = ${ props [keylocation] :- } cryptKey cryptRoot
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
get-zfs-crypt-props " ${ dataset [name] } " props cryptKey cryptRoot
2022-07-29 12:49:55 +02:00
if $zfs get -o value -H name " ${ dataset [name] } " & >/dev/null ; then # dataset exists: check its properties
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
if [ [ ${ props [mountpoint] :- } ] ] ; then # don't set the current mount point again (no-op), cuz that fails if the dataset is mounted
2023-08-14 18:59:47 +02:00
# (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 / }
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
if [ [ ${ props [mountpoint] } = = " ${ current :- / } " ] ] ; then unset props[ mountpoint] ; fi
fi
if [ [ ${ props [keyformat] :- } = = ephemeral ] ] ; then
2023-05-02 02:13:24 +02:00
cryptRoot = ; unset props[ keyformat] ; props[ keylocation] = file:///dev/null
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
fi
2022-06-04 20:54:09 +02:00
if [ [ $explicitKeylocation ] ] ; then props[ keylocation] = $explicitKeylocation ; fi
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
unset props[ encryption] ; unset props[ keyformat] # can't change these anyway
2023-05-02 02:13:24 +02:00
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
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
fi
2023-05-02 02:13:24 +02:00
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
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2022-06-04 20:54:09 +02:00
else ( # create dataset
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
if [ [ ${ props [keyformat] :- } = = ephemeral ] ] ; then
props[ encryption] = aes-256-gcm ; props[ keyformat] = hex ; props[ keylocation] = file:///dev/stdin ; explicitKeylocation = file:///dev/null
2023-01-29 15:55:56 +01:00
declare -a zfsCreate = ( ) ; for name in " ${ !props[@] } " ; do zfsCreate += ( -o " ${ name } = ${ props [ $name ] } " ) ; done
{ </dev/urandom tr -dc 0-9a-f || true ; } | head -c 64 | ( PATH = @{ native.zfs} /bin ; ${ _set_x :- : } ; zfs create " ${ zfsCreate [@] } " " ${ dataset [name] } " ) || exit
$zfs unload-key " ${ dataset [name] } " || exit
2022-06-04 20:54:09 +02:00
else
2022-07-29 12:49:55 +02:00
if [ [ $cryptRoot && $cryptRoot != ${ dataset [name] } && $( $zfs get -o value -H keystatus " $cryptRoot " ) != available ] ] ; then
2023-01-29 15:55:56 +01:00
$zfs load-key -L file://" $cryptKey " " $cryptRoot " || exit
trap " $zfs unload-key $cryptRoot || true " EXIT
2022-06-04 20:54:09 +02:00
fi
2023-01-29 15:55:56 +01:00
declare -a zfsCreate = ( ) ; for name in " ${ !props[@] } " ; do zfsCreate += ( -o " ${ name } = ${ props [ $name ] } " ) ; done
2023-05-02 02:13:24 +02:00
( PATH = @{ native.zfs} /bin ; ${ _set_x :- : } ; zfs create " ${ zfsCreate [@] } " " ${ dataset [name] } " ) || exit
2022-06-04 20:54:09 +02:00
fi
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
if [ [ ${ props [canmount] } != off ] ] ; then (
2023-07-20 17:47:47 +02:00
tmp = $( mktemp -d ) && mkdir $tmp /mnt && @{ native.util-linux} /bin/mount -t zfs -o zfsutil " ${ dataset [name] } " $tmp /mnt || exit
trap " @{native.util-linux}/bin/umount ' ${ dataset [name] } ' ; rmdir $tmp {/mnt,} " EXIT || exit
chmod 000 -- $tmp /mnt && ( cd $tmp ; chown " ${ dataset [uid] } : ${ dataset [gid] } " -- mnt && chmod " ${ dataset [mode] } " -- mnt ) || exit
2023-01-29 15:55:56 +01:00
) || exit ; fi
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
if [ [ $explicitKeylocation && $explicitKeylocation != " ${ props [keylocation] :- } " ] ] ; then
2023-01-29 15:55:56 +01:00
( PATH = @{ native.zfs} /bin ; ${ _set_x :- : } ; zfs set keylocation = " $explicitKeylocation " " ${ dataset [name] } " ) || exit
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
fi
2023-01-29 15:55:56 +01:00
$zfs snapshot -r " ${ dataset [name] } " @empty || exit
) || return ; fi
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2023-01-29 15:55:56 +01:00
eval 'local -A allows=' " ${ dataset [permissions] } "
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
for who in " ${ !allows[@] } " ; do
# »zfs allow $dataset« seems to be the only way to view permissions, and that is not very parsable -.-
2023-01-29 15:55:56 +01:00
( PATH = @{ native.zfs} /bin ; ${ _set_x :- : } ; zfs allow -$who " ${ allows [ $who ] } " " ${ dataset [name] } " >& 2 ) || return
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
done
2023-06-16 02:14:51 +02:00
done 3< <( printf '%s\0' "@{!config.setup.zfs.datasets[@]}" | LC_ALL = C sort -z )
2024-02-01 13:30:57 +01:00
# zfs list -o name,referenced,encryption,encryptionroot,keylocation,keystatus,mounted,mountpoint
2023-05-02 02:13:24 +02:00
}
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2023-06-16 02:14:51 +02:00
## Given the name (»datasetPath«) of a ZFS dataset, this deducts crypto-related options from the declared keys (»config.setup.keystore.keys."zfs/..."«).
2022-11-30 13:41:21 +01:00
function get-zfs-crypt-props { # 1: datasetPath, 2?: name_cryptProps, 3?: name_cryptKey, 4?: name_cryptRoot
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
local hash = @{ config.networking.hostName!hashString.sha256:0:8}
local keystore = /run/keystore-$hash
local -n __cryptProps = ${ 2 :- props } ; local -n __cryptKey = ${ 3 :- cryptKey } ; local -n __cryptRoot = ${ 4 :- cryptRoot }
local name = $1 ; {
if [ [ $name = = */* ] ] ; then local pool = ${ name / \/ */ } / ; local path = /${ name / $pool / } ; else local pool = $name / ; local path = ; fi
} ; local key = ${ pool /- $hash '/' / } $path # strip hash from pool name
__cryptKey = '' ; __cryptRoot = ''
2023-06-16 02:14:51 +02:00
if [ [ @{ config.setup.keystore.keys[ zfs/$name ] :-} ] ] ; then
if [ [ @{ config.setup.keystore.keys[ zfs/$name ] } = = unencrypted ] ] ; then
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
__cryptProps[ encryption] = off # empty key to disable encryption
else
__cryptProps[ encryption] = aes-256-gcm ; __cryptProps[ keyformat] = hex ; __cryptProps[ keylocation] = file://" $keystore " /zfs/" $name " .key
__cryptKey = $keystore /zfs/$name .key ; __cryptRoot = $name
fi
else
while true ; do
name = $( dirname $name ) ; if [ [ $name = = . ] ] ; then break ; fi
2023-06-16 02:14:51 +02:00
if [ [ @{ config.setup.keystore.keys[ zfs/$name ] :-} ] ] ; then
if [ [ @{ config.setup.keystore.keys[ zfs/$name ] } != unencrypted ] ] ; then
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
__cryptKey = $keystore /zfs/$name .key ; __cryptRoot = $name
fi ; break
fi
done
fi
}