Skip to content

Instantly share code, notes, and snippets.

@jose-pr
Last active January 23, 2023 03:25
Show Gist options
  • Save jose-pr/a59ce29b4455e8539f604cd922fc118d to your computer and use it in GitHub Desktop.
Save jose-pr/a59ce29b4455e8539f604cd922fc118d to your computer and use it in GitHub Desktop.
Truenas Install mods on install for zfsbootmenu with encryption in zpool
#!/bin/sh
# vim: noexpandtab ts=8 sw=4 softtabstop=4
# Setup a semi-sane environment
PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH
HOME=/root
export HOME
TERM=${TERM:-xterm}
export TERM
AVATAR_PROJECT="TrueNAS"
if [ -e "/etc/version" ]; then
AVATAR_VERSION=$(cat /etc/version)
fi
# Boot Pool
BOOT_POOL="boot-pool"
NEW_BOOT_POOL="boot-pool"
PASS="bootpool"
INSTALL_SCRIPT_ROOT="/remote/scale-build"
# Constants for base 10 and base 2 units
: ${kB:=$((1000))} ${kiB:=$((1024))}; readonly kB kiB
: ${MB:=$((1000 * kB))} ${MiB:=$((1024 * kiB))}; readonly MB MiB
: ${GB:=$((1000 * MB))} ${GiB:=$((1024 * MiB))}; readonly GB GiB
: ${TB:=$((1000 * GB))} ${TiB:=$((1024 * GiB))}; readonly TB TiB
# The old pre-install checks did several things
# 1: Don't allow going from FreeNAS to TrueNAS or vice versa
# 2: Don't allow downgrading. (Not sure we can do that now.)
# 3: Check memory size and cpu speed.
# This does memory size only for now.
pre_install_check()
{
# We need at least 8 GB of RAM
# minus 1 GB to allow for reserved memory
local minmem=$((7 * GiB))
local kbsize=$(awk '/MemTotal/ { print $2 }' /proc/meminfo)
local memsize=$((kbsize * kiB))
if [ ${memsize} -lt ${minmem} ]; then
dialog --clear --title "${AVATAR_PROJECT}" --defaultno \
--yesno "This computer has less than the recommended 8 GB of RAM.\n\nOperation without enough RAM is not recommended. Continue anyway?" 7 74 || return 1
fi
return 0
}
wait_keypress()
{
local _tmp
read -p "Press ENTER to continue." _tmp
}
# Constant media size threshold for allowing swap partitions.
: ${MIN_SWAPSAFE_MEDIASIZE:=$((60 * GB))}; readonly MIN_SWAPSAFE_MEDIASIZE
# Check if it is safe to create swap partitions on the given disks.
#
# The result can be forced by setting SWAP_IS_SAFE in the environment to either
# "YES" or "NO".
#
# Sets SWAP_IS_SAFE to "YES" if
# every disk in $@ is >= ${MIN_SWAPSAFE_MEDIASIZE}
# and none is USB
# and user says ok
# Otherwise sets SWAP_IS_SAFE to "NO".
#
# Use `is_swap_safe` to check the value of ${SWAP_IS_SAFE}.
check_is_swap_safe()
{
local _disk _size
# We try to use the existing value for ${SWAP_IS_SAFE} if already set.
if [ -z "${SWAP_IS_SAFE}" ] ; then
# Check every disk in $@, aborting if an unsafe disk is found.
for _disk ; do
_size=$(get_disk_size /dev/${_disk})
if [ ${_size} -lt ${MIN_SWAPSAFE_MEDIASIZE} ] ||
grep -qF "1" /sys/block/${_disk}/removable ; then
SWAP_IS_SAFE="NO"
break
fi
done
fi
# Make sure we have a valid value for ${SWAP_IS_SAFE}.
# If unset, we didn't find an unsafe disk.
case "${SWAP_IS_SAFE:="YES"}" in
# Accept YES or NO (case-insensitive).
[Yy][Ee][Ss])
# Confirm swap setup
if ! dialog --clear --title "${AVATAR_PROJECT}" \
--yes-label "Create swap" --no-label "No swap" --yesno \
"Create 16GB swap partition on boot devices?" \
7 74 ; then
SWAP_IS_SAFE="NO"
fi
;;
[Nn][Oo]) ;;
# Reject other values.
*) echo "Ignoring invalid value for SWAP_IS_SAFE: ${SWAP_IS_SAFE}"
unset SWAP_IS_SAFE
check_is_swap_safe "$@"
;;
esac
export SWAP_IS_SAFE
}
# A specialized checkyesno for SWAP_IS_SAFE.
# Returns 0 if it is ok to set up swap on the chosen disks, otherwise 1.
# `check_is_swap_safe` must be called once before calling `is_swap_safe`.
is_swap_safe()
{
case "${SWAP_IS_SAFE:?}" in
[Yy][Ee][Ss]) true;;
*) false;;
esac
}
get_physical_disks_list()
{
local _disk
ls /sys/block/ |
# Skip inappropriate devices.
awk '!/^(md|dm|sr|st|loop)/' |
while read _disk; do
if [ -b /dev/${_disk} ] &&
[ $(get_disk_size /dev/${_disk}) -gt ${MIN_ZFS_PARTITION_SIZE} ] &&
! disk_is_mounted ${_disk}; then
echo ${_disk}
fi
done
}
get_partition()
{
local _partition=$(ls /dev/$1$2 /dev/$1p$2 2>/dev/null)
if [ -z $_partition ]; then
echo CANT_FIND_$1$2_OR_$1p$2
else
echo $_partition
fi
}
get_media_description()
{
local _media
local _description
local _cap
local _lsblk
local _label
local _root_fstype
local _children
_media=$1
if [ -n "${_media}" ]; then
_description=`sgdisk -p /dev/${_media} | grep "Model:" \
| cut -d ' ' -f 2-`
if [ -z "${_description}" ]; then
_description="Unknown Device"
fi
# need to settle so that lsblk output is stable
udevadm settle
_lsblk=$(lsblk -fJ -o fstype,name,label "/dev/${_media}" | jq .blockdevices[0])
_root_fstype=$(echo "${_lsblk}" | jq .fstype)
_children=$(echo "${_lsblk}" | jq .children)
if [ "${_root_fstype}" = "null" ] && [ "${_children}" = "null" ]; then
# no fs info in lsblk output
_label=""
elif [ "${_root_fstype}" != "null" ]; then
_label=$(echo "${_root_fstype}" | cut -c -15)
else
_label=$(echo "${_children}" | jq '[.[] | select(.fstype=="zfs_member")][0].label')
if [ "${_label}" = "null" ]; then
# disk is not ZFS, work way through a few different possibilties
# start with looking for ext4 partition, then xfs, finally settle
# on anything that has a fstype populated
_label=$(echo "${_children}" | jq '[.[] | select(.fstype=="ext4")][0]')
if [ "${_label}" = "null" ]; then
_label=$(echo "${_children}" | jq '[.[] | select(.fstype=="xfs")][0]')
fi
if [ "${_label}" = "null" ]; then
_label=$(echo "${_children}" | jq '[.[] | select(.fstype!="null")][0]')
fi
if [ "${_label}" = "null" ]; then
# no child partitions have fstype info
_label=""
else
_root_fstype=$(echo "${_label}" | jq .fstype | tr -d '"')
_label=$(echo "${_label}" | jq .label)
if [ "${_label}" = "null" ]; then
_label=$(echo "${_root_fstype}" | cut -c -15)
else
_label=$(echo "${_root_fstype}-${_label}" | cut -c -15)
fi
fi
else
echo "media: /dev/${_media} -- label: ${_label}" >> /log.install
_label=$(echo "zfs-${_label}" | cut -c -15)
fi
fi
_cap=`sgdisk -p /dev/${_media} | grep "Disk /dev/${_media}" | cut -d ' ' -f 5-6`
echo "${_description} ${_label} -- ${_cap}"
fi
}
disk_is_mounted()
{
local _dev
for _dev
do
if mount -v | grep -qE "^/dev/${_dev}p?[0-9]+"
then
return 0
fi
done
return 1
}
new_install_verify()
{
local _type="$1"
shift
local _upgradetype="$1"
shift
local _disks="$*"
local _tmpfile="/tmp/msg"
cat << EOD > "${_tmpfile}"
WARNING:
EOD
if [ "$_upgradetype" = "inplace" ] ; then
echo "- This will install into existing zpool on ${_disks}." >> ${_tmpfile}
else
echo "- This will erase ALL partitions and data on ${_disks}." >> ${_tmpfile}
fi
cat << EOD >> "${_tmpfile}"
- You can't use ${_disks} for sharing data.
NOTE:
- Installing on SATA, SAS, or NVMe flash media is recommended.
USB flash sticks are discouraged.
Proceed with the ${_type}?
EOD
_msg=`cat "${_tmpfile}"`
rm -f "${_tmpfile}"
dialog --clear --title "$AVATAR_PROJECT ${_type}" --yesno "${_msg}" 13 74
[ $? -eq 0 ] || abort
}
ask_upgrade()
{
local _disk="$1"
local _tmpfile="/tmp/msg"
cat << EOD > "${_tmpfile}"
Upgrading the installation will preserve your existing configuration.
Do you wish to perform an upgrade or a fresh installation on ${_disk}?
EOD
_msg=`cat "${_tmpfile}"`
rm -f "${_tmpfile}"
dialog --title "Upgrade this $AVATAR_PROJECT installation" --no-label "Fresh Install" --yes-label "Upgrade Install" --yesno "${_msg}" 8 74
return $?
}
ask_upgrade_inplace()
{
local _tmpfile="/tmp/msg"
cat << EOD > "${_tmpfile}"
User configuration settings and storage volumes are preserved and not affected by this step.\n\n
The boot device can be formatted to remove old versions, or the upgrade can be installed in a new boot environment without affecting any existing versions.
EOD
_msg=`cat "${_tmpfile}"`
rm -f "${_tmpfile}"
dialog --trim --title "Update Method Selection" --yes-label "Install in new boot environment" --no-label "Format the boot device" --yesno "${_msg}" 0 0
return $?
}
ask_boot_method()
{
# If we are not on efi, set BIOS as the default selected option
dlgflags=""
if [ "$BOOTMODE" != "UEFI" ] ; then
dlgflags="--defaultno"
fi
local _tmpfile="/tmp/msg"
cat << EOD > "${_tmpfile}"
$AVATAR_PROJECT can be booted in either BIOS or UEFI mode.
BIOS mode is recommended for legacy and enterprise hardware,
whereas UEFI may be required for newer consumer motherboards.
EOD
_msg=`cat "${_tmpfile}"`
rm -f "${_tmpfile}"
dialog ${dlgflags} --title "$AVATAR_PROJECT Boot Mode" --no-label "Boot via BIOS" --yes-label "Boot via UEFI" --yesno "${_msg}" 8 74
return $?
}
install_loader()
{
local _disk _disks
local _mnt partition_disk
_mnt="$1"
shift
_disks="$*"
# Tell GRUB we are booting from ZFS
echo "GRUB_CMDLINE_LINUX=\"root=ZFS=${BOOT_POOL}/ROOT/default\"" >> ${_mnt}/etc/default/grub
mkdir -p ${_mnt}/boot/efi
chroot ${_mnt} update-initramfs -c -k $(uname -r) -v
chroot ${_mnt} update-grub
for _disk in $_disks
do
echo "Stamping GPT loader on: /dev/${_disk}"
chroot ${_mnt} grub-install --target=i386-pc /dev/${_disk}
partition_disk=$(get_partition $_disk 2)
echo "Stamping EFI loader on: ${_disk}"
chroot ${_mnt} mkdosfs -F 32 -s 1 -n EFI ${partition_disk}
chroot ${_mnt} mount -t vfat ${partition_disk} /boot/efi
chroot ${_mnt} grub-install --target=x86_64-efi \
--efi-directory=/boot/efi \
--bootloader-id=debian --recheck --no-floppy
chroot ${_mnt} mkdir -p /boot/efi/EFI/boot
chroot ${_mnt} cp /boot/efi/EFI/debian/grubx64.efi /boot/efi/EFI/boot/bootx64.efi
chroot ${_mnt} umount /boot/efi
done
return 0
}
save_serial_settings()
{
# If the installer was booted with serial mode enabled, we should
# save these values to the installed system
dmesg|grep "Kernel command line"|grep -q "console=ttyS"
USESERIAL=$?
if [ "$USESERIAL" -ne 0 ] ; then return 0; fi
echo "update system_advanced set adv_serialconsole = 1;"
tty=$(dmesg|grep -E "ttyS[0-9] at I/O"|head -1|awk '{print $4}')
SERIALSPEED=$(setserial -G /dev/$tty|awk '{print $9}')
if [ -n "$SERIALSPEED" ] ; then
echo "update system_advanced set adv_serialspeed = $SERIALSPEED;"
fi
if [ -n "$tty" ] ; then
echo "update system_advanced set adv_serialport = '$tty';"
fi
}
get_disk_info()
{
lsblk --bytes --nodeps --noheadings --output "$@"
}
get_disk_size()
{
get_disk_info size "$@"
}
get_disk_logical_sector_size()
{
get_disk_info log-sec "$@"
}
get_disk_part_type()
{
get_disk_info parttype "$@"
}
create_partitions()
{
local _disk="$1"
local _sector_size=$(get_disk_logical_sector_size /dev/${_disk})
local _alignment_multiple=4096
# Create BIOS boot partition
if ! sgdisk -a${_alignment_multiple} -n1:0:+1024K -t1:EF02 -A1:set:2 /dev/${_disk}; then
return 1
fi
# Create EFI partition (Even if not used, allows user to switch to UEFI later)
if ! sgdisk -n2:0:+524288K -t2:EF00 /dev/${_disk}; then
return 1
fi
mkdir -p /tmp/efi
mount /dev/${_disk}2 /tmp/efi
cp /remote/efi/* /tmp/efi -R
umount /tmp/efi
if is_swap_safe; then
if ! sgdisk -n4:0:+16777216K -t4:8200 /dev/${_disk}; then
return 1
fi
wipefs -a -t zfs_member $(get_partition ${_disk} 4)
fi
# Create boot pool
if ! sgdisk -n3:0:0 -t3:BF01 /dev/${_disk}; then
return 1
fi
return 0
}
get_minimum_size_and_partition()
{
local _min=0
local _disk
local _size
for _disk
do
if ! create_partitions ${_disk} 1>&2; then
echo "Could not do anything with ${_disk}, skipping" 1>&2
continue
fi
_size=$(get_disk_size $(get_partition ${_disk} 3))
if [ ${_min} -eq 0 -o ${_size} -lt ${_min} ]; then
_min=${_size}
fi
done
echo ${_min}
}
# Minimum required space for an installation.
# Docs state 8 GiB is the bare minimum, but we specify 8 GB here for wiggle room.
# That should leave enough slop for alignment, boot partition, etc.
: ${MIN_ZFS_PARTITION_SIZE:=$((8 * GB))}; readonly MIN_ZFS_PARTITION_SIZE
partition_disks()
{
local _disk _disks _disksparts
local _mirror
local _minsize
# Create and destroy existing pool (if exists)
zpool import -N -f ${BOOT_POOL} 2> /dev/null || true
zpool destroy -f ${BOOT_POOL} 2> /dev/null || true
_disks=$*
# Erase both typical metadata area.
for _disk in ${_disks}; do
sgdisk -Z /dev/${_disk} >/dev/null 2>&1 || true
sgdisk -Z /dev/${_disk} >/dev/null 2>&1 || true
done
check_is_swap_safe ${_disks}
_minsize=$(get_minimum_size_and_partition ${_disks})
if [ ${_minsize} -lt ${MIN_ZFS_PARTITION_SIZE} ]; then
echo "Disk is too small to install ${AVATAR_PROJECT}" 1>&2
return 1
fi
_disksparts=$(for _disk in ${_disks}; do
echo $(get_partition ${_disk} 3)
done)
if [ $# -gt 1 ]; then
_mirror="mirror"
else
_mirror=""
fi
# Regardless of upgrade/fresh installation, if we are creating a new pool, it's going to be named after value of NEW_BOOT_POOL
BOOT_POOL=${NEW_BOOT_POOL}
KEYSTORE="/etc/keystore"
mkdir -p "$KEYSTORE"
echo "$PASS" > "$KEYSTORE/$BOOT_POOL.key"
zpool create -f -o cachefile=/tmp/zpool.cache -o ashift=12 -d \
-o feature@async_destroy=enabled \
-o feature@bookmarks=enabled \
-o feature@embedded_data=enabled \
-o feature@empty_bpobj=enabled \
-o feature@enabled_txg=enabled \
-o feature@extensible_dataset=enabled \
-o feature@filesystem_limits=enabled \
-o feature@hole_birth=enabled \
-o feature@large_blocks=enabled \
-o feature@lz4_compress=enabled \
-o feature@spacemap_histogram=enabled \
-o feature@userobj_accounting=enabled \
-o feature@encryption=enabled \
-O acltype=off -O canmount=off -O compression=lz4 -O devices=off -O mountpoint=none \
-O normalization=formD -O relatime=on -O xattr=sa \
-O encryption=on -O keylocation=file://"$KEYSTORE/$BOOT_POOL.key" -O keyformat=passphrase \
${BOOT_POOL} ${_mirror} ${_disksparts}
zfs set compression=on ${BOOT_POOL}
cp "$KEYSTORE/$BOOT_POOL.key" "/tmp/$BOOT_POOL.key"
zfs create -o mountpoint=$KEYSTORE ${BOOT_POOL}/keystore
cp "/tmp/$BOOT_POOL.key" "$KEYSTORE/$BOOT_POOL.key"
chmod 000 "$KEYSTORE/$BOOT_POOL.key"
zfs umount "${BOOT_POOL}/keystore"
zfs create -o canmount=off ${BOOT_POOL}/ROOT
zfs set org.zfsbootmenu:keysource=${BOOT_POOL}/keystore ${BOOT_POOL}
return 0
}
disk_is_freenas()
{
local _disk="$1"
local _rv=1
local _uuid _os_part _disk_data
mkdir -p /tmp/data_old
# This code is very clumsy. There
# should be a way to structure it such that
# all of the cleanup happens as we want it to.
_os_part=$(get_partition ${_disk} 2) # freebsd-boot (1) + zfs (2)
_disk_data=$(zdb -l ${_os_part})
if [ $? -ne 0 ]; then
_os_part=$(get_partition ${_disk} 3) # bios-boot (1) + efi (2) + zfs (3)
_disk_data=$(zdb -l ${_os_part})
if [ $? -ne 0 ]; then
return 1
fi
fi
echo ${_disk_data} | grep -qF "name: '${BOOT_POOL}'"
if [ $? -eq 1 ]; then
echo ${_disk_data} | grep -qF "name: 'freenas-boot'" || return 1
BOOT_POOL="freenas-boot"
fi
zpool import -N -f ${BOOT_POOL} || return 1
# Now we want to figure out which dataset to use.
DS=$(zpool list -H -o bootfs ${BOOT_POOL} | head -n 1 | cut -d '/' -f 3)
if [ -z "$DS" ]; then
zpool export ${BOOT_POOL} || true
return 1
fi
zfs set mountpoint=legacy ${BOOT_POOL}/ROOT/"${DS}" || return 1
mount -t zfs ${BOOT_POOL}/ROOT/"${DS}" /tmp/data_old || return 1
# If the active dataset doesn't have a database file,
# then it's not FN as far as we're concerned (the upgrade code
# will go badly).
# We also check for the Corral database directory.
if [ ! -f /tmp/data_old/data/freenas-v1.db -o \
-d /tmp/data_old/data/freenas.db ]; then
umount /tmp/data_old || true
zpool export ${BOOT_POOL} || true
return 1
fi
(
cd /tmp/data_old &&
rsync -aRx \
--exclude data/factory-v1.db \
--exclude data/manifest.json \
data \
root \
/tmp/data_preserved/
)
if [ -f /tmp/data_old/bin/freebsd-version ]; then
(
cd /tmp/data_old &&
rsync -aRx \
bin/freebsd-version \
/tmp/data_preserved/
)
(
cd /tmp/data_old/conf/base &&
rsync -aRx \
etc/hostid \
/tmp/data_preserved/
)
_uuid=$(get_disk_parttype $(get_partition ${_disk} 1))
if [ "${_uuid}" = "83bd6b9d-7f41-11dc-be0b-001560b84f0f" ]; then # FreeBSD boot
sgdisk -t1:EF02 /dev/${_disk} || return 1
fi
else
(
cd /tmp/data_old &&
rsync -aRx \
etc/hostid \
etc/machine-id \
/tmp/data_preserved/
)
fi
umount /tmp/data_old || return 1
zpool export ${BOOT_POOL}
}
prompt_password()
{
local values value password="" password1 password2 _counter _tmpfile="/tmp/pwd.$$"
cat << __EOF__ > /tmp/dialogconf
bindkey formfield TAB FORM_NEXT
bindkey formfield DOWN FORM_NEXT
bindkey formfield UP FORM_PREV
bindkey formbox DOWN FORM_NEXT
bindkey formbox TAB FORM_NEXT
bindkey formbox UP FORM_PREV
__EOF__
export DIALOGRC="/tmp/dialogconf"
while true; do
dialog --insecure \
--extra-button --extra-label "Do not set password" \
--output-fd 3 \
--visit-items \
--passwordform "Enter your root password; cancel for cancelling installation" \
10 70 0 \
"Password:" 1 10 "" 0 30 25 50 \
"Confirm Password:" 2 10 "" 2 30 25 50 \
3> ${_tmpfile}
ret=$?
if [ $ret -eq 1 ]; then
rm -f ${_tmpfile}
return 1
fi
{ read password1 ; read password2; } < ${_tmpfile}
rm -f ${_tmpfile}
if [ $ret -eq 3 ]; then
password=""
break
elif [ "${password1}" != "${password2}" ]; then
dialog --msgbox "Passwords do not match." 7 60 2> /dev/null
elif [ -z "${password1}" ]; then
dialog --msgbox "Empty password is not secure" 7 60 2> /dev/null
else
password="${password1}"
break
fi
done
rm -f ${DIALOGRC}
unset DIALOGRC
echo -n "${password}" 1>&2
}
cleanup()
{
zpool export -f ${BOOT_POOL}
zpool export -f ${NEW_BOOT_POOL}
}
abort()
{
set +e +x
trap - EXIT
exit 1
}
fail()
{
local _action=${1}
shift
local _disks=${@}
set +x
read -p "The ${AVATAR_PROJECT} ${_action} on ${_disks} has failed. Press enter to continue..." junk
abort
}
doing_upgrade()
{
test -d /tmp/data_preserved
}
menu_install()
{
local _action
local _disklist
local _tmpfile
local _answer
local _cdlist
local _items
local _disk
local _disks=""
local _realdisks=""
local _disk_old
local _config_file
local _desc
local _list
local _msg
local _i
local _do_upgrade=""
local _msg
local _dlv
local _password
local os_part
local data_part
local whendone=""
local readonly CD_UPGRADE_SENTINEL="/data/cd-upgrade"
local readonly NEED_UPDATE_SENTINEL="/data/need-update"
# create a sentinel file for post-fresh-install boots
local readonly FIRST_INSTALL_SENTINEL="/data/first-boot"
local readonly TRUENAS_EULA_PENDING_SENTINEL="/data/truenas-eula-pending"
local readonly POOL=${BOOT_POOL}
_tmpfile="/tmp/answer"
TMPFILE=$_tmpfile
REALDISKS="/tmp/realdisks"
while getopts "U:P:X:" opt; do
case "${opt}" in
U) if ${OPTARG}; then _do_upgrade=1 ; else _do_upgrade=0; fi
;;
P) _password="${OPTARG}"
;;
X) case "${OPTARG}" in
reboot) whendone=reboot ;;
"wait") whendone=wait ;;
halt) whendone="shutdown now" ;;
*) whendone="" ;;
esac
;;
*) echo "Unknown option ${opt}" 1>&2
;;
esac
done
shift $((OPTIND-1))
if [ $# -gt 0 ]
then
_disks="$@"
INTERACTIVE=false
else
INTERACTIVE=true
fi
# Make sure we are working from a clean slate.
cleanup >/dev/null 2>&1
if ${INTERACTIVE}; then
pre_install_check || return 0
while [ -z "${_disks}" ]; do
_list=""
_items=0
for _disk in $(get_physical_disks_list); do
_desc=$(get_media_description "${_disk}" | sed "s/'/'\\\''/g")
_list="${_list} ${_disk} '${_desc}' off"
_items=$((${_items} + 1))
done
_tmpfile="/tmp/answer"
if [ ${_items} -eq 0 ]; then
# Inform the user
eval "dialog --title 'Choose destination media' --msgbox 'No drives available' 5 60" 2>${_tmpfile}
continue
fi
eval "dialog --title 'Choose destination media' \
--checklist 'Install $AVATAR_PROJECT to a drive. Multiple drives can be selected to provide redundancy. Chosen drives are not available for use in the TrueNAS UI. Arrow keys highlight options, spacebar selects.' \
20 60 0 ${_list}" 2>${_tmpfile}
if [ -f "${_tmpfile}" ]; then
_disks=$(eval "echo `cat "${_tmpfile}"`")
rm -f "${_tmpfile}"
fi
if [ -z "${_disks}" ]; then
dialog --msgbox "You need to select at least one disk!" 6 74
continue
fi
done
else
if [ -z "${_disks}" ] || disk_is_mounted ${_disks}; then
abort
fi
fi
_action="installation"
_upgrade_type="format"
# This needs to be re-done.
# If we're not interactive, then we have
# to assume _disks is correct.
# If we do have more than one disk given,
# we should also do something if they're all
# freenas disks. But how do we figure out which
# one to use? The current code in disk_is_freenas
# is very, very heavy -- it actually backs up the
# data from a freenas installation. It also does
# a zpool import.
for _disk in ${_disks}; do
if disk_is_freenas ${_disk} ; then
if ${INTERACTIVE}; then
if ask_upgrade ${_disk} ; then
_do_upgrade=1
_action="upgrade"
fi
else
if [ "${_do_upgrade}" != "0" ]; then
_do_upgrade=1
_action="upgrade"
fi
fi
# Ask if we want to do a format or inplace upgrade
if ${INTERACTIVE}; then
if ask_upgrade_inplace ; then
_upgrade_type="inplace"
fi
fi
break
fi
done
# If we haven't set _do_upgrade by now, we're not
# doing an upgrade.
if [ -z "${_do_upgrade}" ]; then
_do_upgrade=0
fi
_realdisks=${_disks}
${INTERACTIVE} && new_install_verify "${_action}" "${_upgrade_type}" ${_realdisks}
if ${INTERACTIVE} && [ ${_do_upgrade} -eq 0 ]; then
prompt_password 2> /tmp/password
if [ $? -eq 0 ]; then
_password="$(cat /tmp/password 2> /dev/null)"
else
echo "Installation cancelled"
exit 1
fi
fi
if [ ${_do_upgrade} -eq 0 ]; then
# With the new partitioning, disk_is_freenas may
# copy /data. So if we don't need it, remove it,
# or else it'll do an update anyway. Oops.
rm -rf /tmp/data_preserved
fi
# Start critical section.
trap "fail ${_action} ${_realdisks}" EXIT
set -e
# set -x
# _disk, _image, _config_file
if [ "${_upgrade_type}" = "inplace" ]
then
# When doing new-style upgrades, we can keep the old zpool
# and instead do a new BE creation
zpool import -N -f ${BOOT_POOL}
zfs create -o canmount=off -o mountpoint=legacy ${BOOT_POOL}/grub || true
else
# Destroy existing partition table, if there is any but tolerate
# failure.
for _disk in ${_realdisks}; do
wipefs -a /dev/${_disk} || echo Warning: unable to wipe partition table
done
# We repartition on fresh install, or old upgrade_style
# This destroys all of the pool data, and
# ensures a clean filesystems.
partition_disks ${_realdisks}
fi
local OS=TrueNAS
# Mount update image
umount /mnt || true
mount /cdrom/TrueNAS-SCALE.update /mnt -t squashfs -o loop
space_required=$(cat /mnt/manifest.json | jq ".size")
free_space=$(zpool get -H -o value -p free "$BOOT_POOL")
if [ "$free_space" -lt "$space_required" ] ; then
${INTERACTIVE} && dialog --msgbox "Insufficient disk space available. TrueNAS requires \
$(numfmt --to=iec-i --suffix=B --format="%.1f" $space_required) but only \
$(numfmt --to=iec-i --suffix=B --format="%.1f" $free_space) are available" 6 74
trap - EXIT
exit 1
fi
local sql="$(save_serial_settings | tr '\n' ' ')"
local json=$(cat <<EOF
{"disks": [], "force_grub_install": true, "pool_name": "${BOOT_POOL}", "sql": "${sql}", "src": "/mnt"}
EOF
)
for _disk in ${_realdisks}; do
json="$(echo "$json" | jq --arg v "${_disk}" '.disks += [$v]' -)"
done
if doing_upgrade; then
json="$(echo "$json" | jq --arg v "/tmp/data_preserved" '.old_root = $v' -)"
else
if [ -n "${_password}" ]; then
json="$(echo "$json" | jq --arg v "${_password}" '.password = $v' -)"
fi
fi
(cd "$INSTALL_SCRIPT_ROOT" && echo "$json" | python3 -m truenas_install)
# TODO: Bring this back
if doing_upgrade; then
${INTERACTIVE} && dialog --msgbox "The installer has preserved your database file.
$AVATAR_PROJECT will migrate this file, if necessary, to the current format." 6 74
fi
umount -f /mnt
# Export the pool now
zpool export ${BOOT_POOL}
# End critical section.
set +e
trap - EXIT
_msg="The $AVATAR_PROJECT $_action on ${_realdisks} succeeded!\n"
_msg="${_msg}Please reboot and remove the installation media."
if ${INTERACTIVE}; then
dialog --msgbox "$_msg" 6 74
elif [ -n "${whendone}" ]; then
case "${whendone}" in
halt) shutdown now ;;
"wait") dialog --msgbox "$_msg" 6 74 ;;
esac
return 0
fi
return 0
}
menu_shell()
{
/bin/sh
}
menu_reboot()
{
echo "Rebooting..."
reboot >/dev/null
}
menu_shutdown()
{
echo "Halting and powering down..."
shutdown now >/dev/null
}
report_installation()
{
for iface in /sys/class/net/*; do
name=${iface##*/};
if [ $name = "lo" ]; then
continue;
fi
udhcpc -i "${name}";
done
hash=$(cat /etc/hostid | sha256sum | tr -d "[:space:]-");
version=$(cat /etc/version);
usage_str=$( jq -n \
--arg p "TrueNAS-SCALE" \
--arg h "${hash}" \
--argjson v "[{\"version\": \"$version\"}]" \
'{system_hash: $h, platform: $p, "install": $v}' )
echo $usage_str | curl -X POST -m 15 -d '@-' http://usage.freenas.org/submit
echo "Completed reporting installation"
}
main()
{
local _tmpfile="/tmp/answer"
local _number
if [ $# -gt 0 ]; then
# $1 will have the device name
menu_install "$@"
exit $?
fi
# Keep ZFS happy
[ -f /etc/hostid ] || zgenhostid
depmod
modprobe zfs
if [ ! -f /tmp/usage_stats.log ]; then
[ -f /etc/version ] && report_installation > /tmp/usage_stats.log 2>&1 &
fi
while :; do
dialog --clear --title "$AVATAR_PROJECT $AVATAR_VERSION Console Setup" --menu "" 12 73 6 \
"1" "Install/Upgrade" \
"2" "Shell" \
"3" "Reboot System" \
"4" "Shutdown System" \
2> "${_tmpfile}"
_number=`cat "${_tmpfile}"`
case "${_number}" in
1) menu_install ;;
2) menu_shell ;;
3) menu_reboot ;;
4) menu_shutdown ;;
esac
# Unset cached setting
unset SWAP_IS_SAFE
done
}
# Parse a config file.
# We don't do much in the way of error checking.
# Format is very simple:
# <opt>=<value>
# <value> may be a list (e.g., disk devices)
# The output is suitable to be used as the arguments
# to main(), which will directl ycall menu_install().
yesno()
{
# Output "true" or "false" depending on the argument
if [ $# -ne 1 ]; then
echo "false"
return 0
fi
case "$1" in
[yY][eE][sS] | [tT][rR][uU][eE]) echo true ;;
*) echo false;;
esac
return 0
}
getsize()
{
# Given a size specifier, convert it to bytes.
# No suffix, or a suffix of "[bBcC]", means bytes;
# [kK] is 1024, etc.
if [ $# -ne 1 ]; then
echo 0
return 0
fi
case "$1" in
*[bB][cC]) expr "$1" : "^\([0-9]*\)[bB][cC]" || echo 0;;
*[kK]) expr $(expr "$1" : "^\([0-9]*\)[kK]") \* 1024 || echo 0;;
*[mM]) expr $(expr "$1" : "^\([0-9]*\)[gG]") \* 1024 \* 1024 || echo 0;;
*[gG]) expr $(expr "$1" : "^\([0-9]*\)[gG]") \* 1024 \* 1024 \* 1024 || echo 0;;
*[tT]) expr $(expr "$1" : "^\([0-9]*\)[tT]") \* 1024 \* 1024 \* 1024 \* 1024 || echo 0;;
*) expr "$1" : "^\([0-9]*\)$" || echo 0;;
esac
return 0
}
main "$@"
# -*- coding=utf-8 -*-
import contextlib
import itertools
import json
import logging
import os
import pathlib
import platform
import re
import shutil
import sqlite3
import stat
import subprocess
import sys
import tempfile
import textwrap
import psutil
logger = logging.getLogger(__name__)
EFI_SYSTEM_PARTITION_GUID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
FREEBSD_BOOT_PARTITION_GUID = "83BD6B9D-7F41-11DC-BE0B-001560B84F0F"
CORE_BSD_LOADER_PATH = "/boot/efi/efi/boot/BOOTx64.efi"
SCALE_BSD_LOADER_PATH = "/boot/efi/efi/boot/FreeBSD.efi"
RE_UNSQUASHFS_PROGRESS = re.compile(r"\[.+\]\s+(?P<extracted>[0-9]+)/(?P<total>[0-9]+)\s+(?P<progress>[0-9]+)%")
run_kw = dict(check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", errors="ignore")
IS_FREEBSD = platform.system().upper() == "FREEBSD"
is_json_output = False
def write_progress(progress, message):
if is_json_output:
sys.stdout.write(json.dumps({"progress": progress, "message": message}) + "\n")
else:
sys.stdout.write(f"[{int(progress * 100)}%] {message}\n")
sys.stdout.flush()
def write_error(error, raise_=False):
if is_json_output:
sys.stdout.write(json.dumps({"error": error}) + "\n")
else:
sys.stdout.write(f"Error: {error}\n")
sys.stdout.flush()
if raise_:
raise Exception(error)
def run_command(cmd, **kwargs):
try:
return subprocess.run(cmd, **dict(run_kw, **kwargs))
except subprocess.CalledProcessError as e:
write_error(f"Command {cmd} failed with exit code {e.returncode}: {e.stderr}")
raise
def get_partition(disk, partition):
paths = [f"/dev/{disk}{partition}", f"/dev/{disk}p{partition}"]
for path in paths:
if os.path.exists(path):
return path
raise Exception(f"Neither {' or '.join(paths)} exist")
def get_partition_guid(disk, partition):
return dict(map(
lambda s: s.split(": ", 1),
run_command(["sgdisk", "-i", str(partition), f"/dev/{disk}"]).stdout.splitlines(),
))["Partition GUID code"].split()[0]
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def query_config_table(table, database_path, prefix=None):
database_path = database_path
conn = sqlite3.connect(database_path)
try:
conn.row_factory = dict_factory
c = conn.cursor()
try:
c.execute(f"SELECT * FROM {table}")
result = c.fetchone()
finally:
c.close()
finally:
conn.close()
if prefix:
result = {k.replace(prefix, ""): v for k, v in result.items()}
return result
def configure_serial_port(root, db_path):
if not os.path.exists(db_path):
return
# We would like to explicitly enable/disable serial-getty in the new BE based on db configuration
advanced = query_config_table("system_advanced", db_path, prefix="adv_")
if advanced["serialconsole"]:
run_command(
["chroot", root, "systemctl", "enable", f"serial-getty@{advanced['serialport']}.service"], check=False
)
def enable_system_user_services(root, old_root):
configure_serial_port(root, os.path.join(old_root, "data/freenas-v1.db"))
enable_user_services(root, old_root)
def enable_user_services(root, old_root):
user_services_file = os.path.join(old_root, "data/user-services.json")
if not os.path.exists(user_services_file):
return
with open(user_services_file, 'r') as f:
systemd_units = [
srv for srv, enabled in json.loads(f.read()).items() if enabled
]
if systemd_units:
run_command(["chroot", root, "systemctl", "enable"] + systemd_units, check=False)
def install_grub_freebsd(input, manifest, pool_name, dataset_name, disks):
boot_partition_type = None
for disk in disks:
gpart_backup = run_command(["gpart", "backup", disk]).stdout.splitlines()
partition_table_type = gpart_backup[0].split()[0]
if partition_table_type == "GPT":
boot_partition_type_probe = gpart_backup[1].split()[1]
if boot_partition_type_probe not in ["bios-boot", "freebsd-boot", "efi"]:
write_error(f"Invalid first partition type {boot_partition_type_probe} on {disk}", raise_=True)
if boot_partition_type and boot_partition_type != boot_partition_type_probe:
write_error("Non-matching first partition types across disks", raise_=True)
boot_partition_type = boot_partition_type_probe
else:
write_error(f"Invalid partition table type {partition_table_type} on {disk}", raise_=True)
run_command(["zpool", "set", f"bootfs={dataset_name}", pool_name])
for f in ["/usr/local/etc/grub.d/10_kfreebsd", "/usr/local/etc/grub.d/30_os-prober"]:
with contextlib.suppress(FileNotFoundError):
os.unlink(f)
os.makedirs("/usr/local/etc/default", exist_ok=True)
run_command(["truenas-grub.py"])
cmdline = run_command(["sh", "-c", ". /usr/local/etc/default/grub; echo $GRUB_CMDLINE_LINUX"]).stdout.strip()
for device in input["devices"]:
fs_uuid = run_command(["grub-probe", "--device", f"/dev/{device}", "--target=fs_uuid"]).stdout.strip()
if fs_uuid:
break
else:
write_error(f"None of {input['devices']!r} has GRUB fs_uuid", raise_=True)
grub_script_path = "/usr/local/etc/grub.d/10_truenas"
with open(grub_script_path, "w") as f:
freebsd_root_dataset = [p for p in psutil.disk_partitions() if p.mountpoint == "/"][0].device
run_command(["zfs", "set", "truenas:12=1", freebsd_root_dataset])
f.write(textwrap.dedent(f"""\
#!/bin/sh
cat << 'EOF'
menuentry 'TrueNAS SCALE' --class truenas --class gnu-linux --class gnu --class os """
f"""$menuentry_id_option 'gnulinux-simple-{fs_uuid}' {{
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod zfs
search --no-floppy --fs-uuid --set=root {fs_uuid}
echo 'Loading Linux {manifest['kernel_version']} ...'
linux /ROOT/{manifest['version']}@/boot/vmlinuz-{manifest['kernel_version']} """
f"""root=ZFS={dataset_name} ro {cmdline} console=tty1 zfs_force=yes
echo 'Loading initial ramdisk ...'
initrd /ROOT/{manifest['version']}@/boot/initrd.img-{manifest['kernel_version']}
}}
menuentry 'TrueNAS CORE' --class truenas --class gnu-linux --class gnu --class os """
f"""$menuentry_id_option 'gnulinux-simple-{fs_uuid}-core' {{
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod zfs
search --no-floppy --fs-uuid --set=root {fs_uuid}
echo 'Loading Linux {manifest['kernel_version']} ...'
linux /ROOT/{manifest['version']}@/boot/vmlinuz-{manifest['kernel_version']} """
f"""root=ZFS={dataset_name} ro {cmdline} console=tty1 zfs_force=yes """
f"""systemd.setenv=_BOOT_TRUENAS_CORE=1
echo 'Loading initial ramdisk ...'
initrd /ROOT/{manifest['version']}@/boot/initrd.img-{manifest['kernel_version']}
}}
"""))
os.chmod(grub_script_path, 0o0755)
os.makedirs("/boot/grub", exist_ok=True)
run_command(["zfs", "destroy", "-r", f"{pool_name}/grub"], check=False)
run_command(["zfs", "create", "-o", "mountpoint=legacy", f"{pool_name}/grub"])
run_command(["mount", "-t", "zfs", f"{pool_name}/grub", "/boot/grub"])
run_command(["grub-mkconfig", "-o", "/boot/grub/grub.cfg"])
for disk in disks:
if boot_partition_type in ["bios-boot", "freebsd-boot"]:
if boot_partition_type != "bios-boot":
run_command(["gpart", "modify", "-i", "1", "-t", "bios-boot", f"/dev/{disk}"])
run_command(["grub-install", "--target=i386-pc", f"/dev/{disk}"])
elif boot_partition_type == "efi":
os.makedirs("/boot/efi", exist_ok=True)
run_command(["umount", "/boot/efi"], check=False)
run_command(["mount", "-t", "msdosfs", get_partition(disk, 1), "/boot/efi"])
try:
if not os.path.exists(SCALE_BSD_LOADER_PATH):
shutil.copyfile(CORE_BSD_LOADER_PATH, SCALE_BSD_LOADER_PATH)
run_command(["grub-install", "--target=x86_64-efi", "--efi-directory=/boot/efi", "--removable"])
finally:
run_command(["umount", "/boot/efi"])
def configure_system_for_zectl(boot_pool):
root_ds = os.path.join(boot_pool, "ROOT")
set_prop = IS_FREEBSD or run_command([
"zfs", "get", "-H", "-o", "value", "org.zectl:bootloader", root_ds
]).stdout.strip() != 'grub'
if set_prop:
run_command(["zfs", "set", "org.zectl:bootloader=grub", root_ds])
def main():
global is_json_output
input = json.loads(sys.stdin.read())
cleanup = input.get("cleanup", True)
disks = input["disks"]
force_grub_install = input.get("force_grub_install", False)
if input.get("json"):
is_json_output = True
old_root = input.get("old_root", None)
password = input.get("password", None)
pool_name = input["pool_name"]
sql = input.get("sql", None)
src = input["src"]
with open(os.path.join(src, "manifest.json")) as f:
manifest = json.load(f)
dataset_name = f"{pool_name}/ROOT/{manifest['version']}"
old_bootfs_prop = run_command(["zpool", "get", "-H", "-o", "value", "bootfs", pool_name]).stdout.strip()
write_progress(0, "Creating dataset")
existing_datasets = set(filter(None, run_command(["zfs", "list", "-H", "-o", "name"]).stdout.split("\n")))
if dataset_name in existing_datasets:
for i in itertools.count(1):
probe_dataset_name = f"{dataset_name}-{i}"
if probe_dataset_name not in existing_datasets:
dataset_name = probe_dataset_name
break
run_command([
"zfs", "create",
"-o", "mountpoint=legacy",
"-o", f"truenas:kernel_version={manifest['kernel_version']}",
"-o", "zectl:keep=False",
dataset_name,
])
try:
root = pathlib.Path("/boot_pool/root")
root.mkdir(exist_ok=True, parents=True)
root = root.__str__()
# with tempfile.TemporaryDirectory() as root:
run_command(["mount", "-t", "zfs", dataset_name, root])
try:
write_progress(0, "Extracting")
cmd = [
"unsquashfs",
"-d", root,
"-f",
"-da", "16",
"-fr", "16",
os.path.join(src, "rootfs.squashfs"),
]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout = ""
buffer = b""
for char in iter(lambda: p.stdout.read(1), b""):
buffer += char
if char == b"\n":
stdout += buffer.decode("utf-8", "ignore")
buffer = b""
if buffer and buffer[0:1] == b"\r" and buffer[-1:] == b"%":
if m := RE_UNSQUASHFS_PROGRESS.match(buffer[1:].decode("utf-8", "ignore")):
write_progress(
int(m.group("extracted")) / int(m.group("total")) * 0.9,
"Extracting",
)
buffer = b""
p.wait()
if p.returncode != 0:
write_error({"error": f"unsquashfs failed with exit code {p.returncode}: {stdout}"})
raise subprocess.CalledProcessError(p.returncode, cmd, stdout)
write_progress(0.9, "Performing post-install tasks")
with contextlib.suppress(FileNotFoundError):
# We want to remove this for fresh installation + upgrade both
# In this case, /etc/machine-id would be treated as the valid
# machine-id which it will be otherwise as well if we use
# systemd-machine-id-setup --print to confirm but just to be cautious
# we remove this as it will be generated automatically by systemd then
# complying with /etc/machine-id contents
os.unlink(f"{root}/var/lib/dbus/machine-id")
is_freebsd_upgrade = False
setup_machine_id = configure_serial = False
if old_root is not None:
if os.path.exists(f"{old_root}/bin/freebsd-version"):
is_freebsd_upgrade = True
rsync = [
"etc/hostid",
"data",
"root",
]
if is_freebsd_upgrade:
if not IS_FREEBSD:
setup_machine_id = True
else:
rsync.append("etc/machine-id")
run_command([
"rsync", "-aRx",
"--exclude", "data/factory-v1.db",
"--exclude", "data/manifest.json",
"--exclude", "data/sentinels",
] + rsync + [
f"{root}/",
], cwd=old_root)
with open(f"{root}/data/need-update", "w"):
pass
if is_freebsd_upgrade:
with open(f"{root}/data/freebsd-to-scale-update", "w"):
pass
else:
enable_system_user_services(root, old_root)
else:
run_command(["cp", "/etc/hostid", f"{root}/etc/"])
with open(f"{root}/data/first-boot", "w"):
pass
with open(f"{root}/data/truenas-eula-pending", "w"):
pass
setup_machine_id = configure_serial = True
if setup_machine_id:
with contextlib.suppress(FileNotFoundError):
os.unlink(f"{root}/etc/machine-id")
run_command(["systemd-machine-id-setup", f"--root={root}"])
if IS_FREEBSD:
install_grub_freebsd(input, manifest, pool_name, dataset_name, disks)
else:
if password is not None:
run_command(["chroot", root, "/etc/netcli", "reset_root_pw", password])
if sql is not None:
run_command(["chroot", root, "sqlite3", "/data/freenas-v1.db"], input=sql)
if configure_serial:
configure_serial_port(root, os.path.join(root, "data/freenas-v1.db"))
undo = []
try:
run_command(["mount", "-t", "devtmpfs", "udev", f"{root}/dev"])
undo.append(["umount", f"{root}/dev"])
run_command(["mount", "-t", "proc", "none", f"{root}/proc"])
undo.append(["umount", f"{root}/proc"])
run_command(["mount", "-t", "sysfs", "none", f"{root}/sys"])
undo.append(["umount", f"{root}/sys"])
# Set bootfs before running update-grub
run_command(["zpool", "set", f"bootfs={dataset_name}", pool_name])
if is_freebsd_upgrade:
if old_bootfs_prop != "-":
run_command(["zfs", "set", "truenas:12=1", old_bootfs_prop])
cp = run_command([f"{root}/usr/local/bin/truenas-initrd.py", root], check=False)
if cp.returncode > 1:
raise subprocess.CalledProcessError(
cp.returncode, f'Failed to execute truenas-initrd: {cp.stderr}'
)
keystore = run_command([
"zfs", "get", "-H", "-o", "value", "org.zfsbootmenu:keysource", pool_name
]).stdout.strip()
keystore_mnt = run_command([
"zfs", "get", "-H", "-o", "value", "mountpoint", keystore
]).stdout.strip()
run_command(["chroot", root, "zfs", "mount", keystore])
undo.append(["chroot", root, "zfs", "unmount", keystore])
zol_conf = pathlib.Path(f"{root}/usr/share/initramfs-tools/hooks/keystore")
zol_conf.write_text(f"""#!/bin/sh
mkdir -p "${{DESTDIR}}/{keystore_mnt}"
cp {keystore_mnt}/{pool_name}.key "${{DESTDIR}}/{keystore_mnt}/{pool_name}.key"
exit 0
""")
zol_conf.chmod(zol_conf.stat().st_mode | stat.S_IEXEC)
run_command(["chmod", "+x", zol_conf.__str__()])
run_command(["chroot", root, "update-initramfs", "-k", "all", "-u"])
if old_root is None or force_grub_install:
if os.path.exists("/sys/firmware/efi"):
run_command(["mount", "-t", "efivarfs", "efivarfs", f"{root}/sys/firmware/efi/efivars"])
undo.append(["umount", f"{root}/sys/firmware/efi/efivars"])
# Clean up dumps from NVRAM to prevent
# "failed to register the EFI boot entry: No space left on device"
for item in os.listdir("/sys/firmware/efi/efivars"):
if item.startswith("dump-"):
with contextlib.suppress(Exception):
os.unlink(os.path.join("/sys/firmware/efi/efivars", item))
os.makedirs(f"{root}/boot/efi", exist_ok=True)
for i, disk in enumerate(disks):
efi_partition_number = 2
format_efi_partition = False
if is_freebsd_upgrade:
first_partition_guid = get_partition_guid(disk, 1)
if first_partition_guid == EFI_SYSTEM_PARTITION_GUID:
efi_partition_number = 1
format_efi_partition = False
if first_partition_guid == FREEBSD_BOOT_PARTITION_GUID:
run_command([
"sgdisk", "-t1:EF02", f"/dev/{disk}",
])
if get_partition_guid(disk, efi_partition_number) != EFI_SYSTEM_PARTITION_GUID:
continue
partition = get_partition(disk, efi_partition_number)
if format_efi_partition:
run_command(["chroot", root, "mkdosfs", "-F", "32", "-s", "1", "-n", "EFI",
partition])
run_command(["chroot", root, "mount", "-t", "vfat", partition, "/boot/efi"])
try:
if os.path.exists("/sys/firmware/efi"):
run_command(["chroot", root, "efibootmgr", "-c",
"-d", f"/dev/{disk}",
"-p", f"{efi_partition_number}",
"-L", f"ZFSBootMenu",
"-l", "\\EFI\\zfsbootmenu.EFI"
])
finally:
run_command(["chroot", root, "umount", "/boot/efi"])
finally:
for cmd in reversed(undo):
run_command(cmd)
finally:
run_command(["umount", root])
except Exception:
if old_bootfs_prop != "-":
run_command(["zpool", "set", f"bootfs={old_bootfs_prop}", pool_name])
if cleanup:
run_command(["zfs", "destroy", dataset_name])
raise
run_command(["zfs", "set", "mountpoint=/", dataset_name])
run_command(["zfs", "set", "canmount=noauto", dataset_name])
#configure_system_for_zectl(pool_name)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment