gentoo-install/scripts/utils.sh

448 lines
11 KiB
Bash
Raw Normal View History

# shellcheck source=./scripts/protection.sh
2020-01-08 16:21:01 -01:00
source "$GENTOO_INSTALL_REPO_DIR/scripts/protection.sh" || exit 1
function elog() {
2020-04-23 20:31:18 +00:00
echo "[+] $*"
}
function einfo() {
2021-05-30 01:01:35 +00:00
echo "[+] $*"
}
function ewarn() {
echo "[!] $*" >&2
}
function eerror() {
echo "error: $*" >&2
}
function die() {
eerror "$*"
[[ -v GENTOO_INSTALL_REPO_SCRIPT_PID && $$ -ne $GENTOO_INSTALL_REPO_SCRIPT_PID ]] \
&& kill "$GENTOO_INSTALL_REPO_SCRIPT_PID"
exit 1
}
# Prints an error with file:line info of the nth "stack frame".
# 0 is this function, 1 the calling function, 2 its parent, and so on.
function die_trace() {
local idx="${1:-0}"
shift
echo "${BASH_SOURCE[$((idx + 1))]}:${BASH_LINENO[$idx]}: error: ${FUNCNAME[$idx]}: $*" >&2
exit 1
}
function for_line_in() {
while IFS="" read -r line || [[ -n $line ]]; do
"$2" "$line"
done <"$1"
}
function flush_stdin() {
local empty_stdin
# Unused variable is intentional.
# shellcheck disable=SC2034
while read -r -t 0.01 empty_stdin; do true; done
2020-01-04 10:55:31 -01:00
}
function ask() {
2020-01-04 10:55:31 -01:00
local response
while true; do
2020-01-04 10:55:31 -01:00
flush_stdin
2020-01-04 11:09:29 -01:00
read -r -p "$* (Y/n) " response \
|| die "Error in read"
case "${response,,}" in
'') return 0 ;;
y|yes) return 0 ;;
n|no) return 1 ;;
*) continue ;;
esac
done
}
function try() {
2020-01-04 10:55:31 -01:00
local response
local cmd_status
2020-01-04 11:09:29 -01:00
local prompt_parens="(Shell/retry/abort/continue/print)"
2020-01-04 10:55:31 -01:00
# Outer loop, allows us to retry the command
while true; do
# Try command
"$@"
cmd_status="$?"
if [[ $cmd_status != 0 ]]; then
echo " * Command failed: \$ $*"
echo "Last command failed with exit code $cmd_status"
2020-01-04 10:55:31 -01:00
# Prompt until input is valid
while true; do
echo -n "Specify next action $prompt_parens "
2020-01-04 10:55:31 -01:00
flush_stdin
2020-01-04 11:09:29 -01:00
read -r response \
|| die "Error in read"
2020-01-04 10:55:31 -01:00
case "${response,,}" in
''|s|shell)
echo "You will be prompted for action again after exiting this shell."
/bin/bash --init-file <(echo "init_bash")
2020-01-04 10:55:31 -01:00
;;
r|retry) continue 2 ;;
2020-01-04 11:09:29 -01:00
a|abort) die "Installation aborted" ;;
c|continue) return 0 ;;
p|print) echo "\$ $*" ;;
2020-01-04 11:31:39 -01:00
*) ;;
2020-01-04 10:55:31 -01:00
esac
done
fi
return
2020-01-04 10:55:31 -01:00
done
}
function countdown() {
echo -n "$1" >&2
local i="$2"
while [[ $i -gt 0 ]]; do
echo -n "$i " >&2
i=$((i - 1))
sleep 1
done
echo >&2
}
function download_stdout() {
wget --quiet --https-only --secure-protocol=PFS -O - -- "$1"
}
function download() {
wget --quiet --https-only --secure-protocol=PFS --show-progress -O "$2" -- "$1"
}
function get_blkid_field_by_device() {
2020-04-25 13:12:35 +00:00
local blkid_field="$1"
local device="$2"
blkid -g -c /dev/null \
|| die "Error while executing blkid"
2021-05-30 19:30:45 +00:00
partprobe &>/dev/null
2020-04-25 13:12:35 +00:00
local val
val="$(blkid -c /dev/null -o export "$device")" \
2020-04-25 13:12:35 +00:00
|| die "Error while executing blkid '$device'"
2020-04-25 18:12:00 +00:00
val="$(grep -- "^$blkid_field=" <<< "$val")" \
2020-04-25 13:12:35 +00:00
|| die "Could not find $blkid_field=... in blkid output"
val="${val#"$blkid_field="}"
echo -n "$val"
}
function get_blkid_uuid_for_id() {
local dev
dev="$(resolve_device_by_id "$1")" \
|| die "Could not resolve device with id=$dev"
local uuid
uuid="$(get_blkid_field_by_device 'UUID' "$dev")" \
|| die "Could not get UUID from blkid for device=$dev"
2020-04-25 16:06:38 +00:00
echo -n "$uuid"
}
function get_device_by_blkid_field() {
2020-04-21 18:58:21 +00:00
local blkid_field="$1"
local field_value="$2"
blkid -g -c /dev/null \
|| die "Error while executing blkid"
2021-05-30 19:30:45 +00:00
type partprobe &>/dev/null && partprobe &>/dev/null
local dev
dev="$(blkid -c /dev/null -o export -t "$blkid_field=$field_value")" \
2020-04-21 18:58:21 +00:00
|| die "Error while executing blkid to find $blkid_field=$field_value"
dev="$(grep DEVNAME <<< "$dev")" \
|| die "Could not find DEVNAME=... in blkid output"
2020-04-25 13:12:35 +00:00
dev="${dev#"DEVNAME="}"
echo -n "$dev"
}
function get_device_by_partuuid() {
if [[ -e "/dev/disk/by-partuuid/$1" ]]; then
echo -n "/dev/disk/by-partuuid/$1"
else
get_device_by_blkid_field 'PARTUUID' "$1"
fi
2020-04-21 18:58:21 +00:00
}
function get_device_by_uuid() {
if [[ -e "/dev/disk/by-uuid/$1" ]]; then
echo -n "/dev/disk/by-uuid/$1"
else
get_device_by_blkid_field 'UUID' "$1"
fi
2020-04-21 21:41:27 +00:00
}
function cache_lsblk_output() {
2020-10-08 20:16:26 +00:00
CACHED_LSBLK_OUTPUT="$(lsblk --all --path --pairs --output NAME,PTUUID,PARTUUID)" \
|| die "Error while executing lsblk to cache output"
}
function get_device_by_ptuuid() {
local ptuuid="${1,,}"
local dev
2021-05-30 01:10:43 +00:00
if [[ -v CACHED_LSBLK_OUTPUT && -n "$CACHED_LSBLK_OUTPUT" ]]; then
2020-10-08 20:16:26 +00:00
dev="$CACHED_LSBLK_OUTPUT"
else
dev="$(lsblk --all --path --pairs --output NAME,PTUUID,PARTUUID)" \
|| die "Error while executing lsblk to find PTUUID=$ptuuid"
fi
dev="$(grep "ptuuid=\"$ptuuid\" partuuid=\"\"" <<< "${dev,,}")" \
|| die "Could not find PTUUID=... in lsblk output"
2020-04-23 21:19:00 +00:00
dev="${dev%'" ptuuid='*}"
dev="${dev#'name="'}"
echo -n "$dev"
}
function uuid_to_mduuid() {
local mduuid="${1,,}"
mduuid="${mduuid//-/}"
mduuid="${mduuid:0:8}:${mduuid:8:8}:${mduuid:16:8}:${mduuid:24:8}"
2020-04-25 13:12:35 +00:00
echo -n "$mduuid"
}
function get_device_by_mdadm_uuid() {
local mduuid
mduuid="$(uuid_to_mduuid "$1")" \
|| die "Could not resolve mduuid from uuid=$1"
local dev
dev="$(mdadm --examine --scan)" \
|| die "Error while executing mdadm to find array with UUID=$mduuid"
dev="$(grep "uuid=$mduuid" <<< "${dev,,}")" \
|| die "Could not find UUID=... in mdadm output"
dev="${dev%'metadata='*}"
dev="${dev#'array'}"
dev="${dev#"${dev%%[![:space:]]*}"}"
dev="${dev%"${dev##*[![:space:]]}"}"
echo -n "$dev"
}
function get_device_by_luks_name() {
echo -n "/dev/mapper/$1"
2020-04-23 20:31:18 +00:00
}
function create_resolve_entry() {
local id="$1"
local type="$2"
local arg="${3,,}"
DISK_ID_TO_RESOLVABLE[$id]="$type:$arg"
}
function create_resolve_entry_device() {
2020-10-03 14:24:48 +00:00
local id="$1"
local dev="$2"
DISK_ID_TO_RESOLVABLE[$id]="device:$dev"
}
# Returns the basename of the device, if its path starts with /dev/disk/by-id/
2022-05-13 20:56:25 +00:00
function shorten_device() {
echo -n "${1#/dev/disk/by-id/}"
}
# Return matching device from /dev/disk/by-id/ if possible,
# otherwise return the parameter unchanged.
function canonicalize_device() {
given_dev="$(realpath "$1")"
for dev in /dev/disk/by-id/*; do
if [[ "$(realpath "$dev")" == "$given_dev" ]]; then
echo -n "$dev"
return 0
fi
done
echo -n "$1"
}
function resolve_device_by_id() {
local id="$1"
[[ -v DISK_ID_TO_RESOLVABLE[$id] ]] \
|| die "Cannot resolve id='$id' to a block device (no table entry)"
local type="${DISK_ID_TO_RESOLVABLE[$id]%%:*}"
local arg="${DISK_ID_TO_RESOLVABLE[$id]#*:}"
local dev
case "$type" in
'partuuid') dev=$(get_device_by_partuuid "$arg") ;;
'ptuuid') dev=$(get_device_by_ptuuid "$arg") ;;
'uuid') dev=$(get_device_by_uuid "$arg") ;;
'mdadm') dev=$(get_device_by_mdadm_uuid "$arg") ;;
'luks') dev=$(get_device_by_luks_name "$arg") ;;
'device') dev="$arg" ;;
*) die "Cannot resolve '$type:$arg' to device (unknown type)"
esac
canonicalize_device "$dev"
}
function load_or_generate_uuid() {
local uuid
local uuid_file="$UUID_STORAGE_DIR/$1"
if [[ -e $uuid_file ]]; then
uuid="$(cat "$uuid_file")"
else
uuid="$(uuidgen -r)"
mkdir -p "$UUID_STORAGE_DIR"
echo -n "$uuid" > "$uuid_file"
fi
echo -n "$uuid"
}
# Parses named arguments and stores them in the associative array `arguments`.
# If given, the associative array `known_arguments` must contain a list of arguments
# prefixed with + (mandatory) or ? (optional). "at least one of" can be expressed by +a|b|c.
function parse_arguments() {
local key
local value
local a
for a in "$@"; do
key="${a%%=*}"
value="${a#*=}"
2020-04-21 19:52:46 +00:00
2020-04-21 20:04:39 +00:00
if [[ $key == "$a" ]]; then
2020-04-21 19:52:46 +00:00
extra_arguments+=("$a")
continue
fi
arguments[$key]="$value"
done
declare -A allowed_keys
if [[ -v known_arguments ]]; then
local m
for m in "${known_arguments[@]}"; do
case "${m:0:1}" in
'+')
m="${m:1}"
local has_opt=false
local m_opt
# Splitting is intentional here
# shellcheck disable=SC2086
for m_opt in ${m//|/ }; do
allowed_keys[$m_opt]=true
if [[ -v arguments[$m_opt] ]]; then
has_opt=true
fi
done
[[ $has_opt == "true" ]] \
|| die_trace 2 "Missing mandatory argument $m=..."
;;
'?')
allowed_keys[${m:1}]=true
;;
*) die_trace 2 "Invalid start character in known_arguments, in argument '$m'" ;;
esac
done
for a in "${!arguments[@]}"; do
[[ -v allowed_keys[$a] ]] \
|| die_trace 2 "Unknown argument '$a'"
done
fi
}
2021-05-29 20:14:46 +00:00
# $1: program
# $2: checkfile
function has_program() {
local program="$1"
local checkfile="$2"
if [[ -z "$checkfile" ]]; then
type "$program" &>/dev/null \
|| return 1
elif [[ "${checkfile:0:1}" == "/" ]]; then
[[ -e "$checkfile" ]] \
|| return 1
else
type "$checkfile" &>/dev/null \
|| return 1
fi
return 0
}
function check_wanted_programs() {
local missing_required=()
local missing_wanted=()
local tuple
2021-05-29 20:14:46 +00:00
local program
local checkfile
for tuple in "$@"; do
program="${tuple%%=*}"
checkfile=""
[[ "$tuple" == *=* ]] \
2022-10-01 11:33:55 +00:00
&& checkfile="${tuple##*=}"
if ! has_program "${program#"?"}" "$checkfile"; then
if [[ "$program" == "?"* ]]; then
missing_wanted+=("${program#"?"}")
else
missing_required+=("$program")
fi
fi
2021-05-29 20:14:46 +00:00
done
[[ "${#missing_required[@]}" -eq 0 && "${#missing_wanted[@]}" -eq 0 ]] \
&& return
if [[ "${#missing_required[@]}" -gt 0 ]]; then
elog "The following programs are required for the installer to work, but are currently missing on your system:" >&2
elog " ${missing_required[*]}" >&2
fi
if [[ "${#missing_wanted[@]}" -gt 0 ]]; then
elog "Missing optional programs:" >&2
elog " ${missing_wanted[*]}" >&2
fi
2021-05-29 20:14:46 +00:00
if type pacman &>/dev/null; then
declare -A pacman_packages
pacman_packages=(
[ntpd]=ntp
[zfs]=""
)
elog "Detected pacman package manager."
if ask "Do you want to install all missing programs automatically?"; then
local packages
local need_zfs=false
2021-05-30 00:30:02 +00:00
for program in "${missing_required[@]}" "${missing_wanted[@]}"; do
2021-05-30 00:30:02 +00:00
[[ "$program" == "zfs" ]] \
&& need_zfs=true
if [[ -v "pacman_packages[$program]" ]]; then
# Assignments to the empty string are explcitly ignored,
# as for example zfs needs to be handeled separately.
[[ -n "${pacman_packages[$program]}" ]] \
&& packages+=("${pacman_packages[$program]}")
2021-05-30 00:30:02 +00:00
else
packages+=("$program")
fi
done
pacman -Sy "${packages[@]}"
if [[ "$need_zfs" == true ]]; then
elog "On an Arch live-stick you need the archzfs repository and some tools and modifications to use zfs."
elog "There is an automated installer available at https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init."
if ask "Do you want to automatically download and execute this zfs installation script?"; then
curl -s "https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init" | bash
fi
fi
2021-05-29 20:14:46 +00:00
return
fi
fi
if [[ "${#missing_required[@]}" -gt 0 ]]; then
die "Aborted installer because of missing required programs."
else
ask "Continue without recommended programs?"
fi
2021-05-29 20:14:46 +00:00
}