mirror of
https://github.com/NiklasGollenstede/nixos-installer.git
synced 2025-08-09 07:31:24 +02:00
lots of fixes and tweaks,
generate partition tables in nix, add open-system maintenance function
This commit is contained in:
@ -20,7 +20,7 @@ function add-key-hostname {( set -eu # 1: usage
|
||||
## Adds a key by copying it from a bootkey partition (see »add-bootkey-to-keydev«) to the keystore.
|
||||
function add-key-usb-part {( set -eu # 1: usage
|
||||
keystore=/run/keystore-@{config.networking.hostName!hashString.sha256:0:8} ; usage=$1
|
||||
if [[ ! "$usage" =~ ^(luks/keystore/.*)$ ]] ; then printf '»usb-part« key mode is only available for the keystore itself.\n' ; exit 1 ; fi
|
||||
if [[ ! "$usage" =~ ^(luks/keystore-[^/]+/[1-8])$ ]] ; then printf '»usb-part« key mode is only available for the keystore itself.\n' ; exit 1 ; fi
|
||||
bootkeyPartlabel=bootkey-"@{config.networking.hostName!hashString.sha256:0:8}"
|
||||
cat /dev/disk/by-partlabel/"$bootkeyPartlabel" | write-secret "$keystore"/"$usage".key
|
||||
)}
|
||||
|
@ -33,7 +33,8 @@ function do-disk-setup { # 1: diskPaths
|
||||
# * So alignment at the default »align=8MiB« actually seems a decent choice.
|
||||
|
||||
|
||||
## Partitions all »config.wip.fs.disks.devices« to ensure that all (correctly) specified »config.wip.fs.disks.partitions« exist.
|
||||
## Partitions the »diskPaths« instances of all »config.wip.fs.disks.devices« to ensure that all specified »config.wip.fs.disks.partitions« exist.
|
||||
# Parses »diskPaths«, creates and loop-mounts images for non-/dev/ paths, and tries to abort if any partition already exists on the host.
|
||||
function partition-disks { { # 1: diskPaths
|
||||
beQuiet=/dev/null ; if [[ ${args[debug]:-} ]] ; then beQuiet=/dev/stdout ; fi
|
||||
declare -g -A blockDevs=( ) # this ends up in the caller's scope
|
||||
@ -45,11 +46,11 @@ function partition-disks { { # 1: diskPaths
|
||||
|
||||
local name ; for name in "@{!config.wip.fs.disks.devices[@]}" ; do
|
||||
if [[ ! ${blockDevs[$name]:-} ]] ; then echo "Path for block device $name not provided" ; exit 1 ; fi
|
||||
if [[ ! ${blockDevs[$name]} =~ ^(/dev/.*)$ ]] ; then
|
||||
if [[ ${blockDevs[$name]} != /dev/* ]] ; then
|
||||
local outFile=${blockDevs[$name]} ; ( set -eu
|
||||
eval 'declare -A disk='"@{config.wip.fs.disks.devices[$name]}"
|
||||
install -o root -g root -m 640 -T /dev/null "$outFile" && fallocate -l "${disk[size]}" "$outFile"
|
||||
) && blockDevs[$name]=$(losetup --show -f "$outFile") && prepend_trap "losetup -d ${blockDevs[$name]}" EXIT # NOTE: this must not be inside a sub-shell!
|
||||
install -o root -g root -m 640 -T /dev/null "$outFile" && truncate -s "${disk[size]}" "$outFile"
|
||||
) && blockDevs[$name]=$(losetup --show -f "$outFile") && prepend_trap "losetup -d '${blockDevs[$name]}'" EXIT # NOTE: this must not be inside a sub-shell!
|
||||
else
|
||||
if [[ ! "$(blockdev --getsize64 "${blockDevs[$name]}")" ]] ; then echo "Block device $name does not exist at ${blockDevs[$name]}" ; exit 1 ; fi
|
||||
blockDevs[$name]=$(realpath "${blockDevs[$name]}")
|
||||
@ -60,55 +61,17 @@ function partition-disks { { # 1: diskPaths
|
||||
|
||||
for partDecl in "@{config.wip.fs.disks.partitionList[@]}" ; do
|
||||
eval 'declare -A part='"$partDecl"
|
||||
if [[ -e /dev/disk/by-partlabel/"${part[name]}" ]] && ! is-partition-on-disks /dev/disk/by-partlabel/"${part[name]}" "${blockDevs[@]}" ; then echo "Partition /dev/disk/by-partlabel/${part[name]} exists but does not reside on one of the target disks ${blockDevs[@]}" ; exit 1 ; fi
|
||||
if [[ -e /dev/disk/by-partlabel/"${part[name]}" ]] && ! is-partition-on-disks /dev/disk/by-partlabel/"${part[name]}" "${blockDevs[@]}" ; then echo "Partition /dev/disk/by-partlabel/${part[name]} already exists on this host and does not reside on one of the target disks ${blockDevs[@]}. Refusing to create another partition with the same partlabel!" ; exit 1 ; fi
|
||||
done
|
||||
|
||||
for name in "@{!config.wip.fs.disks.devices[@]}" ; do (
|
||||
for name in "@{!config.wip.fs.disks.devices[@]}" ; do
|
||||
eval 'declare -A disk='"@{config.wip.fs.disks.devices[$name]}"
|
||||
if [[ ${disk[serial]:-} ]] ; then
|
||||
actual=$(udevadm info --query=property --name="${blockDevs[${disk[name]}]}" | @{native.gnugrep}/bin/grep -oP 'ID_SERIAL_SHORT=\K.*')
|
||||
if [[ ${disk[serial]} != "$actual" ]] ; then echo "Block device ${blockDevs[${disk[name]}]} does not match the serial declared for ${disk[name]}" ; exit 1 ; fi
|
||||
actual=$(udevadm info --query=property --name="$blockDev" | grep -oP 'ID_SERIAL_SHORT=\K.*')
|
||||
if [[ ${disk[serial]} != "$actual" ]] ; then echo "Block device $blockDev does not match the serial declared for ${disk[name]}" ; exit 1 ; fi
|
||||
fi
|
||||
|
||||
declare -a sgdisk=( --zap-all ) # delete existing part tables
|
||||
for partDecl in "@{config.wip.fs.disks.partitionList[@]}" ; do
|
||||
eval 'declare -A part='"$partDecl"
|
||||
if [[ ${part[disk]} != "${disk[name]}" ]] ; then continue ; fi
|
||||
if [[ ${part[size]:-} =~ ^[0-9]+%$ ]] ; then
|
||||
devSize=$(blockdev --getsize64 "${blockDevs[${disk[name]}]}")
|
||||
part[size]=$(( $devSize / 1024 * ${part[size]:0:(-1)} / 100 ))K
|
||||
fi
|
||||
sgdisk+=( -a "${part[alignment]:-${disk[alignment]}}" -n "${part[index]:-0}":"${part[position]}":+"${part[size]:-}" -t 0:"${part[type]}" -c 0:"${part[name]}" )
|
||||
done
|
||||
|
||||
if [[ ${disk[mbrParts]:-} ]] ; then
|
||||
sgdisk+=( --hybrid "${disk[mbrParts]}" ) # --hybrid: create MBR in addition to GPT; ${disk[mbrParts]}: make these GPT part 1 MBR parts 2[3[4]]
|
||||
fi
|
||||
|
||||
( PATH=@{native.gptfdisk}/bin ; set -x ; sgdisk "${sgdisk[@]}" "${blockDevs[${disk[name]}]}" >$beQuiet ) # running all at once is much faster
|
||||
|
||||
if [[ ${disk[mbrParts]:-} ]] ; then
|
||||
printf "
|
||||
M # edit hybrid MBR
|
||||
d;1 # delete parts 1 (GPT)
|
||||
|
||||
# move the selected »mbrParts« to slots 1[2[3]] instead of 2[3[4]] (by re-creating part1 in the last sector, then sorting)
|
||||
n;p;1 # new ; primary ; part1
|
||||
$(( $(blockSectorCount "${blockDevs[${disk[name]}]}") - 1)) # start (size 1sec)
|
||||
x;f;r # expert mode ; fix order ; return
|
||||
d;$(( (${#disk[mbrParts]} + 1) / 2 + 1 )) # delete ; part(last)
|
||||
|
||||
# create GPT part (spanning primary GPT area) as last part
|
||||
n;p;4 # new ; primary ; part4
|
||||
1;33 # start ; end
|
||||
t;4;ee # type ; part4 ; GPT
|
||||
|
||||
${disk[extraFDiskCommands]}
|
||||
p;w;q # print ; write ; quit
|
||||
" | @{native.gnused}/bin/sed -E 's/^ *| *(#.*)?$//g' | @{native.gnused}/bin/sed -E 's/\n\n+| *; */\n/g' | tee >((echo -n '++ ' ; tr $'\n' '|' ; echo) 1>&2) | ( set -x ; fdisk "${blockDevs[${disk[name]}]}" &>$beQuiet )
|
||||
fi
|
||||
|
||||
) ; done
|
||||
partition-disk "${disk[name]}" "${blockDevs[${disk[name]}]}"
|
||||
done
|
||||
@{native.parted}/bin/partprobe "${blockDevs[@]}"
|
||||
@{native.systemd}/bin/udevadm settle -t 15 || true # sometimes partitions aren't quite made available yet
|
||||
|
||||
@ -116,6 +79,50 @@ function partition-disks { { # 1: diskPaths
|
||||
wipefs --all "@{config.wip.fs.disks.partitions!attrNames[@]/#/'/dev/disk/by-partlabel/'}" >$beQuiet
|
||||
)}
|
||||
|
||||
## Given a declared disk device's »name« and a path to an actual »blockDev« (or image) file, partitions the device as declared in the config.
|
||||
function partition-disk {( set -eu # 1: name, 2: blockDev, 3?: devSize
|
||||
name=$1 ; blockDev=$2
|
||||
eval 'declare -A disk='"@{config.wip.fs.disks.devices[$name]}"
|
||||
devSize=${3:-$(@{native.util-linux}/bin/blockdev --getsize64 "$blockDev")}
|
||||
|
||||
declare -a sgdisk=( --zap-all ) # delete existing part tables
|
||||
for partDecl in "@{config.wip.fs.disks.partitionList[@]}" ; do
|
||||
eval 'declare -A part='"$partDecl"
|
||||
if [[ ${part[disk]} != "${disk[name]}" ]] ; then continue ; fi
|
||||
if [[ ${part[size]:-} =~ ^[0-9]+%$ ]] ; then
|
||||
part[size]=$(( $devSize / 1024 * ${part[size]:0:(-1)} / 100 ))K
|
||||
fi
|
||||
sgdisk+=( -a "${part[alignment]:-${disk[alignment]}}" -n "${part[index]:-0}":"${part[position]}":+"${part[size]:-}" -t 0:"${part[type]}" -c 0:"${part[name]}" )
|
||||
done
|
||||
|
||||
if [[ ${disk[mbrParts]:-} ]] ; then
|
||||
sgdisk+=( --hybrid "${disk[mbrParts]}" ) # --hybrid: create MBR in addition to GPT; ${disk[mbrParts]}: make these GPT part 1 MBR parts 2[3[4]]
|
||||
fi
|
||||
|
||||
( PATH=@{native.gptfdisk}/bin ; set -x ; sgdisk "${sgdisk[@]}" "$blockDev" >$beQuiet ) # running all at once is much faster
|
||||
|
||||
if [[ ${disk[mbrParts]:-} ]] ; then
|
||||
printf "
|
||||
M # edit hybrid MBR
|
||||
d;1 # delete parts 1 (GPT)
|
||||
|
||||
# move the selected »mbrParts« to slots 1[2[3]] instead of 2[3[4]] (by re-creating part1 in the last sector, then sorting)
|
||||
n;p;1 # new ; primary ; part1
|
||||
$(( ($devSize/512) - 1)) # start (size 1sec)
|
||||
x;f;r # expert mode ; fix order ; return
|
||||
d;$(( (${#disk[mbrParts]} + 1) / 2 + 1 )) # delete ; part(last)
|
||||
|
||||
# create GPT part (spanning primary GPT area) as last part
|
||||
n;p;4 # new ; primary ; part4
|
||||
1;33 # start ; end
|
||||
t;4;ee # type ; part4 ; GPT
|
||||
|
||||
${disk[extraFDiskCommands]}
|
||||
p;w;q # print ; write ; quit
|
||||
" | @{native.gnused}/bin/sed -E 's/^ *| *(#.*)?$//g' | @{native.gnused}/bin/sed -E 's/\n\n+| *; */\n/g' | tee >((echo -n '++ ' ; tr $'\n' '|' ; echo) 1>&2) | ( PATH=@{native.util-linux}/bin ; set -x ; fdisk "$blockDev" &>$beQuiet )
|
||||
fi
|
||||
)}
|
||||
|
||||
## Checks whether a »partition« resides on one of the provided »blockDevs«.
|
||||
function is-partition-on-disks {( set -eu # 1: partition, ...: blockDevs
|
||||
partition=$1 ; shift ; declare -a blockDevs=( "$@" )
|
||||
@ -152,31 +159,35 @@ function format-partitions {( set -eu
|
||||
|
||||
## Mounts all file systems as it would happen during boot, but at path prefix »$mnt« (instead of »/«).
|
||||
function mount-system {( set -eu # 1: mnt, 2?: fstabPath
|
||||
# TODO: »config.system.build.fileSystems« is a dependency-sorted list. Could use that ...
|
||||
# mount --all --fstab @{config.system.build.toplevel}/etc/fstab --target-prefix "$1" -o X-mount.mkdir # (»--target-prefix« is not supported on Ubuntu 20.04)
|
||||
mnt=$1 ; fstabPath=${2:-"@{config.system.build.toplevel}/etc/fstab"}
|
||||
PATH=@{native.e2fsprogs}/bin:@{native.f2fs-tools}/bin:@{native.xfsprogs}/bin:@{native.dosfstools}/bin:$PATH
|
||||
<$fstabPath @{native.gnugrep}/bin/grep -v '^#' | LC_ALL=C sort -k2 | while read source target type options numbers ; do
|
||||
<$fstabPath grep -v '^#' | LC_ALL=C sort -k2 | while read source target type options numbers ; do
|
||||
if [[ ! $target || $target == none ]] ; then continue ; fi
|
||||
options=,$options, ; options=${options//,ro,/,}
|
||||
if [[ $options =~ ,r?bind, ]] || [[ $type == overlay ]] ; then continue ; fi
|
||||
if ! mountpoint -q "$mnt"/"$target" ; then (
|
||||
mkdir -p "$mnt"/"$target"
|
||||
[[ $type == tmpfs ]] || @{native.kmod}/bin/modprobe --quiet $type || true # (this does help sometimes)
|
||||
[[ $type == tmpfs || $type == */* ]] || @{native.kmod}/bin/modprobe --quiet $type || true # (this does help sometimes)
|
||||
mount -t $type -o "${options:1:(-1)}" "$source" "$mnt"/"$target"
|
||||
) || [[ $options == *,nofail,* ]] ; fi # (actually, nofail already makes mount fail silently)
|
||||
done
|
||||
# Since bind mounts may depend on other mounts not only for the target (which the sort takes care of) but also for the source, do all bind mounts last. This would break if there was a different bind mountpoint within a bind-mounted target.
|
||||
<$fstabPath @{native.gnugrep}/bin/grep -v '^#' | LC_ALL=C sort -k2 | while read source target type options numbers ; do
|
||||
<$fstabPath grep -v '^#' | LC_ALL=C sort -k2 | while read source target type options numbers ; do
|
||||
if [[ ! $target || $target == none ]] ; then continue ; fi
|
||||
options=,$options, ; options=${options//,ro,/,}
|
||||
if [[ $options =~ ,r?bind, ]] || [[ $type == overlay ]] ; then : ; else continue ; fi
|
||||
if ! mountpoint -q "$mnt"/"$target" ; then (
|
||||
mkdir -p "$mnt"/"$target"
|
||||
if [[ $type == overlay ]] ; then
|
||||
options=${options//,workdir=/,workdir=$mnt\/} ; options=${options//,upperdir=/,upperdir=$mnt\/} # work and upper dirs must be in target, lower dirs are probably store paths
|
||||
workdir=$(<<<"$options" @{native.gnugrep}/bin/grep -o -P ',workdir=\K[^,]+' || true) ; if [[ $workdir ]] ; then mkdir -p "$workdir" ; fi
|
||||
upperdir=$(<<<"$options" @{native.gnugrep}/bin/grep -o -P ',upperdir=\K[^,]+' || true) ; if [[ $upperdir ]] ; then mkdir -p "$upperdir" ; fi
|
||||
options=${options//,workdir=/,workdir=$mnt\/} ; options=${options//,upperdir=/,upperdir=$mnt\/} # Work and upper dirs must be in target.
|
||||
workdir=$(<<<"$options" grep -o -P ',workdir=\K[^,]+' || true) ; if [[ $workdir ]] ; then mkdir -p "$workdir" ; fi
|
||||
upperdir=$(<<<"$options" grep -o -P ',upperdir=\K[^,]+' || true) ; if [[ $upperdir ]] ; then mkdir -p "$upperdir" ; fi
|
||||
lowerdir=$(<<<"$options" grep -o -P ',lowerdir=\K[^,]+' || true) # TODO: test the lowerdir stuff
|
||||
options=${options//,lowerdir=$lowerdir,/,lowerdir=$mnt/${lowerdir//:/:$mnt\/},} ; source=overlay
|
||||
else
|
||||
if [[ $source == /nix/store/* ]] ; then options=,ro$options ; fi
|
||||
source=$mnt/$source ; if [[ ! -e $source ]] ; then mkdir -p "$source" ; fi
|
||||
fi
|
||||
mount -t $type -o "${options:1:(-1)}" "$source" "$mnt"/"$target"
|
||||
@ -187,13 +198,10 @@ function mount-system {( set -eu # 1: mnt, 2?: fstabPath
|
||||
## Unmounts all file systems (that would be mounted during boot / by »mount-system«).
|
||||
function unmount-system {( set -eu # 1: mnt, 2?: fstabPath
|
||||
mnt=$1 ; fstabPath=${2:-"@{config.system.build.toplevel}/etc/fstab"}
|
||||
<$fstabPath @{native.gnugrep}/bin/grep -v '^#' | LC_ALL=C sort -k2 -r | while read source target rest ; do
|
||||
<$fstabPath grep -v '^#' | LC_ALL=C sort -k2 -r | while read source target rest ; do
|
||||
if [[ ! $target || $target == none ]] ; then continue ; fi
|
||||
if mountpoint -q "$mnt"/"$target" ; then
|
||||
umount "$mnt"/"$target"
|
||||
fi
|
||||
done
|
||||
)}
|
||||
|
||||
## Given a block device path, returns the number of 512byte sectors it can hold.
|
||||
function blockSectorCount { printf %s "$(( $(blockdev --getsize64 "$1") / 512 ))" ; }
|
||||
|
@ -33,9 +33,9 @@ function prepare-installer { # ...
|
||||
if zfs get -o value -H name "$poolName" &>/dev/null ; then echo "ZFS pool »$poolName« is already imported. Export the pool before running the installer." ; exit 1 ; fi
|
||||
done
|
||||
|
||||
if [[ ${SUDO_USER:-} ]] ; then function nix {( set +x ; declare -a args=("$@") ; PATH=/bin:/usr/bin su - "$SUDO_USER" -c "$(declare -p args)"' ; nix "${args[@]}"' )} ; fi
|
||||
if [[ ${SUDO_USER:-} ]] ; then function nix {( set +x ; declare -a args=("$@") ; PATH=$hostPath su - "$SUDO_USER" -c "$(declare -p args)"' ; nix "${args[@]}"' )} ; fi
|
||||
|
||||
if [[ ${args[debug]:-} ]] ; then set +e ; set -E ; trap 'code= ; @{native.bashInteractive}/bin/bash --init-file @{config.environment.etc.bashrc.source} || code=$? ; if [[ $code ]] ; then exit $code ; fi' ERR ; fi # On error, instead of exiting straight away, open a shell to allow diagnosing/fixing the issue. Only exit if that shell reports failure (e.g. CtrlC + CtrlD). Unfortunately, the exiting has to be repeated for level of each nested sub-shells.
|
||||
if [[ ${args[debug]:-} ]] ; then set +e ; set -E ; trap 'code= ; cat 2>/dev/null || true ; @{native.bashInteractive}/bin/bash --init-file @{config.environment.etc.bashrc.source} || code=$? ; if [[ $code ]] ; then exit $code ; fi' ERR ; fi # On error, instead of exiting straight away, open a shell to allow diagnosing/fixing the issue. Only exit if that shell reports failure (e.g. CtrlC + CtrlD). Unfortunately, the exiting has to be repeated for level of each nested sub-shells. The cat eats anything lined up on stdin, which would otherwise be run in the shell (TODO: but it blocks if there is nothing on stdin, requiring Ctrl+D to be pressed).
|
||||
|
||||
}
|
||||
|
||||
@ -44,11 +44,11 @@ function prepare-installer { # ...
|
||||
# The restore commands are expected to pull in a backup of the systems secrets and state from somewhere, and need to acknowledge that something happened by running »restore-supported-callback«.
|
||||
function init-or-restore-system {( set -eu # (void)
|
||||
if [[ ! ${args[restore]:-} ]] ; then
|
||||
run-hook-script 'System Initialization' @{config.wip.fs.disks.initSystemCommands!writeText.postPartitionCommands} # TODO: Do this later inside the chroot?
|
||||
run-hook-script 'System Initialization' @{config.wip.fs.disks.initSystemCommands!writeText.initSystemCommands} # TODO: Do this later inside the chroot?
|
||||
return # usually, this would be it ...
|
||||
fi
|
||||
requiresRestoration=$(mktemp) ; trap "rm -f '$requiresRestoration'" EXIT ; function restore-supported-callback {( rm -f "$requiresRestoration" )}
|
||||
run-hook-script 'System Restoration' @{config.wip.fs.disks.restoreSystemCommands!writeText.postPartitionCommands}
|
||||
run-hook-script 'System Restoration' @{config.wip.fs.disks.restoreSystemCommands!writeText.restoreSystemCommands}
|
||||
if [[ -e $requiresRestoration ]] ; then echo 'The »restoreSystemCommands« did not call »restore-supported-callback« to mark backup restoration as supported for this system. Assuming incomplete configuration.' 1>&2 ; exit 1 ; fi
|
||||
)}
|
||||
|
||||
|
@ -7,7 +7,7 @@ function prompt-for-user-passwords { # (void)
|
||||
userPasswords[$user]=@{config.users.users!catAttrSets.password[$user]}
|
||||
done
|
||||
for user in "@{!config.users.users!catAttrSets.passwordFile[@]}" ; do
|
||||
if ! userPasswords[$user]=$(prompt-new-password "for the user account »$user«") ; then exit 1 ; fi
|
||||
if ! userPasswords[$user]=$(prompt-new-password "for the user account »$user«") ; then return 1 ; fi
|
||||
done
|
||||
}
|
||||
|
||||
@ -35,15 +35,15 @@ function populate-keystore { { # (void)
|
||||
done
|
||||
for usage in "${!methods[@]}" ; do
|
||||
if [[ "${methods[$usage]}" == home-pw || "${methods[$usage]}" == copy ]] ; then continue ; fi
|
||||
add-key-"${methods[$usage]}" "$usage" "${options[$usage]}"
|
||||
add-key-"${methods[$usage]}" "$usage" "${options[$usage]}" || return 1
|
||||
done
|
||||
for usage in "${!methods[@]}" ; do
|
||||
if [[ "${methods[$usage]}" != home-pw ]] ; then continue ; fi
|
||||
add-key-"${methods[$usage]}" "$usage" "${options[$usage]}"
|
||||
add-key-"${methods[$usage]}" "$usage" "${options[$usage]}" || return 1
|
||||
done
|
||||
for usage in "${!methods[@]}" ; do
|
||||
if [[ "${methods[$usage]}" != copy ]] ; then continue ; fi
|
||||
add-key-"${methods[$usage]}" "$usage" "${options[$usage]}"
|
||||
add-key-"${methods[$usage]}" "$usage" "${options[$usage]}" || return 1
|
||||
done
|
||||
)}
|
||||
|
||||
@ -70,6 +70,7 @@ function create-luks-layers {( set -eu # (void)
|
||||
function open-luks-layers { # (void)
|
||||
keystore=/run/keystore-@{config.networking.hostName!hashString.sha256:0:8}
|
||||
for luksName in "@{!config.boot.initrd.luks.devices!catAttrSets.device[@]}" ; do
|
||||
if [[ -e /dev/mapper/$luksName ]] ; then continue ; fi
|
||||
rawDev=@{config.boot.initrd.luks.devices!catAttrSets.device[$luksName]}
|
||||
primaryKey="$keystore"/luks/"$luksName"/0.key
|
||||
|
||||
|
@ -106,3 +106,62 @@ function add-bootkey-to-keydev {( set -eu # 1: blockDev, 2?: hostHash
|
||||
@{native.parted}/bin/partprobe "$blockDev" ; @{native.systemd}/bin/udevadm settle -t 15 # wait for partitions to update
|
||||
</dev/urandom tr -dc 0-9a-f | head -c 512 >/dev/disk/by-partlabel/"$bootkeyPartlabel"
|
||||
)}
|
||||
|
||||
|
||||
## Tries to open and mount the systems keystore from its LUKS partition. If successful, adds the traps to close it when the parent shell exits.
|
||||
# See »open-system«'s implementation for some example calls to this function.
|
||||
function mount-keystore-luks { # ...: cryptsetupOptions
|
||||
# (For the traps to work, this can't run in a sub shell. The function therefore can't use »( set -eu ; ... )« internally and instead has to use »&&« after every command and in place of most »;«, and the function can't be called from a pipeline.)
|
||||
keystore=keystore-@{config.networking.hostName!hashString.sha256:0:8} &&
|
||||
mkdir -p -- /run/$keystore &&
|
||||
@{native.cryptsetup}/bin/cryptsetup open "$@" /dev/disk/by-partlabel/$keystore $keystore &&
|
||||
mount -o nodev,umask=0077,fmask=0077,dmask=0077,ro /dev/mapper/$keystore /run/$keystore &&
|
||||
prepend_trap "umount /run/$keystore ; @{native.cryptsetup}/bin/cryptsetup close $keystore ; rmdir /run/$keystore" EXIT
|
||||
}
|
||||
|
||||
## Performs any steps necessary to mount the target system at »/tmp/nixos-install-@{config.networking.hostName}« on the current host.
|
||||
# For any steps taken, it also adds the reaps to undo them on exit from the calling shell, and it always adds the exit trap to do the unmounting itself.
|
||||
# »diskImages« may be passed in the same format as to the installer. If so, any image files are ensured to be loop-mounted.
|
||||
# Perfect to inspect/update/amend/repair a system's installation afterwards, e.g.:
|
||||
# $ source ${config_wip_fs_disks_initSystemCommands1writeText_initSystemCommands}
|
||||
# $ source ${config_wip_fs_disks_restoreSystemCommands1writeText_restoreSystemCommands}
|
||||
# $ install-system-to /tmp/nixos-install-${config_networking_hostName}
|
||||
# $ nixos-enter --root /tmp/nixos-install-${config_networking_hostName}
|
||||
function open-system { # 1?: diskImages
|
||||
# (for the traps to work, this can't run in a sub shell, so also can't »set -eu«, so use »&&« after every command and in place of most »;«)
|
||||
|
||||
local diskImages=${1:-} # If »diskImages« were specified and they point at files that aren't loop-mounted yet, then loop-mount them now:
|
||||
local images=$( losetup --list --all --raw --noheadings --output BACK-FILE )
|
||||
local decl && for decl in ${diskImages//:/ } ; do
|
||||
local image=${decl/*=/} && if [[ $image != /dev/* ]] && ! <<<$images grep -xF $image ; then
|
||||
local blockDev=$( losetup --show -f "$image" ) && prepend_trap "losetup -d '$blockDev'" EXIT &&
|
||||
@{native.parted}/bin/partprobe "$blockDev" &&
|
||||
:;fi &&
|
||||
:;done &&
|
||||
( @{native.systemd}/bin/udevadm settle -t 15 || true ) && # sometimes partitions aren't quite made available yet
|
||||
|
||||
if [[ @{config.wip.fs.keystore.enable} && ! -e /dev/mapper/keystore-@{config.networking.hostName!hashString.sha256:0:8} ]] ; then # Try a bunch of approaches for opening the keystore:
|
||||
mount-keystore-luks --key-file=<(printf %s "@{config.networking.hostName}") ||
|
||||
mount-keystore-luks --key-file=/dev/disk/by-partlabel/bootkey-@{config.networking.hostName!hashString.sha256:0:8} ||
|
||||
mount-keystore-luks --key-file=<( read -s -p PIN: pin && echo ' touch!' >&2 && ykchalresp -2 "$pin" ) ||
|
||||
# TODO: try static yubikey challenge
|
||||
mount-keystore-luks
|
||||
fi &&
|
||||
|
||||
local mnt=/tmp/nixos-install-@{config.networking.hostName} && if [[ ! -e $mnt ]] ; then mkdir -p "$mnt" && prepend_trap "rmdir '$mnt'" EXIT ; fi &&
|
||||
|
||||
open-luks-layers && # Load crypt layers and zfs pools:
|
||||
if [[ $( LC_ALL=C type -t ensure-datasets ) == 'function' ]] ; then
|
||||
local poolName && for poolName in "@{!config.wip.fs.zfs.pools[@]}" ; do
|
||||
if ! zfs get -o value -H name "$poolName" &>/dev/null ; then
|
||||
zpool import -f -N -R "$mnt" "$poolName" && prepend_trap "zpool export '$poolName'" EXIT &&
|
||||
:;fi &&
|
||||
: | zfs load-key -r "$poolName" || true &&
|
||||
:;done &&
|
||||
ensure-datasets "$mnt" &&
|
||||
:;fi &&
|
||||
|
||||
prepend_trap "unmount-system '$mnt'" EXIT && mount-system "$mnt" &&
|
||||
|
||||
true # (success)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ function generic-arg-parse { # ...
|
||||
|
||||
## Prepends a command to a trap. Especially useful fo define »finally« commands via »prepend_trap '<command>' EXIT«.
|
||||
# NOTE: When calling this in a sub-shell whose parents already has traps installed, make sure to do »trap - trapName« first. On a new shell, this should be a no-op, but without it, the parent shell's traps will be added to the sub-shell as well (due to strange behavior of »trap -p« (in bash ~5.1.8)).
|
||||
prepend_trap() { # 1: command, ...: trapNames
|
||||
function prepend_trap { # 1: command, ...: trapNames
|
||||
fatal() { printf "ERROR: $@\n" >&2 ; return 1 ; }
|
||||
local cmd=$1 ; shift || fatal "${FUNCNAME} usage error"
|
||||
local name ; for name in "$@" ; do
|
||||
@ -30,7 +30,8 @@ prepend_trap() { # 1: command, ...: trapNames
|
||||
p3() { printf '%s\n' "${3:-}" ; } ; eval "p3 $(trap -p "${name}")"
|
||||
)" "${name}" || fatal "unable to add to trap ${name}"
|
||||
done
|
||||
} ; declare -f -t prepend_trap # required to modify DEBUG or RETURN traps
|
||||
}
|
||||
declare -f -t prepend_trap # required to modify DEBUG or RETURN traps
|
||||
|
||||
|
||||
## Writes a »$name«d secret from stdin to »$targetDir«, ensuring proper file permissions.
|
||||
|
@ -31,8 +31,8 @@ function create-zpools { # 1: 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.
|
||||
# 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 be loaded.
|
||||
# »keystatus« and »mounted« of existing datasets should remain unchanged, 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)
|
||||
@ -58,6 +58,7 @@ function ensure-datasets {( set -eu # 1: mnt, 2?: filterExp
|
||||
if [[ ${props[keyformat]:-} == ephemeral ]] ; then
|
||||
cryptRoot=${dataset[name]} ; 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
|
||||
names=$(IFS=, ; echo "${!props[*]}") ; values=$(IFS=$'\n' ; echo "${props[*]}")
|
||||
if [[ $values != "$(zfs get -o value -H "$names" "${dataset[name]}")" ]] ; then (
|
||||
@ -66,27 +67,28 @@ function ensure-datasets {( set -eu # 1: mnt, 2?: filterExp
|
||||
) ; 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
|
||||
if [[ $(zfs get -o value -H keystatus "$cryptRoot") != available ]] ; then
|
||||
zfs load-key -L file://"$cryptKey" "$cryptRoot" ; trap "zfs unload-key $cryptRoot || 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
|
||||
zfs load-key -L file://"$cryptKey" "${dataset[name]}" # will unload with cryptRoot
|
||||
fi
|
||||
( set -x ; zfs change-key -i "${dataset[name]}" )
|
||||
) ; fi
|
||||
|
||||
else # create dataset
|
||||
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
|
||||
</dev/urandom tr -dc 0-9a-f | head -c 64 | ( set -x ; zfs create "${args[@]}" "${dataset[name]}" )
|
||||
zfs unload-key "${dataset[name]}"
|
||||
else (
|
||||
# TODO: if [[ $cryptRoot && $(zfs get -o value -H keystatus "$cryptRoot") != available ]] ; then ... load key while in this block ...
|
||||
else
|
||||
if [[ $cryptRoot && $cryptRoot != ${dataset[name]} && $(zfs get -o value -H keystatus "$cryptRoot") != available ]] ; then
|
||||
zfs load-key -L file://"$cryptKey" "$cryptRoot" ; trap "zfs unload-key $cryptRoot || true" EXIT
|
||||
fi
|
||||
declare -a args=( ) ; for name in "${!props[@]}" ; do args+=( -o "${name}=${props[$name]}" ) ; done
|
||||
( set -x ; zfs create "${args[@]}" "${dataset[name]}" )
|
||||
) ; fi
|
||||
fi
|
||||
if [[ ${props[canmount]} != off ]] ; then (
|
||||
mount -t zfs -o zfsutil "${dataset[name]}" $tmpMnt ; trap "umount '${dataset[name]}'" EXIT
|
||||
chmod 000 "$tmpMnt" ; ( chown "${dataset[uid]}:${dataset[gid]}" -- "$tmpMnt" ; chmod "${dataset[mode]}" -- "$tmpMnt" )
|
||||
@ -95,7 +97,7 @@ function ensure-datasets {( set -eu # 1: mnt, 2?: filterExp
|
||||
( set -x ; zfs set keylocation="$explicitKeylocation" "${dataset[name]}" )
|
||||
fi
|
||||
zfs snapshot -r "${dataset[name]}"@empty
|
||||
fi
|
||||
) ; fi
|
||||
|
||||
eval 'declare -A allows='"${dataset[permissions]}"
|
||||
for who in "${!allows[@]}" ; do
|
||||
|
Reference in New Issue
Block a user