gentoo-install/scripts/functions.sh

960 lines
28 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# shellcheck source=./scripts/protection.sh
source "$GENTOO_INSTALL_REPO_DIR/scripts/protection.sh" || exit 1
################################################
# Functions
function sync_time() {
einfo "Syncing time"
try ntpd -g -q
einfo "Current date: $(LANG=C date)"
einfo "Writing time to hardware clock"
hwclock --systohc --utc \
|| die "Could not save time to hardware clock"
}
function check_config() {
[[ $KEYMAP =~ ^[0-9A-Za-z-]*$ ]] \
|| die "KEYMAP contains invalid characters"
if [[ "$SYSTEMD" == "true" ]]; then
[[ "$STAGE3_BASENAME" == *systemd* ]] \
|| die "Using systemd requires a systemd stage3 archive!"
else
[[ "$STAGE3_BASENAME" != *systemd* ]] \
|| die "Using OpenRC requires a non-systemd stage3 archive!"
fi
# Check hostname per RFC1123
local hostname_regex='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
[[ $HOSTNAME =~ $hostname_regex ]] \
|| die "'$HOSTNAME' is not a valid hostname"
[[ -v "DISK_ID_ROOT" && -n $DISK_ID_ROOT ]] \
|| die "You must assign DISK_ID_ROOT"
[[ -v "DISK_ID_EFI" && -n $DISK_ID_EFI ]] || [[ -v "DISK_ID_BIOS" && -n $DISK_ID_BIOS ]] \
|| die "You must assign DISK_ID_EFI or DISK_ID_BIOS"
[[ -v "DISK_ID_BIOS" ]] && [[ ! -v "DISK_ID_TO_UUID[$DISK_ID_BIOS]" ]] \
&& die "Missing uuid for DISK_ID_BIOS, have you made sure it is used?"
[[ -v "DISK_ID_EFI" ]] && [[ ! -v "DISK_ID_TO_UUID[$DISK_ID_EFI]" ]] \
&& die "Missing uuid for DISK_ID_EFI, have you made sure it is used?"
[[ -v "DISK_ID_SWAP" ]] && [[ ! -v "DISK_ID_TO_UUID[$DISK_ID_SWAP]" ]] \
&& die "Missing uuid for DISK_ID_SWAP, have you made sure it is used?"
[[ -v "DISK_ID_ROOT" ]] && [[ ! -v "DISK_ID_TO_UUID[$DISK_ID_ROOT]" ]] \
&& die "Missing uuid for DISK_ID_ROOT, have you made sure it is used?"
if [[ -v "DISK_ID_EFI" ]]; then
IS_EFI=true
else
IS_EFI=false
fi
}
function preprocess_config() {
disk_configuration
# Check encryption key if used
[[ $USED_ENCRYPTION == "true" ]] \
&& check_encryption_key
check_config
}
function prepare_installation_environment() {
einfo "Preparing installation environment"
local wanted_programs=(
gpg
hwclock
lsblk
ntpd
partprobe
python3
"?rhash"
sha512sum
sgdisk
uuidgen
wget
)
[[ $USED_BTRFS == "true" ]] \
&& wanted_programs+=(btrfs)
[[ $USED_ZFS == "true" ]] \
&& wanted_programs+=(zfs)
[[ $USED_RAID == "true" ]] \
&& wanted_programs+=(mdadm)
[[ $USED_LUKS == "true" ]] \
&& wanted_programs+=(cryptsetup)
# Check for existence of required programs
check_wanted_programs "${wanted_programs[@]}"
# Sync time now to prevent issues later
sync_time
}
function check_encryption_key() {
if [[ -z "${GENTOO_INSTALL_ENCRYPTION_KEY+set}" ]]; then
elog "You have enabled encryption, but haven't specified a key in the environment variable GENTOO_INSTALL_ENCRYPTION_KEY."
if ask "Do you want to enter an encryption key now?"; then
local encryption_key_1
local encryption_key_2
while true; do
flush_stdin
IFS="" read -s -r -p "Enter encryption key: " encryption_key_1 \
|| die "Error in read"
echo
[[ ${#encryption_key_1} -ge 8 ]] \
|| { ewarn "Your encryption key must be at least 8 characters long."; continue; }
flush_stdin
IFS="" read -s -r -p "Repeat encryption key: " encryption_key_2 \
|| die "Error in read"
echo
[[ "$encryption_key_1" == "$encryption_key_2" ]] \
|| { ewarn "Encryption keys mismatch."; continue; }
break
done
export GENTOO_INSTALL_ENCRYPTION_KEY="$encryption_key_1"
else
die "Please export GENTOO_INSTALL_ENCRYPTION_KEY with the desired key."
fi
fi
[[ ${#GENTOO_INSTALL_ENCRYPTION_KEY} -ge 8 ]] \
|| die "Your encryption key must be at least 8 characters long."
}
function add_summary_entry() {
local parent="$1"
local id="$2"
local name="$3"
local hint="$4"
local desc="$5"
local ptr
case "$id" in
"${DISK_ID_BIOS-__unused__}") ptr="← bios" ;;
"${DISK_ID_EFI-__unused__}") ptr="← efi" ;;
"${DISK_ID_SWAP-__unused__}") ptr="← swap" ;;
"${DISK_ID_ROOT-__unused__}") ptr="← root" ;;
# \x1f characters compensate for printf byte count and unicode character count mismatch due to '←'
*) ptr="$(echo -e "\x1f\x1f")" ;;
esac
summary_tree[$parent]+=";$id"
summary_name[$id]="$name"
summary_hint[$id]="$hint"
summary_ptr[$id]="$ptr"
summary_desc[$id]="$desc"
}
function summary_color_args() {
for arg in "$@"; do
if [[ -v "arguments[$arg]" ]]; then
printf '%-28s ' "$arg=${arguments[$arg]}"
fi
done
}
function disk_existing() {
local new_id="${arguments[new_id]}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
add_summary_entry __root__ "$new_id" "${arguments[device]}" "(no-format, existing)" ""
fi
# no-op;
}
function disk_create_gpt() {
local new_id="${arguments[new_id]}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
if [[ -v arguments[id] ]]; then
add_summary_entry "${arguments[id]}" "$new_id" "gpt" "" ""
else
add_summary_entry __root__ "$new_id" "${arguments[device]}" "(gpt)" ""
fi
return 0
fi
local device
local device_desc=""
if [[ -v arguments[id] ]]; then
device="$(resolve_device_by_id "${arguments[id]}")"
device_desc="$device ($id)"
else
device="${arguments[device]}"
device_desc="$device"
fi
local ptuuid="${DISK_ID_TO_UUID[$new_id]}"
einfo "Creating new gpt partition table ($new_id) on $device_desc"
wipefs --quiet --all --force "$device" \
|| die "Could not erase previous file system signatures from '$device'"
sgdisk -Z -U "$ptuuid" "$device" >/dev/null \
|| die "Could not create new gpt partition table ($new_id) on '$device'"
partprobe "$device"
}
function disk_create_partition() {
local new_id="${arguments[new_id]}"
local id="${arguments[id]}"
local size="${arguments[size]}"
local type="${arguments[type]}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
add_summary_entry "$id" "$new_id" "part" "($type)" "$(summary_color_args size)"
return 0
fi
if [[ $size == "remaining" ]]; then
arg_size=0
else
arg_size="+$size"
fi
local device
device="$(resolve_device_by_id "$id")" \
|| die "Could not resolve device with id=$id"
local partuuid="${DISK_ID_TO_UUID[$new_id]}"
local extra_args=""
case "$type" in
'bios') type='ef02' extra_args='--attributes=0:set:2';;
'efi') type='ef00' ;;
'swap') type='8200' ;;
'raid') type='fd00' ;;
'luks') type='8309' ;;
'linux') type='8300' ;;
*) ;;
esac
einfo "Creating partition ($new_id) with type=$type, size=$size on $device"
# shellcheck disable=SC2086
sgdisk -n "0:0:$arg_size" -t "0:$type" -u "0:$partuuid" $extra_args "$device" >/dev/null \
|| die "Could not create new gpt partition ($new_id) on '$device' ($id)"
partprobe "$device"
}
function disk_create_raid() {
local new_id="${arguments[new_id]}"
local level="${arguments[level]}"
local name="${arguments[name]}"
local ids="${arguments[ids]}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
local id
# Splitting is intentional here
# shellcheck disable=SC2086
for id in ${ids//';'/ }; do
add_summary_entry "$id" "_$new_id" "raid$level" "" "$(summary_color_args name)"
done
add_summary_entry __root__ "$new_id" "raid$level" "" "$(summary_color_args name)"
return 0
fi
local devices_desc=""
local devices=()
local id
local dev
# Splitting is intentional here
# shellcheck disable=SC2086
for id in ${ids//';'/ }; do
dev="$(resolve_device_by_id "$id")" \
|| die "Could not resolve device with id=$id"
devices+=("$dev")
devices_desc+="$dev ($id), "
done
devices_desc="${devices_desc:0:-2}"
local mddevice="/dev/md/$name"
local uuid="${DISK_ID_TO_UUID[$new_id]}"
einfo "Creating raid$level ($new_id) on $devices_desc"
mdadm \
--create "$mddevice" \
--verbose \
--homehost="$HOSTNAME" \
--metadata=1.2 \
--raid-devices="${#devices[@]}" \
--uuid="$uuid" \
--level="$level" \
"${devices[@]}" \
|| die "Could not create raid$level array '$mddevice' ($new_id) on $devices_desc"
}
function disk_create_luks() {
local new_id="${arguments[new_id]}"
local name="${arguments[name]}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
if [[ -v arguments[id] ]]; then
add_summary_entry "${arguments[id]}" "$new_id" "luks" "" ""
else
add_summary_entry __root__ "$new_id" "${arguments[device]}" "(luks)" ""
fi
return 0
fi
local device
local device_desc=""
if [[ -v arguments[id] ]]; then
device="$(resolve_device_by_id "${arguments[id]}")"
device_desc="$device ($id)"
else
device="${arguments[device]}"
device_desc="$device"
fi
local uuid="${DISK_ID_TO_UUID[$new_id]}"
einfo "Creating luks ($new_id) on $device_desc"
cryptsetup luksFormat \
--type luks2 \
--uuid "$uuid" \
--key-file <(echo -n "$GENTOO_INSTALL_ENCRYPTION_KEY") \
--cipher aes-xts-plain64 \
--hash sha512 \
--pbkdf argon2id \
--iter-time 4000 \
--key-size 512 \
--batch-mode \
"$device" \
|| die "Could not create luks on $device_desc"
mkdir -p "$LUKS_HEADER_BACKUP_DIR" \
|| die "Could not create luks header backup dir '$LUKS_HEADER_BACKUP_DIR'"
local header_file="$LUKS_HEADER_BACKUP_DIR/luks-header-$id-${uuid,,}.img"
[[ ! -e $header_file ]] \
|| rm "$header_file" \
|| die "Could not remove old luks header backup file '$header_file'"
cryptsetup luksHeaderBackup "$device" \
--header-backup-file "$header_file" \
|| die "Could not backup luks header on $device_desc"
cryptsetup open --type luks2 \
--key-file <(echo -n "$GENTOO_INSTALL_ENCRYPTION_KEY") \
"$device" "$name" \
|| die "Could not open luks encrypted device $device_desc"
}
function disk_create_dummy() {
local new_id="${arguments[new_id]}"
local device="${arguments[device]}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
add_summary_entry __root__ "$new_id" "$device" "" ""
return 0
fi
}
function init_btrfs() {
local device="$1"
local desc="$2"
mkdir -p /btrfs \
|| die "Could not create /btrfs directory"
mount "$device" /btrfs \
|| die "Could not mount $desc to /btrfs"
btrfs subvolume create /btrfs/root \
|| die "Could not create btrfs subvolume /root on $desc"
btrfs subvolume set-default /btrfs/root \
|| die "Could not set default btrfs subvolume to /root on $desc"
umount /btrfs \
|| die "Could not unmount btrfs on $desc"
}
function disk_format() {
local id="${arguments[id]}"
local type="${arguments[type]}"
local label="${arguments[label]}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
add_summary_entry "${arguments[id]}" "__fs__${arguments[id]}" "${arguments[type]}" "(fs)" "$(summary_color_args label)"
return 0
fi
local device
device="$(resolve_device_by_id "$id")" \
|| die "Could not resolve device with id=$id"
einfo "Formatting $device ($id) with $type"
wipefs --quiet --all --force "$device" \
|| die "Could not erase previous file system signatures from '$device' ($id)"
case "$type" in
'bios'|'efi')
if [[ -v "arguments[label]" ]]; then
mkfs.fat -F 32 -n "$label" "$device" \
|| die "Could not format device '$device' ($id)"
else
mkfs.fat -F 32 "$device" \
|| die "Could not format device '$device' ($id)"
fi
;;
'swap')
if [[ -v "arguments[label]" ]]; then
mkswap -L "$label" "$device" \
|| die "Could not format device '$device' ($id)"
else
mkswap "$device" \
|| die "Could not format device '$device' ($id)"
fi
;;
'ext4')
if [[ -v "arguments[label]" ]]; then
mkfs.ext4 -q -L "$label" "$device" \
|| die "Could not format device '$device' ($id)"
else
mkfs.ext4 -q "$device" \
|| die "Could not format device '$device' ($id)"
fi
;;
'btrfs')
if [[ -v "arguments[label]" ]]; then
mkfs.btrfs -q -L "$label" "$device" \
|| die "Could not format device '$device' ($id)"
else
mkfs.btrfs -q "$device" \
|| die "Could not format device '$device' ($id)"
fi
init_btrfs "$device" "'$device' ($id)"
;;
*) die "Unknown filesystem type" ;;
esac
}
# This function will be called when a custom zfs pool type has been chosen.
# $1: either 'true' or 'false' determining if the datasets should be encrypted
# $2: either 'false' or a value determining the dataset compression algorithm
# $3: a string describing all device paths (for error messages)
# $@: device paths
function format_zfs_standard() {
local encrypt="$1"
local compress="$2"
local device_desc="$3"
shift 3
local devices=("$@")
local extra_args=()
einfo "Creating zfs pool on $devices_desc"
local zfs_stdin=""
if [[ "$encrypt" == true ]]; then
extra_args+=(
"-O" "encryption=aes-256-gcm"
"-O" "keyformat=passphrase"
"-O" "keylocation=prompt"
)
zfs_stdin="$GENTOO_INSTALL_ENCRYPTION_KEY"
fi
# dnodesize=legacy might be needed for GRUB2, but auto is preferred for xattr=sa.
zpool create \
-R "$ROOT_MOUNTPOINT" \
-o ashift=12 \
-O acltype=posix \
-O atime=off \
-O xattr=sa \
-O dnodesize=auto \
-O mountpoint=none \
-O canmount=noauto \
-O devices=off \
"${extra_args[@]}" \
rpool \
"${devices[@]}" \
<<< "$zfs_stdin" \
|| die "Could not create zfs pool on $devices_desc"
if [[ "$compress" != false ]]; then
zfs set "compression=$compress" rpool/ROOT \
|| die "Could enable compression on dataset 'rpool'"
fi
zfs create rpool/ROOT \
|| die "Could not create zfs dataset 'rpool/ROOT'"
zfs create -o mountpoint=/ rpool/ROOT/default \
|| die "Could not create zfs dataset 'rpool/ROOT/default'"
zpool set bootfs=rpool/ROOT/default rpool \
|| die "Could not set zfs property bootfs on rpool"
}
function disk_format_zfs() {
local ids="${arguments[ids]}"
local pool_type="${arguments[pool_type]}"
local encrypt="${arguments[encrypt]-false}"
local compress="${arguments[compress]-false}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
local id
# Splitting is intentional here
# shellcheck disable=SC2086
for id in ${ids//';'/ }; do
add_summary_entry "$id" "__fs__$id" "zfs" "(fs)" "$(summary_color_args label)"
done
return 0
fi
local devices_desc=""
local devices=()
local id
local dev
# Splitting is intentional here
# shellcheck disable=SC2086
for id in ${ids//';'/ }; do
dev="$(resolve_device_by_id "$id")" \
|| die "Could not resolve device with id=$id"
devices+=("$dev")
devices_desc+="$dev ($id), "
done
devices_desc="${devices_desc:0:-2}"
wipefs --quiet --all --force "${devices[@]}" \
|| die "Could not erase previous file system signatures from $devices_desc"
if [[ "$pool_type" == "custom" ]]; then
format_zfs_custom "$devices_desc" "${devices[@]}"
else
format_zfs_standard "$encrypt" "$compress" "$devices_desc" "${devices[@]}"
fi
}
function disk_format_btrfs() {
local ids="${arguments[ids]}"
local label="${arguments[label]}"
local raid_type="${arguments[raid_type]}"
if [[ ${disk_action_summarize_only-false} == "true" ]]; then
local id
# Splitting is intentional here
# shellcheck disable=SC2086
for id in ${ids//';'/ }; do
add_summary_entry "$id" "__fs__$id" "btrfs" "(fs)" "$(summary_color_args label)"
done
return 0
fi
local devices_desc=""
local devices=()
local id
local dev
# Splitting is intentional here
# shellcheck disable=SC2086
for id in ${ids//';'/ }; do
dev="$(resolve_device_by_id "$id")" \
|| die "Could not resolve device with id=$id"
devices+=("$dev")
devices_desc+="$dev ($id), "
done
devices_desc="${devices_desc:0:-2}"
wipefs --quiet --all --force "${devices[@]}" \
|| die "Could not erase previous file system signatures from $devices_desc"
# Collect extra arguments
extra_args=()
if [[ "${#devices}" -gt 1 ]] && [[ -v "arguments[raid_type]" ]]; then
extra_args+=("-d" "$raid_type")
fi
if [[ -v "arguments[label]" ]]; then
extra_args+=("-L" "$label")
fi
einfo "Creating btrfs on $devices_desc"
mkfs.btrfs -q "${extra_args[@]}" "${devices[@]}" \
|| die "Could not create btrfs on $devices_desc"
init_btrfs "${devices[0]}" "btrfs array ($devices_desc)"
}
function apply_disk_action() {
unset known_arguments
unset arguments; declare -A arguments; parse_arguments "$@"
case "${arguments[action]}" in
'existing') disk_existing ;;
'create_gpt') disk_create_gpt ;;
'create_partition') disk_create_partition ;;
'create_raid') disk_create_raid ;;
'create_luks') disk_create_luks ;;
'create_dummy') disk_create_dummy ;;
'format') disk_format ;;
'format_zfs') disk_format_zfs ;;
'format_btrfs') disk_format_btrfs ;;
*) echo "Ignoring invalid action: ${arguments[action]}" ;;
esac
}
function print_summary_tree_entry() {
local indent_chars=""
local indent="0"
local d="1"
local maxd="$((depth - 1))"
while [[ $d -lt $maxd ]]; do
if [[ ${summary_depth_continues[$d]} == "true" ]]; then
indent_chars+='│ '
else
indent_chars+=' '
fi
indent=$((indent + 2))
d="$((d + 1))"
done
if [[ $maxd -gt 0 ]]; then
if [[ ${summary_depth_continues[$maxd]} == "true" ]]; then
indent_chars+='├─'
else
indent_chars+='└─'
fi
indent=$((indent + 2))
fi
local name="${summary_name[$root]}"
local hint="${summary_hint[$root]}"
local desc="${summary_desc[$root]}"
local ptr="${summary_ptr[$root]}"
local id_name=""
if [[ $root != __* ]]; then
if [[ $root == _* ]]; then
id_name="${root:1}"
else
id_name="${root}"
fi
fi
local align=0
if [[ $indent -lt 33 ]]; then
align="$((33 - indent))"
fi
elog "$indent_chars$(printf "%-${align}s %-47s %s" \
"$name $hint" \
"$id_name $ptr" \
"$desc")"
}
function print_summary_tree() {
local root="$1"
local depth="$((depth + 1))"
local has_children=false
if [[ -v "summary_tree[$root]" ]]; then
local children="${summary_tree[$root]}"
has_children=true
summary_depth_continues[$depth]=true
else
summary_depth_continues[$depth]=false
fi
if [[ $root != __root__ ]]; then
print_summary_tree_entry "$root"
fi
if [[ $has_children == "true" ]]; then
local count
count="$(tr ';' '\n' <<< "$children" | grep -c '\S')" \
|| count=0
local idx=0
# Splitting is intentional here
# shellcheck disable=SC2086
for id in ${children//';'/ }; do
idx="$((idx + 1))"
[[ $idx == "$count" ]] \
&& summary_depth_continues[$depth]=false
print_summary_tree "$id"
# separate blocks by newline
[[ ${summary_depth_continues[0]} == "true" ]] && [[ $depth == 1 ]] && [[ $idx == "$count" ]] \
&& elog
done
fi
}
function apply_disk_actions() {
local param
local current_params=()
for param in "${DISK_ACTIONS[@]}"; do
if [[ $param == ';' ]]; then
apply_disk_action "${current_params[@]}"
current_params=()
else
current_params+=("$param")
fi
done
}
function summarize_disk_actions() {
elog "Current lsblk output:"
for_line_in <(lsblk \
|| die "Error in lsblk") elog
local disk_action_summarize_only=true
declare -A summary_tree
declare -A summary_name
declare -A summary_hint
declare -A summary_ptr
declare -A summary_desc
declare -A summary_depth_continues
apply_disk_actions
local depth=-1
elog
elog "Configured disk layout:"
elog ────────────────────────────────────────────────────────────────────────────────
elog "$(printf '%-26s %-28s %s' NODE ID OPTIONS)"
elog ────────────────────────────────────────────────────────────────────────────────
print_summary_tree __root__
elog ────────────────────────────────────────────────────────────────────────────────
}
function apply_disk_configuration() {
summarize_disk_actions
if [[ $NO_PARTITIONING_OR_FORMATTING == true ]]; then
elog "You have chosen an existing disk configuration. No devices will"
elog "actually be re-partitioned or formatted. Please make sure that all"
elog "devices are already formatted."
else
ewarn "Please ensure that all selected devices are fully unmounted and are"
ewarn "not otherwise in use by the system. This includes stopping mdadm arrays"
ewarn "and closing opened luks volumes if applicable for all relevant devices."
ewarn "Otherwise, automatic partitioning may fail."
fi
ask "Do you really want to apply this disk configuration?" \
|| die "Aborted"
countdown "Applying in " 5
einfo "Applying disk configuration"
apply_disk_actions
einfo "Disk configuration was applied successfully"
elog "New lsblk output:"
for_line_in <(lsblk \
|| die "Error in lsblk") elog
}
function mount_efivars() {
# Skip if already mounted
mountpoint -q -- "/sys/firmware/efi/efivars" \
&& return
# Mount efivars
einfo "Mounting efivars"
mount -t efivarfs efivarfs "/sys/firmware/efi/efivars" \
|| die "Could not mount efivarfs"
}
function mount_by_id() {
local dev
local id="$1"
local mountpoint="$2"
# Skip if already mounted
mountpoint -q -- "$mountpoint" \
&& return
# Mount device
einfo "Mounting device with id=$id to '$mountpoint'"
mkdir -p "$mountpoint" \
|| die "Could not create mountpoint directory '$mountpoint'"
dev="$(resolve_device_by_id "$id")" \
|| die "Could not resolve device with id=$id"
mount "$dev" "$mountpoint" \
|| die "Could not mount device '$dev'"
}
function mount_root() {
if [[ $USED_ZFS == "true" ]] && ! mountpoint -q -- "$ROOT_MOUNTPOINT"; then
die "Error: Expected zfs to be mounted under '$ROOT_MOUNTPOINT', but it isn't."
else
mount_by_id "$DISK_ID_ROOT" "$ROOT_MOUNTPOINT"
fi
}
function bind_repo_dir() {
# Use new location by default
export GENTOO_INSTALL_REPO_DIR="$GENTOO_INSTALL_REPO_BIND"
# Bind the repo dir to a location in /tmp,
# so it can be accessed from within the chroot
mountpoint -q -- "$GENTOO_INSTALL_REPO_BIND" \
&& return
# Mount root device
einfo "Bind mounting repo directory"
mkdir -p "$GENTOO_INSTALL_REPO_BIND" \
|| die "Could not create mountpoint directory '$GENTOO_INSTALL_REPO_BIND'"
mount --bind "$GENTOO_INSTALL_REPO_DIR_ORIGINAL" "$GENTOO_INSTALL_REPO_BIND" \
|| die "Could not bind mount '$GENTOO_INSTALL_REPO_DIR_ORIGINAL' to '$GENTOO_INSTALL_REPO_BIND'"
}
function download_stage3() {
cd "$TMP_DIR" \
|| die "Could not cd into '$TMP_DIR'"
local STAGE3_RELEASES="$GENTOO_MIRROR/releases/amd64/autobuilds/current-$STAGE3_BASENAME/"
# Download upstream list of files
CURRENT_STAGE3="$(download_stdout "$STAGE3_RELEASES")" \
|| die "Could not retrieve list of tarballs"
# Decode urlencoded strings
CURRENT_STAGE3=$(python3 -c 'import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read()))' <<< "$CURRENT_STAGE3")
# Parse output for correct filename
CURRENT_STAGE3="$(grep -o "\"${STAGE3_BASENAME}-[0-9A-Z]*.tar.xz\"" <<< "$CURRENT_STAGE3" \
| sort -u | head -1)" \
|| die "Could not parse list of tarballs"
# Strip quotes
CURRENT_STAGE3="${CURRENT_STAGE3:1:-1}"
# File to indiciate successful verification
CURRENT_STAGE3_VERIFIED="${CURRENT_STAGE3}.verified"
# Download file if not already downloaded
if [[ -e $CURRENT_STAGE3_VERIFIED ]]; then
einfo "$STAGE3_BASENAME tarball already downloaded and verified"
else
einfo "Downloading $STAGE3_BASENAME tarball"
download "$STAGE3_RELEASES/${CURRENT_STAGE3}" "${CURRENT_STAGE3}"
download "$STAGE3_RELEASES/${CURRENT_STAGE3}.DIGESTS" "${CURRENT_STAGE3}.DIGESTS"
# Import gentoo keys
einfo "Importing gentoo gpg key"
local GENTOO_GPG_KEY="$TMP_DIR/gentoo-keys.gpg"
download "https://gentoo.org/.well-known/openpgpkey/hu/wtktzo4gyuhzu8a4z5fdj3fgmr1u6tob?l=releng" "$GENTOO_GPG_KEY" \
|| die "Could not retrieve gentoo gpg key"
gpg --quiet --import < "$GENTOO_GPG_KEY" \
|| die "Could not import gentoo gpg key"
# Verify DIGESTS signature
einfo "Verifying tarball signature"
gpg --quiet --verify "${CURRENT_STAGE3}.DIGESTS" \
|| die "Signature of '${CURRENT_STAGE3}.DIGESTS' invalid!"
# Check hashes
einfo "Verifying tarball integrity"
# Replace any absolute paths in the digest file with just the stage3 basename, so it will be found by rhash
digest_line=$(grep 'tar.xz$' "${CURRENT_STAGE3}.DIGESTS" | sed -e 's/ .*stage3-/ stage3-/')
if type rhash &>/dev/null; then
rhash -P --check <(echo "# SHA512"; echo "$digest_line") \
|| die "Checksum mismatch!"
else
sha512sum --check <<< "$digest_line" \
|| die "Checksum mismatch!"
fi
# Create verification file in case the script is restarted
touch_or_die 0644 "$CURRENT_STAGE3_VERIFIED"
fi
}
function extract_stage3() {
mount_root
[[ -n $CURRENT_STAGE3 ]] \
|| die "CURRENT_STAGE3 is not set"
[[ -e "$TMP_DIR/$CURRENT_STAGE3" ]] \
|| die "stage3 file does not exist"
# Go to root directory
cd "$ROOT_MOUNTPOINT" \
|| die "Could not move to '$ROOT_MOUNTPOINT'"
# Ensure the directory is empty
find . -mindepth 1 -maxdepth 1 -not -name 'lost+found' \
| grep -q . \
&& die "root directory '$ROOT_MOUNTPOINT' is not empty"
# Extract tarball
einfo "Extracting stage3 tarball"
tar xpf "$TMP_DIR/$CURRENT_STAGE3" --xattrs --numeric-owner \
|| die "Error while extracting tarball"
cd "$TMP_DIR" \
|| die "Could not cd into '$TMP_DIR'"
}
function gentoo_umount() {
if mountpoint -q -- "$ROOT_MOUNTPOINT"; then
einfo "Unmounting root filesystem"
umount -R -l "$ROOT_MOUNTPOINT" \
|| die "Could not unmount filesystems"
fi
}
function init_bash() {
source /etc/profile
umask 0077
export PS1='(chroot) \[\]\u\[\]@\h \[\]\w \[\]\$ \[\]'
}; export -f init_bash
function env_update() {
env-update \
|| die "Error in env-update"
source /etc/profile \
|| die "Could not source /etc/profile"
umask 0077
}
function mkdir_or_die() {
# shellcheck disable=SC2174
mkdir -m "$1" -p "$2" \
|| die "Could not create directory '$2'"
}
function touch_or_die() {
touch "$2" \
|| die "Could not touch '$2'"
chmod "$1" "$2"
}
# $1: root directory
# $@: command...
function gentoo_chroot() {
if [[ $# -eq 1 ]]; then
einfo "To later unmount all virtual filesystems, simply use umount -l ${1@Q}"
gentoo_chroot "$1" /bin/bash --init-file <(echo 'init_bash')
fi
[[ ${EXECUTED_IN_CHROOT-false} == "false" ]] \
|| die "Already in chroot"
local chroot_dir="$1"
shift
# Bind repo directory to tmp
bind_repo_dir
# Copy resolv.conf
einfo "Preparing chroot environment"
install --mode=0644 /etc/resolv.conf "$chroot_dir/etc/resolv.conf" \
|| die "Could not copy resolv.conf"
# Mount virtual filesystems
einfo "Mounting virtual filesystems"
(
mountpoint -q -- "$chroot_dir/proc" || mount -t proc /proc "$chroot_dir/proc" || exit 1
mountpoint -q -- "$chroot_dir/run" || mount --rbind /run "$chroot_dir/run" || exit 1
mountpoint -q -- "$chroot_dir/tmp" || mount --rbind /tmp "$chroot_dir/tmp" || exit 1
mountpoint -q -- "$chroot_dir/sys" || {
mount --rbind /sys "$chroot_dir/sys" &&
mount --make-rslave "$chroot_dir/sys"; } || exit 1
mountpoint -q -- "$chroot_dir/dev" || {
mount --rbind /dev "$chroot_dir/dev" &&
mount --make-rslave "$chroot_dir/dev"; } || exit 1
) || die "Could not mount virtual filesystems"
# Cache lsblk output, because it doesn't work correctly in chroot (returns almost no info for devices, e.g. empty uuids)
cache_lsblk_output
# Execute command
einfo "Chrooting..."
EXECUTED_IN_CHROOT=true \
TMP_DIR="$TMP_DIR" \
CACHED_LSBLK_OUTPUT="$CACHED_LSBLK_OUTPUT" \
exec chroot -- "$chroot_dir" "$GENTOO_INSTALL_REPO_DIR/scripts/dispatch_chroot.sh" "$@" \
|| die "Failed to chroot into '$chroot_dir'."
}
function enable_service() {
if [[ $SYSTEMD == "true" ]]; then
systemctl enable "$1" \
|| die "Could not enable $1 service"
else
rc-update add "$1" default \
|| die "Could not add $1 to default services"
fi
}