Skip to content

Instantly share code, notes, and snippets.

@arrjay
Last active January 5, 2026 22:03
Show Gist options
  • Select an option

  • Save arrjay/72f871cba40dea787a08f1c657e98fb4 to your computer and use it in GitHub Desktop.

Select an option

Save arrjay/72f871cba40dea787a08f1c657e98fb4 to your computer and use it in GitHub Desktop.
latest fs-layout from fossil
#!/usr/bin/env bash
# Created by argbash-init v2.10.0
# ARG_OPTIONAL_SINGLE([targetpath],[T],[mounted target system path],[/mnt/sysimage])
# ARG_OPTIONAL_SINGLE([lukspass],[p],[password for LUKS unlock])
# ARG_OPTIONAL_SINGLE([minsize],[m],[minimum size in bytes to consider disk for usage],[6442450944])
# ARG_OPTIONAL_BOOLEAN([data-partition],[D],[create slices for data volumes],[off])
# ARG_OPTIONAL_BOOLEAN([noop],[W],[actually run partitioning commands],[on])
# ARG_OPTIONAL_BOOLEAN([force-hfs-efi],[H],[force creating EFI partition as HFS filesystem],[off])
# ARG_OPTIONAL_BOOLEAN([stack-integrity-volumes],[I],[create integrity volumes in storage stack (SLOW)],[off])
# ARG_OPTIONAL_REPEATED([exclude],[X],[exclude device from disk usage])
# ARG_OPTIONAL_REPEATED([device],[d],[candidate device glob for partitioning],["/dev/[hsvx]d[a-z]" "/dev/[hsvx]d[a-z][a-z]" "/dev/fio?" "/dev/mmcblk[0-9]" "/dev/nvme?n?"])
# ARG_OPTIONAL_BOOLEAN([use-zfs],[Z],[use ZFS over LVM stack],[off])
# ARG_OPTIONAL_SINGLE([zfs-ashift],[i],[set ashift for ZFS],[12])
# ARG_OPTIONAL_SINGLE([zfs-bootpool-props],[b],[zfs boot pool properties (excluding ashift, cachefile)],[autotrim=on,compatibility=grub2])
# ARG_OPTIONAL_SINGLE([zfs-bootfs-props],[B],[zfs boot fs properties],[devices=off,acltype=posixacl,xattr=sa,compression=lz4,normalization=formD,relatime=on,canmount=off])
# ARG_OPTIONAL_SINGLE([zfs-rootpool-props],[r],[zfs root pool properties (excluding ashift, cachefile)],[autotrim=on])
# ARG_OPTIONAL_SINGLE([zfs-rootfs-props],[R],[zfs root fs properties],[acltype=posixacl,xattr=sa,dnodesize=auto,compression=lz4,normalization=formD,relatime=on,canmount=off])
# ARG_OPTIONAL_SINGLE([zfs-swappart-min],[s],[minimum partition size for swap when using ZFS (GB)],[1])
# ARG_OPTIONAL_SINGLE([zfs-swappart-max],[S],[maximum partition size for swap when using ZFS (GB)],[8])
# ARG_OPTIONAL_SINGLE([target-hostname],[N],[hostname for newly created system])
# ARG_HELP([create filesystem layout for new system from discovered devices])
# ARGBASH_GO()
# needed because of Argbash --> m4_ignore([
### START OF CODE GENERATED BY Argbash v2.10.0 one line above ###
# Argbash is a bash code generator used to get arguments parsing right.
# Argbash is FREE SOFTWARE, see https://argbash.io for more info
die()
{
local _ret="${2:-1}"
test "${_PRINT_HELP:-no}" = yes && print_help >&2
echo "$1" >&2
exit "${_ret}"
}
begins_with_short_option()
{
local first_option all_short_options='TpmDWHIXdZibBrRsSNh'
first_option="${1:0:1}"
test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}
# THE DEFAULTS INITIALIZATION - OPTIONALS
_arg_targetpath="/mnt/sysimage"
_arg_lukspass=
_arg_minsize="6442450944"
_arg_data_partition="off"
_arg_noop="on"
_arg_force_hfs_efi="off"
_arg_stack_integrity_volumes="off"
_arg_exclude=()
_arg_device=("/dev/[hsvx]d[a-z]" "/dev/[hsvx]d[a-z][a-z]" "/dev/fio?" "/dev/mmcblk[0-9]" "/dev/nvme?n?")
_arg_use_zfs="off"
_arg_zfs_ashift="12"
_arg_zfs_bootpool_props="autotrim=on,compatibility=grub2"
_arg_zfs_bootfs_props="devices=off,acltype=posixacl,xattr=sa,compression=lz4,normalization=formD,relatime=on,canmount=off"
_arg_zfs_rootpool_props="autotrim=on"
_arg_zfs_rootfs_props="acltype=posixacl,xattr=sa,dnodesize=auto,compression=lz4,normalization=formD,relatime=on,canmount=off"
_arg_zfs_swappart_min="1"
_arg_zfs_swappart_max="8"
_arg_target_hostname=
print_help()
{
printf '%s\n' "create filesystem layout for new system from discovered devices"
printf 'Usage: %s [-T|--targetpath <arg>] [-p|--lukspass <arg>] [-m|--minsize <arg>] [-D|--(no-)data-partition] [-W|--(no-)noop] [-H|--(no-)force-hfs-efi] [-I|--(no-)stack-integrity-volumes] [-X|--exclude <arg>] [-d|--device <arg>] [-Z|--(no-)use-zfs] [-i|--zfs-ashift <arg>] [-b|--zfs-bootpool-props <arg>] [-B|--zfs-bootfs-props <arg>] [-r|--zfs-rootpool-props <arg>] [-R|--zfs-rootfs-props <arg>] [-s|--zfs-swappart-min <arg>] [-S|--zfs-swappart-max <arg>] [-N|--target-hostname <arg>] [-h|--help]\n' "$0"
printf '\t%s\n' "-T, --targetpath: mounted target system path (default: '/mnt/sysimage')"
printf '\t%s\n' "-p, --lukspass: password for LUKS unlock (no default)"
printf '\t%s\n' "-m, --minsize: minimum size in bytes to consider disk for usage (default: '6442450944')"
printf '\t%s\n' "-D, --data-partition, --no-data-partition: create slices for data volumes (off by default)"
printf '\t%s\n' "-W, --noop, --no-noop: actually run partitioning commands (on by default)"
printf '\t%s\n' "-H, --force-hfs-efi, --no-force-hfs-efi: force creating EFI partition as HFS filesystem (off by default)"
printf '\t%s\n' "-I, --stack-integrity-volumes, --no-stack-integrity-volumes: create integrity volumes in storage stack (SLOW) (off by default)"
printf '\t%s\n' "-X, --exclude: exclude device from disk usage (empty by default)"
printf '\t%s' "-d, --device: candidate device glob for partitioning (default array elements:"
printf " '%s'" "/dev/[hsvx]d[a-z]" "/dev/[hsvx]d[a-z][a-z]" "/dev/fio?" "/dev/mmcblk[0-9]" "/dev/nvme?n?"
printf ')\n'
printf '\t%s\n' "-Z, --use-zfs, --no-use-zfs: use ZFS over LVM stack (off by default)"
printf '\t%s\n' "-i, --zfs-ashift: set ashift for ZFS (default: '12')"
printf '\t%s\n' "-b, --zfs-bootpool-props: zfs boot pool properties (excluding ashift, cachefile) (default: 'autotrim=on,compatibility=grub2')"
printf '\t%s\n' "-B, --zfs-bootfs-props: zfs boot fs properties (default: 'devices=off,acltype=posixacl,xattr=sa,compression=lz4,normalization=formD,relatime=on,canmount=off')"
printf '\t%s\n' "-r, --zfs-rootpool-props: zfs root pool properties (excluding ashift, cachefile) (default: 'autotrim=on')"
printf '\t%s\n' "-R, --zfs-rootfs-props: zfs root fs properties (default: 'acltype=posixacl,xattr=sa,dnodesize=auto,compression=lz4,normalization=formD,relatime=on,canmount=off')"
printf '\t%s\n' "-s, --zfs-swappart-min: minimum partition size for swap when using ZFS (GB) (default: '1')"
printf '\t%s\n' "-S, --zfs-swappart-max: maximum partition size for swap when using ZFS (GB) (default: '8')"
printf '\t%s\n' "-N, --target-hostname: hostname for newly created system (no default)"
printf '\t%s\n' "-h, --help: Prints help"
}
parse_commandline()
{
while test $# -gt 0
do
_key="$1"
case "$_key" in
-T|--targetpath)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_targetpath="$2"
shift
;;
--targetpath=*)
_arg_targetpath="${_key##--targetpath=}"
;;
-T*)
_arg_targetpath="${_key##-T}"
;;
-p|--lukspass)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_lukspass="$2"
shift
;;
--lukspass=*)
_arg_lukspass="${_key##--lukspass=}"
;;
-p*)
_arg_lukspass="${_key##-p}"
;;
-m|--minsize)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_minsize="$2"
shift
;;
--minsize=*)
_arg_minsize="${_key##--minsize=}"
;;
-m*)
_arg_minsize="${_key##-m}"
;;
-D|--no-data-partition|--data-partition)
_arg_data_partition="on"
test "${1:0:5}" = "--no-" && _arg_data_partition="off"
;;
-D*)
_arg_data_partition="on"
_next="${_key##-D}"
if test -n "$_next" -a "$_next" != "$_key"
then
{ begins_with_short_option "$_next" && shift && set -- "-D" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
fi
;;
-W|--no-noop|--noop)
_arg_noop="on"
test "${1:0:5}" = "--no-" && _arg_noop="off"
;;
-W*)
_arg_noop="on"
_next="${_key##-W}"
if test -n "$_next" -a "$_next" != "$_key"
then
{ begins_with_short_option "$_next" && shift && set -- "-W" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
fi
;;
-H|--no-force-hfs-efi|--force-hfs-efi)
_arg_force_hfs_efi="on"
test "${1:0:5}" = "--no-" && _arg_force_hfs_efi="off"
;;
-H*)
_arg_force_hfs_efi="on"
_next="${_key##-H}"
if test -n "$_next" -a "$_next" != "$_key"
then
{ begins_with_short_option "$_next" && shift && set -- "-H" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
fi
;;
-I|--no-stack-integrity-volumes|--stack-integrity-volumes)
_arg_stack_integrity_volumes="on"
test "${1:0:5}" = "--no-" && _arg_stack_integrity_volumes="off"
;;
-I*)
_arg_stack_integrity_volumes="on"
_next="${_key##-I}"
if test -n "$_next" -a "$_next" != "$_key"
then
{ begins_with_short_option "$_next" && shift && set -- "-I" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
fi
;;
-X|--exclude)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_exclude+=("$2")
shift
;;
--exclude=*)
_arg_exclude+=("${_key##--exclude=}")
;;
-X*)
_arg_exclude+=("${_key##-X}")
;;
-d|--device)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_device+=("$2")
shift
;;
--device=*)
_arg_device+=("${_key##--device=}")
;;
-d*)
_arg_device+=("${_key##-d}")
;;
-Z|--no-use-zfs|--use-zfs)
_arg_use_zfs="on"
test "${1:0:5}" = "--no-" && _arg_use_zfs="off"
;;
-Z*)
_arg_use_zfs="on"
_next="${_key##-Z}"
if test -n "$_next" -a "$_next" != "$_key"
then
{ begins_with_short_option "$_next" && shift && set -- "-Z" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
fi
;;
-i|--zfs-ashift)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_zfs_ashift="$2"
shift
;;
--zfs-ashift=*)
_arg_zfs_ashift="${_key##--zfs-ashift=}"
;;
-i*)
_arg_zfs_ashift="${_key##-i}"
;;
-b|--zfs-bootpool-props)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_zfs_bootpool_props="$2"
shift
;;
--zfs-bootpool-props=*)
_arg_zfs_bootpool_props="${_key##--zfs-bootpool-props=}"
;;
-b*)
_arg_zfs_bootpool_props="${_key##-b}"
;;
-B|--zfs-bootfs-props)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_zfs_bootfs_props="$2"
shift
;;
--zfs-bootfs-props=*)
_arg_zfs_bootfs_props="${_key##--zfs-bootfs-props=}"
;;
-B*)
_arg_zfs_bootfs_props="${_key##-B}"
;;
-r|--zfs-rootpool-props)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_zfs_rootpool_props="$2"
shift
;;
--zfs-rootpool-props=*)
_arg_zfs_rootpool_props="${_key##--zfs-rootpool-props=}"
;;
-r*)
_arg_zfs_rootpool_props="${_key##-r}"
;;
-R|--zfs-rootfs-props)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_zfs_rootfs_props="$2"
shift
;;
--zfs-rootfs-props=*)
_arg_zfs_rootfs_props="${_key##--zfs-rootfs-props=}"
;;
-R*)
_arg_zfs_rootfs_props="${_key##-R}"
;;
-s|--zfs-swappart-min)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_zfs_swappart_min="$2"
shift
;;
--zfs-swappart-min=*)
_arg_zfs_swappart_min="${_key##--zfs-swappart-min=}"
;;
-s*)
_arg_zfs_swappart_min="${_key##-s}"
;;
-S|--zfs-swappart-max)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_zfs_swappart_max="$2"
shift
;;
--zfs-swappart-max=*)
_arg_zfs_swappart_max="${_key##--zfs-swappart-max=}"
;;
-S*)
_arg_zfs_swappart_max="${_key##-S}"
;;
-N|--target-hostname)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_target_hostname="$2"
shift
;;
--target-hostname=*)
_arg_target_hostname="${_key##--target-hostname=}"
;;
-N*)
_arg_target_hostname="${_key##-N}"
;;
-h|--help)
print_help
exit 0
;;
-h*)
print_help
exit 0
;;
*)
_PRINT_HELP=yes die "FATAL ERROR: Got an unexpected argument '$1'" 1
;;
esac
shift
done
}
parse_commandline "$@"
# OTHER STUFF GENERATED BY Argbash
### END OF CODE GENERATED BY Argbash (sortof) ### ])
# [ <-- needed because of Argbash
set -eu
set -o pipefail
shopt -s nullglob
# luks options are hardcoded up here...
luksopts=("-c" "aes-xts-plain64" "-s" "512" "-h" "sha256" "-i" "8000" "--align-payload=8192")
# stderr
infomsg () {
printf '%s\n' 1>&2 "${@}"
}
# define a cleanup handler
_cleanup() {
[[ "${_WORKDIR:-}" ]] && [[ -d "${_WORKDIR}" ]] && {
case "${_WORKDIR}" in
/) : ;;
*) rm -rf "${_WORKDIR}" ;;
esac
}
}
trap _cleanup EXIT ERR
# lsblk groveling, get a value...
get_lsblk_val () {
local blkline blk key
declare -A blkline
blk="${1}"
key="${2}"
for word in $blk ; do
if [[ $word = *"="* ]] ; then
val=${word#*=}
val=${val#'"'}
val=${val%'"'}
blkline[${word%%=*}]=${val}
fi
done
echo "${blkline[$key]:-}"
}
# guessing at lsblk devices
get_lsblk_dev () {
local dev
dev="${1}"
if [ -b "/dev/${dev}" ] ; then
dev="/dev/${dev}"
elif [ -b "/dev/mapper/${dev}" ] ; then
dev="/dev/mapper/${dev}"
fi
echo "${dev}"
}
# run dmsetup to get block device underlying a dm-synthetic device (luks, lvm)
grovel_dm() {
local blkname type dmoutput
blkname="${1}"
type="${2}"
blkname=$(get_lsblk_dev "${blkname}")
dmoutput=$(sudo dmsetup table "${blkname}")
dma=(${dmoutput})
# yes, these are magic numbers.
case "${type}" in
lvm)
echo "${dma[3]}"
;;
crypt)
echo "${dma[6]}"
;;
esac
}
# given a lsblk k/v pair, try to find the actual disk behind it
# NOTE: recursive ;)
key2disk () {
local filt blk type
filt="${1}"
blk=$(lsblk -P -o NAME,MAJ:MIN,RM,SIZE,RO,TYPE,MOUNTPOINT | grep -F "${filt}")
[[ "${blk}" ]] || return 0
type=$(get_lsblk_val "${blk}" TYPE)
case "${type}" in
lvm|crypt)
local dmblk nblk blkname nblkname
blkname=$(get_lsblk_val "${blk}" NAME)
dmblk=$(grovel_dm "${blkname}" "${type}")
nblk=$(lsblk -P | grep -F "MAJ:MIN=\"${dmblk}\"")
nblkname=$(get_lsblk_val "${nblk}" NAME)
key2disk "NAME=\"${nblkname}\""
;;
part)
local blkname disk part
blkname=$(get_lsblk_val "${blk}" NAME)
part="${blkname##*[[:alpha:]]}"
disk="${blkname%"${part}"}"
disk="${disk%p}"
key2disk "NAME=\"${disk}\""
;;
disk)
local blkname
blkname=$(get_lsblk_val "${blk}" NAME)
echo "${blkname}"
;;
esac
}
# exclude iODD/ZVME devices from disk candidate pool
return_iodds () {
local glob string ent scsiven
glob=( "${_arg_device[@]/"/dev/"/"/sys/class/block/"}" )
for string in "${glob[@]}" ; do
for ent in $(compgen -G "${string}") ; do
scsiven=""
[[ -e "${ent}/device/vendor" ]] && read -r scsiven < "${ent}/device/vendor"
case "${scsiven}" in
iODD|ZMVE) echo "${ent##*/}" ;;
esac
done
done
}
# return resolved list of candidate disks, filtering excluded ones
get_baseblocks () {
local string ent res blocks disktype queue_rotational
disktype="${1:-}"
[[ "${disktype:-}" ]] && {
case "${disktype}" in
flash|rotational) : ;;
*) "you may ask for flash or rotational disks" ; return 1 ;;
esac
}
res=()
for string in "${_arg_device[@]}" ; do
for ent in $(compgen -G "${string}") ; do
ent="${ent#/dev/}"
case " ${_arg_exclude[*]} " in
*" ${ent} "*) continue ;;
esac
blocks="$(sudo blockdev --getsize64 "/dev/${ent}" 2>/dev/null)" || true
[[ "${blocks}" ]] || continue
[[ "${blocks}" -ge "${_arg_minsize}" ]] || continue
[[ "${disktype:-}" ]] && {
[[ -e "/sys/class/block/${ent}/queue/rotational" ]] || continue
read -r queue_rotational < "/sys/class/block/${ent}/queue/rotational"
case "${disktype}" in
flash) [[ "${queue_rotational}" -eq 0 ]] || continue ;;
rotational) [[ "${queue_rotational}" -eq 1 ]] || continue ;;
esac
}
res+=("${ent}")
done
done
echo "${res[@]}"
}
# given a string (or rather, any collection of 1 or more arguments), count the words by whitespace in it.
count_words() {
local count word
count=0
# shellcheck disable=SC2034,SC2068
for word in ${@} ; do
count=$((count + 1))
done
echo "${count}"
}
# detach all io stacked on top of a disk, wipe the partitions, then the partition table
wipedisk () {
local part disk vgs vg lsline mtpt usetype arrays array lvs lv lvname submount crypts crypt caches cache name
arrays=()
disk="${1}"
case "${disk}" in
mmcblk*|nvme*) disk="${disk}p" ;;
esac
for part in "/dev/${disk}"[0-9]* ; do
arrays=() ; lvs=() ; crypts=() ; name='' ; caches=()
# stop any raid device using the partition
# unmount anything using the partition. stop any arrays using the partition.
while read -r lsline ; do
usetype=$(get_lsblk_val "${lsline}" TYPE)
mtpt=$(get_lsblk_val "${lsline}" MOUNTPOINT)
[[ "${mtpt:-}" ]] && {
for submount in $(grep " ${mtpt}" /proc/mounts | sort -k 2 -r | cut -d' ' -f 2) ; do
umount "${submount}"
done
}
case "${usetype}" in
raid*) arrays+=("$(get_lsblk_val "${lsline}" NAME)") ;;
lvm) lvs+=("$(get_lsblk_val "${lsline}" NAME)") ;;
# integrity volumes show as crypt.
crypt) crypts+=("$(get_lsblk_val "${lsline}" NAME)") ;;
# bcache shows as a disk device...
disk)
name="$(get_lsblk_val "${lsline}" NAME)"
case "${name}" in
bcache*) caches+=("${name}")
esac
;;
esac
done < <(lsblk -P -o NAME,MAJ:MIN,RM,SIZE,RO,TYPE,MOUNTPOINT "${part}")
# check for any volume groups directly using the device and stop them. it's ok to fail.
read -r -a vgs <<< "$(pvs --devices "${part}" -o vg_uuid --noheadings)" || true
[[ "${vgs[0]:-}" ]] && {
for vg in "${vgs[@]}" ; do
vgchange -a n --select vg_uuid="${vg}"
done
}
# check for any volume groups using a child of the device and stop them, too
[[ "${lvs[0]:-}" ]] && {
for lv in "${lvs[@]}" ; do
# the lvm command is just...bad
lvname="$(lvdisplay -S "lv_dm_path=/dev/mapper/${lv}" | awk '$0 ~ "VG Name" { print $3 }')"
[[ "${lvname}" ]] && vgchange -a n "${lvname}"
done
}
# shut down any LUKS devices
[[ "${crypts[0]:-}" ]] && {
for crypt in "${crypts[@]}" ; do
# skip integ devices here
case "${crypt}" in
integ-*) : ;;
*) cryptsetup remove "${crypt}" ;;
esac
done
}
# shut down any bcache devices (backing)
[[ "${caches[0]:-}" ]] && {
for cache in "${caches[@]}" ; do
# ask for a stop
printf 1 > "/sys/block/${cache}/bcache/stop"
# wait for it to go away
while [[ -e "/sys/block/${cache}" ]] ; do
sleep 1
done
done
}
# shut down any bcache devices (cache) on a raw partition.
[[ -e "/sys/class/block/${part##*/}/bcache" ]] && {
printf 1 > "/sys/class/block/${part##*/}/bcache/set/stop"
while [[ -e "/sys/class/block/${part##*/}/bcache" ]] ; do
sleep 1
done
}
[[ "${arrays[0]:-}" ]] && {
for array in "${arrays[@]}" ; do
# shut down any bcache devices on arrays here...
[[ -e "/sys/class/block/${array}/bcache" ]] && {
printf 1 > "/sys/class/block/${array}/bcache/set/stop"
while [[ -e "/sys/class/block/${array}/bcache" ]] ; do
sleep 1
done
}
# then stop the array
mdadm --stop "/dev/${array}"
done
}
# stop any integ devices now, reusing the crypt array
[[ "${crypts[0]:-}" ]] && {
for crypt in "${crypts[@]}" ; do
case "${crypt}" in
integ-*) cryptsetup remove "${crypt}" ;;
esac
done
}
wipefs -a "${part}"
done
wipefs -a "/dev/${disk%p}"
}
calc_alignment () {
local name="${1}"
local size="${2:-}"
local pblsz optio lbsize align chunk mult disk
local partend newstart newend step res
disk="/dev/${name}"
read -r pblsz < "/sys/class/block/${name}/queue/physical_block_size"
read -r optio < "/sys/class/block/${name}/queue/optimal_io_size"
read -r lbsize < "/sys/class/block/${name}/queue/hw_sector_size"
read -r align < "/sys/class/block/${name}/alignment_offset"
chunk=$(($((optio + align)) / pblsz))
[ "${chunk}" -eq 0 ] && chunk=4096
# we use 4096 bytes as a 'base' unit for small partition calculations
mult=$((4096 / pblsz))
# return *two* pieces, in sectors, of the start and end locations for a desired request.
# we assume you've asked for this in a linear fashion, so let's find the end of any existing
# partition first. (awk is finding all partitions, setting e to the end field, but only
# printing the last e, at the end)
partend=$(parted -s "${disk}" unit s print | awk -F' ' "\$1 ~ /[0-9]+/ {e=\$3} END{print e}")
partend="${partend%s}"
# handle no partitions case
[[ "${partend}" == "" ]] && partend=0
# force a misalignment on the partition end so the *next* partition never overlaps
partend="$((partend + 1))"
# partition beginning alignment
step=1
while [ $((step * chunk)) -lt "${partend}" ] ; do
step=$((step + 1))
done
newstart="$((step * chunk))"
res="${newstart}s"
# handle if we wanted a partition size - we *only* take chunks though
# though they're not the chunks we used internally
# they're handed over to mult instead
[[ "${size}" != "" ]] && {
case "${size}" in
*c)
size="${size%c}"
newend=$(($((mult * size)) + chunk))
res="${res} ${newend}s"
;;
*g)
size="${size%g}"
size="$((1073741824 * size))"
size="$((size / lbsize))"
newend="$((size + newstart))"
res="${res} ${newend}s"
;;
*m)
size="${size%m}"
# this is in bytes
size="$((1048576 * size))"
# convert to sectors
size="$((size / lbsize))"
# add the sectors from the start in
newend="$((size + newstart))"
res="${res} ${newend}s"
;;
*) printf '%s\n' "Size must be in chunks (c), gigabytes (g) or megabytes (m)" 1>&2 ; return 1 ; ;;
esac
# subtract one from this for reasons I can only describe as "someone in this stack is a dick"
newend=$((newend - 1))
res="${res} ${newend}s"
}
printf '%s\n' "${res}"
}
# this is actually what partitions disks - can set raidflags but doesn't set up RAIDs.
# it returns which device got which partition encoded here (biosboot,efiboot,sysboot,system,data)
# I use it with a while loop to create strings of partitions _to_ RAID.
partition_disk () {
# setup
local disk name total_disks
# counters
local partition
# partition chunk holders
local partstart partend holder
partition="0"
total_disks="1"
name="${1}"
disk="/dev/${name}"
[[ "${2+x}" ]] && {
total_disks="${2}"
}
# partition label
parted "${disk}" mklabel gpt > /dev/null
# legacy BIOS boot partition
{
holder="$(calc_alignment "${name}" 1024c)"
partstart="${holder%% *}"
partend="${holder##* }"
parted -a optimal "${disk}" mkpart biosboot "${partstart}" "${partend}" && partition=$((partition + 1))
parted "${disk}" toggle "${partition}" bios_grub
parted "${disk}" toggle "${partition}" legacy_boot
} > /dev/null 2>&1
case "${name}" in
mmcblk*|nvme*) echo "biosboot=${disk}p${partition}" ;;
*) echo "biosboot=${disk}${partition}" ;;
esac
# EFI system partition
{
holder="$(calc_alignment "${name}" 300m)"
partstart="${holder%% *}"
partend="${holder##* }"
case "${_arg_force_hfs_efi}" in
on)
parted -a optimal "${disk}" mkpart '"EFI System Partition"' hfs+ "${partstart}" "${partend}" && partition=$((partition + 1))
;;
off)
parted -a optimal "${disk}" mkpart '"EFI System Partition"' "${partstart}" "${partend}" && partition=$((partition + 1))
parted "${disk}" toggle "${partition}" boot
;;
esac
} > /dev/null 2>&1
case "${name}" in
mmcblk*|nvme*) echo "efiboot=${disk}p${partition}" ;;
*) echo "efiboot=${disk}${partition}" ;;
esac
# /boot partition
local pdev=""
local pcan=""
{
holder="$(calc_alignment "${name}" 600m)"
partstart="${holder%% *}"
partend="${holder##* }"
parted "${disk}" mkpart sysboot "${partstart}" "${partend}" && partition=$((partition + 1))
if [[ "${_arg_use_zfs}" == "on" ]] ; then
sgdisk -t"${partition}:bf01" "${disk}" # set the zfs type code for boot - parted knows not
elif [[ "${total_disks}" -gt 1 ]] ; then
parted "${disk}" toggle "${partition}" raid
fi
} > /dev/null 2>&1
case "${name}" in
mmcblk*|nvme*) pdev="${disk}p${partition}" ;;
*) pdev="${disk}${partition}" ;;
esac
if [[ "${_arg_use_zfs}" == "on" ]] ; then
udevadm settle
# we would _prefer_ using the dev/disk/by-id so go ask udev what that is.
for pcan in $(udevadm test "${pdev}" 2>/dev/null|grep DEVLINKS=|sed -e s/DEVLINKS=//) ; do
case ${pcan} in
*disk/by-id/ata*) pdev="${pcan#/dev/}" ;;
*disk/by-id*) [[ "${pdev}" == "" ]] && pdev="${pcan#/dev/}" ;;
esac
done
echo "${pdev}" | grep -q '^disk/by-id' || pdev="disk/by-partuuid/$(lsblk -no PARTUUID "${pdev}")"
fi
echo "sysboot=${pdev}"
# swap partition (if ZFS root is enabled)
local sysswap_dev=""
local sysswap_can=""
{
[[ "${_arg_use_zfs}" == "on" ]] && {
# get the whole-disk size in megabytes
local ddevtot
ddevtot="$(blockdev --getsize64 "${disk}")"
ddevtot=$((ddevtot / 1048576))
# start with the max slice size and step down in megabytes until we're ~ 1/16th the disk.
local candidate_size="${_arg_zfs_swappart_max}"
local ddev_slice=$((ddevtot / candidate_size))
while [[ "${ddev_slice}" -lt 16 ]] ; do
candidate_size=$((candidate_size - 1024))
ddev_slice=$((ddevtot / candidate_size))
done
# convert back to gigabytes
candidate_size=$((candidate_size / 1024))
# correct if we dropped below our minimum here
[[ "${candidate_size}" -lt "${_arg_zfs_swappart_min}" ]] && candidate_size="${_arg_zfs_swappart_min}"
holder="$(calc_alignment "${name}" "${candidate_size}g")"
partstart="${holder%% *}"
partend="${holder##* }"
parted "${disk}" mkpart sysswap "${partstart}" "${partend}" && partition=$((partition + 1))
case "${name}" in
mmcblk*|nvme*) sysswap_dev="${disk}p${partition}" ;;
*) sysswap_dev="${disk}${partition}" ;;
esac
udevadm settle
for sysswap_can in $(udevadm test "${sysswap_dev}" 2>/dev/null|grep DEVLINKS=|sed -e s/DEVLINKS=//) ; do
case ${sysswap_can} in
*disk/by-id*) sysswap_dev="${sysswap_can}" ;;
esac
done
}
} > /dev/null # I think the math operators made some unwanted noise.
[[ "${sysswap_dev}" != "" ]] && echo "sysswap=${sysswap_dev}"
# system partition
local sysdisk=""
local sfind=""
local scan=""
{
holder="$(calc_alignment "${name}" 24g)"
partstart="${holder%% *}"
partend="${holder##* }"
if [[ "${_arg_data_partition}" == "off" ]] ; then
partend="100%"
fi
parted "${disk}" mkpart system "${partstart}" "${partend}" && partition=$((partition + 1))
if [[ "${_arg_use_zfs}" == "on" ]] ; then
if [[ "${_arg_stack_integrity_volumes}" != "on" ]] ; then
sgdisk -t"${partition}:bf00" "${disk}"
fi
elif [[ "${total_disks}" -gt 1 ]] ; then
parted "${disk}" toggle "${partition}" raid
fi
} > /dev/null 2>&1
case "${name}" in
mmcblk*|nvme*) sysdisk="${disk}p${partition}" ;;
*) sysdisk="${disk}${partition}" ;;
esac
# now, if we are in integrity mode, do integritysetup. here.
if [[ "${_arg_stack_integrity_volumes}" == "on" ]] ; then
while [[ ! -e "${sysdisk}" ]] ; do sleep 1 ; done
sleep 1 # what? why?
integritysetup format -s 4096 --progress-frequency=1 --batch-mode "${sysdisk}"
partuuid="$(get_lsblk_val "$(blkid "${sysdisk}")" PARTUUID)"
integritysetup open "${sysdisk}" "integ-${partuuid}"
sysdisk="/dev/mapper/integ-${partuuid}"
elif [[ "${_arg_use_zfs}" == "on" ]] ; then
udevadm settle
# we would _prefer_ using the dev/disk/by-id so go ask udev what that is.
for scan in $(udevadm test "${sysdisk}" 2>/dev/null|grep DEVLINKS=|sed -e s/DEVLINKS=//) ; do
case ${scan} in
*disk/by-id/ata*) sfind="${scan#/dev/}" ;;
*disk/by-id*) [[ "${sfind}" == "" ]] && sfind="${scan#/dev/}" ;;
esac
done
[[ "${sfind}" != "" ]] && sysdisk="${sfind}"
echo "${sysdisk}" | grep -q '^disk/by-id' || sysdisk="disk/by-partuuid/$(lsblk -no PARTUUID "${sysdisk}")"
fi
echo "system=${sysdisk}"
# data partition
# I didn't add any zfs support because you probably don't want to mix this with it
if [ "${_arg_data_partition}" == "on" ] ; then
{
partstart="$(calc_alignment "${name}")"
partend="100%"
parted "${disk}" mkpart data "${partstart}" "${partend}" && partition=$((partition + 1))
if [ "${total_disks}" -gt 1 ] ; then
parted "${disk}" toggle "${partition}" raid
fi
} > /dev/null 2>&1
case "${name}" in
mmcblk*|nvme*) datadisk="${disk}p${partition}" ;;
*) datadisk="${disk}${partition}" ;;
esac
# now, if we are in integrity mode, do integritysetup. here.
if [[ "${_arg_stack_integrity_volumes}" == "on" ]] ; then
while [[ ! -e "${datadisk}" ]] ; do sleep 1 ; done
sleep 1 # what? why?
integritysetup format -s 4096 --progress-frequency=1 --batch-mode "${datadisk}"
partuuid="$(get_lsblk_val "$(blkid "${datadisk}")" PARTUUID)"
integritysetup open "${datadisk}" "integ-${partuuid}"
datadisk="/dev/mapper/integ-${partuuid}"
fi
echo "data=${datadisk}"
fi
# give the kernel/udev/drive a moment to settle
sleep 1
}
# this does partitioning for cache disks. same sort of deal, but only returns 'cache' value.
# TBD: zfs?
partition_cache () {
local disk name total_disks partition
partition="0"
total_disks="1"
name="${1}"
disk="/dev/${name}"
[[ "${2+x}" ]] && {
total_disks="${2}"
}
# partition label
parted "${disk}" mklabel gpt > /dev/null
# cache partition
{
parted "${disk}" mkpart cache 1m 100% && partition=$((partition +1))
if [ "${total_disks}" -gt 1 ] ; then
parted "${disk}" toggle "${partition}" raid
fi
} > /dev/null
case "${name}" in
mmcblk*|nvme*) echo "cache=${disk}p${partition}" ;;
*) echo "cache=${disk}${partition}" ;;
esac
}
# create a boot pool
ready_zfs_boot () {
local targets=("${@}")
local argct='' ; argct="$(count_words "${@}")"
local pooltarg=()
if [[ "${argct}" -gt 1 ]] ; then
local mod=("${targets[@]:1}")
pooltarg=("${targets[0]}" "${mod[@]/#//dev/}")
else
pooltarg=("${targets[@]/#//dev/}")
fi
local poolprops=() poolargs=() arg='' fsprops=() fsargs=() keydev=''
IFS=, read -r -a poolprops <<< "${_arg_zfs_bootpool_props}"
poolprops+=("ashift=${_arg_zfs_ashift}")
IFS=, read -r -a fsprops <<< "${_arg_zfs_bootfs_props}"
for arg in "${poolprops[@]}" ; do poolargs+=("-o" "${arg}") ; done
fsprops+=("mountpoint=/boot")
for arg in "${fsprops[@]}" ; do fsargs+=("-O" "${arg}") ; done
zpool create "${poolargs[@]}" "${fsargs[@]}" -R "${_arg_targetpath}" bpool "${pooltarg[@]}"
zfs create -o canmount=off -o mountpoint=none bpool/BOOT
zfs create -o mountpoint=/boot bpool/BOOT/system
# if we're doing encryption, create a zvol here to hold the rpool key (we're gonna make it LUKS)
[[ "${_arg_lukspass}" ]] && {
# create a double-copy ZFS volume that we're gonna put luks data in. the actual FS is FAT. keep it simple.
zfs create -V 32M -o copies=2 bpool/keys
printf '%s' "${_arg_lukspass}" | cryptsetup luksFormat "${luksopts[@]}" /dev/zvol/bpool/keys
keydev="$(luks_open /dev/zvol/bpool/keys luks,noauto)" # TODO: dracut hook to add to crypttab with nofail opt (due to ordering)
mkfs.vfat "${keydev}"
mcopy -i "${keydev}" "${ZFS_FSKEY}" ::rpool.key
LUKS_ZFSKDEV="${keydev##*luks-}"
} || true
}
# create the zfs system pool
ready_zfs_sys () {
local targets=("${@}")
local argct='' ; argct="$(count_words "${@}")"
local pooltarg=()
if [[ "${argct}" -gt 1 ]] ; then
local mod=("${targets[@]:1}")
pooltarg=("${targets[0]}" "${mod[@]/#//dev/}")
else
pooltarg=("${targets[@]/#//dev/}")
fi
local poolprops=() poolargs=() arg='' fsprops=() fsargs=()
IFS=, read -r -a poolprops <<< "${_arg_zfs_rootpool_props}"
poolprops+=("ashift=${_arg_zfs_ashift}")
IFS=, read -r -a fsprops <<< "${_arg_zfs_rootfs_props}"
for arg in "${poolprops[@]}" ; do poolargs+=("-o" "${arg}") ; done
fsprops+=("mountpoint=/")
[[ "${_arg_lukspass}" ]] && {
# global!
ZFS_FSKEY="$(mktemp)"
openssl rand -hex -out "${ZFS_FSKEY}" 32
fsprops+=("encryption=on" "keyformat=hex")
# currently, this is not set here due to calling order...
[[ "${LUKS_ZFSKDEV:-}" ]] && {
fsprops+=("io.github.arrjay:luksholder=${LUKS_ZFSKDEV}")
}
}
for arg in "${fsprops[@]}" ; do fsargs+=("-O" "${arg}") ; done
if [[ "${_arg_lukspass}" ]] ; then
zpool create "${poolargs[@]}" "${fsargs[@]}" -R "${_arg_targetpath}" rpool "${pooltarg[@]}" < "${ZFS_FSKEY}"
else
zpool create "${poolargs[@]}" "${fsargs[@]}" -R "${_arg_targetpath}" rpool "${pooltarg[@]}"
fi
zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o canmount=noauto -o mountpoint=/ rpool/ROOT/system
zfs mount rpool/ROOT/system
chmod 0755 "${_arg_targetpath}"
# ask for tmpfs on /tmp here
printf '%s %s %s %s %s %s\n' "tmpfs" "${_arg_targetpath}/tmp" "tmpfs" "defaults,nosuid,nodev,nr_inodes=1m,strictatime,mode=1777,size=25%" "0" "0" >> "${fstab}"
}
# create flexibly-block-aligned pv
lvm_create () {
local pv vg
pv="${2}"
vg="${1}"
wipefs -a "${pv}"
pvcreate --dataalignment 8192s "${pv}"
vgcreate -An --dataalignment 8192s "${vg}" "${pv}"
}
ready_lv () {
local lvname vgname fstyp sizeM lmount devpath mpath fs_opts fs_nos
lvname="${1}"
vgname="${2}"
fstyp="${3}"
sizeM="${4}"
lmount=""
fs_opts="defaults"
fs_nos="1 2"
case "${fstyp}" in swap) lmount="swap" ; mpath="swap" fs_nos="0 0" ;; esac
[[ "${5+x}" ]] && {
lmount="${5}" ; mpath="${_arg_targetpath}${lmount}"
case "${lmount}" in /) fs_nos="1 1" ;; esac
}
devpath="/dev/${vgname}/${lvname}"
printf '%s %s %s %s %s\n' "${devpath}" "${mpath}" "${fstyp}" "${fs_opts}" "${fs_nos}" >> "${fstab}"
lvcreate -An -Wy -y "-L${sizeM}M" "-n${lvname}" "${vgname}"
wipefs -a "${devpath}"
case "${fstyp}" in
ext4) mkfs.ext4 -I 256 "${devpath}" ;;
# TODO: detect if the underlying device is dm-crypted and, if not, crypt it?
swap) mkswap "${devpath}" ;;
esac
}
ready_thin () {
local lvname vgname tpoolname fstyp sizeM lmount devpath mpath fs_opts fs_nos
lvname="${1}"
vgname="${2}"
tpoolname="${3}"
fstyp="${4}"
sizeM="${5}"
lmount=""
fs_opts="defaults,discard"
fs_nos="1 2"
[[ "${6+x}" ]] && { lmount="${6}" ; mpath="${_arg_targetpath}${lmount}" ; }
devpath="/dev/${vgname}/${lvname}"
printf '%s %s %s %s %s\n' "${devpath}" "${mpath}" "${fstyp}" "${fs_opts}" "${fs_nos}" >> "${fstab}"
lvcreate -An "-V${sizeM}M" "-n${lvname}" --thinpool "${tpoolname}" "${vgname}"
case "${fstyp}" in
ext4) mkfs.ext4 "${devpath}" ;;
swap) mkswap "${devpath}" ;;
esac
}
# create a md device, format it
ready_md () {
local mdname mdlevel datalevel mddev fstyp mount extra fs_opts fs_nos rname uuid sw_uuid
datalevel="1.0"
mdname="${1}"
mdlevel="${2}"
mddev="${3}"
fstyp="${4}"
mount="${5}"
extra=""
fs_opts="defaults"
fs_nos="1 2"
# MD_COUNTER is a global for the next time we call mdadm
[[ "${MD_COUNTER:-}" ]] || MD_COUNTER=0
MD_COUNTER=$(( MD_COUNTER + 1 ))
[[ "${_arg_target_hostname}" ]] || { infomsg "creating MD arrays without hostname - you may have initrd problems post-reboot" ; }
mpath="${_arg_targetpath}${mount}"
# efi and boot mds get 1.0 metadata, others 1.1
case "${mdname}" in
efi)
[[ "${_arg_force_hfs_efi}" == "on" ]] && { infomsg "oh no, we don't handle hfs efi partitions with raid1" ; exit 1 ; }
extra='--label="EFISP" --noformat --fsoptions="umask=0077,shortname=winnt"'
fs_opts='umask=0077,shortname=winnt,x-systemd.after=boot.mount,x-systemd.after=zfs-mount.service,fscontext="system_u:object_r:boot_t:s0"'
fs_nos='0 2'
;;
boot) extra='--label="/boot"' ;;
*) datalevel="1.1" ;;
esac
# shellcheck disable=SC2086
if [[ "${_arg_target_hostname}" ]] ; then
printf '%s\n' "${_arg_target_hostname}" > "${scratchtree}/etc/hostname"
mdadm --create "/dev/md/${mdname}" --homehost="${_arg_target_hostname}" -N"${mdname}" -l"${mdlevel}" -n "$(count_words "${mddev}")" --metadata="${datalevel}" ${mddev}
else
mdadm --create "/dev/md/${mdname}" -N"${mdname}" -l"${mdlevel}" -n "$(count_words "${mddev}")" --metadata="${datalevel}" ${mddev}
fi
wipefs -a "/dev/md/${mdname}"
case "${fstyp}" in
efi) mkfs.vfat -F32 -n EFISP "/dev/md/${mdname}" ;;
# ext2 is special cased to create with the stupidest options for bootloader-ing
# you can always tune2fs and rethink your life later.
ext2) mkfs.ext2 -O none,ext_attr,resize_inode,dir_index,filetype,sparse_super "/dev/md/${mdname}" ;;
swap)
sw_uuid=$(uuidgen)
printf 'swap-%s %s /dev/urandom swap,cipher=aes-xts-plain64,size=512,sector-size=4096\n' "${sw_uuid}" "/dev/md/${mdname}" >> "${scratchtree}/etc/crypttab"
;;
esac
case "${fstyp}" in
lvmpv|bcache) : ;;
swap) printf '/dev/mapper/swap-%s %s %s %s %s %s\n' "${sw_uuid}" "swap" "swap" "defaults" "0" "0" >> "${fstab}" ;;
efi) printf '/dev/md/%s %s %s %s %s\n' "${mdname}" "${mpath}" "vfat" "${fs_opts}" "${fs_nos}" >> "${fstab}" ;;
*) printf '/dev/md/%s %s %s %s %s\n' "${mdname}" "${mpath}" "${fstyp}" "${fs_opts}" "${fs_nos}" >> "${fstab}" ;;
esac
rname=$(readlink "/dev/md/${mdname}")
rname=${rname##*/}
read -r uuid < "/sys/class/block/${rname}/md/uuid"
uuid="${uuid//-/}"
printf 'ARRAY /dev/md/%s metadata=%s UUID=%s name=%s\n' \
"${mdname}" "${datalevel}" "${uuid:0:8}:${uuid:8:8}:${uuid:16:8}:${uuid:24:8}" "${mdname}" \
>> "${scratchtree}/etc/mdadm/mdadm.conf"
sleep 1
case "${mdname}" in
data)
printf 'idle' > "/sys/class/block/${rname}/md/sync_action"
;;
esac
}
# format a partition, updating fstab if needed
ready_part () {
local partition fstyp mount extra fs_opts fs_nos mpath fsuuid efifs_type
partition="${1}"
fstyp="${2}"
mount="${3}"
extra=""
fs_opts="defaults"
fs_nos="1 2"
mpath="${_arg_targetpath}${mount}"
# always update fstab, though we may rewrite later.
case "${fstyp}" in
lvmpv|bcache|swap) : ;;
efi)
case "${_arg_force_hfs_efi}" in
on) efifs_type="hfsplus" ;;
off) efifs_type="vfat" ; fs_opts='umask=0077,shortname=winnt,x-systemd.after=boot.mount,x-systemd.after=zfs-mount.service,fscontext="system_u:object_r:boot_t:s0"' ;;
esac
printf '%s %s %s %s %s\n' "${partition}" "${mpath}" "${efifs_type}" "${fs_opts}" "${fs_nos}" >> "${fstab}"
;;
*)
printf '%s %s %s %s %s\n' "${partition}" "${mpath}" "${fstyp}" "${fs_opts}" "${fs_nos}" >> "${fstab}"
;;
esac
# format and update faketab
case "${fstyp}" in
lvmpv|bcache) : ;;
efi)
case "${efifs_type}" in
vfat) mkfs.vfat -F32 -n EFISP "${partition}" ;;
hfsplus) mkfs.hfsplus -v EFISP "${partition}" ;;
esac
;;
# ext2 is special cased to create with the stupidest options for bootloader-ing
# you can always tune2fs and rethink your life later.
ext2)
mkfs.ext2 -O none,ext_attr,resize_inode,dir_index,filetype,sparse_super "${partition}"
;;
# swap gets to have setup via crypttab if we called on a raw disk...
swap)
swap_uuid="$(uuidgen)"
printf 'swap-%s %s /dev/urandom swap,cipher=aes-xts-plain64,size=512,sector-size=4096\n' "${swap_uuid}" "${partition}" >> "${scratchtree}/etc/crypttab"
# but fstab needs an entry too. we're not using the defaults at all.
printf '/dev/mapper/swap-%s swap swap defaults 0 0\n' "${swap_uuid}" >> "${scratchtree}/etc/fstab"
;;
*)
"mkfs.${fstyp}" "${partition}"
;;
esac
# handle efi for the next bit..
case "${fstyp}" in efi) fstyp="${efifs_type}" ;; esac
# rewrite fstab with UUID now.
# shellcheck disable=SC2015
grep -q "${partition}" "${fstab}" && {
fsuuid="$(blkid -s UUID "${partition}")"
fsuuid="${fsuuid#*UUID=\"}"
fsuuid="${fsuuid%\"}"
sed -i -e 's@^'"${partition}"'.*@UUID='"${fsuuid} ${mpath} ${fstyp} ${fs_opts} ${fs_nos}"'@' "${fstab}"
} || true
}
# walk our fstab and mkdir/mount as needed
make_n_mount () {
local pass found dev path fstyp fsopt dump chk
pass=0
found=1
while [ "${found}" -eq 1 ] ; do
found=0
# shellcheck disable=SC2034
while read -r dev path fstyp fsopt dump chk ; do
if [ "${chk}" -eq "${pass}" ] ; then
found=1
case "${fstyp}" in swap) continue ;; esac
fsopt="${fsopt#defaults,}"
fsopt="${fsopt#defaults}"
mkdir -p "${path}"
if [[ "${fsopt}" ]] ; then
mount -t "${fstyp}" -o "${fsopt}" "${dev}" "${path}"
else
mount -t "${fstyp}" "${dev}" "${path}"
fi
fi
done <<< "$(sort -k2 < "${fstab}")"
pass=$(( pass + 1 ))
done
# the above code does something very nice about mounting all the filesystem trees.
# the below will mount /boot/efi if it's not there. ZFS doesn't really do fstab...
grep -q "${_arg_targetpath}/boot/efi" /proc/mounts || {
fsline="$(grep /boot/efi "${fstab}")"
[[ "${fsline}" ]] && {
mkdir -p "${_arg_targetpath}/boot/efi"
read -r dev path fstyp fsopt dump chk <<< "${fsline}"
fsopt="${fsopt#defaults,}"
fsopt="${fsopt#defaults}"
if [[ "${fsopt}" ]] ; then
mount -t "${fstyp}" -o "${fsopt}" "${dev}" "${path}"
else
mount -t "${fstyp}" "${dev}" "${path}"
fi
}
}
}
# open a luks device and return what we mapped it to (/dev/mapper/luks-UUID)
luks_open () {
local linkunwind dir candidate luks_map options
candidate="${1}"
options="${2:-luks}"
dir="${candidate%/*}"
if [ -L "${candidate}" ] ; then linkunwind="$(readlink "${candidate}")" ; else linkunwind="${candidate##*/}" ; fi
luks_uuid=$(cryptsetup luksUUID "${dir}/${linkunwind}")
luks_map="luks-${luks_uuid}"
printf '%s' "${_arg_lukspass}" | cryptsetup luksOpen "${candidate}" "${luks_map}" -
echo "/dev/mapper/${luks_map}"
printf 'luks-%s UUID=%s none %s\n' "${luks_uuid}" "${luks_uuid}" "${options}" >> "${scratchtree}/etc/crypttab"
}
### END OF FUNCTIONS
# get a nice temp directory.
_WORKDIR="$(env TMPDIR=/tmp mktemp -d)"
export TMPDIR="${_WORKDIR}"
# if we are using ZFS, check for transient-hostid
[[ "${_arg_use_zfs}" == "on" ]] && {
[[ -f /run/zfs/transient-hostid ]] || {
infomsg "we need /var/run/transient-hostid for ZFS manipulation (are you in an installenv?)"
exit 1
}
}
# exclude the running root disk from fs-layout
_arg_exclude+=("$(key2disk 'MOUNTPOINT="/"')")
# exlude any iodds from fs-layout
iodds=()
IFS=" " read -r -a iodds <<< "$(return_iodds)"
_arg_exclude+=("${iodds[@]}")
# gather any candidate devices
all_disks=$(get_baseblocks)
disknr=$(count_words "${all_disks}")
[[ "${disknr}" -lt 1 ]] && { infomsg "no suitable disk devices discovered" ; exit 1 ; }
infomsg "considering ${disknr} disk devices - ${all_disks}"
if [[ "${disknr}" -eq 1 ]] ; then
# if we only have one disk do this
candidate_disks="${all_disks}"
elif [[ "${disknr}" -eq 2 ]] ; then
# we're going to guess raid1...
candidate_disks="${all_disks}"
elif [[ "${disknr}" -gt 2 ]] ; then
# do we have flash or spinny disks?
candidate_disks=$(get_baseblocks rotational)
flash_disks=$(get_baseblocks flash)
fi
# we want to know how many devices are going into arrays
candidate_disk_nr=$(count_words "${candidate_disks}")
flash_disk_nr=$(count_words "${flash_disks:-}")
infomsg "proceed?"
read -r input
case "${input}" in
y*|Y*) : ;;
*) infomsg "aborting" ; exit 1 ;;
esac
# create mixin tree
scratchtree="$(mktemp -d)"
mkdir -p "${scratchtree}/etc"/{keys,mdadm}
fstab="${scratchtree}/etc/fstab"
touch "${fstab}"
[[ "${_arg_use_zfs}" == "on" ]] && {
cp /run/zfs/transient-hostid "${scratchtree}/etc/hostid"
}
if [[ "${_arg_target_hostname}" ]] ; then
printf '%s\n' "${_arg_target_hostname}" > "${scratchtree}/etc/hostname"
fi
# wipe partitions now
for disk in ${candidate_disks} ${flash_disks:-}; do
wipedisk "${disk}"
done
# holding variables for mdadm and such
bios_bootdevs=""
efi_bootdevs=""
sys_bootdevs=""
sys_devs=""
data_devs=""
cache_devs=""
sys_swapdevs=""
# new partition tables
for disk in ${candidate_disks} ; do
while read -r kv ; do
key="${kv%=*}" ; val="${kv#*=}"
case "${key}" in
biosboot) printf -v bios_bootdevs '%s%s ' "${bios_bootdevs}" "${val}" ;;
efiboot) printf -v efi_bootdevs '%s%s ' "${efi_bootdevs}" "${val}" ;;
sysboot) printf -v sys_bootdevs '%s%s ' "${sys_bootdevs}" "${val}" ;;
sysswap) printf -v sys_swapdevs '%s%s ' "${sys_swapdevs}" "${val}" ;;
system) printf -v sys_devs '%s%s ' "${sys_devs}" "${val}" ;;
data) printf -v data_devs '%s%s ' "${data_devs}" "${val}" ;;
esac
done < <(partition_disk "${disk}" "${candidate_disk_nr}")
done
# take a moment to consider the flash drives.
for disk in ${flash_disks:-} ; do
while read -r kv ; do
key=${kv%=*} ;val=${kv#*=}
case ${key} in
cache) printf -v cache_devs '%s%s ' "${cache_devs}" "${val}" ;;
esac
done < <(partition_cache "${disk}" "${flash_disk_nr}")
done
# adjust raid levels depending on numbers of disks
efiboot_raid_level=1
sysboot_raid_level=1
system_raid_level=1
data_raid_level=1
cache_raid_level=1
swap_raid_level=1
# if we have 4 or more drives, switch to raid10/raid6 for system/data
if [ "${candidate_disk_nr}" -ge 4 ] ; then
system_raid_level=10
data_raid_level=6
swap_raid_level=10
fi
# create arrays/partitions - EFI is set up regardless of ZFS
if [[ "${candidate_disk_nr}" -gt 1 ]] ; then
ready_md efi "${efiboot_raid_level}" "${efi_bootdevs}" "efi" "/boot/efi"
else
for part in ${efi_bootdevs} ; do ready_part "${part}" "efi" "/boot/efi" ; done
fi
# if we have ZFS requested, start pool setup...
if [[ "${_arg_use_zfs}" == "on" ]] ; then
if [[ "${candidate_disk_nr}" -gt 1 ]] ; then
ready_zfs_sys mirror ${sys_devs}
ready_zfs_boot mirror ${sys_bootdevs}
ready_md swap "${swap_raid_level}" "${sys_swapdevs}" "swap" "swap"
else
# create the system pool first, then the boot, due to ZFS loving to mount
for part in ${sys_devs} ; do ready_zfs_sys "${part}" ; done
for part in ${sys_bootdevs} ; do ready_zfs_boot "${part}" ; done
for part in ${sys_swapdevs} ; do ready_part "${part}" "swap" "swap" ; done
fi
else
# do the MD or partition layout for lvm/luks
if [[ "${candidate_disk_nr}" -gt 1 ]] ; then
ready_md boot "${sysboot_raid_level}" "${sys_bootdevs}" "ext2" "/boot"
ready_md system "${system_raid_level}" "${sys_devs}" "lvmpv" "pv.0"
if [[ "${_arg_data_partition}" == "on" ]] ; then
ready_md data "${data_raid_level}" "${data_devs}" "lvmpv" "pv.1"
fi
else
for part in ${sys_bootdevs} ; do ready_part "${part}" "ext2" "/boot" ; done
for part in ${sys_devs} ; do ready_part "${part}" "lvmpv" "pv.0" ; done
if [[ "${_arg_data_partition}" == "on" ]] ; then
for part in ${data_devs} ; do ready_part "${part}" "lvmpv" "pv.1" ; done
fi
fi
fi
# we will create a bcache setup _if_ we are using the data partition
[[ "${_arg_data_partition}" == "on" ]] && {
# partitions/arrays
if [[ "${flash_disk_nr}" -gt 1 ]] ; then
ready_md cache "${cache_raid_level}" "${cache_devs}" "bcache" "bcache0"
elif [[ "${flash_disk_nr}" -eq 1 ]] ; then
for part in ${cache_devs} ; do
ready_part "${part}" "bcache" "bcache0"
done
fi
# create bcache device if we have flash disks
bcache_backing=""
bcache_cache=""
# we need the backing device for bcache
if [[ "${candidate_disk_nr}" -gt 1 ]] ; then
bcache_backing=/dev/md/data
else
for part in ${data_devs} ; do
bcache_backing="${part}"
done
fi
# determine the cache device here
if [[ "${flash_disk_nr}" -gt 1 ]] ; then
bcache_cache=/dev/md/cache
bcache_bdev="$(readlink "${bcache_cache}")"
bcache_bdev="${bcache_bdev##*/}"
else
for part in ${cache_devs} ; do
bcache_cache="${part}"
bcache_bdev="${part##*/}"
done
fi
# create the bcache setup if we filled out a cache device
[[ "${bcache_cache}" ]] && {
# devices
make-bcache --data-offset 161280k --block 4k --bucket 4M -B "${bcache_backing}"
make-bcache --block 4k --bucket 4M -C "${bcache_cache}"
# now, we need to get the uuid off the cache device and bind it to the backing one.
cacheuuid="$(bcache-super-show "${bcache_cache}" | awk '$1 ~ "cset.uuid" { print $2 }')"
# wait for backing device to become attachable
while [[ ! -f /sys/block/bcache0/bcache/attach ]] ; do sleep 1 ; done
# wait for cache device to become attachable
while [[ ! -e "/sys/block/${bcache_bdev}/bcache" ]] ; do sleep 1 ; done
# plug it in
echo "${cacheuuid}" > /sys/block/bcache0/bcache/attach
# configure the cache mode
echo "writeback" > /sys/block/bcache0/bcache/cache_mode
}
}
# if we're asked to encrypt on not ZFS do that here...
data_luks_source=""
sys_luks_source=""
if [ "${_arg_data_partition}" == "on" ] ; then
if [ -b /dev/bcache0 ] ; then
data_luks_source="/dev/bcache0"
elif [ -e /dev/md/data ] ; then
data_luks_source=$(readlink /dev/md/data)
data_luks_source="/dev/md/${data_luks_source}"
else
for part in ${data_devs} ; do data_luks_source="${part}" ; done
fi
fi
if [ -e /dev/md/system ] ; then
sys_luks_source="/dev/md/system"
else
for part in ${sys_devs} ; do sys_luks_source="${part}" ; done
fi
if [[ "${_arg_use_zfs}" != "on" ]] ; then
if [[ "${_arg_lukspass}" ]] ; then
# set up encryption via cryptsetup
# anaconda sets the aes-xts-plain64 cipher out of the box. no bets on the rest tho. YOLO.
{
printf '%s' "${_arg_lukspass}" | cryptsetup luksFormat "${luksopts[@]}" "${sys_luks_source}"
if [[ "${data_luks_source}" ]] ; then
printf '%s' "${_arg_lukspass}" | cryptsetup luksFormat "${luksopts[@]}" "${data_luks_source}"
fi
}
fi
# create system volume group
system_pv=""
if [[ "${_arg_lukspass}" ]] ; then
# we have a cryptodev, open it and get the uuid
system_pv="$(luks_open "${sys_luks_source}")"
else
# handle array case
if [[ -e /dev/md/system ]] ; then
system_pv=/dev/md/system
else
for part in ${sys_devs} ; do system_pv="${part}" ; done
fi
fi
lvm_create system "${system_pv}"
# lvname vgname fstype sizeM (lmount)
ready_lv swap system swap 512
ready_lv root system ext4 4096 /
else
# configure the luks key path if we have that
[[ "${LUKS_ZFSKDEV:-}" ]] && {
zfs set "io.github.arrjay:luksholder=${LUKS_ZFSKDEV}" rpool
}
# by virtue of ZFS we already made root and boot datasets, make some other stuff.
zfs create -o mountpoint=none rpool/home
zfs create -o mountpoint=/root rpool/home/root
chmod 0700 "${_arg_targetpath}/root"
mkdir "${_arg_targetpath}/var"
mkdir "${_arg_targetpath}/var"/{log,cache,lib,tmp}
mkdir "${_arg_targetpath}/var/lib"/{containers,nfs,libvirt}
mkdir "${_arg_targetpath}/var/lib/containers/storage"
mkdir -p "${_arg_targetpath}/etc/untrustedhost/imd"
zfs create -o mountpoint=/var/log rpool/var-log
zfs create -o com.sun:auto-snapshot=false -o mountpoint=/var/cache rpool/var-cache
zfs create -o com.sun:auto-snapshot=false -o mountpoint=/var/lib/containers/storage rpool/containers-storage
zfs create -o com.sun:auto-snapshot=false -o mountpoint=/var/lib/nfs rpool/var-lib-nfs
zfs create -o mountpoint=/var/lib/libvirt rpool/libvirt
zfs create -o com.sun:auto-snapshot=false rpool/libvirt/images
zfs create -o com.sun:auto-snapshot=false -o mountpoint=/var/tmp rpool/var-tmp
chmod 1777 "${_arg_targetpath}/var/tmp"
zfs create rpool/srv
zfs create -o mountpoint=/etc/untrustedhost/imd -o copies=2 rpool/imd
fi
[[ "${_arg_data_partition}" == "on" ]] && {
# data
data_pv=""
if [[ "${_arg_lukspass}" ]] ; then
data_pv="$(luks_open "${data_luks_source}")"
else
if [[ -e /dev/bcache0 ]] ; then
data_pv=/dev/bcache0
elif [[ -e /dev/md/data ]] ; then
data_pv=/dev/md/data
else
for part in ${data_devs} ; do data_pv="${part}" ; done
fi
fi
lvm_create data "${data_pv}"
# create a thinpool on it
lvcreate -An -l100%FREE --type thin-pool --thinpool thinpool data
# udev/kernel sync
udevadm settle
}
# at this point we've virtualized away block devices :) go forth and create logical volumes?
[[ "${_arg_data_partition}" == "on" ]] && {
ready_thin var data thinpool ext4 8192 /var
}
# mount errything, making needed parent fs directories as we go.
make_n_mount
# copy scratchtree to new system
tar cf - -C "${scratchtree}" . | tar xf - -C "${_arg_targetpath}"
# edit fstab on new system
sed -e 's@'"${_arg_targetpath}"'@@g' -i "${_arg_targetpath}/etc/fstab"
# if we set up luks, rekey the data pv now.
[[ "${_arg_data_partition}" == "on" ]] && [[ "${_arg_lukspass}" ]] && {
dd if=/dev/random of="${_arg_targetpath}/etc/keys/datavol.luks" bs=1 count=32
printf '%s' "${_arg_lukspass}" | cryptsetup luksAddKey "${data_luks_source}" "${_arg_targetpath}/etc/keys/datavol.luks" -
printf '%s' "${_arg_lukspass}" | cryptsetup luksRemoveKey "${data_luks_source}"
}
mkdir -p "${_arg_targetpath}/root"
printf 'BIOS_BOOTDEVS="%s"\n' "${bios_bootdevs}" > "${_arg_targetpath}/root/fs-env"
# move scratchtree to overlay handoff
sed -e 's@'"${_arg_targetpath}"'@@g' -i "${scratchtree}/etc/fstab"
rm -rf /tmp/overlay
mv "${scratchtree}" /tmp/overlay
[[ -n "${ZFS_FSKEY:-}" ]] && { cat "${ZFS_FSKEY}" ; rm -f "${ZFS_FSKEY}" ; }
# ^^^ TERMINATE YOUR CODE BEFORE THE BOTTOM ARGBASH MARKER ^^^
# ] <-- needed because of Argbash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment