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.
999 lines
34 KiB
Bash
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
|