forked from extern/nixos-installer
155 lines
8.9 KiB
Bash
155 lines
8.9 KiB
Bash
|
|
##
|
|
# Disk Partitioning and Formatting
|
|
##
|
|
|
|
## Prepares the disks of the target system for the copying of files.
|
|
function do-disk-setup { # 1: diskPaths
|
|
|
|
mnt=/tmp/nixos-install-@{config.networking.hostName} ; mkdir -p "$mnt" ; prepend_trap "rmdir $mnt" EXIT # »mnt=/run/user/0/...« would be more appropriate, but »nixos-install« does not like the »700« permissions on »/run/user/0«
|
|
|
|
partition-disks "$1"
|
|
# ... block layers would go here ...
|
|
source @{config.wip.installer.postPartitionCommands!writeText.postPartitionCommands}
|
|
format-partitions
|
|
source @{config.wip.installer.postFormatCommands!writeText.postFormatCommands}
|
|
prepend_trap "unmount-system $mnt" EXIT ; mount-system $mnt
|
|
source @{config.wip.installer.postMountCommands!writeText.postMountCommands}
|
|
|
|
}
|
|
|
|
## Partitions all »config.installer.disks« to ensure that all (correctly) specified »{config.installer.partitions« exist.
|
|
function partition-disks { { # 1: diskPaths
|
|
beQuiet=/dev/null ; if [[ ${debug:=} ]] ; then beQuiet=/dev/stdout ; fi
|
|
declare -g -A blockDevs=( ) # this ends up in the caller's scope
|
|
local path ; for path in ${1/:/ } ; do
|
|
name=${path/=*/} ; if [[ $name != "$path" ]] ; then path=${path/$name=/} ; else name=primary ; fi
|
|
if [[ ${blockDevs[$name]:-} ]] ; then echo "Path for block device $name specified more than once. Duplicate definition: $path" ; exit 1 ; fi
|
|
blockDevs[$name]=$path
|
|
done
|
|
|
|
local name ; for name in "@{!config.wip.installer.disks!attrsAsBashEvalSets[@]}" ; do
|
|
if [[ ! ${blockDevs[$name]:-} ]] ; then echo "Path for block device $name not provided" ; exit 1 ; fi
|
|
if [[ ! ${blockDevs[$name]} =~ ^(/dev/.*)$ ]] ; then
|
|
local outFile=${blockDevs[$name]} ; ( set -eu
|
|
eval "@{config.wip.installer.disks!attrsAsBashEvalSets[$name]}" # _size
|
|
install -o root -g root -m 640 -T /dev/null "$outFile" && fallocate -l "$_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]}")
|
|
fi
|
|
done
|
|
|
|
} ; ( set -eu
|
|
|
|
for name in "@{!config.wip.installer.disks!attrsAsBashEvalSets[@]}" ; do (
|
|
eval "@{config.wip.installer.disks!attrsAsBashEvalSets[$name]}" # _name ; _size ; _serial ; _alignment ; _mbrParts ; _extraFDiskCommands
|
|
if [[ $_serial ]] ; then
|
|
actual=$(udevadm info --query=property --name="${blockDevs[$name]}" | grep -oP 'ID_SERIAL_SHORT=\K.*')
|
|
if [[ $_serial != "$actual" ]] ; then echo "Block device ${blockDevs[$name]} does not match the serial declared for $name" ; exit 1 ; fi
|
|
fi
|
|
|
|
sgdisk=( --zap-all ) # delete existing part tables
|
|
for partDecl in "@{config.wip.installer.partitionList!listAsBashEvalSets[@]}" ; do
|
|
eval "$partDecl" # _name ; _disk ; _type ; _size ; _index
|
|
if [[ $_disk != "$name" ]] ; then exit ; fi # i.e. continue
|
|
if [[ $_position =~ ^[0-9]+$ ]] ; then alignment=1 ; else alignment=$_alignment ; fi # if position is an absolute number, start precisely there
|
|
sgdisk+=( -a "$alignment" -n "${_index:-0}":"$_position":+"$_size" -t 0:"$_type" -c 0:"$_name" )
|
|
done
|
|
|
|
if [[ $_mbrParts ]] ; then
|
|
sgdisk+=( --hybrid "$_mbrParts" ) # --hybrid: create MBR in addition to GPT; $_mbrParts: make these GPT part 1 MBR parts 2[3[4]]
|
|
fi
|
|
|
|
sgdisk "${sgdisk[@]}" "${blockDevs[$name]}" >$beQuiet # running all at once is much faster
|
|
|
|
if [[ $_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[$name]}") - 1)) # start (size 1sec)
|
|
x;f;r # expert mode ; fix order ; return
|
|
d;$(( (${#_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
|
|
|
|
${_extraFDiskCommands}
|
|
p;w;q # print ; write ; quit
|
|
" | perl -pe 's/^ *| *(#.*)?$//g' | perl -pe 's/\n\n+| *; */\n/g' | fdisk "${blockDevs[$name]}" &>$beQuiet
|
|
fi
|
|
|
|
partprobe "${blockDevs[$name]}"
|
|
) ; done
|
|
sleep 1 # sometimes partitions aren't quite made available yet (TODO: wait "for udev to settle" instead?)
|
|
)}
|
|
|
|
## For each filesystem in »config.fileSystems« whose ».device« is in »/dev/disk/by-partlabel/«, this creates the specified file system on that partition.
|
|
function format-partitions {( set -eu
|
|
beQuiet=/dev/null ; if [[ ${debug:=} ]] ; then beQuiet=/dev/stdout ; fi
|
|
for fsDecl in "@{config.fileSystems!attrsAsBashEvalSets[@]}" ; do (
|
|
eval "$fsDecl" # _name ; _device ; _fsType ; _formatOptions ; ...
|
|
if [[ $_device != /dev/disk/by-partlabel/* ]] ; then exit ; fi # i.e. continue
|
|
blockDev=$(realpath "$_device") ; if [[ $blockDev == /dev/sd* ]] ; then
|
|
blockDev=$( shopt -s extglob ; echo "${blockDev%%+([0-9])}" )
|
|
else
|
|
blockDev=$( shopt -s extglob ; echo "${blockDev%%p+([0-9])}" )
|
|
fi
|
|
if [[ ' '"${blockDevs[@]}"' ' != *' '"$blockDev"' '* ]] ; then echo "Partition alias $_device does not point at one of the target disks ${blockDevs[@]}" ; exit 1 ; fi
|
|
mkfs.${_fsType} ${_formatOptions} "${_device}" >$beQuiet
|
|
partprobe "${_device}"
|
|
) ; done
|
|
)}
|
|
|
|
## Mounts all file systems as it would happen during boot, but at path prefix »$mnt«.
|
|
function mount-system {( set -eu # 1: mnt, 2?: fstabPath
|
|
# mount --all --fstab @{config.system.build.toplevel.outPath}/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.outPath}/etc/fstab"}
|
|
<$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"
|
|
mount -t $type -o "${options:1:(-1)}" "$source" "$mnt"/"$target"
|
|
fi
|
|
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 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" 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
|
|
else
|
|
source=$mnt/$source ; if [[ ! -e $source ]] ; then mkdir -p "$source" ; fi
|
|
fi
|
|
mount -t $type -o "${options:1:(-1)}" "$source" "$mnt"/"$target"
|
|
fi
|
|
done
|
|
)}
|
|
|
|
## 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.outPath}/etc/fstab"}
|
|
<$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 ))" ; }
|