arch-zbm/setup.sh
hygienic-books 1bc09b7f8b refactor(os): Install paru-bin instead of paru (#21)
We straight up install paru-bi via its PKGBUILD from
AUR, we skip the additional step we used to do
where we first installed paru from its GitHub
project. This saves time and most importantly
scarce RAM that Rust otherwise needs during
paru compilation.

On systems with little RAM (as in 4 GiB) paru's
compilation process would sometimes fail when
/etc/makepkg.conf when its MAKEFLAGS was
set to use many cores e.g. via "-j$(nproc --ignore 1)".

Without the manual paru compilation step we now
don't need the ability anymore to
--replace-conflicting packages.
2023-11-05 23:29:54 +01:00

1357 lines
48 KiB
Bash

#!/bin/bash
# Whatever comes in on file descriptor (FD) 3 gets redirected to where file
# descriptor 1 is pointing. File descriptor 1 points to stdout so when we
# output-redirect something into FD 3 it shows up on stdout. We can use this
# to produce arbitrary logging output inside a subshell like so:
#
# function my_func () {
# some_command "${1:?}"
# >&3 echo 'A log message'
# }
#
# var="$(my_func arg_1)"
#
# Here "${var}" will only capture the output of some_command "${1:?}". It
# will not capture 'echo' which will instead show up on our stdout/FD 1.
exec 3>&1
declare this_script_url
this_script_url="${SCRIPT_URL:?}"
declare zpool_name zfs_arch_dataset_name settings_file
zpool_name='zpool'
zfs_arch_dataset_name='archlinux'
settings_file='archzbm_settings.env'
declare -A partition_types
partition_types[gpt_zfs]='6a85cf4d-1dd2-11b2-99a6-080020736631'
partition_types[gpt_efi]='c12a7328-f81f-11d2-ba4b-00a0c93ec93b'
partition_types[mbr_zfs]='0xbf'
partition_types[mbr_boot]='0x83'
# https://unix.stackexchange.com/a/48550
set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR
declare zpool_drive efi_drive boot_drive part_schema
function setup_env_vars () {
printf -- '%s\n' \
'We will go over a series of questions to create an answer file with' \
'options you want the script to use.' \
'' \
'Current working directory is:'\
"$(pwd)" \
'' \
'After we'"'"'re done answer file will be written to current working dir:' \
'./'"${settings_file}" \
'' \
'Press <Ctrl>+C to abort this process. No answer file will' \
'be written to ./'"${settings_file}"' if you abort the script.' \
'' \
'When done rerun the same command you just did without '"'"'setup'"'"' argument.' \
''
read -u3 -n 1 -s -r -p "Press any key to begin questionnaire"
echo
echo '----------------------------------------'
echo
echo "Do you want compressed datasets?"
select arg_compressed in "Compressed" "Uncompressed"; do
case "${arg_compressed}" in
Compressed)
break
;;
Uncompressed)
ARCHZBM_ZFSPROPS_NO_COMPRESSION='true'
break
;;
esac
done <&3 && echo
echo "Do you want encrypted datasets?"
select arg_encrypted in "Encrypted" "Unencrypted"; do
case "${arg_encrypted}" in
Encrypted)
break
;;
Unencrypted)
ARCHZBM_ZFSPROPS_NO_ENCRYPTION='true'
break
;;
esac
done <&3 && echo
if [[ "${arg_encrypted}" = 'Encrypted' ]]; then
echo "Do you want a custom dataset decryption password?"
select arg_custom_dataset_pw in "Yes" "No"; do
case "${arg_custom_dataset_pw}" in
Yes)
want_custom_dataset_pw='true'
break
;;
No)
break
;;
esac
done <&3 && echo
if [[ "${want_custom_dataset_pw}" ]]; then
read -u3 -p 'Please type password, confirm with <Enter>: ' -s ARCHZBM_ZPOOL_PASSWORD
echo
echo
fi
fi
echo "Do you want a custom 'root' user password?"
select arg_custom_root_pw in "Yes" "No"; do
case "${arg_custom_root_pw}" in
Yes)
want_custom_root_pw='true'
break
;;
No)
break
;;
esac
done <&3 && echo
if [[ "${want_custom_root_pw}" ]]; then
read -u3 -p 'Please type password, confirm with <Enter>: ' -s ARCHZBM_ROOT_PASSWORD
echo
echo
fi
echo "Do you want an SSH daemon in ZFSBootMenu?"
select arg_ssh_in_zbm in "Yes" "No"; do
case "${arg_ssh_in_zbm}" in
Yes)
want_ssh_in_zbm='true'
break
;;
No)
break
;;
esac
done <&3 && echo
if [[ "${want_ssh_in_zbm}" ]]; then
echo "How do you want to assign an IP address in ZFSBootMenu?"
select arg_ip_autoconf_method in "Statically" "Dynamically, DHCP" "Dynamically, BOOTP" "Dynamically, RARP"; do
case "${arg_ip_autoconf_method}" in
'Statically')
ARCHZBM_NET_AUTOCONF='none'
break
;;
'Dynamically, DHCP')
ARCHZBM_NET_AUTOCONF='dhcp'
break
;;
'Dynamically, BOOTP')
ARCHZBM_NET_AUTOCONF='bootp'
break
;;
'Dynamically, RARP')
ARCHZBM_NET_AUTOCONF='rarp'
break
;;
esac
done <&3 && echo
read -u3 -p 'Which device to configure (eth0 is a good guess): ' ARCHZBM_NET_DEVICE
echo
if [[ "${arg_ip_autoconf_method}" = 'Statically' ]]; then
read -u3 -p 'Interface IP address: ' ARCHZBM_NET_CLIENT_IP
echo
read -u3 -p 'Netmask: ' ARCHZBM_NET_NETMASK
echo
read -u3 -p 'Gateway IP address: ' ARCHZBM_NET_GATEWAY_IP
echo
fi
echo "Do you want a custom SSH listening port?"
select arg_custom_ssh_port in "Yes (let me specify)" "No (keep port 22)"; do
case "${arg_custom_ssh_port}" in
'Yes (let me specify)')
want_custom_ssh_port='true'
break
;;
'No (keep port 22)')
break
;;
esac
done <&3 && echo
if [[ "${want_custom_ssh_port}" ]]; then
read -u3 -p 'Please type SSH port, confirm with <Enter>: ' ARCHZBM_SSH_PORT
echo
fi
echo "Do you want the SSH daemon to use a custom keepalive send interval?"
select arg_custom_ssh_keepalive_intvl in "Yes (let me specify)" "No (keep 1)"; do
case "${arg_custom_ssh_keepalive_intvl}" in
'Yes (let me specify)')
want_custom_keepalive_intvl='true'
break
;;
'No (keep 1)')
break
;;
esac
done <&3 && echo
if [[ "${want_custom_keepalive_intvl}" ]]; then
read -u3 -p 'Please type server keepalive send interval, confirm with <Enter>: ' ARCHZBM_SSH_KEEPALIVE_INTVL
echo
fi
if [[ "${want_custom_keepalive_intvl}" ]]; then
read -u3 -p 'Please type SSH pub keys on one line separated by double-commas (,,) and confirm with <Enter>: ' ARCHZBM_SSH_AUTH_KEYS
echo
fi
fi
for env_var in 'ARCHZBM_ZFSPROPS_NO_COMPRESSION' 'ARCHZBM_ZFSPROPS_NO_ENCRYPTION' 'ARCHZBM_ZPOOL_PASSWORD' 'ARCHZBM_ROOT_PASSWORD' 'ARCHZBM_NET_AUTOCONF' 'ARCHZBM_NET_DEVICE' 'ARCHZBM_NET_CLIENT_IP' 'ARCHZBM_NET_NETMASK' 'ARCHZBM_NET_GATEWAY_IP' 'ARCHZBM_SSH_PORT' 'ARCHZBM_SSH_KEEPALIVE_INTVL' 'ARCHZBM_SSH_AUTH_KEYS'; do
if [[ "${!env_var}" ]]; then
printf -- '%s='"'"'%s'"'"'\n' \
"${env_var}" "${!env_var}" \
>> "${settings_file}"
fi
done
printf -- '%s\n' \
'Done, please rerun script now with just' \
'... | bash' \
'so without the '"'"'setup'"'"' argument's
exit 77
}
function arg_parse () {
[[ "${1}" ]] && while :; do
case "${1}" in
-[[:alnum:]]*)
>&3 printf -- '%s\n' \
'Short options '"'${1}'"' detected. Only known option is' \
'literal string '"'"'setup'"'"'. No idea what '"'${1}'"' is.' \
'Exiting ...'
exit 77
;;
--*)
>&3 printf -- '%s\n' \
'Long-form option '"'${1}'"' detected. Only known option is' \
'literal string '"'"'setup'"'"'. No idea what '"'${1}'"' is.' \
'Exiting ...'
exit 77
;;
setup)
setup_env_vars
return
;;
*)
>&3 printf -- '%s\n' \
'Argument '"'${1}'"' detected. Only known option is literal' \
'string '"'"'setup'"'"'. No idea what '"'${1}'"' is.' \
'Exiting ...'
exit 77
;;
esac
done
}
function we_are_changerooted () {
if [ "$(stat -c '%d:%i' '/')" != "$(stat -c '%d:%i' '/proc/1/root/.')" ]; then
return 0
else
return 1
fi
}
function no_kernel_update_in_iso () {
#1.1
sed -ri -e 's'$'\x1''#(IgnorePkg)[^\r\n\f]+'$'\x1''\1 = linux linux-headers'$'\x1''g' /etc/pacman.conf
}
function set_ntp () {
#1.2
timedatectl set-ntp true
}
function resize_cow_space () {
#1.3
mount -o remount,size='50%' /run/archiso/cowspace
}
function update_pacman_db () {
#1.4
pacman_dl_parallel
printf -- '%s\n' 'Refreshing mirror list ...'
printf -- '%s\n' \
'--save /etc/pacman.d/mirrorlist' \
'--protocol https' \
'--latest 5' \
'--sort age' \
> '/etc/xdg/reflector/reflector.conf'
systemctl start reflector
# In an ISO and for the minimal number of packages we need we do not
# care about partial upgrades
pacman -Syyuu --noconfirm
}
function install_pkgs () {
#1.5
printf -- '%s\n' 'Installing packages ...'
pacman -S --needed --noconfirm "${@}"
}
function install_zfs () {
#1.6
declare reset_colors='\033[0m'
curl -s 'https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init' | bash
printf -- "${reset_colors}"
}
function uefi_or_bios () {
#1.7
local part_count_linux part_count_efi
# Select disks with at least one partition. Among them count how many
# with the given partition type code we have.
part_count_linux="$(get_partitions | jq --raw-output '[.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${partition_types[mbr_boot]}"'") | .path] | length')"
part_count_efi="$(get_partitions | jq --raw-output '[.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${partition_types[gpt_efi]}"'") | .path] | length')"
if [[ "${part_count_linux}" -eq '1' ]] && [[ "${part_count_efi}" -eq '0' ]]; then
part_schema='mbr'
>&3 printf -- '%s\n' \
'Treating this as an MBR/legacy BIOS machine ...'
elif [[ "${part_count_linux}" -eq '0' ]] && [[ "${part_count_efi}" -eq '1' ]]; then
part_schema='gpt'
>&3 printf -- '%s\n' \
'Treating this as a GPT/UEFI machine ...'
else
>&3 printf -- '%s\n' \
'We are seeing partitions as follows:' \
'- Partition type code '"${partition_types[mbr_boot]}"': '"${part_count_linux}" \
'- Partition type code '"${partition_types[gpt_efi]}"': '"${part_count_efi}" \
'' \
'We are instead expecting either 1 and 0 indicating a legacy' \
'BIOS setup or 0 and 1 indicating UEFI. '"${part_count_linux}"' and '"${part_count_efi}"' is' \
'neither. Cowardly exiting ...'
exit 77
fi
export part_schema="${part_schema}"
}
function get_partitions () {
declare partitions_json
partitions_json="$(lsblk --output PATH,PARTTYPE --json --tree)" || return 1
printf -- '%s' "${partitions_json}"
return 0
}
function get_part_parent () {
local child_partition parent_partition
child_partition="${1:?}"
parent_partition="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].path=="'"${child_partition:?}"'") | .path')"
printf -- '%s' "${parent_partition}"
return 0
}
function get_parts () {
local zfs_install_drive part_type_code
declare parttype parts
parttype="${1:?}"
zfs_install_drive="${2:-}"
case "${parttype}" in
zfs)
if [[ "${part_schema}" = 'mbr' ]]; then
part_type_code="${partition_types[mbr_zfs]}"
else
part_type_code="${partition_types[gpt_zfs]}"
fi
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${part_type_code}"'") | .path')" || exit 1
;;
boot)
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="'"${partition_types[mbr_boot]}"'") | .path')" || exit 1
;;
efi)
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="'"${partition_types[gpt_efi]}"'") | .path')" || exit 1
;;
*)
>&3 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...'
exit 77
;;
esac
printf -- '%s' "${parts}"
return 0
}
function we_have_exactly_one_part () {
local parttype parts_list parts_count
parttype="${1:?}"
parts_list="${2:?}"
parts_count="$(wc -l <<<"${parts_list}")"
if [[ "$(wc -c <<<"${parts_list}")" -gt '1' ]]; then
case "${parts_count}" in
0)
>&3 printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting ...'
exit 77
;;
1)
return 0
;;
*)
return 1
;;
esac
>&3 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting ...'
exit 77
fi
}
function get_drive_id () {
local drive_id_list drive_id_single
drive_id_list="$(find -L /dev/disk/by-partuuid -samefile "${1:?}" | sort)"
drive_id_single="$(head -n1 <<<"${drive_id_list}")"
if [[ "$(wc -l <<<"${drive_id_single}")" -eq '1' ]] && [[ "$(wc -c <<<"${drive_id_single}")" -gt '1' ]]; then
printf -- '%s' "${drive_id_single}"
return 0
fi
>&3 printf -- '%s\n' 'No '"'${1:?}'"' partition entry in /dev/disk/by-partuuid, exiting ...'
exit 77
}
function select_part () {
local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive part_type_code specific_part_type
declare part
part_type="${1:?}" # 'boot' or 'zfs'
zfs_install_drive="${2:-}"
if [[ "${part_type}" = 'boot' ]]; then
if [[ "${part_schema}" = 'mbr' ]]; then
specific_part_type='boot'
else
specific_part_type='efi'
fi
else
specific_part_type="${part_type}"
fi
if [[ "${zfs_install_drive}" ]]; then
# This is intended to find correct boot/EFI partition
parts="$(get_parts "${specific_part_type}" "${zfs_install_drive}")"
else
parts="$(get_parts "${specific_part_type}")"
fi
if [[ ! "${parts}" ]]; then
case "${part_type}" in
efi)
part_type_human_readable='EFI System Partition (ESP) with partition type code EF00'
;;
zfs)
if [[ "${part_schema}" = 'mbr' ]]; then
part_type_code='bf'
else
part_type_code='BF00'
fi
part_type_human_readable='ZFS zpool partition with partition type code '"${part_type_code}"
;;
esac
>&3 printf -- '%s\n' \
'It looks as if there is no '"${part_type_human_readable}" \
'on any of the disks. Did you correctly partition a disk before starting?' \
'Check https://quico.space/quico-os-setup/arch-zbm#prep. Exiting ...'
exit 77
fi
if we_have_exactly_one_part "${part_type}" "${parts}"; then
part="${parts}"
else
>&3 printf -- '%s\n' 'More than one '"${part_type^^}"' partition to pick for installation. Cowardly exiting ...'
exit 77
fi
printf -- '%s' "${part}"
return 0
}
function no_zpool_exists () {
declare zpool_list
zpool_list="$(zpool list -H)"
[[ "$(wc -l <<<"${zpool_list}")" -le '1' ]] && [[ "$(wc -c <<<"${zpool_list}")" -le '1' ]] && return 0
return 1
}
function set_zpool_password () {
local zpool_password
if [[ "${ARCHZBM_ZPOOL_PASSWORD}" ]]; then
zpool_password="${ARCHZBM_ZPOOL_PASSWORD}"
else
zpool_password='password'
fi
# May or may not have a newline at the end, ZFS doesn't care
printf -- '%s' "${zpool_password}" > '/etc/zfs/'"${zpool_name}"'.key'
chmod '000' '/etc/zfs/'"${zpool_name}"'.key'
}
function import_pool () {
zpool import -d '/dev/disk/by-partuuid' -R '/mnt' "${zpool_name}" -N -f
[[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && zfs load-key "${zpool_name}"
}
function create_pool () {
# Create a temporary pool that is not cached
#
# Add zfsprops 'compression' unless environment variable
# ARCHZBM_ZFSPROPS_NO_COMPRESSION is set to any value.
#
# Add zfsprops 'encryption' along with 'keyformat' and a 'keylocation'
# unless environment variable ARCHZBM_ZFSPROPS_NO_ENCRYPTION is set to
# any value.
zpool create -f \
-o 'ashift=12' \
-o 'autotrim=on' \
-O 'acltype=posix' \
$([[ ! "${ARCHZBM_ZFSPROPS_NO_COMPRESSION}" ]] && \
printf -- '%s ' \
'-O compression=on') \
-O 'relatime=on' \
-O 'xattr=sa' \
$([[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && \
printf -- '%s ' \
'-O encryption=on' \
'-O keyformat=passphrase' \
'-O keylocation=file:///etc/zfs/'"${zpool_name}"'.key') \
-O 'normalization=formD' \
-O 'mountpoint=none' \
-O 'canmount=off' \
-O 'devices=off' \
-R '/mnt' \
"${zpool_name}" "${1:?}"
}
function create_root_dataset () {
zfs create -o 'mountpoint=none' -o 'canmount=off' "${zpool_name}"'/root'
# zfs set org.zfsbootmenu:commandline="ro quiet" "${zpool_name}"'/root'
zfs set org.zfsbootmenu:commandline="ro" "${zpool_name}"'/root'
}
function create_system_dataset () {
zfs create -o 'mountpoint=/' -o 'canmount=noauto' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
zgenhostid
zpool set bootfs="${zpool_name}"'/root/'"${zfs_arch_dataset_name}" "${zpool_name}"
zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
}
function create_home_dataset () {
zfs create -o 'mountpoint=/' -o 'canmount=off' "${zpool_name}"'/data'
zfs create "${zpool_name}"'/data/home'
}
function export_pool () {
zpool export "${zpool_name}"
}
function load_settings_file () {
#1.8
local working_dir settings_file settings_abs
working_dir="$(pwd)"
settings_abs="${working_dir}"'/'"${settings_file}"
if [[ -r "${settings_abs}" ]]; then
set -a
source "${settings_abs}"
set +a
fi
}
function setup_zpool () {
#1.9
local drive_by_id
zpool_drive="$(select_part 'zfs')"
drive_by_id="$(get_drive_id "${zpool_drive}")"
[[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && set_zpool_password
if no_zpool_exists; then
create_pool "${drive_by_id}"
create_root_dataset
create_system_dataset
create_home_dataset
export_pool
import_pool
else
>&3 printf -- '%s\n' 'A zpool already exists, that is unexpected. Cowardly exiting 1 ...'
exit 1
fi
}
function mount_system () {
#1.10
zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
zfs mount -a
local zfs_parent efi_part boot_part
zfs_parent="$(get_part_parent "${zpool_drive:?}")"
if [[ "${part_schema}" = 'mbr' ]]; then
boot_drive="${zfs_parent}"
export boot_drive="${boot_drive}"
boot_part="$(select_part 'boot' "${zfs_parent:?}")"
mkdir -p '/mnt/boot/syslinux'
mount "${boot_part}" '/mnt/boot/syslinux'
else
efi_drive="${zfs_parent}"
export efi_drive="${efi_drive}"
efi_part="$(select_part 'boot' "${zfs_parent:?}")"
mkdir -p '/mnt/efi'
mount "${efi_part}" '/mnt/efi'
fi
}
function copy_zpool_cache () {
#1.11
mkdir -p '/mnt/etc/zfs'
zpool set 'cachefile=/etc/zfs/'"${zpool_name}"'.cache' "${zpool_name}"
}
function pacman_dont_check_space () {
# See pacman bug comment
# https://bugs.archlinux.org/task/45070#comment142712
#
# When we pacstrap onto ZFS pacman incorrectly calculates and
# overestimates required disk space. We instead assume an installation
# gets done with at least a 10 GiB drive which is plenty. Skip pacman's
# size check.
#
# We're setting this in Arch Linux ISO CD while we install proper Arch.
# No need to revert this later as it is ephemeral anyway.
sed -ri -e 's'$'\x1''^.*?(CheckSpace)([^\r\n\f]*)'$'\x1''#\1\2'$'\x1''g' '/etc/pacman.conf'
}
function install_archlinux () {
#1.12
pacman_dl_parallel
pacman_dont_check_space
pacstrap /mnt \
base \
base-devel \
linux \
linux-headers \
linux-firmware \
amd-ucode \
efibootmgr \
vim \
git \
iwd \
networkmanager \
network-manager-applet \
dialog \
os-prober \
reflector \
bluez \
bluez-utils \
man-db \
xdg-utils \
xdg-user-dirs
}
function gen_fstab () {
#1.13
genfstab -U /mnt | grep -v "${zpool_name}" | tr -s '\n' | sed -r -e 's/\/mnt//' -e '/./,$!d' > '/mnt/etc/fstab'
}
function configure_hosts_file () {
cat > '/mnt/etc/hosts' <<EOF
#<ip-address> <hostname.domain.org> <hostname>
127.0.0.1 localhost ${1}
::1 localhost ${1}
EOF
}
function set_hostname () {
#1.14
declare new_hostname
install_pkgs 'pwgen'
new_hostname="$(pwgen --no-numerals --no-capitalize --ambiguous 8)"
printf -- '%s\n' "${new_hostname}" > '/mnt/etc/hostname'
configure_hosts_file "${new_hostname}"
}
function set_locale () {
#1.15
printf -- '%s\n' \
'KEYMAP=de-latin1' \
'FONT=Lat2-Terminus16' \
'FONT_MAP=8859-1' \
> '/mnt/etc/vconsole.conf'
sed -ri -e 's'$'\x1''^(#)(en_US.UTF-8[^\r\n\f]*)'$'\x1''\2'$'\x1''g' '/mnt/etc/locale.gen'
printf -- '%s\n' 'LANG=en_US.UTF-8' > '/mnt/etc/locale.conf'
}
function in_file_in_array_insert_n_before_m () {
local arg_file arg_array arg_string arg_precede
arg_file="${1:?}"
arg_array="${2:?}"
arg_string="${3:?}"
arg_precede="${4:?}"
# Look for a line that contains in this order
# - String "${arg_array}" and a equals sign (=), assign capture group \1
# - Followed by as few characters as possible followed by either an
# opening parenthesis or a space character, assign capture group \2
# - String "${arg_precede}", capture as capture group \3
# - Followed by either a closing parenthesis or a space which are then
# followed by as many non-line break characters as possible, capture
# as capture group \2
#
# For following example text we're assuming that:
# - "${arg_array}" equals 'HOOKS'
# - "${arg_precede}" equals 'filesystems'
# - "${arg_string}" equals 'zfs'
#
# This finds a 'HOOKS=' array definition that contains the string
# 'filesystems' either at the beginning of the 'HOOKS=(...)' opening
# parenthesis, at the very end or somewhere in the middle where it may
# be preceded or followed by one or more space characters. It saves
# 'HOOKS=', it saves whatever precedes 'filesystems' and 'filesystems'
# itself plus whatever comes after 'filesystems' until end of line. It
# lastly inserts 'zfs' and a space character right in front of
# 'filesystems'.
sed -ri \
-e 's'$'\x1''('"${arg_array}"'=)(.*?[( ])('"${arg_precede}"')([) ][^\r\n\f]*)'$'\x1''\1\2'"${arg_string}"' \3\4'$'\x1''g' \
"${arg_file}"
}
function in_file_in_array_insert_n_at_the_end () {
local arg_file arg_array arg_string
arg_file="${1:?}"
arg_array="${2:?}"
arg_string="${3:?}"
# Look for end of array, insert "${arg_string}" right before
#
# For following example text we're assuming that:
# - "${arg_array}" equals 'HOOKS'
# - "${arg_string}" equals 'net'
#
# This:
# - Finds a 'HOOKS=' array definition, saves it as \1
# - Finds as many non-closing parenthesis characters as possible, so all
# characters until just before the final ')' character of the line and
# saves this as \2
# - Finds the closing parenthesis character plus all non-line break
# characters until end of line that come after it and saves this as \3
# - Adds a space character and 'net' after \2
sed -ri \
-e 's'$'\x1''('"${arg_array}"'=)([^)]*)(\)[^\r\n\f]*)'$'\x1''\1\2 '"${arg_string}"'\3'$'\x1''g' \
"${arg_file}"
}
function in_file_in_array_remove_n () {
local arg_file arg_array arg_string
arg_file="${1:?}"
arg_array="${2:?}"
arg_string="${3:?}"
# Look for any line that contains "${arg_string}", delete that string
#
# For following example text we're assuming that:
# - "${arg_array}" equals 'HOOKS'
# - "${arg_string}" equals 'fsck'
#
# First -e expression removes 'fsck' wherever it is defined as the one
# and only element of any HOOKS=(fsck) should such a line exist.
#
# Second -e expression finds string 'fsck' where it's preceded by space
# character(s) and followed by either space character(s) or a closing
# parenthesis.
#
# Third -e expression finds string 'fsck' where it's preceded by space
# character(s) or an opening parenthesis and followed space
# character(s).
sed -ri \
-e 's'$'\x1''((\()('"${arg_string}"')(\)))'$'\x1''\2\4'$'\x1''g' \
-e 's'$'\x1''('"${arg_array}"'=.*?)([[:space:]]+'"${arg_string}"')([[:space:]]+|\))'$'\x1''\1\3'$'\x1''g' \
-e 's'$'\x1''('"${arg_array}"'=.*?)([[:space:]]+|\()('"${arg_string}"'[[:space:]]+)'$'\x1''\1\2'$'\x1''g' \
"${arg_file}"
}
function add_zfs_hook_to_initramfs () {
#1.16
# Add zfs hook, remove fsck hook from initramfs.
in_file_in_array_insert_n_before_m '/mnt/etc/mkinitcpio.conf' 'HOOKS' 'zfs' 'filesystems'
in_file_in_array_remove_n '/mnt/etc/mkinitcpio.conf' 'HOOKS' 'fsck'
# Also unless encryption's unwanted add plain text key file into
# initramfs since it's living inside an encrypted pool anyway.
[[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && sed -ri \
-e 's'$'\x1''^(FILES=)[^\r\n\f]*'$'\x1''\1(/etc/zfs/'"${zpool_name}"'.key)'$'\x1''g' \
'/mnt/etc/mkinitcpio.conf'
}
function set_initramfs_build_list () {
#1.17
# No need to build fallback initramfs, our new fallback is ZFS snapshots
sed -ri \
-e '/^#/d' \
-e '/^$/d' \
-e '/^fallback/d' \
-e 's'$'\x1''^(PRESETS=)[^\r\n\f]*'$'\x1''\1('"'"'default'"'"')'$'\x1''g' \
'/mnt/etc/mkinitcpio.d/linux.preset'
# Remove any existing fallback initramfs files
find '/mnt/boot' -type f -regextype posix-extended -iregex '^/mnt/boot/initramfs-.*?-fallback.img' -delete
}
function add_zfs_files_to_new_os () {
#1.18
for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' $([[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && printf -- '%s' '/etc/zfs/'"${zpool_name}"'.key'); do
rsync -av --itemize-changes {'','/mnt'}"${zfs_file}"
done
}
function create_unpriv_user () {
account_name="${1:?}"
full_name="${2:-${account_name}}"
authorized_keys_abs_path='/home/'"${account_name}"'/.ssh/authorized_keys'
useradd --create-home --shell '/bin/bash' --comment 'Personal account of '"${full_name}" --user-group "${account_name}"
chfn --full-name "${full_name}" "${account_name}"
mkdir -p "$(dirname "${authorized_keys_abs_path}")"
touch "${authorized_keys_abs_path}"
chown -R "${account_name}"':' '/home/'"${account_name}"; chmod -R 'u=rwX,go=' "$(dirname "${authorized_keys_abs_path}")"
}
function enter_chroot () {
#2.1
arch-chroot /mnt /bin/bash -xe <<EOF
curl --silent '${this_script_url}' | bash
EOF
}
function keep_initiramfs_root_only_rw () {
#2.3
declare systemd_local_admin_override_path unit_name
systemd_local_admin_override_path='/etc/systemd/system'
unit_name='chmod-initramfs'
path_unit="${systemd_local_admin_override_path%/}"'/'"${unit_name}"'.path'
service_unit="${systemd_local_admin_override_path%/}"'/'"${unit_name}"'.service'
cat > "${path_unit}" <<"EOF"
[Unit]
Description=chmod initramfs to be root-read-writable only
[Path]
PathChanged=/boot/initramfs-linux.img
[Install]
WantedBy=multi-user.target
WantedBy=system-update.target
EOF
cat > "${service_unit}" <<"EOF"
[Unit]
Description=chmod initramfs to be root-read-writable only
[Service]
Type=oneshot
ExecStart=/usr/bin/chmod 600 /boot/initramfs-linux.img
EOF
systemctl enable "${path_unit}"
}
function pacman_dl_parallel () {
#2.4
# We're setting this in Arch Linux ISO CD while we install proper Arch.
# No need to revert this later as it is ephemeral anyway.
sed -ri -e 's'$'\x1''^.*?(ParallelDownloads)[^\r\n\f]*'$'\x1''\1 = 20'$'\x1''g' '/etc/pacman.conf'
}
function unleash_makepkg () {
#2.5
local path_prefix
path_prefix='/mnt'
if we_are_changerooted; then
path_prefix=''
fi
sed -ri \
-e 's'$'\x1''^(#?(MAKEFLAGS=))[^\r\n\f]*'$'\x1''\2"-j$(nproc --ignore 1)"'$'\x1''g' \
"${path_prefix}"'/etc/makepkg.conf'
}
function add_motd_getting_started_msg () {
#2.6
cat > '/etc/motd' <<"EOF"
####################
GUI basics:
paru -S xorg plasma-meta kde-applications-meta sddm
localectl set-x11-keymap de
useradd --create-home --shell /bin/bash --user-group --groups wheel <user>
passwd <user>
systemctl enable --now sddm.service
####################
EOF
}
function get_aur_helper () {
#2.7
create_unpriv_user 'build'
usermod --append --groups 'wheel' 'build'
printf -- '%s\n' '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' > '/etc/sudoers.d/10-wheel-group-no-passwd-prompt'
pushd /tmp
git clone https://aur.archlinux.org/paru-bin.git
chown -R 'build:' 'paru-bin'
pushd 'paru-bin'
sudo --user 'build' makepkg -si --noconfirm
popd
rm -rf 'paru-bin'
popd
}
function paru_install () {
sudo --user build paru -S --noconfirm "${@}"
}
function configure_syslinux () {
paru_install 'syslinux'
cp /usr/lib/syslinux/bios/*.c32 /boot/syslinux
extlinux --install /boot/syslinux
cat > /boot/syslinux/syslinux.cfg <<EOF
UI menu.c32
PROMPT 0
MENU TITLE ZFSBootMenu
TIMEOUT 100
DEFAULT zfsbootmenu
LABEL zfsbootmenu
MENU LABEL ZFSBootMenu
KERNEL /zfsbootmenu/vmlinuz-bootmenu
INITRD /zfsbootmenu/initramfs-bootmenu.img
APPEND zfsbootmenu quiet loglevel=0
LABEL zfsbootmenu-backup
MENU LABEL ZFSBootMenu (Backup)
KERNEL /zfsbootmenu/vmlinuz-bootmenu-backup
INITRD /zfsbootmenu/initramfs-bootmenu-backup.img
APPEND zfsbootmenu quiet loglevel=0
EOF
dd bs=440 count=1 conv=notrunc if='/usr/lib/syslinux/bios/mbr.bin' of="${boot_drive}"
}
function configure_zfsbootmenu () {
#2.9
paru_install 'zfsbootmenu'
in_file_in_array_remove_n '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' 'zfsbootmenu'
if [[ "${part_schema}" = 'gpt' ]]; then
cat > '/etc/zfsbootmenu/config.yaml' <<EOF
Global:
ManageImages: true
BootMountPoint: /efi
InitCPIO: true
Components:
Enabled: false
EFI:
ImageDir: /efi/EFI/ZBM
Versions: false
Enabled: true
Stub: /usr/share/zfsbootmenu/stubs/linuxx64.efi.stub/linuxx64.efi.stub # workaround: https://github.com/zbm-dev/zfsbootmenu/discussions/501
Kernel:
CommandLine: ro loglevel=0 zbm.import_policy=hostid
Prefix: vmlinuz
EOF
else
configure_syslinux
cat > '/etc/zfsbootmenu/config.yaml' <<EOF
Global:
ManageImages: true
BootMountPoint: /boot/syslinux
InitCPIO: true
Components:
Enabled: true
Versions: false
ImageDir: /boot/syslinux/zfsbootmenu
Kernel:
Prefix: vmlinuz
EOF
fi
# Up here maybe 'ro quiet' instead of 'ro'. This is ZFSBootMenu's kernel
# command line. In MBR/Syslinux/extlinux mode /it/ must pass arguments to
# ZFSBootMenu's kernel so check /boot/syslinux/syslinux.cfg for how we start
# ZFSBootMenu in this mode. The .Kernel.CommandLine in
# /etc/zfsbootmenu/config.yaml is irrelevant for us in MBR/Syslinux/extlinux
# mode.
# Assign cmdline for final kernel start. This is our Arch Linx kernel
# command line.
zfs set org.zfsbootmenu:commandline='rw nowatchdog rd.vconsole.keymap=de-latin1' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
}
function get_dropbear_hooks () {
mkdir -p '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main'
git -C '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook.git' .
ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-hook.sh' '/usr/local/bin/pacman-mkinitcpio-dropbear-hook'
ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-install.sh' '/usr/local/bin/pacman-mkinitcpio-dropbear-install'
ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-hook.hook' '/usr/share/libalpm/hooks/pacman-mkinitcpio-dropbear-hook.hook'
ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-install.hook' '/usr/share/libalpm/hooks/pacman-mkinitcpio-dropbear-install.hook'
}
function customize_dropbear_hooks () {
local env_archzbm_ssh_port env_archzbm_ssh_keepalive_intvl
env_archzbm_ssh_port="${ARCHZBM_SSH_PORT:-22}"
env_archzbm_ssh_keepalive_intvl="${ARCHZBM_SSH_KEEPALIVE_INTVL:-1}"
if [[ "${env_archzbm_ssh_port}" -ne '22' ]] || [[ "${env_archzbm_ssh_keepalive_intvl}" -ne '1' ]]; then
paru_install 'rsync'
rsync -av '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook'{,'.override'}'.patch'
fi
if [[ "${env_archzbm_ssh_port}" -ne '22' ]]; then
sed -ri -e 's'$'\x1''-p [[:digit:]]+'$'\x1''-p '"${env_archzbm_ssh_port}"''$'\x1''g' '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook.override.patch'
fi
if [[ "${env_archzbm_ssh_keepalive_intvl}" -ne '1' ]]; then
sed -ri -e 's'$'\x1''-K [[:digit:]]+'$'\x1''-K '"${env_archzbm_ssh_keepalive_intvl}"''$'\x1''g' '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook.override.patch'
fi
}
function set_unique_ip_in_syslinux_kcl () {
local zbm_config default_ip
zbm_config="${1:?}"
default_ip="${2:?}"
# First -e expression removes first looks for lines that contain
# 'APPEND' plus a space character and only in those lines removes all
# occurrences of ' ip=' followed by as many non-space characters as
# possible. This removes whatever 'ip=' definition already was present.
#
# Second -e expression similarly looks for lines that contain 'APPEND'
# plus a space character then at their end inserts a space plus our
# desired new 'ip=' definition. This puts in place the 'ip=' we want.
sed -ri \
-e \\$'\x1''APPEND '$'\x1 s'$'\x1'' ip=([^[:space:]]*)'$'\x1'''$'\x1''gi' \
-e \\$'\x1''APPEND '$'\x1 s'$'\x1''$'$'\x1'' '"${default_ip}"''$'\x1''gi' \
"${zbm_config}"
}
function ensure_ip_in_kcl () {
local default_ip
default_ip='ip='"${ARCHZBM_NET_CLIENT_IP}"':'"${ARCHZBM_NET_SERVER_IP}"':'"${ARCHZBM_NET_GATEWAY_IP}"':'"${ARCHZBM_NET_NETMASK}"':'"${ARCHZBM_NET_HOSTNAME}"':'"${ARCHZBM_NET_DEVICE}"':'"${ARCHZBM_NET_AUTOCONF}"
if [[ "${part_schema}" = 'gpt' ]]; then
local zbm_config kcl_length kcl_string ip_addr_found new_kcl first_kcl_elem
local -a kcl
paru_install 'go-yq'
zbm_config='/etc/zfsbootmenu/config.yaml'
kcl_length="$(yq '.Kernel.CommandLine | length' "${zbm_config}")"
if [[ "${kcl_length}" -eq '0' ]]; then
>&3 printf -- '%s\n' \
'No .Kernel.CommandLine YAML element with content found in '"${zbm_config}"'. Exiting ...'
exit 77
else
kcl_string="$(yq '.Kernel.CommandLine' "${zbm_config}")"
fi
mapfile -t kcl < <(<<<"${kcl_string}" tr ' ' '\n' | sed '/^$/d')
for kcl_elem in "${!kcl[@]}"; do
if grep -Piq -- 'ip=' <<<"${kcl[$kcl_elem]}"; then
ip_addr_found='true'
kcl["${kcl_elem}"]="${default_ip}"
fi
done
if [[ ! "${ip_addr_found}" ]]; then
kcl+=("${default_ip}")
fi
new_kcl=''
first_kcl_elem='true'
for kcl_elem in "${kcl[@]}"; do
if [[ ! "${first_kcl_elem}" ]]; then
new_kcl+=' '"${kcl_elem}"
else
new_kcl+="${kcl_elem}"
unset -v first_kcl_elem
fi
done
yq -i '.Kernel.CommandLine = "'"${new_kcl}"'"' "${zbm_config}"
else
local zbm_config
zbm_config='/boot/syslinux/syslinux.cfg'
set_unique_ip_in_syslinux_kcl "${zbm_config}" "${default_ip}"
fi
}
function set_pub_keys () {
local authorized_keys_file raw_pub_keys
authorized_keys_file="${1:?}"
raw_pub_keys="${2:?}"
:> "${authorized_keys_file}"
while IFS= read -r pub_key_line; do
printf -- '%s\n' "${pub_key_line}" >> "${authorized_keys_file}"
done < <(<<<"${raw_pub_keys}" sed -r -e 's/,,/\n/g')
sed -i '/^$/d' "${authorized_keys_file}"
}
function we_want_ssh () {
#2.10
if [[ "${ARCHZBM_NET_CLIENT_IP}" ]] || \
[[ "${ARCHZBM_NET_SERVER_IP}" ]] || \
[[ "${ARCHZBM_NET_GATEWAY_IP}" ]] || \
[[ "${ARCHZBM_NET_NETMASK}" ]] || \
[[ "${ARCHZBM_NET_HOSTNAME}" ]] || \
[[ "${ARCHZBM_NET_DEVICE}" ]] || \
[[ "${ARCHZBM_NET_AUTOCONF}" ]] || \
[[ "${ARCHZBM_SSH_PORT}" ]] || \
[[ "${ARCHZBM_SSH_KEEPALIVE_INTVL}" ]] || \
[[ "${ARCHZBM_SSH_AUTH_KEYS}" ]]; then
>&3 printf -- '%s\n' 'Installing SSH in ZFSBootMenu'
return 0
fi
>&3 printf -- '%s\n' 'Not installing SSH in ZFSBootMenu'
return 1
}
function configure_ssh_in_zbm () {
#2.11
get_dropbear_hooks
customize_dropbear_hooks
paru_install 'mkinitcpio-nfs-utils' 'dropbear' 'mkinitcpio-dropbear'
in_file_in_array_insert_n_at_the_end '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' 'net'
in_file_in_array_insert_n_at_the_end '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' 'dropbear'
for key_type in 'dss' 'ecdsa' 'ed25519' 'rsa'; do
dropbearkey -t "${key_type}" -f '/etc/dropbear/dropbear_'"${key_type}"'_host_key'
done
set_pub_keys '/etc/dropbear/root_key' "${ARCHZBM_SSH_AUTH_KEYS}"
ensure_ip_in_kcl
}
function add_syslinux_pacman_hook () {
mkdir -p '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main'
git -C '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/zbm-syslinux-pacman-hook.git' .
chmod +x '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.sh'
ln -s '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.sh' '/usr/local/bin/pacman-zbm-syslinux-regen'
ln -s '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.hook' '/usr/share/libalpm/hooks/pacman-zbm-syslinux-regen.hook'
}
function add_zbm_pacman_hook () {
mkdir -p '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main'
git -C '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/zbm-regen-pacman-hook.git' .
ln -s '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main/pacman-zbm-image-regen.hook' '/usr/share/libalpm/hooks/pacman-zbm-image-regen.hook'
}
function get_disks_with_one_efipart () {
local disks_with_one_efipart
# Find disks that have exactly one EFI partition and where that EFI
# partition is partition number 1. We expect exactly one disk to meet
# these criteria. Anything else and we bail.
disks_with_one_efipart="$(lsblk --output PATH,PARTTYPE --json --tree | jq --raw-output '.[][] | select(.children | length > 0) | select( any (.children[]; (.path | test("^[^[:digit:]]+1")) and (.parttype=="'"${partition_types[gpt_efi]}"'")) and ([select(.children[].parttype=="'"${partition_types[gpt_efi]}"'")] | length == 1) ) | .path')"
if [[ "$(wc -l <<<"${disks_with_one_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_one_efipart}")" -gt '1' ]]; then
printf -- '%s' "${disks_with_one_efipart}"
return 0
fi
return 1
}
function install_os_in_chroot () {
#2.2
### Reinit keyring
# As keyring is initialized at boot, and copied to the install dir with pacstrap, and ntp is running
# Time changed after keyring initialization, it leads to malfunction
# Keyring needs to be reinitialised properly to be able to sign keys.
rm -rf '/etc/pacman.d/gnupg'
pacman-key --init
pacman-key --populate archlinux
pacman -S archlinux-keyring --noconfirm
locale-gen
source /etc/locale.conf
keep_initiramfs_root_only_rw #2.3
pacman_dl_parallel #2.4
unleash_makepkg #2.5
add_motd_getting_started_msg #2.6
get_aur_helper #2.7
paru_install 'zfs-dkms' 'zfs-utils' 'jq'
hwclock --systohc
mkinitcpio -P
# Install ZFSBootMenu image
configure_zfsbootmenu #2.9
if we_want_ssh; then #2.10
configure_ssh_in_zbm #2.11
fi
generate-zbm
# Yes, we do this twice so we immediately get a functional backup file
generate-zbm
if [[ "${part_schema}" = 'mbr' ]]; then
paru_install 'rsync'
add_syslinux_pacman_hook
fi
add_zbm_pacman_hook
}
function set_root_pw () {
#3.2
local root_password
if [[ "${ARCHZBM_ROOT_PASSWORD}" ]]; then
root_password="${ARCHZBM_ROOT_PASSWORD}"
else
root_password='password'
fi
printf -- '%s\n' 'root:'"${root_password}" | chpasswd --crypt-method 'SHA512' --root '/mnt'
}
function configure_networking () {
#3.3
cat > '/mnt/etc/systemd/network/50-wired.network' <<"EOF"
[Match]
Name=en*
[Network]
DHCP=ipv4
IPForward=yes
[DHCP]
UseDNS=yes
RouteMetric=10
EOF
systemctl enable 'systemd-networkd' --root='/mnt'
systemctl disable 'systemd-networkd-wait-online' --root='/mnt'
}
function configure_dns () {
#3.4
rm '/mnt/etc/resolv.conf'
ln -s '/run/systemd/resolve/stub-resolv.conf' '/mnt/etc/resolv.conf'
# Optionally you may want /etc/systemd/network/50-wired.network to use
# UseDNS=no and hardcode DNS server(s) here:
# sed -i 's/^#DNS=.*/DNS=1.1.1.1/' /mnt/etc/systemd/resolved.conf
systemctl enable 'systemd-resolved' --root='/mnt'
}
function configure_reflector () {
#3.5
systemctl enable 'reflector.service' 'reflector.timer' --root='/mnt'
}
function configure_zfs () {
#3.6
systemctl enable 'zfs-import-cache' 'zfs-mount' 'zfs-import.target' 'zfs.target' --root='/mnt'
}
function configure_zfs_mount_gen () {
#3.7
mkdir -p '/mnt/etc/zfs/zfs-list.cache'
touch '/mnt/etc/zfs/zfs-list.cache/'"${zpool_name}"
zfs list -H -o name,mountpoint,canmount,atime,relatime,devices,exec,readonly,setuid,nbmand | sed 's/\/mnt//' > '/mnt/etc/zfs/zfs-list.cache/'"${zpool_name}"
systemctl enable 'zfs-zed.service' --root='/mnt'
}
function set_new_uefi_boot_entries () {
#3.8
declare -a uefi_images
mapfile -t uefi_images < \
<(find '/mnt/efi/EFI/ZBM' -type f -iname '*.efi' -print0 | \
xargs -0 --no-run-if-empty --max-args '1' stat -c '%Y %n' | \
sort -V | \
awk '{print $2}')
if efibootmgr | grep -Piq -- 'ZFSBootMenu'; then
local -a old_uefi_entries
mapfile -t old_uefi_entries < \
<(efibootmgr | \
grep -Pio -- '(?<=^Boot)[^\*[:space:]]+(?=\*? ZFSBootMenu)')
for old_uefi_entry in "${old_uefi_entries[@]}"; do
efibootmgr --bootnum "${old_uefi_entry}" --delete-bootnum &>/dev/null && {
>&3 printf -- '%s\n' \
'EFI boot entry '"${old_uefi_entry}"' deleted.'
}
done
fi
if ! efibootmgr | grep -Piq -- 'ZFSBootMenu'; then
local efi_disks_list
efi_disks_list="$(get_disks_with_one_efipart)"
if grep -Piq -- '^'"${efi_drive}"'$' <<<"${efi_disks_list}"; then
for uefi_image in "${uefi_images[@]}"; do
uefi_image_version="$(basename "${uefi_image%%.EFI}")"
uefi_image_inverted="${uefi_image#/mnt/efi}"
uefi_image_inverted="${uefi_image_inverted//\//\\}"
efibootmgr --disk "${efi_drive}" \
--part 1 \
--create \
--label 'ZFSBootMenu '"${uefi_image_version}" \
--loader "${uefi_image_inverted}" &>/dev/null && {
>&3 printf -- '%s\n' \
'EFI boot entry ZFSBootMenu '"${uefi_image_version}"' added.'
}
done
fi
fi
}
function umount_all () {
#3.9
if [[ "${part_schema}" = 'mbr' ]]; then
umount '/mnt/boot/syslinux'
else
umount '/mnt/efi'
fi
zfs umount -a
zpool export "${zpool_name}"
}
function finalize_os_setup () {
#3.1
set_root_pw #3.2
configure_networking #3.3
configure_dns #3.4
configure_reflector #3.5
configure_zfs #3.6
configure_zfs_mount_gen #3.7
if [[ "${part_schema}" = 'gpt' ]]; then
set_new_uefi_boot_entries #3.8
fi
umount_all #3.9
}
function main () {
if [[ "${#@}" -gt '0' ]]; then
arg_parse "${@}"
fi
if we_are_changerooted; then
install_os_in_chroot #2.2
else
no_kernel_update_in_iso #1.1
set_ntp #1.2
resize_cow_space #1.3
update_pacman_db #1.4
install_pkgs 'jq' #1.5
install_zfs #1.6
uefi_or_bios #1.7
load_settings_file #1.8
setup_zpool #1.9
mount_system #1.10
copy_zpool_cache #1.11
install_archlinux #1.12
gen_fstab #1.13
set_hostname #1.14
set_locale #1.15
add_zfs_hook_to_initramfs #1.16
set_initramfs_build_list #1.17
add_zfs_files_to_new_os #1.18
enter_chroot #2.1
# We're done in chroot
finalize_os_setup #3.1
fi
}
main "${@}"