arch-zbm/setup.sh
hygienic-books 9a89ce4f75 fix(os): Remove zfsbootmenu hook from ZFSBootMenu (#6)
While unintuitive this prevents us from having to
move this hook to the end of the HOOKS array.
If user wants SSH in ZBM we first append 'net' to
the HOOKS array then append 'dropbear'. At this
point the explicit 'zfsbootmenu' hook is no longer
last in line in 'HOOKS' array. This causes ZBM to
trip and to not correctly load net and dropbear.

Since the 'zfsbootmenu' hook is added implicitly
by generate-zbm we don't need the explicit hook
at all. Better remove it. This way it doesn't get in
the way.
2023-11-01 03:53:18 +01:00

999 lines
34 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
zpool_name='zpool'
zfs_arch_dataset_name='archlinux'
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 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
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_file='archzbm_settings.env'
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:?}"
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:?}"
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:?}"
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.git'
chown -R 'build:' 'paru'
pushd 'paru'
sudo --user 'build' makepkg -si --noconfirm
popd
rm -rf 'paru'
popd
}
function paru_install () {
declare -a paru_install_packages
[[ "${1}" ]] && while :; do
case "${1}" in
-[[:alnum:]]*)
>&3 printf -- '%s\n' \
'Short-form argument '"'${1}'"' not supported for function '"'${FUNCNAME[0]}()'"'. Only known accepted argument' \
'is '"'"'--replace-conflicting'"'"' without a value given. Exiting ...'
exit 77
;;
--replace-conflicting)
pacman_force_yes='true'
shift
continue
;;
--*)
>&3 printf -- '%s\n' \
'Long-form argument '"'${1}'"' not supported for function '"'${FUNCNAME[0]}()'"'. Only known accepted argument' \
'is '"'"'--replace-conflicting'"'"' without a value given. Exiting ...'
exit 77
;;
'')
# All arguments processed
break
;;
*)
paru_install_packages+=("${1}")
shift
;;
esac
done || {
>&3 printf -- '%s\n' \
'No argument '"'${1}'"' given for function '"'${FUNCNAME[0]}'"'. Exiting ...'
exit 77
}
if [[ "${pacman_force_yes}" ]]; then
yes 'y' | sudo --user 'build' paru -S "${paru_install_packages[@]}"
unset -v pacman_force_yes
else
sudo --user 'build' paru -S --noconfirm "${paru_install_packages[@]}"
fi
}
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/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.
# 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 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
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'
}
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 --replace-conflicting 'paru-bin'
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 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