#!/bin/bash
# shellcheck disable=SC2181
# shellcheck disable=SC2236
#
# Version 4.1.1
#
# bootiso - create a bootable USB drive from an image file
# Copyright (C) 2018-2020 jules randolph <jules.sam.randolph@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# CODE STRUCTURE
#
# This file is organized in "pseudo-modules".
# Refer to the style.md file for a detailed definition.
set -o pipefail
set -E
version="4.1.1"
scriptName=$(basename "$0")
bashVersion=$(echo "$BASH_VERSION" | cut -d. -f1)
if [ -z "$BASH_VERSION" ] || [ "$bashVersion" -lt 4 ]; then
echo >&2 "You need bash v4+ to run this script. Aborting..."
exit 1
fi
# .
# .o8
# .ooooo. .o888oo
# d88' `"Y8 888
# 888 888
# 888 .o8 888 .
# `Y8bod8P' "888"
#
# CONSTANT GLOBAL VARIABLES (0)
#
# Readonly (constant) declarations are prefixed with 'ct_'.
# Modules can have their own constant declarations, prefixed with
# the module name + '_'.
typeset -r ct_syslinuxLibRoot=${BOOTISO_SYSLINUX_LIB_ROOT:-'/usr/lib/syslinux'}
typeset -r ct_tempRoot=/var/tmp/bootiso
typeset -r ct_cacheRoot=/var/cache/bootiso
typeset -r ct_mountRoot=/var/tmp/bootiso/mnt
typeset -r ct_shortOptions='bydJahlMftLpD'
typeset -r ct_ticketsURL="https://github.com/jsamr/bootiso/issues/new/choose"
typeset -r ct_dependenciesURL="https://github.com/jsamr/bootiso/blob/master/install.md#dependencies"
typeset -r ct_kernelOrgSyslinuxURL="https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/Testing"
typeset -r ct_openBugReportMessage="This is not expected: please open a 'Bug Report' ticket at $ct_ticketsURL."
typeset -r ct_openImageSupportRequestMessage="You can open an 'Image Support' request here: $ct_ticketsURL"
typeset -r ct_architecture=$(getconf LONG_BIT)
# .
# .o8
# .oooo.o .o888oo
# d88( "8 888
# `"Y88b. 888
# o. )88b 888 .
# 8""888P' "888"
#
# STATE GLOBAL VARIABLES (0)
typeset st_targetPartition
typeset st_elToritoMountPoint
typeset st_usbMountPoint
typeset st_execStartTime
typeset st_execEndTime
typeset st_shouldInstallSyslinux=false
typeset st_targetSyslinuxVersion
typeset st_completedAction
typeset st_expectingISOFile
typeset st_hasActionDuration
typeset st_foundSyslinuxMbrBinary
typeset st_foundSyslinuxBiosFolder
typeset st_shouldMakePartition
typeset st_backgroundProcess=''
typeset st_packageManager
typeset st_hasLegacyColumn
typeset -A st_syslinuxBinaries
typeset -a st_temporaryAssets=()
typeset -a st_devicesList=()
typeset -A st_isoInspections=(
[syslinuxBin]=''
[syslinuxVer]=''
[syslinuxConf]=''
[isHybrid]=false
[supportsEFIBoot]=false
[supportsBIOSBoot]=false
[hasWimFile]=false
)
typeset -A st_userFlags=(
# Actions
['help']=''
['version']=''
['list-usb-drives']=''
['format']=''
['install-image-copy']=''
['install-mount-rsync']=''
['inspect']=''
['probe']=''
# Options
['local-bootloader']=''
['assume-yes']=''
['device']=''
['no-eject']=''
['gpt']=''
['autoselect']=''
['no-mime-check']=''
['no-usb-check']=''
['no-size-check']=''
['no-hash-check']=''
['force-hash-check']=''
['no-wimsplit']=''
['data-part']=''
)
typeset -A st_userVars=(
['iso-file']=''
['hash-file']=''
['device']=''
['fs']=''
['label']=''
['remote-bootloader']=''
['part-type']=''
['data-part-fs']=''
)
# oo.ooooo. .oooo. oooo d8b
# 888' `88b `P )88b `888""8P
# 888 888 .oP"888 888
# 888 888 d8( 888 888
# 888bod8P' `Y888""8o d888b
# 888
# o888o
#
# PARAMETER GLOBAL VARIABLES (0)
#
# Global variables derived from program arguments or configuration.
# These are the only unprefixed global variables.
typeset sourceImageFile
typeset sourceHashFile
typeset targetDevice
typeset targetPartitionLabel
typeset targetFilesystem
typeset targetPartitionScheme
typeset targetAction='install-auto'
typeset targetBootloaderVersion
typeset targetDDBusSize
typeset targetDataPartFstype
typeset enableForceHashCheck
typeset enableAutoselect
typeset enableForceLocalBootloader
typeset enableGPT
typeset enableDataPart
typeset disableMimeCheck
typeset disableUSBCheck
typeset disableSizeCheck
typeset disableConfirmation
typeset disableHashCheck
typeset disableWimsplit
typeset disableDeviceEjection
# oooo
# `888
# .oooo.o 888 .oo.
# d88( "8 888P"Y88b
# `"Y88b. 888 888
# o. )88b 888 888
# 8""888P' o888o o888o
#
#
# SH MODULE (1)
# $@ - The expression piped to bc
function sh_compute() {
local _answer
_answer=$(echo "$@" | bc)
if ((_answer == 0)); then
return 1
else
return 0
fi
}
# $1 - The string by which elements will be joined.
# $2+ - The elements to join
function sh_joinBy() {
local -r IFS=$1
shift
echo "$*"
}
# $1 - The element to check.
# $2+ - The list to check against.
function sh_elementIsInList() {
local -r _match="$1"
local _arg
shift
for _arg in "$@"; do [[ "$_arg" == "$_match" ]] && return 0; done
return 1
}
# $1: fsType
function sh_normalizeFSType() {
local -r _fsType="${1:-vfat}"
if [ "${_fsType,,}" == fat32 ]; then
echo vfat
else
echo "$_fsType"
fi
}
# .
# .o8
# .o888oo .ooooo. oooo d8b ooo. .oo. .oo.
# 888 d88' `88b `888""8P `888P"Y88bP"Y88b
# 888 888ooo888 888 888 888 888
# 888 . 888 .o 888 888 888 888
# "888" `Y8bod8P' d888b o888o o888o o888o
#
#
# TERMINAL MODULE (1)
# See console_codes GNU-Linux man page
typeset -r term_setRed="\e[31m"
typeset -r term_setGreen="\e[32m"
typeset -r term_setYellow="\e[33m"
typeset -r term_unsetColor="\e[39m"
typeset -r term_setUnderline="\e[4m"
typeset -r term_unsetUnderline="\e[24m"
typeset -r term_setBold="\e[1m"
typeset -r term_unsetBold="\e[22m"
# Spacing
typeset -r term_logPrefix="$scriptName: "
typeset -r term_logPrefixLength="${#term_logPrefix}"
typeset -r term_logPrefixEmpty="$(printf "%${term_logPrefixLength}s")"
function term_boldify() {
echo -e "$term_setBold$1$term_unsetBold"
}
function term_underline() {
echo -e "$term_setUnderline$1$term_unsetUnderline"
}
# $1: The text to colorify.
function term_redify() {
echo -e "$term_setRed$1$term_unsetColor"
}
# $1: The text to colorify.
function term_greenify() {
echo -e "$term_setGreen$1$term_unsetColor"
}
# $1: The text to colorify.
function term_yellowify() {
echo -e "$term_setYellow$1$term_unsetColor"
}
function term_printColumn() {
local -r _prefix=$1
local -r _prefixLength=$2
local _termWidth
local _sep='\t'
_termWidth="$(tput cols)"
shift 2
local _rawInput="$*"
echo -n -e "$_prefix$_sep$_prefixLength$_sep$_termWidth$_sep$_rawInput" | awk -F $_sep '
{
prefix = $1
prefixlen = $2
termwidth = $3
len = prefixlen
printargfill = sprintf("%s%s%s", "%-", prefixlen, "s")
printf printargfill, prefix
for(j=4;j<=NF;j++) {
n = split($j,x," ")
for(i=1;i<=n;i++){
if(len+length(x[i])>=termwidth){
print ""
printf printargfill, " "
len = prefixlen
}
printf "%s ",x[i]
len += 1+length(x[i])
}
}
print ""
}'
}
function term_printLog() {
term_printColumn "$term_logPrefix" "$term_logPrefixLength" "$*"
}
# shellcheck disable=SC2120
function term_indentAll() {
while read -r line; do
term_printColumn " " "$term_logPrefixLength" "$line"
done < "${1:-/dev/stdin}"
}
# $*: The message to print.
function term_echoerr() {
term_redify "$(term_printLog "$*")"
}
# $*: The message to print.
function term_echowarn() {
term_yellowify "$(term_printLog "$*")"
}
# $*: The message to print.
function term_echogood() {
term_greenify "$(term_printLog "$*")"
}
# $*: The message to print.
function term_echoinfo() {
term_printLog "$*"
}
# Print an ASCII "spin" character
# depending on the modulo of variable _i in scope,
# and sleep for 250ms.
function term_updateProgress() {
local _sp="/-\\|"
# print when launched from terminal
if tty -s; then
printf "\\b%s" "${_sp:_i++%${#_sp}:1}"
fi
sleep 0.25
}
function term_cleanProgress() {
# print when launched from terminal
if tty -s; then
printf "\\b%s\\n" " "
fi
}
# .o88o.
# 888 `"
# o888oo .oooo.o
# 888 d88( "8
# 888 `"Y88b.
# 888 o. )88b
# o888o 8""888P'
#
#
# FILESYSTEM MODULE (1)
typeset -Ar fs_gptPartitionCodes=(
[efi]="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
# Windows Data partition
[wdp]="EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"
# Linux Filesystem Data
[lfd]="0FC63DAF-8483-4772-8E79-3D69D8477DE4"
)
typeset -Ar fs_mbrPartitionCodes=(
[efi]=ef
)
function fs_firstMatchInFolder() {
local -r _path=$1
local -r _pattern=$2
find "$_path" -type f -iname "$_pattern" -print -quit
}
# $1 - From path
# $2+ - Patterns (see find -iname)
function fs_matchFirstExpression() {
local -r _path=$1
local _pattern
local _match
shift
for _pattern in "$@"; do
_match=$(fs_firstMatchInFolder "$_path" "$_pattern")
if [ ! -z "$_match" ]; then
echo "$_match"
break
fi
done
}
# $1 - From path
# $2+ - Path segments (see find -path)
function fs_findFileFromPatterns() {
local -r _path=$1
shift
local _pathSegment
local _found
local _candidate
for _pathSegment in "$@"; do
if [ -f "${_path}/$_pathSegment" ]; then
_found="${_path}/$_pathSegment"
break
fi
done
if [ -z "$_found" ]; then
for _pathSegment in "$@"; do
_candidate=$(find "$_path" -type f -path "*/$_pathSegment" -print -quit)
if [ ! -z "$_candidate" ]; then
_found="$_candidate"
break
fi
done
fi
echo "$_found"
}
function fs_createTempFile() {
local -r _prefix=$1
local _tmpFileTemplate="$ct_tempRoot/$_prefix-XXX"
mktemp "$_tmpFileTemplate" || ps_failAndExit IO_ERROR "Failed to create temporary file."
}
# Print the name of the new folder if operation
# succeeded, fails otherwise.
#
# $1 - The folder name prefix
function fs_createMountFolder() {
mktemp -d "$ct_mountRoot/$1-XXX" || ps_failAndExit IO_ERROR "Failed to create temporary mount point with pattern '$_tmpFileTemplate'."
}
function fs_syncdev() {
sync
}
function fs_isMounted() {
local -r _partitionBlock=$1
if [ ! -z "$_partitionBlock" ] && grep -q -e "$_partitionBlock" /etc/mtab; then
return 0
else
return 1
fi
}
function fs_umountUSB() {
if fs_isMounted "$st_usbMountPoint"; then
if umount "$st_usbMountPoint" |& term_indentAll; then
term_echogood "USB device partition succesfully unmounted."
else
term_echowarn "Could not unmount USB mount point."
fi
fi
}
# $1 - mountPoint
function fs_umountPartition() {
local -r _mountPoint="$1"
if fs_isMounted "$_mountPoint"; then
if ! umount "$_mountPoint" |& term_indentAll; then
term_echowarn "Could not unmount image mount point."
fi
fi
}
function fs_umountElTorito() {
if fs_isMounted "$st_elToritoMountPoint"; then
if ! umount "$st_elToritoMountPoint" |& term_indentAll; then
term_echowarn "Could not unmount image mount point."
fi
fi
}
function fs_mountUSB() {
local _type="$targetFilesystem"
st_usbMountPoint=$(fs_createMountFolder usb) || exit "$?"
st_temporaryAssets+=("$st_usbMountPoint")
term_echoinfo "Created USB device mount point at '$st_usbMountPoint'"
if ! mount -t "$_type" "$st_targetPartition" "$st_usbMountPoint" > /dev/null; then
ps_failAndExit IO_ERROR "Could not mount USB device."
fi
}
# $1 - mountPoint
function fs_mountElToritoFile() {
local -r _mountPoint="$1"
if ! mount -r -o loop -- "$sourceImageFile" "$_mountPoint" > /dev/null; then
ps_failAndExit IO_ERROR "Could not mount image file."
else
st_temporaryAssets+=("$_mountPoint")
fi
}
# $1 - fstype
# $2 - target partition full path
# $3 - partition label
function fs_formatPartition() {
local -r _fstype=$1
local -r _targetPart=$2
local -r _partLabel=$3
# These options always end up with the label flag setter
local -Ar _mkfsOpts=(
['vfat']="-v -F 32 -n" # Fat32 mode
['exfat']="-n"
['ntfs']="-Q -c 4096 -L" # Quick mode + cluster size = 4096 for syslinux support
['ext2']="-O ^64bit -L" # Disabling pure 64 bits compression for syslinux compatibility
['ext3']="-O ^64bit -L" # see https://www.syslinux.org/wiki/index.php?title=Filesystem#ext
['ext4']="-O ^64bit -L"
['f2fs']="-l"
)
# format
term_echogood "Creating $_fstype partition on '$_targetPart'..."
# shellcheck disable=SC2086
"mkfs.$targetFilesystem" ${_mkfsOpts[$_fstype]} "$_partLabel" "$_targetPart" |&
term_indentAll ||
ps_failAndExit IO_ERROR "Failed to create $_fstype partition on USB device."
}
# Given a partition scheme (1), output a partition type
# that would be a good fit for filesystem type (2).
#
# $1 - partScheme
# $2 - fsType
function fs_inferFSType() {
local -r _partScheme=$1
local -r _fsType=$2
local -Ar _gptTypeCodes=(
['vfat']="${fs_gptPartitionCodes[wdp]}"
['exfat']="${fs_gptPartitionCodes[wdp]}"
['ntfs']="${fs_gptPartitionCodes[wdp]}"
['ext2']="${fs_gptPartitionCodes[lfd]}"
['ext3']="${fs_gptPartitionCodes[lfd]}"
['ext4']="${fs_gptPartitionCodes[lfd]}"
['f2fs']="${fs_gptPartitionCodes[lfd]}"
)
local -Ar _mbrTypeCodes=(
['vfat']='c'
['exfat']='7'
['ntfs']='7'
['ext2']='83'
['ext3']='83'
['ext4']='83'
['f2fs']='83'
)
case "$_partScheme" in
dos | mbr) echo "${_mbrTypeCodes[$_fsType]}" ;;
gpt) echo "${_gptTypeCodes[$_fsType]}" ;;
*) ps_failAndExit STATE_ERROR "(fs_inferFSType) unhandled partition scheme: $_partScheme" ;;
esac
}
function fs_syncWithProgress() {
# _i defined for term_updateProgress
local -i _i=1
local -i _status
local _syncPid
local _statusFile
_statusFile=$(fs_createTempFile "bootiso-sync-status")
st_temporaryAssets+=("$_statusFile")
fs_syncdev &
_syncPid=$!
echo -n "$scriptName: Synchronizing writes on device '${targetDevice}' "
while [ -e "/proc/$_syncPid" ]; do
term_updateProgress
done
term_cleanProgress
_status=$(cat "$_statusFile")
if [ ! "$_status" -eq 0 ]; then
ps_failAndExit IO_ERROR "Sync call failed."
fi
}
# .oooo.o oooo ooo .oooo.o
# d88( "8 `88. .8' d88( "8
# `"Y88b. `88..8' `"Y88b.
# o. )88b `888' o. )88b
# 8""888P' .8' 8""888P'
# .o..P'
# `Y8P'
#
#
# OPERATING SYSTEM MODULE (1)
# $1 - The name of the command to check in $PATH.
function sys_hasCommand() {
command -v "$1" &> /dev/null
return $?
}
# $1 - The name of the package command to check.
function sys_checkCommand() {
local -r _command=$1
local _answer
if ! sys_hasCommand "$_command"; then
term_echowarn "Command '$_command' not found!"
if ((EUID != 0)) || [[ -z ${ct_commandPackages["$_command"]} ]]; then
ps_failAndExit MISSING_DEPENDENCY \
"Please install the missing package providing this command manually."
fi
term_echowarn "Should be in package '${ct_commandPackages["$_command"]}'."
if [ -n "$st_packageManager" ]; then
read -r -n1 -p "${term_logPrefixEmpty}Attempt installation? (y/n)> " _answer
echo
case $_answer in
y | Y)
if ! $st_packageManager "${ct_commandPackages["$_command"]}"; then
ps_failAndExit MISSING_DEPENDENCY "Installation of dependency '$_command' failed." \
"Perhaps this dependency has a slightly different name on your distribution." \
"Find it and install manually."
else
if ! sys_hasCommand "$_command"; then
ps_failAndExit MISSING_DEPENDENCY "Program '$_command' is not accessible in the \$PATH environment even though the package ${ct_commandPackages["$_command"]} has just been installed."
fi
fi
;;
*)
ps_failAndExit MISSING_DEPENDENCY "Missing dependency '$_command'."
;;
esac
else
ps_failAndExit MISSING_DEPENDENCY "Missing dependency '$_command'."
fi
fi
}
# $1 - a device block
# Returns "usb", "ata", "nvme" ... or fallback to "other"
function sys_getDeviceType() {
local -r _deviceBlock=$1
local _deviceType
_deviceType=$(lsblk -dlno TRAN "$_deviceBlock")
echo "${_deviceType:-other}"
}
# $1 - a device block
function sys_isDeviceDisk() {
local -r _deviceBlock="$1"
lsblk -lno TYPE "$_deviceBlock" | grep -q disk
return $?
}
# oo.ooooo. .oooo.o
# 888' `88b d88( "8
# 888 888 `"Y88b.
# 888 888 o. )88b
# 888bod8P' 8""888P'
# 888
# o888o
#
#
# PROCESS STATE MODULE (2)
typeset -Ar ps_exitStatus=(
# Exceptions
[ASSERTION_FAILED]=1
[SYNOPSIS_NONCOMPL]=2
[MISSING_BOOT_CAP]=3
[FILE_NOEXIST]=4
[BADFILE]=5
[DEVICE_NOEXIST]=6
[BAD_DEVICE]=7
[NO_DEVICES]=8
[MISSING_DEPENDENCY]=9
[HOST_UNREACHABLE]=10
[USER_ABORTED]=11
[MISSING_PRIVILEGE]=12
[FAILED_POSTULATE]=13
# Errors
[IO_ERROR]=64
[STATE_ERROR]=65
[THIRD_PARTY_ERROR]=66
)
# Normalize PATH on some distro which don't export system binary paths
# by default, such as Debian. The normalization follows FHS 3.0 defined paths
# for system binaries.
function ps_normalizePath() {
local -a _paths
mapfile -t _paths < <(echo "$PATH" | tr ':' '\n')
if ! sh_elementIsInList /sbin "${_paths[@]}"; then
export PATH="$PATH:/sbin"
fi
if ! sh_elementIsInList /usr/sbin "${_paths[@]}"; then
export PATH="$PATH:/usr/sbin"
fi
if ! sh_elementIsInList /usr/local/sbin "${_paths[@]}"; then
export PATH="$PATH:/usr/local/sbin"
fi
}
function ps_cleanupOnExit() {
local _asset
function _removeTempAsset() {
if [[ "$1" =~ ^$ct_tempRoot ]] || [[ "$1" =~ ^$ct_mountRoot ]]; then
if [ -d "$1" ]; then
rm -rf "$1"
elif [ -f "$1" ]; then
rm "$1"
fi
else
term_echowarn "Skipping deletion of unexpected temporary asset at '$1'."
fi
}
function _ejectDevice() {
if [[ "$st_completedAction" =~ ^install ]]; then
if [[ "$disableDeviceEjection" == false ]]; then
if eject "$targetDevice" |& term_indentAll; then
term_echogood "USB device succesfully ejected." \
"You can safely remove it!"
else
term_echowarn "Failed to eject device '$targetDevice'."
fi
else
term_echoinfo "USB device ejection skipped with $(term_boldify '-J, --no-eject')."
fi
fi
}
if ((EUID == 0)); then
fs_umountElTorito
fs_umountUSB
for _asset in "${st_temporaryAssets[@]}"; do
_removeTempAsset "$_asset"
done
_ejectDevice
fi
}
function ps_cleanupOnInterrupt() {
function _waitBackgroundProcess() {
if [[ -n "$st_backgroundProcess" ]]; then
wait "$st_backgroundProcess"
fi
}
if ((EUID == 0)); then
echo
term_echowarn "Received INT or TERM signal!..." \
"Synchronizing pending writes before unmounting..." \
"PLEASE WAIT SYNC PROCEDURE COMPLETION TO AVOID DAMAGING DEVICES!"
_waitBackgroundProcess
fs_syncdev
ps_cleanupOnExit
exit "${ps_exitStatus[USER_ABORTED]}"
fi
}
function ps_startTimer() {
st_execStartTime=$(date +%s)
}
function ps_stopTimerAndPrintLapsed() {
st_execEndTime=$(date +%s)
term_echogood "Took $((st_execEndTime - st_execStartTime)) seconds to perform $(term_boldify "$targetAction") action."
}
# $1 - The status code, see ps_exitStatus keys
# $2+ - The message to print.
function ps_failAndExit() {
local -r _statusCode=$1 _status
if ! sh_elementIsInList "$_statusCode" "${!ps_exitStatus[@]}"; then
term_echoerr "(ps_failAndExit) Internal state error. Unknown status code '$_statusCode'"
_statusCode=STATE_ERROR
else
shift
if [ "$_statusCode" == SYNOPSIS_NONCOMPL ]; then
[ $# -ne 0 ] && term_echoerr "$@"
term_echoerr "Check $(term_boldify 'man 1 bootiso')."
else
term_echoerr "$@" "Exiting..."
fi
fi
if [ "$_statusCode" == STATE_ERROR ]; then
term_echoerr "Provide a bug report at $ct_openBugReportMessage."
fi
if [ "$_statusCode" == MISSING_DEPENDENCY ]; then
term_echoerr "See $ct_dependenciesURL"
fi
exit "${ps_exitStatus[$_statusCode]}"
}
# .
# .o8
# .oooo. .oooo.o oooo d8b .o888oo
# `P )88b d88( "8 `888""8P 888
# .oP"888 `"Y88b. 888 888
# d8( 888 o. )88b 888 888 .
# `Y888""8o 8""888P' d888b "888"
#
#
# ASSERTION MODULE (2)
typeset -ar asrt_supportedFS=('vfat' 'exfat' 'ntfs' 'ext2' 'ext3' 'ext4' 'f2fs')
typeset -Ar asrt_userVarsCompatibilityMatrix=(
['iso-file']='install-auto install-mount-rsync install-image-copy inspect probe'
['hash-file']='install-auto install-mount-rsync install-image-copy inspect probe'
[device]='install-auto install-mount-rsync install-image-copy format'
[type]='install-mount-rsync format'
[label]='install-mount-rsync format'
['remote-bootloader']='install-mount-rsync'
['part-type']='format install-mount-rsync'
['dd-bs']='install-image-copy'
['data-part-fs']='install-image-copy'
)
typeset -Ar ct_userFlagsCompatibilityMatrix=(
['assume-yes']='install-auto install-mount-rsync install-image-copy format'
['no-eject']='install-auto install-mount-rsync install-image-copy format'
['autoselect']='install-auto install-mount-rsync install-image-copy format'
['no-mime-check']='install-auto install-mount-rsync install-image-copy probe inspect'
['no-hash-check']='install-auto install-mount-rsync install-image-copy probe inspect'
['force-hash-check']='install-auto install-mount-rsync install-image-copy probe inspect'
['no-usb-check']='install-auto install-mount-rsync install-image-copy list-usb-drives probe format'
['no-size-check']='install-auto install-mount-rsync install-image-copy'
[gpt]='format install-mount-rsync'
['data-part']='install-image-copy'
['local-bootloader']='install-mount-rsync'
['no-wimsplit']='install-mount-rsync'
)
typeset -ar asrt_commandDependencies=(
awk
basename
bc
blkid
blockdev
cat
chmod
column
curl
cut
date
dd
dirname
du
eject
file
find
fmt
getconf
grep
jq
lsblk
md5sum
mkdir
mktemp
mount
mv
numfmt
partx
rm
rsync
sed
sfdisk
sha1sum
sha256sum
sha512sum
sleep
sort
strings
sync
syslinux
tail
tar
tput
tr
tty
umount
wimlib-imagex
wipefs
xargs
)
typeset -Ar ct_commandPackages=(
[awk]='gawk'
[basename]='coreutils'
[bc]='bc'
[blkid]='util-linux'
[blockdev]='util-linux'
[cat]='coreutils'
[chmod]='coreutils'
[column]='util-linux'
[curl]='curl'
[cut]='coreutils'
[date]='coreutils'
[dd]='coreutils'
[dirname]='coreutils'
[du]='coreutils'
[eject]='util-linux'
[file]='file'
[find]='findutils'
[fmt]='coreutils'
[getconf]='glibc'
[grep]='grep'
[jq]='jq'
[lsblk]='util-linux'
[md5sum]='coreutils'
[mkdir]='coreutils'
[mktemp]='coreutils'
[mount]='util-linux'
[mv]='coreutils'
[numfmt]='coreutils'
[partx]='util-linux'
[rsync]='rsync'
[rm]='coreutils'
[sed]='sed'
[sfdisk]='' # Distro-dependent
[sha1sum]='coreutils'
[sha256sum]='coreutils'
[sha512sum]='coreutils'
[sleep]='coreutils'
[sort]='coreutils'
[strings]='binutils'
[sync]='coreutils'
[syslinux]='syslinux'
[tail]='coreutils'
[tar]='tar'
[tput]='' # Distro-dependent
[tr]='coreutils'
[tty]='coreutils'
[umount]='util-linux'
['wimlib-imagex']='' # Distro-dependent
[wipefs]='util-linux'
[xargs]='findutils'
)
function asrt_checkSudo() {
if ((EUID != 0)); then
if [[ -t 1 ]] && sys_hasCommand sudo; then
sudo --preserve-env "$0" "$@"
elif sys_hasCommand gksu; then
exec 1> output_file
gksu --preserve-env "$0" "$@"
else
ps_failAndExit MISSING_PRIVILEGE "You must run $scriptName as root."
fi
exit
fi
}
function asrt_checkFileIsImage() {
local _mimeType
if [ -z "$sourceImageFile" ]; then
term_echoerr "Missing argument 'iso-file'."
exit 2
fi
if [ -d "$sourceImageFile" ]; then
ps_failAndExit BADFILE "Provided file '$sourceImageFile' is a directory."
fi
if [ ! -f "$sourceImageFile" ]; then
ps_failAndExit FILE_NOEXIST "Provided iso file '$sourceImageFile' does not exist."
fi
if [ "$disableMimeCheck" == false ]; then
_mimeType=$(file --mime-type -b -- "$sourceImageFile")
if [[ "$_mimeType" != "application/octet-stream" && "$_mimeType" != "application/x-iso9660-image" ]]; then
term_echoerr "Provided file '$sourceImageFile' doesn't seem to be an image file (wrong mime-type: '$_mimeType')." \
"You can bypass this policy with $(term_boldify '-M, --no-mime-check')."
ps_failAndExit BADFILE
fi
fi
}
function asrt_checkImageHash() {
local _lHash
local _numValidHashes=0
local _isoDirectory
local _isoFileName
local -ar _hashes=("md5sum" "sha1sum" "sha256sum" "sha512sum")
function _computeHashWithProgress() {
local _hashName="$1"
local _imageName="$2"
# _i defined for term_updateProgress
local _hashStoreFile _i
_hashStoreFile=$(fs_createTempFile "bootiso-file-_hash")
term_echoinfo "Checking _hash for '$_imageName'..."
printf "%s" \
"You can disable this check with $(term_boldify "-H, --no-hash-check") flags." | term_indentAll
st_temporaryAssets+=("$_hashStoreFile")
(
local _hash
local -i
_hash=$($_hashName "$_imageName" | awk "{print \$1; exit }")
if (($? == 0)); then
printf "%s" "$_hash" > "$_hashStoreFile"
else
printf "%s" 1 > "$_hashStoreFile"
fi
) &
st_backgroundProcess=$!
while [ -e "/proc/$st_backgroundProcess" ]; do
term_updateProgress
done
st_backgroundProcess=''
term_cleanProgress
_lHash=$(cat "$_hashStoreFile")
if [ "$_lHash" == "1" ]; then
return 1
fi
}
function _checkHash() {
local -r _hashPath=$1 # Path to file containing _hashes
local -r _imageName=$2 # File to be checked
local -r _hashName=$3 # Name of command of _hash
local _answer
# Hash from _hash file
local _gHash
_gHash=$(awk -v pattern="$_imageName$" '$0 ~ pattern { print $1; exit }' "$_hashPath")
if [ -z "$_gHash" ]; then
term_echoerr "No matching filename found in hash file '$_hashPath'"
return
elif [ -z "$_hashName" ]; then
case ${#_gHash} in
32)
_hashName="md5sum"
;;
40)
_hashName="sha1sum"
;;
64)
_hashName="sha256sum"
;;
128)
_hashName="sha512sum"
;;
*)
ps_failAndExit BADFILE "Matching line in '$_hashPath' has an unexpected hash format."
;;
esac
fi
# Hash from iso
_computeHashWithProgress $_hashName "$_imageName" || ps_failAndExit THIRD_PARTY_ERROR "$_hashName command failed with status $?"
if [ "$_gHash" != "$_lHash" ]; then
if [ "$enableForceHashCheck" == 'true' ]; then
ps_failAndExit ASSERTION_FAILED "Hash mismatch in '$_hashPath' (${_hashName%sum})."
else
term_echowarn "Hash mismatch in '$_hashPath' (${_hashName%sum})."
read -r -n1 -p "${term_logPrefixEmpty}Do you still want to continue? (y/n)> " _answer
echo
case $_answer in
y | Y)
return
;;
*)
ps_failAndExit USER_ABORTED
;;
esac
term_echoinfo "Ignoring mismatching _hash."
fi
else
term_echogood "Matching ${_hashName%sum} hash found in '$_hashPath'"
_numValidHashes=$((_numValidHashes + 1))
fi
}
_isoDirectory=$(dirname "$sourceImageFile")
_isoFileName=$(basename "$sourceImageFile")
if [ -n "$sourceHashFile" ]; then
if [ -f "$sourceHashFile" ]; then
_checkHash "$sourceHashFile" "$_isoFileName"
else
ps_failAndExit FILE_NOEXIST "Specified hash file '$sourceHashFile' does not exist."
fi
else
shopt -s nullglob nocaseglob
for _hash in "${_hashes[@]}"; do
for file in "$_isoDirectory/$_hash"*; do
_checkHash "$file" "$_isoFileName" "$_hash"
done
if [ -f "$sourceImageFile.${_hash%sum}" ]; then
_checkHash "$sourceImageFile.${_hash%sum}" "$_isoFileName" "$_hash"
fi
done
shopt -u nullglob nocaseglob
fi
if [ "$enableForceHashCheck" == 'true' ] && [ $_numValidHashes == 0 ]; then
ps_failAndExit ASSERTION_FAILED "No matching hashes found. Assert forced by $(term_boldify '--force-_hash-check')"
fi
}
function asrt_checkPackages() {
local _pkg
for _pkg in "${asrt_commandDependencies[@]}"; do
sys_checkCommand "$_pkg"
done
# test grep supports -P option
if ! echo 1 | grep -P '1' &> /dev/null; then
ps_failAndExit MISSING_DEPENDENCY \
"You're using an old version of grep which does not support perl regular expression (-P option)."
fi
if echo "" | column -t -N t -W t &> /dev/null; then
st_hasLegacyColumn=false
else
# Old BSD command, see https://git.io/JfauE
st_hasLegacyColumn=true
fi
}
function asrt_checkDeviceIsOK() {
local -r _deviceBlock=$1
_failDevice() {
term_echoerr "$1"
exec_listUSBDrives
term_echoerr "Exiting..."
exit "${ps_exitStatus[DEVICE_NOEXIST]}"
}
if [ ! -e "$_deviceBlock" ]; then
_failDevice "The selected device '$_deviceBlock' does not exist."
fi
if [ ! -b "$_deviceBlock" ]; then
_failDevice "The selected device '$_deviceBlock' is not a valid block file."
fi
if [ ! -d "/sys/block/$(basename "$_deviceBlock")" ] || ! sys_isDeviceDisk "$_deviceBlock"; then
ps_failAndExit BAD_DEVICE \
"The selected device '$_deviceBlock' is either unmounted or not a disk (might be a partition or loop)." \
"Select a disk instead or reconnect the USB deviceBlock." \
"You can check the availability of USB drives with $(term_boldify "$scriptName -l")."
fi
}
function asrt_checkDeviceIsUSB() {
local _deviceType
if [ "$disableUSBCheck" == 'true' ]; then
term_echowarn "USB check has been disabled. Skipping."
return 0
fi
_deviceType=$(sys_getDeviceType "$targetDevice")
if [ "$_deviceType" != "usb" ]; then
term_echoerr "The device you selected is not connected via USB (found TRAN: '$_deviceType') and the operation was therefore canceled."
term_echowarn "Use $(term_boldify '--no-usb-check') to bypass this policy at your own risk."
term_echoerr "Exiting..."
exit 1
fi
term_echogood "The selected device '$targetDevice' is connected through USB."
}
function asrt_checkImageSize() {
local -r _deviceBlock=$1
local -r _imageFile=$2
if [ "$disableSizeCheck" == 'true' ]; then
term_echowarn "Size check has been disabled. Skipping."
return 0
fi
if [ "$(blockdev --getsz "$_imageFile")" -gt "$(blockdev --getsz "$_deviceBlock")" ]; then
term_echoerr "The image is larger than the selected _deviceBlock '$_deviceBlock' and the operation was therefore canceled."
term_echowarn "Use $(term_boldify '--no-size-check') to bypass this policy at your own risk."
term_echoerr "Exiting..."
exit 1
fi
}
function asrt_checkAction() {
local -ra _actions=('help' 'version' 'format' 'install-image-copy' 'install-mount-rsync' 'list-usb-drives' 'inspect' 'probe')
local -a _enabledActions=()
local _act
for _act in "${_actions[@]}"; do
if [ "${st_userFlags[$_act]}" == 'true' ]; then
_enabledActions+=("$_act")
fi
done
if ((${#_enabledActions[@]} == 0)); then
targetAction='install-auto'
elif ((${#_enabledActions[@]} == 1)); then
targetAction=${_enabledActions[0]}
else
ps_failAndExit SYNOPSIS_NONCOMPL \
"You cannot invoke multiple actions at once: $(sh_joinBy '+' "${_enabledActions[@]}")."
fi
}
# $1 - fsType
function asrt_checkFSType() {
local -r _fsType="$1"
if ! sh_elementIsInList "$_fsType" "${asrt_supportedFS[@]}"; then
ps_failAndExit SYNOPSIS_NONCOMPL "Filesystem type '$_fsType' not supported." \
"Supported filesystem types: $(sh_joinBy "," "${asrt_supportedFS[*]}")."
fi
if ! command -v "mkfs.$_fsType" &> /dev/null; then
ps_failAndExit MISSING_DEPENDENCY \
"Program 'mkfs.$_fsType' could not be found on your system." \
"Please install it and retry."
fi
}
function asrt_checkUserVars() {
# check partition types
if [ -n "${st_userVars[fs]}" ]; then
st_userVars[fs]=$(sh_normalizeFSType "${st_userVars[fs]}")
asrt_checkFSType "${st_userVars[fs]}"
fi
if [ -n "${st_userVars['data-part-fs']}" ]; then
st_userVars['data-part-fs']=$(sh_normalizeFSType "${st_userVars['data-part-fs']}")
asrt_checkFSType "${st_userVars['data-part-fs']}"
fi
# check device
if [ -n "${st_userVars[device]}" ]; then
if [[ ! "${st_userVars[device]}" =~ '/dev/' ]] && [ -e "/dev/${st_userVars[device]}" ]; then
st_userVars[device]="/dev/${st_userVars[device]}"
fi
asrt_checkDeviceIsOK "${st_userVars[device]}"
fi
if [ -n "${st_userVars['remote-bootloader']}" ] && [[ ! "${st_userVars['remote-bootloader']}" =~ ^[0-9]+\.[0-9]+$ ]]; then
ps_failAndExit SYNOPSIS_NONCOMPL \
"Remote bootloader version '${st_userVars['remote-bootloader']}' set with $(term_boldify '--remote-bootloader') doesn't follow MAJOR.MINOR pattern." \
"Valid examples are 4.10, 6.02"
fi
# Check dd-bs
if [[ -n "${st_userVars['dd-bs']}" && ! ${st_userVars['dd-bs']} =~ ^[0-9]+[kMGT]?$ ]]; then
ps_failAndExit SYNOPSIS_NONCOMPL "$(term_boldify '--dd-bs') argument must be a valid block size quantifier, e.g. 512k, 2M."
fi
}
function asrt_checkUserFlags() {
# Autoselect security
if [ "${st_userFlags['autoselect']}" == 'true' ] && [ "${st_userFlags['no-usb-check']}" == 'true' ]; then
ps_failAndExit MISSING_DEPENDENCY \
"You cannot set $(term_boldify '-a, --autoselect') while disabling USB check with $(term_boldify '--no-usb-check')"
fi
if [ "${st_userFlags['no-hash-check']}" == 'true' ] && [ "${st_userFlags['force-hash-check']}" == 'true' ]; then
ps_failAndExit SYNOPSIS_NONCOMPL \
"You cannot combine $(term_boldify '--no-hash-check') and $(term_boldify '--force-hash-check')"
elif [ "${st_userFlags['no-hash-check']}" == 'true' ] && [ -n "${st_userVars['hash-file']}" ]; then
ps_failAndExit SYNOPSIS_NONCOMPL \
"You cannot combine $(term_boldify '--no-hash-check') and $(term_boldify '--hash-file')"
fi
# warnings (only with sudo)
if ((EUID == 0)); then
# Eject format
if [ "${st_userFlags['no-eject']}" == true ] && [ "$targetAction" == format ]; then
term_echowarn "You don't need to prevent device ejection through $(term_boldify '-J') flag with 'format'."
fi
# Warn autoselecting while assume yes is false
if [ "${st_userFlags[autoselect]}" == true ] && [ "${st_userFlags['assume-yes']}" == false ]; then
term_echowarn "$(term_boldify '-a, --autoselect') is enabled by default when $(term_boldify '-y, --asume-yes') is not set."
fi
if [[ -n "${st_userVars['data-part-fs']}" && -z "${st_userFlags['data-part']}" ]]; then
term_echowarn "You set $(term_boldify '--data-part-fs') option but didn't set $(term_boldify -D). No data partition will be added."
fi
fi
}
#shellcheck disable=SC1087
#shellcheck disable=SC2086
function asrt_checkFlagMatrix() {
local _key
function _failAndHintUser() {
local _matrixName=$1
local _allowedActionsRef="$_matrixName[$_key]"
if [[ "$targetAction" == 'install-auto' ]] && sh_elementIsInList install-mount-rsync ${!_allowedActionsRef}; then
ps_failAndExit SYNOPSIS_NONCOMPL \
"$(term_boldify "--$_key") modifier requires $(term_boldify '--mrsync') to assert Mount-Rsync mode."
elif [[ "$targetAction" == 'install-auto' ]] && sh_elementIsInList install-image-copy ${!_allowedActionsRef}; then
ps_failAndExit SYNOPSIS_NONCOMPL \
"$(term_boldify "--$_key") modifier requires $(term_boldify '--icopy') to assert Image-Copy mode."
else
ps_failAndExit SYNOPSIS_NONCOMPL \
"$(term_boldify "$targetAction") action doesn't support $(term_boldify "--$_key") modifier."
fi
}
for _key in "${!asrt_userVarsCompatibilityMatrix[@]}"; do
if [ ! -z "${st_userVars[$_key]}" ]; then
if ! sh_elementIsInList "$targetAction" ${asrt_userVarsCompatibilityMatrix[$_key]}; then
if [ "$_key" == "iso-file" ]; then
ps_failAndExit SYNOPSIS_NONCOMPL \
"$(term_boldify $targetAction) doesn't require any positional arguments."
else
_failAndHintUser asrt_userVarsCompatibilityMatrix
fi
fi
fi
done
for _key in "${!ct_userFlagsCompatibilityMatrix[@]}"; do
if [ ! -z "${st_userFlags[$_key]}" ]; then
if ! sh_elementIsInList "$targetAction" ${ct_userFlagsCompatibilityMatrix[$_key]}; then
_failAndHintUser ct_userFlagsCompatibilityMatrix
fi
fi
done
}
function asrt_inspectImageBootCapabilities() {
local _uefiCompatible=${st_isoInspections[supportsEFIBoot]}
local _syslinuxCompatible=false
if [ "${st_isoInspections[supportsEFIBoot]}" == true ]; then
if [ "$targetFilesystem" != "vfat" ]; then
term_echowarn "Found UEFI boot capabilities but you selected '$targetFilesystem' type, which is not compatible with UEFI boot." \
"Be warned that only legacy boot might work, if any."
else
_uefiCompatible=true
term_echogood "UEFI boot check validated. Your USB will work with UEFI boot."
fi
fi
if [[ "$enableGPT" == true && "${st_isoInspections[supportsEFIBoot]}" == false ]]; then
term_echowarn "$(term_boldify '--gpt') option ignored because image file solely supports legacy BIOS boot, which requires MBR."
fi
if [ ! -z "${st_isoInspections[syslinuxConf]}" ]; then
_syslinuxCompatible=true
st_shouldInstallSyslinux=true
if [ ! -z "${st_isoInspections[syslinuxVer]}" ]; then
term_echogood "Found SYSLINUX config file and binary at version ${st_isoInspections[syslinuxVer]}."
elif [ ! -z "${st_isoInspections[syslinuxBin]}" ]; then
term_echogood "Found SYSLINUX config file and binary with unknown version."
else
term_echogood "Found SYSLINUX config file."
fi
term_echogood "A SYSLINUX booloader will be installed on your USB device."
fi
if [[ "$_syslinuxCompatible" == false && "$_uefiCompatible" == false ]]; then
ps_failAndExit MISSING_BOOT_CAP \
"The selected image is not hybrid, doesn't support UEFI or legacy booting with SYSLINUX." \
"Therefore, it cannot result in any successful booting with $scriptName." \
"$ct_openImageSupportRequestMessage" \
"In the meantime, consider following the instructions provided with this image file."
fi
}
function asrt_checkSyslinuxInstall() {
sys_checkCommand 'syslinux'
if ! sys_hasCommand extlinux; then
ps_failAndExit MISSING_DEPENDENCY \
"Your distribution doesn't ship 'extlinux' with the 'syslinux' package." \
"Please install 'extlinux' and try again."
fi
st_foundSyslinuxBiosFolder=$(find "$ct_syslinuxLibRoot" -type d -path '*/bios' -print -quit)
st_foundSyslinuxMbrBinary=$(fs_findFileFromPatterns "$ct_syslinuxLibRoot" 'bios/mbr.bin' 'mbr.bin')
if [ -z "$st_foundSyslinuxBiosFolder" ]; then
ps_failAndExit MISSING_DEPENDENCY \
"Could not find a SYSLINUX bios folder containing c32 bios module files on this system."
fi
if [ -z "$st_foundSyslinuxMbrBinary" ]; then
ps_failAndExit MISSING_DEPENDENCY "Could not find a SYSLINUX MBR binary on this system."
fi
}
#
# .o8 o8o
# "888 `"'
# .oooo888 .ooooo. oooo ooo oooo
# d88' `888 d88' `88b `88. .8' `888
# 888 888 888ooo888 `88..8' 888
# 888 888 888 .o `888' 888
# `Y8bod88P" `Y8bod8P' `8' o888o
#
#
# DEVICE AND IMAGES MODULE (3)
function devi_configureLabel() {
local _user _vendor
targetPartitionLabel=${targetPartitionLabel:-$(blkid -o value -s LABEL -- "$sourceImageFile")}
case $targetFilesystem in
vfat)
# Label to uppercase, otherwise some DOS systems won't work properly
targetPartitionLabel=${targetPartitionLabel^^}
# FAT32 labels have maximum 11 chars
targetPartitionLabel=${targetPartitionLabel:0:11}
;;
exfat)
# EXFAT labels have maximum 15 chars
targetPartitionLabel=${targetPartitionLabel:0:15}
;;
ntfs)
# NTFS labels have maximum 32 chars
targetPartitionLabel=${targetPartitionLabel:0:32}
;;
ext2 | ext3 | ext4)
# EXT labels have maximum 16 chars
targetPartitionLabel=${targetPartitionLabel:0:16}
;;
f2fs)
# F2FS labels have maximum 512 glyphs
# approximated with 512 chars
targetPartitionLabel=${targetPartitionLabel:0:512}
;;
*)
term_echowarn "Unexpected partition type '$targetFilesystem'." "$ct_openBugReportMessage"
;;
esac
# Fallback to "USER_VENDOR" if format
if [[ "$targetAction" == format ]]; then
_user=${SUDO_USER:-$USER}
_vendor=$(lsblk -ldno VENDOR "$targetDevice" 2> /dev/null)
_vendor=${_vendor:-FLASH}
targetPartitionLabel=${targetPartitionLabel:-"${_user^^}_${_vendor^^}"}
else
targetPartitionLabel=${targetPartitionLabel:-''}
fi
if [[ -z "${st_userVars['label']}" && -n "$targetPartitionLabel" ]]; then
term_echogood "Partition label automatically set to '$targetPartitionLabel'." \
"You can explicitly set the label with $(term_boldify '-L, --label')."
elif [[ -n "$targetPartitionLabel" ]]; then
term_echogood "Partition label manually set to '$targetPartitionLabel'."
fi
}
function devi_selectDevice() {
function _chooseDevice() {
local _answer
term_echoinfo "Select the device corresponding to the USB device you want to make bootable: $(sh_joinBy ',' "${st_devicesList[@]}")" \
"\n${term_logPrefixEmpty}Type CTRL+D to quit."
read -r -p "${term_logPrefixEmpty}Select device id> " _answer
echo
if sh_elementIsInList "$_answer" "${st_devicesList[@]}"; then
targetDevice="/dev/$_answer"
else
if sh_elementIsInList "$_answer" "" "exit"; then
term_echoinfo "Exiting on user request."
exit 0
else
ps_failAndExit DEVICE_NOEXIST "The drive $_answer does not exist."
fi
fi
}
function _handleDeviceSelection() {
local _selectedDeviceBlock
if [ ${#st_devicesList[@]} -eq 1 ] && [ "$disableUSBCheck" == false ]; then
# autoselect
if [ "$disableConfirmation" == false ] || {
[ "$disableConfirmation" == 'true' ] && [ "$enableAutoselect" == 'true' ]
}; then
_selectedDeviceBlock="${st_devicesList[0]}"
term_echogood "Autoselecting '$_selectedDeviceBlock' (only USB device candidate)"
targetDevice="/dev/$_selectedDeviceBlock"
else
_chooseDevice
fi
else
_chooseDevice
fi
}
if [ -z "$targetDevice" ]; then
# List all hard disk drives
if exec_listUSBDrives; then
_handleDeviceSelection
else
ps_failAndExit NO_DEVICES
fi
fi
st_targetPartition="${targetDevice}1"
}
# return 0 - OK
# return 1 - failed appending partition table
# return 2 - failed formatting partition
function devi_addDataPartition() {
local _dataPartition _diskReport _sectorSize _partSize _humanSize _partScheme _partType
_diskReport=$(sfdisk -lJ "$targetDevice")
_partScheme=$(echo "$_diskReport" | jq -r '.partitiontable.label')
_partType=$(fs_inferFSType "$_partScheme" "$targetDataPartFstype")
sfdisk --append "$targetDevice" < <(echo "-,-,$_partType") |& term_indentAll || return 1
_diskReport=$(sfdisk -lJ "$targetDevice")
_dataPartition=$(echo "$_diskReport" | jq -r '.partitiontable.partitions[-1].node')
fs_formatPartition "$targetDataPartFstype" "$_dataPartition" "DATA" || return 2
_sectorSize=$(echo "$_diskReport" | jq -r '.partitiontable.sectorsize')
_partSize=$(echo "$_diskReport" | jq -r '.partitiontable.partitions[-1].size')
_humanSize=$(numfmt --to=iec-i --suffix=B $((_sectorSize * _partSize)))
term_echogood "Created $targetDataPartFstype data partition $_dataPartition of size $_humanSize"
}
# $1: partScheme - MBR or GPT
# $2: notBootable - true or false
function devi_createPartitionTable() {
local -r _partScheme=$1
local -r _notBootable=$2
local _partitionOptions
local _sfdiskCommand='sfdisk'
local _sfdiskVersion
_sfdiskVersion=$(sfdisk -v | grep -Po '\d+\.\d+')
function _makeSfdiskCommand() {
# Retro compatibility for 'old' sfdisk versions
if sh_compute "$_sfdiskVersion >= 2.28"; then
_sfdiskCommand='sfdisk -W always'
fi
}
function _initGPT() {
local _partitionType=${targetPartitionScheme:-$(fs_inferFSType gpt "$targetFilesystem")}
_partitionOptions="label: gpt\n"
if [ "$_notBootable" == false ]; then
# Windows UEFI boot partitions must be of type "Windows Data Partition",
# otherwise bug with "Drivers not found"
if [[ "${st_isoInspections[supportsEFIBoot]}" == true && "${st_isoInspections[hasWimFile]}" == false ]]; then
# Set typecode to EFI System Partition
_partitionType="${fs_gptPartitionCodes[efi]}"
else
ps_failAndExit STATE_ERROR "(devi_createPartitionTable) GPT partition tables are not compatible with legacy BIOS boot."
fi
fi
_partitionOptions+="type=${_partitionType}"
}
function _initMBR() {
local _partitionType=${targetPartitionScheme:-$(fs_inferFSType mbr "$targetFilesystem")}
_partitionOptions="label: dos\n"
_partitionOptions+="$st_targetPartition : start=2048, type=$_partitionType"
}
_makeSfdiskCommand
case "$_partScheme" in
GPT) _initGPT ;;
MBR) _initMBR ;;
*) ps_failAndExit FAILED_POSTULATE "(devi_createPartitionTable) Unexpected partition scheme '$1'" ;;
esac
if [[ "$_notBootable" == false && "$_partScheme" == MBR && "${st_isoInspections[supportsBIOSBoot]}" == true ]]; then
_partitionOptions+=", bootable"
fi
term_echogood "Creating $_partScheme partition table with 'sfdisk' v$_sfdiskVersion..."
echo -e "$_partitionOptions" | $_sfdiskCommand "$targetDevice" |& term_indentAll || ps_failAndExit IO_ERROR \
"Failed to write USB device $_partScheme partition table."
partx -u "$targetDevice" # Refresh partition table
fs_syncdev
}
# $1: notBootable - true or false, default false
function devi_partitionUSB() {
local -r _notBootable=${1:-false}
local _partScheme
function _shouldWipeUSBKey() {
local _answer='y'
term_echowarn "About to wipe the content of device '$targetDevice'."
if [ "$disableConfirmation" == false ]; then
read -r -p "${term_logPrefixEmpty}Are you sure you want to proceed? (y/n)> " _answer
else
term_echowarn "Bypassing confirmation with $(term_boldify '-y, --assume-yes')."
fi
if [ "$_answer" == 'y' ]; then
return 0
else
return 1
fi
}
function _unmountPartitions() {
local _partition
# unmount any partition on selected device
mapfile -t devicePartitions < <(grep -oP "^\\K$targetDevice\\S*" /proc/mounts)
for _partition in "${devicePartitions[@]}"; do
if ! umount "$_partition" > /dev/null; then
ps_failAndExit IO_ERROR \
"Failed to unmount $_partition. It's likely that the partition is busy."
fi
done
}
function _eraseDevice() {
term_echoinfo "Erasing contents of '$targetDevice'..."
# clean signature from selected device
wipefs --all --force "$targetDevice" &> /dev/null
# erase drive
dd if=/dev/zero of="$targetDevice" bs=512 count=1 conv=notrunc status=none |&
term_indentAll ||
ps_failAndExit IO_ERROR "Failed to erase USB device." \
"It's likely that the device has been ejected and needs to be reconnected." \
"You can check the availability of USB drives with $(term_boldify "$scriptName -l")."
fs_syncdev
}
if _shouldWipeUSBKey; then
_unmountPartitions
_eraseDevice
if [ "$st_shouldMakePartition" == 'true' ]; then
if [[ "$enableGPT" == true && (${st_isoInspections[supportsEFIBoot]} == true || "$_notBootable" == true) ]]; then
_partScheme=GPT
else
_partScheme=MBR
fi
devi_createPartitionTable $_partScheme "$_notBootable"
fs_formatPartition "$targetFilesystem" "$st_targetPartition" "$targetPartitionLabel"
fi
else
ps_failAndExit USER_ABORTED "Canceling operation."
fi
}
function devi_installSyslinuxVersion() {
local -r _desiredSyslinuxVersion=$1
local _versions
local _minor
local _filename
local _syslinuxArchive
local _assetURL
local _status
local _abortingMessage="Aborting SYSLINUX installation and resuming with local install."
function _checkConnexion() {
_status=$(curl -sLIo /dev/null -w "%{http_code}" "$ct_kernelOrgSyslinuxURL")
if [ "$_status" != 200 ]; then
if [ "$_status" == 000 ]; then
ps_failAndExit HOST_UNREACHABLE "kernel.org is unreachable. You don't seem to have an internet connection." \
"Please try again later or use $(term_boldify '--local-bootloader') to force usage of the local SYSLINUX version."
return 9
else
term_echowarn "Couldn't GET $ct_kernelOrgSyslinuxURL." \
"Received status code '$_status'." \
"$ct_openBugReportMessage" \
"$_abortingMessage"
return 10
fi
fi
return 0
}
function _findMinorVersions() {
_versions="$(curl -sL "$ct_kernelOrgSyslinuxURL" | grep -oP 'href="\K\d+\.\d+(?=/")' | sort --version-sort)"
if (($? != 0)); then
term_echowarn "Couldn't GET $ct_kernelOrgSyslinuxURL." \
"Aborting syslinux installation and resuming with local install."
return 10
elif [ -z "$_versions" ]; then
term_echoerr "Couldn't parse the result of $ct_kernelOrgSyslinuxURL." \
"This is not expected: please open a ticket at $ct_ticketsURL." \
"$_abortingMessage"
return 11
fi
_minor=$(echo "$_versions" | grep -E "^$_desiredSyslinuxVersion" | grep "^${_desiredSyslinuxVersion%.}" | tail -n 1)
if [ -z "$_minor" ]; then
term_echoerr "Version '$_desiredSyslinuxVersion' is not available at kernel.org."
return 8
fi
return 0
}
function _findMatchedRelease() {
_filename=$(curl -sL "$ct_kernelOrgSyslinuxURL/$_minor/" | grep -oP 'href="\Ksyslinux-\d+\.\d+-\w+\d+\.tar\.gz(?=")' | sort --version-sort | tail -n1)
if [ -z "$_filename" ]; then
term_echoerr "Couldn't find '$_filename'."
return 11
fi
_assetURL="$ct_kernelOrgSyslinuxURL/$_minor/$_filename"
_syslinuxArchive=$ct_cacheRoot/$_filename
return 0
}
function _downloadMatchedVersion() {
if [ -e "$_syslinuxArchive" ]; then
term_echogood "Found '$_syslinuxArchive' in cache."
return 0
fi
if curl -sL -o "$_syslinuxArchive" "$_assetURL"; then
if [ -f "$_syslinuxArchive" ]; then
term_echogood "Download of '$_syslinuxArchive' completed ($(du -h "$_syslinuxArchive" | awk '{print $1}'))"
else
term_echowarn "Missing file '$_syslinuxArchive'." \
"This is not expected: please open a ticket at $ct_ticketsURL." \
"$_abortingMessage"
return 10
fi
else
term_echowarn "Couldn't get '$_assetURL'." \
"This is not expected: please open a ticket at $ct_ticketsURL." \
"$_abortingMessage"
return 10
fi
return 0
}
function _extractMatchedVersion() {
if tar -xf "$_syslinuxArchive" -C "$ct_tempRoot"; then
syslinuxInstallDir="$ct_tempRoot/$(basename "${_syslinuxArchive%.tar.gz}")"
st_temporaryAssets+=("$syslinuxInstallDir")
else
rm "$_syslinuxArchive"
return 11
fi
}
function _configureSyslinuxInstall() {
local _extlinuxBin
local _mbrBin
_extlinuxBin=$(fs_findFileFromPatterns "$syslinuxInstallDir" 'bios/extlinux/extlinux' 'extlinux/extlinux' 'extlinux')
_mbrBin=$(fs_findFileFromPatterns "$syslinuxInstallDir" 'bios/mbr/mbr.bin' 'mbr/mbr.bin' 'mbr.bin')
if [ -z "$_extlinuxBin" ]; then
term_echowarn "Couldn't find 'extlinux' binary in installation folder." \
"$_abortingMessage"
return 10
fi
if [ -z "$_mbrBin" ]; then
term_echowarn "Couldn't find 'mbr.bin' in installation folder." \
"$_abortingMessage"
return 10
fi
st_syslinuxBinaries['mbrBin']="$_mbrBin"
st_syslinuxBinaries['extBin']="$_extlinuxBin"
return 0
}
sys_checkCommand 'curl'
_inferSyslinuxVersion
_checkConnexion || return "$?"
_findMinorVersions || return "$?"
_findMatchedRelease || return "$?"
_downloadMatchedVersion || return "$?"
_extractMatchedVersion || return "$?"
_configureSyslinuxInstall || return "$?"
term_echogood "SYSLINUX version '$_minor' temporarily set for installation."
}
# $1 - mountPoint
# $2 - inspect syslinux? true or false
function devi_inspectPartition() {
local -r _mountPoint="$1"
local -r _shouldInspectSyslinux=$2
local _supportsEFIBoot _hasWimFile _syslinuxBin _syslinuxConf _syslinuxVer _supportsBIOSBoot
_supportsEFIBoot=${st_isoInspections[supportsEFIBoot]:-false}
_hasWimFile=${st_isoInspections[hasWimFile]:-false}
_syslinuxBin=${st_isoInspections[syslinuxBin]}
_syslinuxConf=${st_isoInspections[syslinuxConf]}
_syslinuxVer=${st_isoInspections[syslinuxVer]}
_supportsBIOSBoot=${st_isoInspections[supportsBIOSBoot]}
local -a _sysLinuxLocations=('boot/syslinux/syslinux.cfg' 'syslinux/syslinux.cfg' 'syslinux.cfg' 'boot/syslinux/extlinux.conf'
'boot/syslinux/extlinux.cfg' 'boot/extlinux/extlinux.conf' 'boot/extlinux/extlinux.cfg' 'syslinux/extlinux.conf'
'syslinux/extlinux.cfg' 'extlinux/extlinux.conf' 'extlinux/extlinux.cfg' 'extlinux.conf' 'extlinux.cfg')
local -a _isoLinuxLocations=('boot/isolinux/isolinux.cfg' 'isolinux/isolinux.cfg' 'isolinux.cfg' 'boot/syslinux/isolinux.cfg' 'syslinux/isolinux.cfg')
function _inspectSyslinux() {
_syslinuxBin=$(fs_matchFirstExpression "$_mountPoint" 'syslinux.bin' 'isolinux.bin' 'extlinux.bin' 'boot.bin' 'extlinux' 'syslinux' 'isolinux')
if [ ! -z "$_syslinuxBin" ]; then
_syslinuxVer=$(strings "$_syslinuxBin" | grep -E 'ISOLINUX|SYSLINUX|EXTLINUX' | grep -oP '(\d+\.\d+)' | awk 'NR==1{print $1}')
fi
_syslinuxConf=$(fs_findFileFromPatterns "$_mountPoint" "${_sysLinuxLocations[@]}")
if [ -z "$_syslinuxConf" ]; then
_syslinuxConf=$(fs_matchFirstExpression "$_mountPoint" 'syslinux.cfg' 'extlinux.conf' 'extlinux.cfg')
fi
if [ -z "$_syslinuxConf" ]; then
_syslinuxConf=$(fs_findFileFromPatterns "$_mountPoint" "${_isoLinuxLocations[@]}")
_syslinuxConf=${_syslinuxConf:-$(fs_firstMatchInFolder "$_mountPoint" 'isolinux.cfg')}
fi
if [ -n "$_syslinuxConf" ]; then
_supportsBIOSBoot=true
fi
}
function _inspectEFICapabilities() {
local _hasEfiRoot
local _hasEfiFile
_hasEfiRoot=$(find "$_mountPoint" -type d -iname 'efi' -print -quit)
_hasEfiFile=$(find "$_mountPoint" -type f -ipath '*/efi/*.efi' -prune -print -quit)
if [ ! -z "$_hasEfiFile" ] && [ ! -z "$_hasEfiRoot" ]; then
_supportsEFIBoot=true
fi
}
function _inspectWindows() {
if [[ -e "$_mountPoint/sources/install.wim" ]]; then
_hasWimFile=true
fi
}
_inspectEFICapabilities
_inspectWindows
st_isoInspections[supportsEFIBoot]=$_supportsEFIBoot
st_isoInspections[hasWimFile]=$_hasWimFile
if [[ $_shouldInspectSyslinux == true ]]; then
_inspectSyslinux
st_isoInspections[syslinuxConf]="${_syslinuxConf#$_mountPoint}"
st_isoInspections[syslinuxBin]="${_syslinuxBin#$_mountPoint}"
st_isoInspections[syslinuxVer]="$_syslinuxVer"
st_isoInspections[supportsBIOSBoot]="$_supportsBIOSBoot"
fi
}
function devi_inspectHybridImage() {
local _diskReport _partScheme
local _supportsEFIBoot _supportsBIOSBoot
function _inspectMBRPartTable() {
local _startSector
# Look for the first ESP partition
_startSector=$(echo "$_diskReport" | jq ".partitiontable.partitions | map(select(.type==\"${fs_mbrPartitionCodes[efi]}\"))[0].start | select (.!=null)")
# If ESP found, look for first bootable
if [[ -n "$_startSector" ]]; then
_supportsEFIBoot=true
else
_supportsEFIBoot=false
fi
_startSector=$(echo "$_diskReport" | jq '.partitiontable.partitions | map(select(.bootable==true))[0].start | select (.!=null)')
if [[ -n "$_startSector" ]]; then
_supportsBIOSBoot=true
else
_supportsBIOSBoot=false
fi
}
function _inspectGPTPartTable() {
local _startSector
# In GPT mode, UEFI is theoretically the only possible boot mode
_startSector=$(echo "$_diskReport" | jq ".partitiontable.partitions | map(select(.type==\"${fs_gptPartitionCodes[efi]}\"))[0].start | select (.!=null)")
_supportsBIOSBoot=false
if [[ -n "$_startSector" ]]; then
_supportsEFIBoot=true
else
_supportsEFIBoot=false
fi
}
_diskReport=$(sfdisk -lJ -- "$sourceImageFile" 2> /dev/null) \
|| ps_failAndExit IO_ERROR "sfdisk couldn't read the partition table on the image file, which is likely corrupted."
_partScheme=$(echo "$_diskReport" | jq -r '.partitiontable.label')
case $_partScheme in
dos) _inspectMBRPartTable ;;
gpt) _inspectGPTPartTable ;;
esac
st_isoInspections[supportsBIOSBoot]="$_supportsBIOSBoot"
st_isoInspections[supportsEFIBoot]="$_supportsEFIBoot"
}
function devi_inspectElToritoImage() {
local _mountPoint
_mountPoint=$(fs_createMountFolder iso) || exit "$?"
fs_mountElToritoFile "$_mountPoint"
devi_inspectPartition "$_mountPoint" true
fs_umountPartition "$_mountPoint"
}
# .
# .o8
# .oooo.o .o888oo .ooooo. oo.ooooo.
# d88( "8 888 d88' `88b 888' `88b
# `"Y88b. 888 888ooo888 888 888
# o. )88b 888 . 888 .o 888 888
# 8""888P' "888" `Y8bod8P' 888bod8P'
# 888
# o888o
#
# STEPS MODULE (3)
function step_initProcess() {
ps_normalizePath
}
function step_configureFolders() {
local _defaultMode=775
if [ ! -e "$ct_tempRoot" ]; then
mkdir -m $_defaultMode "$ct_tempRoot"
elif [ -d "$ct_tempRoot" ]; then
chmod -R $_defaultMode "$ct_tempRoot"
else
ps_failAndExit FAILED_POSTULATE "'$ct_tempRoot' is not a folder." \
"Remove this file and try again."
fi
if [ ! -e "$ct_cacheRoot" ]; then
mkdir -m $_defaultMode "$ct_cacheRoot"
elif [ -d "$ct_cacheRoot" ]; then
chmod -R $_defaultMode "$ct_cacheRoot"
else
ps_failAndExit FAILED_POSTULATE "'$ct_cacheRoot' is not a folder." \
"Remove this file and try again."
fi
if [ ! -e "$ct_mountRoot" ]; then
# shellcheck disable=SC2174
mkdir -pm $_defaultMode "$ct_mountRoot"
elif [ ! -d "$ct_mountRoot" ]; then
ps_failAndExit FAILED_POSTULATE "'$ct_mountRoot' is not a folder." \
"Remove this file and try again."
fi
}
function step_initDevicesList() {
local -a _devices
local _device
mapfile -t _devices < <(lsblk -dno NAME)
st_devicesList=()
for _device in "${_devices[@]}"; do
if [ "$(sys_getDeviceType "/dev/$_device")" == "usb" ] || [ "$disableUSBCheck" == 'true' ]; then
st_devicesList+=("$_device")
fi
done
}
function step_inspectImageFile() {
local _isHybrid
function _inspectImageFilesystem() {
file -b -- "$sourceImageFile" | grep -q '^ISO 9660 CD-ROM filesystem'
if (($? == 0)); then
_isHybrid=false
else
_isHybrid=true
fi
}
_inspectImageFilesystem
if [[ $_isHybrid == true ]]; then
devi_inspectHybridImage
else
devi_inspectElToritoImage
fi
st_isoInspections[isHybrid]=$_isHybrid
}
function step_installBootloader() {
local _syslinuxFolder
local _syslinuxConfig
local _localSyslinuxVersion
function _inferSyslinuxVersion() {
_localSyslinuxVersion=$(syslinux --version |& grep -oP '(\d+\.\d+)')
if [ "$targetBootloaderVersion" != 'auto' ]; then
st_targetSyslinuxVersion="$targetBootloaderVersion"
else
st_targetSyslinuxVersion=${st_isoInspections[syslinuxVer]:-"$_localSyslinuxVersion"}
fi
}
# return 0 - install from kernel.org
# return 1+ - install from local
function _checkSyslinuxVersion() {
local -i _versionsMatch=0
if [ "$enableForceLocalBootloader" == true ]; then
term_echogood "Enforced local SYSLINUX bootloader at version '$_localSyslinuxVersion'."
return 1
fi
if [ -z "$st_targetSyslinuxVersion" ]; then
return 1
fi
if [ "$targetBootloaderVersion" != 'auto' ]; then
term_echoinfo "Searching for SYSLINUX V$st_targetSyslinuxVersion remotely."
if ! devi_installSyslinuxVersion "$st_targetSyslinuxVersion"; then
if [ ! -z "${st_isoInspections[syslinuxVer]}" ]; then
term_echowarn "Falling back to image SYSLINUX version '${st_isoInspections[syslinuxVer]}'"
st_targetSyslinuxVersion="${st_isoInspections[syslinuxVer]}"
devi_installSyslinuxVersion "${st_isoInspections[syslinuxVer]}"
return $?
else
return 1
fi
else
return 0
fi
fi
term_echoinfo "Found local SYSLINUX version '$_localSyslinuxVersion'"
sh_compute "$_localSyslinuxVersion == ${st_isoInspections[syslinuxVer]}" > /dev/null
_versionsMatch=$?
if ((_versionsMatch == 0)); then
term_echogood "image SYSLINUX version matches local version."
return 1
else
term_echowarn "image SYSLINUX version doesn't match local version." \
"Scheduling download of version $st_targetSyslinuxVersion..."
if ! devi_installSyslinuxVersion "$st_targetSyslinuxVersion"; then
term_echowarn "Falling back to local SYSLINUX version '$_localSyslinuxVersion'."
st_targetSyslinuxVersion="$_localSyslinuxVersion"
return 1
fi
fi
}
function _setSyslinuxLocation() {
local _isoFolder
local _isolinuxConfig
if [[ "${st_isoInspections[syslinuxConf]}" =~ isolinux.cfg ]]; then
_isolinuxConfig="$st_usbMountPoint${st_isoInspections[syslinuxConf]}"
_isoFolder=$(dirname "$_isolinuxConfig")
_syslinuxConfig="$_isoFolder/syslinux.cfg"
mv "$_isolinuxConfig" "$_syslinuxConfig"
term_echoinfo "Found ISOLINUX config file at '$_isolinuxConfig'." \
"Moving to '$_syslinuxConfig'."
else
_syslinuxConfig="$st_usbMountPoint${st_isoInspections[syslinuxConf]}"
fi
_syslinuxFolder=$(dirname "$_syslinuxConfig")
}
function _installWtLocalExtlinux() {
st_syslinuxBinaries=(['mbrBin']="$st_foundSyslinuxMbrBinary" ['extBin']='extlinux')
st_targetSyslinuxVersion="$_localSyslinuxVersion"
term_echoinfo "Installing SYSLINUX bootloader in '$_syslinuxFolder' with local version '$st_targetSyslinuxVersion'..."
rsync --no-links --no-perms --no-owner --no-group -I "$st_foundSyslinuxBiosFolder"/*.c32 "$_syslinuxFolder" |&
term_indentAll ||
term_echowarn "SYSLINUX could not install C32 BIOS modules."
fs_syncdev
term_echogood "C32 BIOS modules successfully installed."
${st_syslinuxBinaries['extBin']} --stupid --install "$_syslinuxFolder" |& term_indentAll || ps_failAndExit THIRD_PARTY_ERROR \
"SYSLINUX bootloader could not be installed."
fs_syncdev
}
function _installWtKernelOrgExtlinux() {
local _isExt32 _isHost32 _fallbackToLocal=false
term_echoinfo "Installing SYSLINUX bootloader in '$_syslinuxFolder' with kernel.org version '$st_targetSyslinuxVersion'..."
file -b -- "${st_syslinuxBinaries['extBin']}" | awk '{print $2}' | grep -e '^32' &> /dev/null
_isExt32=$?
echo "$ct_architecture" | grep -e '32|i386'
_isHost32=$?
if ((_isExt32 == _isHost32)); then
_fallbackToLocal=false
elif ((_isExt32 == 0 && _isHost32 > 0)); then
term_echowarn "The SYSLINUX binary from kernel.org is for 32 bits architectures," \
" but your system is 64 bits." \
"If the install fails, you might need to install 32 bits support on your system."
_fallbackToLocal=false
else # _isExt32 > 0 && _isHost32 == 0
term_echowarn "The SYSLINUX binary from kernel.org is for 64 bits architectures." \
"Your system is 32 bits. Falling back to local syslinux installation."
_fallbackToLocal=true
fi
if [[ "$_fallbackToLocal" == false ]]; then
if ! ${st_syslinuxBinaries['extBin']} --stupid --install "$_syslinuxFolder" |& term_indentAll; then
term_echowarn "Could not run SYSLINUX '$st_targetSyslinuxVersion' from kernel.org." \
"Attempting with local SYSLINUX install..."
_fallbackToLocal=true
fi
fi
if [[ "$_fallbackToLocal" == true ]]; then
_installWtLocalExtlinux > /dev/null
fi
fs_syncdev
}
function _installMasterBootRecordProg() {
dd bs=440 count=1 conv=notrunc status=none if="${st_syslinuxBinaries['mbrBin']}" of="$targetDevice" |&
term_indentAll ||
ps_failAndExit IO_ERROR "Failed to install Master Boot Record program."
fs_syncdev
term_echogood "Successfully installed Master Boot Record program."
}
_inferSyslinuxVersion
_setSyslinuxLocation
_checkSyslinuxVersion
case $? in
0) _installWtKernelOrgExtlinux ;;
*) _installWtLocalExtlinux ;;
esac
term_echogood "Successfully installed SYSLINUX bootloader at version '$st_targetSyslinuxVersion'."
_installMasterBootRecordProg
}
function step_copyWithRsync() {
function _rsyncWithProgress() {
# _i defined for term_updateProgress
local -i _i=1
local _statusFile
local _wimFile="$st_elToritoMountPoint/sources/install.wim"
local _rsyncOptions=""
local _status
_statusFile=$(fs_createTempFile "bootiso-rsync-status")
st_temporaryAssets+=("$_statusFile")
if [ -f "$_wimFile" ]; then
if [ "$disableWimsplit" == false ]; then
_rsyncOptions="--exclude sources/install.wim"
term_echogood "Detected a Windows install.wim file, which will be handled by 'wimlib-imagex' utility."
else
term_echowarn "Detected a Windows install.wim file but wimsplit has been disabled with $(term_boldify '--no-wim-split') option."
fi
fi
(
# shellcheck disable=SC2086
rsync -r -q -I --no-links --no-perms --no-owner --no-group $_rsyncOptions "$st_elToritoMountPoint"/. "$st_usbMountPoint"
_status=$?
term_cleanProgress
if ((_status == 0)) && [ -f "$_wimFile" ] && [ "$disableWimsplit" == false ]; then
echo
wimlib-imagex split "$_wimFile" "$st_usbMountPoint/sources/install.swm" 1024 |& term_indentAll
fi
echo "$_status" > "$_statusFile"
) &
st_backgroundProcess=$!
echo -n "$scriptName: Copying files from image to USB device with 'rsync' "
while [ -e "/proc/$st_backgroundProcess" ]; do
term_updateProgress
done
st_backgroundProcess=''
term_cleanProgress
_status=$(cat "$_statusFile")
if [ ! "$_status" -eq 0 ]; then
ps_failAndExit IO_ERROR "Copy command with 'rsync' failed."
fi
}
sys_checkCommand 'rsync'
_rsyncWithProgress
fs_syncWithProgress
}
function step_copyWithDD() {
function _ddWithProgress() {
local -i _i=1
local _statusFile
local _status
_statusFile=$(fs_createTempFile "bootiso-status")
st_temporaryAssets+=("$_statusFile")
(
dd if="$sourceImageFile" of="$targetDevice" bs="$targetDDBusSize" status=none
echo "$?" > "$_statusFile"
) &
st_backgroundProcess=$!
echo -n "$scriptName: Copying files from image to USB device with 'dd' "
while [ -e "/proc/$st_backgroundProcess" ]; do
term_updateProgress
done
st_backgroundProcess=''
term_cleanProgress
_status=$(cat "$_statusFile")
if [ ! "$_status" -eq 0 ]; then
ps_failAndExit IO_ERROR "Copy command with 'dd' failed."
fi
}
_ddWithProgress
fs_syncWithProgress
}
function step_initPckgManager() {
if sys_hasCommand apt-get; then # Debian
st_packageManager="apt-get install"
return 0
fi
if sys_hasCommand dnf; then # Fedora
st_packageManager="dnf install"
return 0
fi
if sys_hasCommand yum; then # Fedora
st_packageManager="yum install"
return 0
fi
if sys_hasCommand pacman; then # Arch
st_packageManager="pacman -S"
return 0
fi
if sys_hasCommand zypper; then # OpenSuse
st_packageManager="zypper install"
return 0
fi
if sys_hasCommand emerge; then # Gentoo
st_packageManager="emerge"
return 0
fi
if sys_hasCommand xbps-install; then # Void
st_packageManager="xbps-install"
return 0
fi
if sys_hasCommand eopkg; then # Solus
st_packageManager="eopkg install"
return 0
fi
return 1
}
function step_parseArguments() {
local _key
local _isEndOfOptions=false
local _wrongOptions
local _options
local -a _extractedOptions
function _enableUserFlag() {
st_userFlags["$1"]=true
}
function _setUserVar() {
st_userVars["$1"]=$2
}
while [[ $# -gt 0 ]]; do
_key="$1"
if [ "$_isEndOfOptions" == false ]; then
case $_key in
# ACTIONS
-h | --help | help)
_enableUserFlag 'help'
shift
;;
-v | --version)
_enableUserFlag 'version'
shift
;;
-l | --list-usb-drives)
_enableUserFlag 'list-usb-drives'
shift
;;
-p | --probe)
_enableUserFlag 'probe'
shift
;;
-f | --format)
_enableUserFlag 'format'
shift
;;
-i | --inspect)
_enableUserFlag 'inspect'
shift
;;
--dd | --icopy)
_enableUserFlag 'install-image-copy'
shift
;;
--mrsync)
_enableUserFlag 'install-mount-rsync'
shift
;;
# OPTIONS
--dd-bs)
if (($# < 2)); then
ps_failAndExit SYNOPSIS_NONCOMPL \
"Missing value for '$1' flag. Please provide a number of bytes, see dd(1)."
fi
_setUserVar 'dd-bs' "$2"
shift 2
;;
-D | --data-part)
_enableUserFlag 'data-part'
shift
;;
--local-bootloader)
_enableUserFlag 'local-bootloader'
shift
;;
--remote-bootloader)
if (($# < 2)); then
ps_failAndExit SYNOPSIS_NONCOMPL \
"Missing value for '$1' flag. Please provide a version following MAJOR.MINOR pattern. ex: '4.10'."
fi
_setUserVar 'remote-bootloader' "$2"
shift 2
;;
--gpt)
_enableUserFlag 'gpt'
shift
;;
-y | --assume-yes)
_enableUserFlag 'assume-yes'
shift
;;
-d | --device)
if (($# < 2)); then
ps_failAndExit SYNOPSIS_NONCOMPL "Missing value for '$1' flag. Please provide a device."
fi
_setUserVar 'device' "$2"
shift 2
;;
--part-type)
if (($# < 2)); then
ps_failAndExit SYNOPSIS_NONCOMPL "Missing value for '$1' flag. Please provide a partition type."
fi
_setUserVar 'part-type' "${2}"
shift 2
;;
-t | --type | -F | --fs)
if (($# < 2)); then
ps_failAndExit SYNOPSIS_NONCOMPL "Missing value for '$1' flag. Please provide a filesystem type."
fi
_setUserVar 'fs' "${2,,}" #lowercased
shift 2
;;
--data-part-fs)
if (($# < 2)); then
ps_failAndExit SYNOPSIS_NONCOMPL "Missing value for '$1' flag. Please provide a filesystem type."
fi
_setUserVar 'data-part-fs' "${2,,}" #lowercased
shift 2
;;
-L | --label)
if (($# < 2)); then
ps_failAndExit SYNOPSIS_NONCOMPL "Missing value for '$1' flag. Please provide a label."
fi
_setUserVar 'label' "$2"
shift 2
;;
--hash-file)
if (($# < 2)); then
ps_failAndExit SYNOPSIS_NONCOMPL "Missing value for '$1' flag. Please provide a hash file."
fi
_setUserVar 'hash-file' "$2"
shift 2
;;
-J | --no-eject)
_enableUserFlag 'no-eject'
shift
;;
-H | --no-hash-check)
_enableUserFlag 'no-hash-check'
shift
;;
-a | --autoselect)
_enableUserFlag 'autoselect'
shift
;;
-M | --no-mime-check)
_enableUserFlag 'no-mime-check'
shift
;;
--no-usb-check)
_enableUserFlag 'no-usb-check'
shift
;;
--no-size-check)
_enableUserFlag 'no-size-check'
shift
;;
--force-hash-check)
_enableUserFlag 'force-hash-check'
shift
;;
--no-wimsplit)
_enableUserFlag 'no-wimsplit'
shift
;;
--)
_isEndOfOptions=true
shift
;;
-*)
if [[ "$_isEndOfOptions" == false ]]; then
if [[ "$_key" =~ ^-- ]]; then
ps_failAndExit SYNOPSIS_NONCOMPL "Unknown option: $(term_boldify "$_key")."
# Handle stacked options
elif [[ "$_key" =~ ^-["$ct_shortOptions"]{2,}$ ]]; then
shift
_options=${_key#*-}
mapfile -t _extractedOptions < <(echo "$_options" | grep -o . | xargs -d '\n' -n1 printf '-%s\n')
set -- "${_extractedOptions[@]}" "$@"
else
printf "\\e[0;31m%s\\e[m" "$scriptName: Unknown options: "
printf '%s.' "$_key" | GREP_COLORS='mt=00;32:sl=00;31' grep --color=always -P "[$ct_shortOptions]"
if [[ "$_key" =~ ^-[a-zA-Z0-9]+$ ]]; then
_wrongOptions=$(printf '%s' "${_key#*-}" | grep -Po "[^$ct_shortOptions]" | tr -d '\n')
if [ ${#_key} -eq 2 ]; then
term_echowarn "$(term_boldify "$_wrongOptions") flag were not recognized."
else
term_echowarn "$(term_boldify "$_wrongOptions") flags were not recognized."
fi
fi
ps_failAndExit SYNOPSIS_NONCOMPL
fi
else
_setUserVar 'iso-file' "$1"
shift
fi
;;
*)
_setUserVar 'iso-file' "$1"
shift
;;
esac
else
_setUserVar 'iso-file' "$1"
break
fi
done
}
function step_assignInternalVariables() {
# Command argument
sourceImageFile=${st_userVars['iso-file']:-''}
# Option flags
disableConfirmation=${st_userFlags['assume-yes']:-'false'}
enableAutoselect=${st_userFlags[autoselect]:-'false'}
enableGPT=${st_userFlags[gpt]:-'false'}
enableForceLocalBootloader=${st_userFlags['local-bootloader']:-'false'}
disableMimeCheck=${st_userFlags['no-mime-check']:-'false'}
disableUSBCheck=${st_userFlags['no-usb-check']:-'false'}
disableSizeCheck=${st_userFlags['no-size-check']:-'false'}
disableHashCheck=${st_userFlags['no-hash-check']:-'false'}
enableForceHashCheck=${st_userFlags['force-hash-check']:-'false'}
disableWimsplit=${st_userFlags['no-wimsplit']:-'false'}
enableDataPart=${st_userFlags['data-part']:-'false'}
# Vars flags
targetFilesystem=${st_userVars[fs]:-'vfat'}
targetPartitionScheme=${st_userVars['part-type']}
sourceHashFile=${st_userVars['hash-file']:-''}
targetDevice=${st_userVars[device]:-''}
targetPartitionLabel=${st_userVars[label]:-''}
targetBootloaderVersion=${st_userVars['remote-bootloader']:-'auto'}
targetDDBusSize=${st_userVars['dd-bs']:-'4M'}
targetDataPartFstype=${st_userVars['data-part-fs']:-'vfat'}
# Action-dependent flags
case $targetAction in
install-*)
st_hasActionDuration='true'
st_expectingISOFile='true'
requiresRoot='true'
disableDeviceEjection=${st_userFlags['no-eject']:-'false'}
;;
format)
st_hasActionDuration='true'
st_expectingISOFile='false'
requiresRoot='true'
;;
version)
st_hasActionDuration='false'
st_expectingISOFile='false'
requiresRoot='false'
;;
help | list-usb-drives)
st_hasActionDuration='false'
st_expectingISOFile='false'
requiresRoot='false'
;;
inspect | probe)
st_hasActionDuration='false'
st_expectingISOFile='true'
requiresRoot='true'
;;
*)
ps_failAndExit STATE_ERROR "Unhandled action $(term_boldify "$targetAction")."
;;
esac
}
function step_runSecurityAssessments() {
devi_configureLabel
devi_selectDevice
ps_startTimer
asrt_checkDeviceIsOK "$targetDevice"
asrt_checkDeviceIsUSB
asrt_checkImageSize "$targetDevice" "$sourceImageFile"
}
function step_checkArguments() {
asrt_checkAction
asrt_checkUserVars
asrt_checkUserFlags
}
function step_finalize() {
if [ "$st_hasActionDuration" == 'true' ]; then
ps_stopTimerAndPrintLapsed
fi
st_completedAction=$targetAction
}
# .ooooo. oooo ooo .ooooo. .ooooo.
# d88' `88b `88b..8P' d88' `88b d88' `"Y8
# 888ooo888 Y888' 888ooo888 888
# 888 .o .o8"'88b 888 .o 888 .o8
# `Y8bod8P' o88' 888o `Y8bod8P' `Y8bod8P'
#
# ACTION EXEC MODULE (4)
# Print stdin properly
# $1 : terminal width
function term_formatFlagDescription() {
local -r _termWidth=$1
if [[ "$st_hasLegacyColumn" == false ]]; then
column -c "$_termWidth" --table -d -N flag,desc -W desc -s \|
else
column -c "$_termWidth" -t -s \|
fi
}
function exec_help() {
local _termWidth
local -r _actionFlagsTable=$(printf "%s\n" \
"-f, --format|Format selected USB drive and exit." \
"-h, --help|Display this help message and exit." \
"-i, --inspect|Inspect <imagefile> boot capabilities." \
"-l, --list-usb-drives|List available USB drives and exit." \
"-p, --probe|Equivalent to -i followed by -l actions.")
local -r _modifierFlagsTable=$(printf "%s\n" \
"-a, --autoselect|In combination with -y, autoselect USB drive when only one is connected." \
"-d, --device <device>|Pick <device> block file as target USB drive." \
"-D, --data-part|Add a data partition." \
"-F, --fstype <fstype>|Format to <fstype>." \
"-H, --no-hash-check|Don't search for hash files and check <imagefile> integrity." \
"-J, --no-eject|Don't eject drive after unmounting." \
"-L, --label <label>|Set partition label to <label>." \
"-M, --no-mime-check|Don't check <imagefile> mime-type." \
"-y, --assume-yes|Don't prompt for confirmation before erasing drive.")
local -r _installModeModifiersTable=$(printf "%s\n" \
"--icopy, --dd|Assert \"Image-Copy\" install mode." \
"--mrsync|Assert \"Mount-Rsync\" install mode.")
local -r _helpIntro="$scriptName v$version - create a bootable USB drive from an image file."
local -r _helpSynopsis=$(printf "%s\n" \
"Usage: $(term_boldify "$scriptName") [$(term_underline modifier...)] <imagefile>" \
" $(term_boldify "$scriptName") $(term_underline action) [$(term_underline modifier...)] <imagefile>" \
" $(term_boldify "$scriptName") $(term_underline action)")
local -r _helpHint=$(printf "%s" \
"\nInvoked with no action flag, $scriptName will default to install action in automatic mode: inspect <imagefile>" \
" boot capabilities and find the best way to make a bootable USB drive.")
_termWidth=$(tput cols)
echo -e "$_helpIntro" | fmt -g "$_termWidth" -w "$_termWidth"
echo -e "\n$_helpSynopsis"
echo -e "$_helpHint" | fmt -g "$_termWidth" -w "$_termWidth"
echo -e "\n$(term_boldify 'ACTIONS')"
echo -e "$_actionFlagsTable" | term_formatFlagDescription "$_termWidth"
echo -e "\n$(term_boldify 'GENERIC MODIFIERS')"
echo -e "$_modifierFlagsTable" | term_formatFlagDescription "$_termWidth"
echo -e "\nAdvanced modifiers are described in $scriptName man page." | fmt -g "$_termWidth" -w "$_termWidth"
echo -e "\n$(term_boldify 'INSTALL MODE MODIFIERS')"
echo -e "$_installModeModifiersTable" | term_formatFlagDescription "$_termWidth"
}
# return 0 - (USB) drives found
# return 1 - No (USB) drives found
function exec_listUSBDrives() {
# The user could run udevadm settle at this point, to make sure all
# udev events have been handled and the device is available.
local _lsblkCmd='lsblk -d -l -o NAME,MODEL,VENDOR,SIZE,TRAN,HOTPLUG'
step_initDevicesList
if [ "$disableUSBCheck" == 'true' ]; then
term_echoinfo "Listing drives available in your system:"
else
term_echoinfo "Listing USB devices available in your system:"
fi
if [ "${#st_devicesList[@]}" -gt 0 ]; then
$_lsblkCmd "${st_devicesList[@]/#/\/dev\/}" | sed "s/^/$term_logPrefixEmpty/"
return 0
else
term_echowarn "Couldn't find any USB drives on your system." \
"If one is physically plugged in, it's likely that it has been ejected and should be reconnected." \
"You can check the availability of USB drives with $(term_boldify "$scriptName -l")."
return 1
fi
}
function exec_inspect() {
local _uefiCompatible=${st_isoInspections[supportsEFIBoot]}
local _biosCompatible=${st_isoInspections[supportsBIOSBoot]}
local _syslinuxCompatible=false
local _isHybrid=${st_isoInspections[isHybrid]}
local _syslinux='SYSLINUX'
local _localSyslinuxVersion
if [ ! -z "${st_isoInspections[syslinuxConf]}" ]; then
_syslinuxCompatible=true
fi
if [ ! -z "${st_isoInspections[syslinuxVer]}" ]; then
_syslinux+=" ${st_isoInspections[syslinuxVer]}"
fi
term_echoinfo "Reporting '$(basename "$sourceImageFile")' boot capabilities:"
if [ "$_isHybrid" == false ] && [ "$_syslinuxCompatible" == false ] && [ "$_uefiCompatible" == false ]; then
term_echoerr "The selected image is not hybrid, doesn't support UEFI or legacy BIOS booting nor SYSLINUX." \
"It cannot result in any successful booting with $scriptName." \
"$ct_openImageSupportRequestMessage"
elif [ "$_isHybrid" == false ] && [ "$_syslinuxCompatible" == true ] && [ "$_uefiCompatible" == false ]; then
term_echogood "The selected image is not hybrid, but supports legacy BIOS booting with $_syslinux." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Mount-Rsync' modes with BIOS-boot capable PCs."
elif [ "$_isHybrid" == false ] && [ "$_syslinuxCompatible" == false ] && [ "$_uefiCompatible" == true ]; then
term_echogood "The selected image is not hybrid, but supports UEFI boot." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Mount-Rsync' modes with modern UEFI-capable PCs."
elif [ "$_isHybrid" == false ] && [ "$_syslinuxCompatible" == true ] && [ "$_uefiCompatible" == true ]; then
term_echogood "The selected image is not hybrid, but supports legacy BIOS booting with $_syslinux and UEFI boot." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Mount-Rsync' modes with any PCs."
elif [ "$_isHybrid" == true ] && [ "$_syslinuxCompatible" == false ] && [[ "$_biosCompatible" == false ]] && [ "$_uefiCompatible" == false ]; then
term_echowarn "The selected image is hybrid, but doesn't support UEFI or legacy BIOS bootloader." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Image-Copy' modes, but $scriptName is not aware of its booting scheme."
elif [ "$_isHybrid" == true ] && [ "$_syslinuxCompatible" == true ] && [ "$_uefiCompatible" == false ]; then
term_echogood "The selected image is hybrid and supports legacy BIOS booting with $_syslinux." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Image-Copy' modes with BIOS-boot capable PCs."
elif [ "$_isHybrid" == true ] && [ "$_biosCompatible" == true ] && [ "$_uefiCompatible" == false ]; then
term_echogood "The selected image is hybrid and supports legacy BIOS booting." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Image-Copy' modes with BIOS-boot capable PCs."
elif [ "$_isHybrid" == true ] && [ "$_syslinuxCompatible" == false ] && [ "$_uefiCompatible" == true ]; then
term_echogood "The selected image is hybrid and supports UEFI boot." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Image-Copy' modes with modern UEFI-capable PCs."
elif [ "$_isHybrid" == true ] && [ "$_syslinuxCompatible" == true ] && [ "$_uefiCompatible" == true ]; then
term_echogood "The selected image is hybrid and supports legacy BIOS booting with $_syslinux along with UEFI boot." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Image-Copy' modes with any PCs."
elif [ "$_isHybrid" == true ] && [ "$_biosCompatible" == true ] && [ "$_uefiCompatible" == true ]; then
term_echogood "The selected image is hybrid and supports legacy BIOS boot along with UEFI boot." \
"It should boot with $scriptName in $(term_boldify install) 'Automatic' and 'Image-Copy' modes with any PCs."
else
ps_failAndExit STATE_ERROR "Unexpected state. isHybrid: $_isHybrid, biosCompatible: $_biosCompatible," \
"uefiCompatible: $_uefiCompatible, syslinuxCompatible: $_syslinuxCompatible"
fi
if [[ "$_uefiCompatible" == false && ("$_syslinuxCompatible" == true || "$_biosCompatible" == true) ]]; then
term_echowarn "You might have to enable CSM in your UEFI system for legacy BIOS-boot support."
fi
_localSyslinuxVersion=$(syslinux --version |& grep -oP '(\d+\.\d+)')
if [ "$_syslinuxCompatible" == true ] && [ ! -z "${st_isoInspections[syslinuxVer]}" ] && [ "$_isHybrid" == false ]; then
if sh_compute "$_localSyslinuxVersion == ${st_isoInspections[syslinuxVer]}"; then
term_echogood "Furthermore, SYSLINUX version in the image file matches local version (${st_isoInspections[syslinuxVer]})."
elif sh_compute "${_localSyslinuxVersion%.*} == ${st_isoInspections[syslinuxVer]%.*}"; then
term_echoinfo "However, SYSLINUX version (${st_isoInspections[syslinuxVer]}) in the image file doesn't match the minor part of local version ($_localSyslinuxVersion), which should not cause any problems."
else
term_echowarn "SYSLINUX version (${st_isoInspections[syslinuxVer]}) in the image file doesn't match the major part of local version ($_localSyslinuxVersion)." \
"$scriptName will try to download and execute this version from kernel.org, unless given the modifier $(term_boldify '--local-bootloader')." \
"If that fails, it will attempt installation with the local version of SYSLINUX."
fi
fi
}
function exec_probe() {
exec_inspect
exec_listUSBDrives
}
function exec_installMountRsync() {
if [ "${st_isoInspections[isHybrid]}" == true ]; then
ps_failAndExit ASSERTION_FAILED "You cannot set Mount-Rsync mode with a hybrid image file." \
"Hybrid ISO or disk image files eventually contain multiple partitions and Mount-Rsync mode can only work with one." \
"If you think this image file is not hybrid and this is a bug, please report at " \
"${ct_ticketsURL}."
fi
st_shouldMakePartition=true
asrt_checkSyslinuxInstall
asrt_inspectImageBootCapabilities
step_runSecurityAssessments
st_elToritoMountPoint=$(fs_createMountFolder iso) || exit "$?"
fs_mountElToritoFile "$st_elToritoMountPoint"
devi_partitionUSB false
fs_mountUSB
step_copyWithRsync
if [ "$st_shouldInstallSyslinux" == true ]; then
step_installBootloader
fi
}
function exec_installImageCopy() {
if [ "${st_isoInspections[isHybrid]}" == false ]; then
ps_failAndExit ASSERTION_FAILED "You cannot set Image-Copy mode with a non-hybrid, 'El-Torito' image file." \
"El-Torito image files don't have a partition table; and thus target device will not be recognized" \
"by any boot system as a boot candidate. If you think this image file is hybrid and this is a bug, please report at " \
"${ct_ticketsURL}."
fi
st_shouldMakePartition=false
step_runSecurityAssessments
devi_partitionUSB false
step_copyWithDD
if [[ "$enableDataPart" == true ]]; then
devi_addDataPartition
case "$?" in
1) term_echoerr "Could not append partition table to add data partition" ;;
2) term_echoerr "Could not format data partition" ;;
esac
fi
}
function exec_installAuto() {
if [ "${st_isoInspections[isHybrid]}" == true ]; then
term_echogood "Found hybrid image; choosing Image-Copy mode."
exec_installImageCopy
else
term_echoinfo "Found non-hybrid image; inspecting image for boot capabilities..."
exec_installMountRsync
fi
}
function exec_format() {
st_shouldMakePartition=true
devi_selectDevice
ps_startTimer
devi_configureLabel
asrt_checkDeviceIsOK "$targetDevice"
asrt_checkDeviceIsUSB
devi_partitionUSB true
}
function exec_version() {
echo "$version"
}
# o8o
# `"'
# ooo. .oo. .oo. .oooo. oooo ooo. .oo.
# `888P"Y88bP"Y88b `P )88b `888 `888P"Y88b
# 888 888 888 .oP"888 888 888 888
# 888 888 888 d8( 888 888 888 888
# o888o o888o o888o `Y888""8o o888o o888o o888o
function main() {
step_initProcess
step_initPckgManager "$@"
step_parseArguments "$@"
step_checkArguments
step_assignInternalVariables
asrt_checkFlagMatrix
if [ "$requiresRoot" == 'true' ]; then
asrt_checkSudo "$@"
step_configureFolders
fi
asrt_checkPackages
if [ "$st_expectingISOFile" == 'true' ]; then
asrt_checkFileIsImage
if [ "$disableHashCheck" == false ]; then
asrt_checkImageHash
else
term_echowarn "Skipping hash check with $(term_boldify '-H, --no-hash-check') flag"
fi
step_inspectImageFile
fi
case "$targetAction" in
'install-auto') exec_installAuto "$@" ;;
'install-image-copy') exec_installImageCopy "$@" ;;
'install-mount-rsync') exec_installMountRsync "$@" ;;
'format') exec_format "$@" ;;
'inspect') exec_inspect ;;
'probe') exec_probe "$@" ;;
'list-usb-drives') exec_listUSBDrives ;;
'version') exec_version ;;
'help') exec_help ;;
*) ps_failAndExit STATE_ERROR "(main) unexpected action $(term_boldify "$targetAction")." ;;
esac
step_finalize
}
trap ps_cleanupOnExit EXIT
trap ps_cleanupOnInterrupt INT TERM
main "$@"