2
\$\begingroup\$

I wrote a bash script that installs Arch Linux. I wanted to simplify the installation of the Arch Linux system by automating things like setting locale, hostname, networking, etc. This is my second bigger script right to the two scripts that automate setting the ZFS filesystem. Any code review of what can I improve is appreciated.

lib/utils

#!/usr/bin/env bash
die() {
 echo "Error: 1ドル. Exiting..." 1>&2
 exit 1
}
require_binaries() {
 local binaries=( "$@" )
 for binary in "${binaries[@]}"; do
 if ! command -v "${binary}" &> /dev/null; then
 die "${binary} binary does not exist"
 fi
 done
}
is_mounted() { # Copied from https://www.baeldung.com/linux/bash-is-directory-mounted
 mount | awk -v DIR="1ドル" '{if (3ドル == DIR) { exit 0}} ENDFILE{exit -1}'
}
insist_root() {
 if [ "$EUID" -ne 0 ]; then
 echo "Please run as root"
 exit 1
 fi
}
array_contains_element() {
 local array=1ドル
 local str=2ドル
 for elem in "${array[@]}"; do
 if [[ "${elem}" == "${str}" ]]; then
 return 0
 fi
 done
 return 1
}
read_password() {
 local password confirm_password
 until
 stty -echo
 read -r -p "Password: " password
 echo
 read -r -p "Confirm password: " confirm_password
 echo
 stty echo
 [[ "${password}" = "${confirm_password}" ]]
 do
 echo "Error: passwords don't match." >&2
 done
 echo "${password}"
}
_another_instance() {
 echo "There is another instance running, exiting"
 exit 1
}
create_lock() {
 LOCKFILE=/var/lock/$(basename "0ドル")
 LOCKFD="100"
 eval "exec ${LOCKFD}>\"${LOCKFILE}\"" || exit 1
 flock -xn ${LOCKFD} || _another_instance
 trap 'flock -u ${LOCKFD}; flock -xn ${LOCKFD} && rm -f ${LOCKFILE}' EXIT
}

install_arch

#!/usr/bin/env bash
source ./lib/utils
insist_root
create_lock
require_binaries "pacman" "pacstrap" "genfstab" "arch-chroot"
ARCHZFS_REPO_KEY="DDF7DB817396A49B2A2723F7403BD972F75D9D76"
CWD="$(pwd)"
ARCH_AVAILABLE_PROFILES=( "grub" "uefi" "devel" "firmware" "parabola_firmware" "zfs" "kernel_headers" "python"
 "zfs_force_dkms" "!dkms" "genfstab" "genfstab_uuid" "genfstab_label" "zfs_snapshot_install"
 "swap_encrypt" "raid" )
ARCH_ENABLED_PROFILES=${ARCH_ENABLED_PROFILES:-"grub uefi firmware genfstab genfstab_uuid"}
usage() {
 cat <<EOF
0ドル - Install Arch Linux base system
Usage:
 0ドル [ options ]
 0ドル --help
Available profiles:
 ${ARCH_AVAILABLE_PROFILES[*]}
Enabled profiles by default:
 ${ARCH_ENABLED_PROFILES[*]}
Options:
 -m|--mnt value - rootfs mount path, required
 -u|--user value - target user name, required (default: user)
 -p|--profiles "quoted list of values" - instructs script how to configure Arch 
 -e|--extra-software "quoted list of additional software to install"
 -t|--timezone "value" - target system timezone (default: UTC)
 --hwclock-timezone (localtime|UTC) - hardware clock timezone (default: UTC)
 -l|--enabled-locales "quoted list of values" - locales to enable in /etc/locale.gen (default: "en_US.UTF-8")
 -L|--locale value - default system locale (default: en_US.UTF-8)
 -H|--hostname value - system hostname (default: host)
 -D|--domain value - system domain (default: localdomain)
 --bootstrap-opts "quotes list of values" - bootstrap options used by initial call of pacstrap (default: -K)
 -k|--keymap - system keymap (default: en)
 -K|--kernels - system kernels (default: linux)
 --kernels-headers "quoted list of values" - system kernels' headers, not required
 -b|--boot-mode - boot mode (default: uefi)
 --efi-bootloader-id value - uefi bootloade id (default: arch)
 -B|--boot-devices "quoted list of values" - boot devices used by GRUB, required
 --initrd-modules "quoted list of modules" - initcpio modules
 --initrd-binaries "quoted list of binaries" - inicpio binaries
 --initrd-files "quoted list of files" - initcpio files
 --initrd-hooks "quoted list of hooks" - inicpio hooks
 --initrd-compression value - initcpio compression algorithm
 --initrd-compression-opts "quoted list of opts" - inicpio compression options
 --initrd-decompress value - specifies whether kernel modules should be decompressed during initramfs creation (default: no)
 --initrd-custom-hooks-files "quoted list of files" - initcpio custom hooks files
 --initrd-custom-install-files "quoted list of files" - initcpio custom hook install files
 --grub-boot-dir value - grub boot directory, only needed in uefi installation (default: /boot/efi/arch)
 --grub-efi-dir value - grub efi directory, only needed in uefi installation (default: /boot/efi)
 --zfs-boot-pool|--zfs-bpool - zfs boot pool (default: bpool)
 --zfs-root-pool|--zfs-rpool - zfs root pool (default: rpool)
 --zfs-home-dataset - zfs home dataset (default: rpool/home/${ARCH_USER})
 --zfs-snapshot-name value - specifies initial installation snapshot name (default: install)
 --zfs-module-pkgname value - specifies zfs kernel module package name if dkms is globally disabled, not required
 --swap-cryptdev value - specifies swap crypt device when swap_encrypt profile is enabled (default: none)
 --config-dir - config directory (default: ${CWD}/install_arch.d)
 -c|--config - script config file, required
 --apply - tells script that changes should be applied, use with caution
 -h|--help - displays this help
EOF
 exit 0
}
[ $# -eq 0 ] && usage
# parse script arguments
while [ "$#" -gt 0 ]; do
 case 1ドル in
 -m|--mnt)
 ARCH_MNT_PATH="2ドル"
 shift
 shift
 ;;
 -u|--user)
 ARCH_USER="2ドル"
 shift
 shift
 ;;
 -p|--profiles)
 ARCH_ENABLED_PROFILES="2ドル"
 shift
 shift
 ;;
 -e|--extra-software)
 ARCH_EXTRA_SOFTWARE="2ドル"
 shift
 shift
 ;;
 -t|--timezone)
 ARCH_TIMEZONE="2ドル"
 shift
 shift
 ;;
 --hwclock-timezone)
 ARCH_HWCLOCK_TIMEZONE="2ドル"
 shift
 shift
 ;;
 -l|--enabled-locales)
 ARCH_ENABLED_LOCALES="2ドル"
 shift
 shift
 ;;
 -L|--locale)
 ARCH_LOCALE="2ドル"
 shift
 shift
 ;;
 -H|--hostname)
 ARCH_HOSTNAME="2ドル"
 shift
 shift
 ;;
 -D|--domain)
 ARCH_DOMAIN="2ドル"
 shift
 shift
 ;;
 --bootstrap-opts)
 ARCH_BOOTSTRAP_OPTS="2ドル"
 shift
 shift
 ;;
 -k|--keymap)
 ARCH_KEYMAP="2ドル"
 shift
 shift
 ;;
 -K|--kernels)
 ARCH_KERNELS="2ドル"
 shift
 shift
 ;;
 --kernels-headers)
 ARCH_KERNELS_HEADERS="2ドル"
 shift
 shift
 ;;
 -b|--boot-mode)
 ARCH_BOOT_MODE="2ドル"
 shift
 shift
 ;;
 --efi-bootloader-id)
 ARCH_EFI_BOOTLOADER_ID="2ドル"
 shift
 shift
 ;;
 -B|--boot-devices)
 ARCH_BOOT_DEVICES="2ドル"
 shift
 shift
 ;;
 --initrd-modules)
 ARCH_INITRD_MODULES="2ドル"
 shift
 shift
 ;;
 --initrd-binaries)
 ARCH_INITRD_BINARIES="2ドル"
 shift
 shift
 ;;
 --initrd-files)
 ARCH_INITRD_FILES="2ドル"
 shift
 shift
 ;;
 --initrd-hooks)
 ARCH_INITRD_HOOKS="2ドル"
 shift
 shift
 ;;
 --initrd-compression)
 ARCH_INITRD_COMPRESSION="2ドル"
 shift
 shift
 ;;
 --initrd-compression-opts)
 ARCH_INITRD_COMPRESSION_OPTS="2ドル"
 shift
 shift
 ;;
 --initrd-modules-decompress)
 ARCH_INITRD_MODULES_DECOMPRESS="y"
 shift
 shift
 ;;
 --initrd-custom-hooks-files)
 ARCH_INITRD_CUSTOM_HOOKS_FILES="2ドル"
 shift
 shift
 ;;
 --initrd-custom-install-files)
 ARCH_INITRD_CUSTOM_INSTALL_FILES="2ドル"
 shift
 shift
 ;;
 --grub-boot-dir)
 ARCH_GRUB_BOOT_DIR="2ドル"
 shift
 shift
 ;;
 --grub-efi-dir)
 ARCH_GRUB_EFI_DIR="2ドル"
 shift
 shift
 ;;
 --zfs-boot-pool|--zfs-bpool)
 ARCH_ZFS_BOOT_POOL="2ドル"
 shift
 shift
 ;;
 --zfs-root-pool|--zfs-rpool)
 ARCH_ZFS_ROOT_POOL="2ドル"
 shift
 shift
 ;;
 --zfs-home-dataset)
 ARCH_ZFS_HOME_DATASET="2ドル"
 shift
 shift
 ;;
 --zfs-snapshot-name)
 ARCH_ZFS_SNAPSHOT_NAME="2ドル"
 shift
 shift
 ;;
 --zfs-module-pkgname)
 ARCH_ZFS_MODULE_PKGNAME="2ドル"
 shift
 shift
 ;;
 --swap-cryptdev)
 ARCH_SWAP_CRYPTDEV="2ドル"
 shift
 shift
 ;;
 --config-dir)
 CONFIG_DIR="2ドル"
 shift
 shift
 ;;
 -c|--config)
 CONFIG_FILE="2ドル"
 shift
 shift
 ;;
 --apply)
 APPLY="y"
 shift
 ;;
 -h|--help)
 usage
 ;;
 *)
 echo "Error: command '1ドル' not recognized."
 usage
 ;;
 esac
done
# shellcheck source=/dev/null
if [[ -f "${CONFIG_FILE}" ]]; then
 source "${CONFIG_FILE}"
else
 die "config file does not exist"
fi
# assign default values to variables
ARCH_MNT_PATH=${ARCH_MNT_PATH:-}
ARCH_USER=${ARCH_USER:-"user"}
ARCH_EXTRA_SOFTWARE=${ARCH_EXTRA_SOFTWARE:-}
ARCH_TIMEZONE=${ARCH_TIMEZONE:-"UTC"}
ARCH_HWCLOCK_TIMEZONE=${ARCH_HWCLOCK_TIMEZONE:-"UTC"}
ARCH_ENABLED_LOCALES=${ARCH_ENABLED_LOCALES:-"en_US.UTF-8 UTF-8"}
ARCH_LOCALE=${ARCH_LOCALE:-"en_US.UTF-8"}
ARCH_HOSTNAME=${ARCH_HOSTNAME:-"host"}
ARCH_DOMAIN=${ARCH_DOMAIN:-"localdomain"}
ARCH_BOOTSTRAP_OPTS=${ARCH_BOOTSTRAP_OPTS:-"-K"}
ARCH_KEYMAP=${ARCH_KEYMAP:-"en"}
ARCH_KERNELS=${ARCH_KERNELS:-"linux"}
ARCH_KERNELS_HEADERS=${ARCH_KERNELS_HEADERS:-}
ARCH_BOOT_MODE=${ARCH_BOOT_MODE:-"uefi"}
ARCH_EFI_BOOTLOADER_ID=${ARCH_EFI_BOOTLOADER_ID:-"arch"}
ARCH_BOOT_DEVICES=${ARCH_BOOT_DEVICES:-}
ARCH_INITRD_MODULES=${ARCH_INITRD_MODULES:-"ext4"}
ARCH_INITRD_BINARIES=${ARCH_INITRD_BINARIES:-}
ARCH_INITRD_FILES=${ARCH_INITRD_FILES:-}
ARCH_INITRD_HOOKS=${ARCH_INITRD_HOOKS:-"base udev autodetect modconf kms keyboard keymap consolefont block filesystems fsck"}
ARCH_INITRD_COMPRESSION=${ARCH_INITRD_COMPRESSION:-}
ARCH_INITRD_COMPRESSION_OPTS=${ARCH_INITRD_COMPRESSION_OPTS:-}
ARCH_INITRD_MODULES_DECOMPRESS=${ARCH_INITRD_MODULES_DECOMPRESS:-}
ARCH_INITRD_CUSTOM_HOOKS_FILES=${ARCH_INITRD_CUSTOM_HOOKS_FILES:-}
ARCH_INITRD_CUSTOM_INSTALL_FILES=${ARCH_INITRD_CUSTOM_INSTALL_FILES:-}
ARCH_GRUB_BOOT_DIR=${ARCH_GRUB_BOOT_DIR:-"/boot"}
ARCH_GRUB_EFI_DIR=${ARCH_GRUB_EFI_DIR:-"/boot/efi"}
ARCH_ZFS_BOOT_POOL=${ARCH_ZFS_BOOT_POOL-"bpool"}
ARCH_ZFS_ROOT_POOL=${ARCH_ZFS_ROOT_POOL:-"rpool"}
ARCH_ZFS_HOME_DATASET=${ARCH_ZFS_HOME_DATASET:-"${ARCH_ZFS_ROOT_POOL}/home/${ARCH_USER}"}
ARCH_ZFS_SNAPSHOT_NAME=${ARCH_ZFS_SNAPSHOT_NAME:-"install"}
ARCH_ZFS_MODULE_PKGNAME=${ARCH_ZFS_MODULE_PKGNAME:-}
ARCH_SWAP_CRYPTDEV=${ARCH_SWAP_CRYPTDEV:-}
ARCH_POSTINST_SERVICES=${ARCH_POSTINST_SERVICES:-}
ARCH_POSTINST_CMDS=${ARCH_POSTINST_CMDS:-}
ARCH_ROOT_PASSWD=${ARCH_ROOT_PASSWD:-"\1ドル\$HNeYMtER\0ドルVoteufFxJBfPM1YkkNpk."} # default password: changeme
ARCH_USER_PASSWD=${ARCH_USER_PASSWD:-"\1ドル\1ドルajoaeQC\$lubTSvAeoWn8kNvXIPoKi/"} # default password: changeme
CONFIG_DIR=${CONFIG_DIR:-"${CWD}/install_arch.d"}
APPLY=${APPLY:-}
validate_args() {
 if [[ -n "${ARCH_MNT_PATH}" ]]; then
 if [[ ! -d "${ARCH_MNT_PATH}" ]]; then
 die "directory '${ARCH_MNT_PATH}' does not exist"
 else
 if ! is_mounted "${ARCH_MNT_PATH}"; then
 die "directory '${ARCH_MNT_PATH}' is not mounted"
 fi
 fi
 else
 die "mount path not specified"
 fi
 [[ -z "${ARCH_USER}" ]] && die "target user name is not specified"
 if [[ -n "${ARCH_ENABLED_PROFILES[*]}" ]]; then
 for profile in "${ARCH_ENABLED_PROFILES[@]}"; do
 if [[ ! ${ARCH_AVAILABLE_PROFILES[*]} =~ ${profile} ]]; then
 die "unknown profile '${profile}'"
 fi
 done
 else
 die "profile list is empty"
 fi
 [[ -z "${ARCH_TIMEZONE}" ]] && die "timezone value is empty"
 if [[ "${ARCH_HWCLOCK_TIMEZONE}" != "UTC" && "${ARCH_HWCLOCK_TIMEZONE}" != "localtime" ]]; then
 die "valid options for --hwclock-timezone are 'UTC' and 'localtime'"
 fi
 if [[ -z "${ARCH_LOCALE[*]}" ]] || [[ -z "${ARCH_ENABLED_LOCALES[*]}" ]]; then
 die "locale not specified"
 fi
 [[ -z "${ARCH_HOSTNAME}" ]] && die "hostname is empty"
 [[ -z "${ARCH_DOMAIN}" ]] && die "domain name is empty"
 [[ -z "${ARCH_KEYMAP}" ]] && die "keymap info isn't specified"
 [[ -z "${ARCH_KERNELS[*]}" ]] && die "kernel list is empty"
 if [[ -n "${ARCH_BOOT_MODE}" ]]; then
 case "${ARCH_BOOT_MODE}" in
 "uefi") ;;
 "legacy_bios") ;;
 *) die "boot mode must be 'uefi' or 'legacy_bios'" ;;
 esac
 else
 die "boot mode value is empty"
 fi
 for device in "${ARCH_BOOT_DEVICES[@]}"; do
 if [[ ! -b "${device}" ]]; then
 die "${device}: not a block device"
 fi
 done
}
is_profile_enabled() {
 for profile in "${ARCH_ENABLED_PROFILES[@]}"; do
 if [[ "${profile}" == "1ドル" ]]; then
 return 0
 fi
 done
 return 1
}
check_profiles_deps() {
 if ! is_profile_enabled "zfs"; then
 is_profile_enabled "zfs_force_dkms" && die "profile 'zfs_force_dkms' depends on the profile 'zfs'"
 is_profile_enabled "zfs_snapshot_install" && die "profile 'zfs_snapshot_install' depends on the profile 'zfs'"
 fi
 if ! is_profile_enabled "genfstab"; then
 is_profile_enabled "genfstab_uuid" && die "profile 'genfstab_uuid' depends on the profile 'genfstab'"
 is_profile_enabled "genfstab_label" && die "profile 'genfstab_label' depends on the profile 'genfstab'"
 fi
 if is_profile_enabled "zfs_force_dkms" && is_profile_enabled "!dkms"; then
 die "mutually exclusive profiles: 'zfs_force_dkms' and '!dkms'"
 fi
 if is_profile_enabled "kernel_headers" && ! is_profile_enabled "devel"; then
 die "profile 'kernel_headers' depends on the profile 'devel'"
 fi
 if is_profile_enabled "python" && ! is_profile_enabled "devel"; then
 die "profile 'python' depends on the profile 'devel'"
 fi
 if is_profile_enabled "genfstab_uuid" && ! is_profile_enabled "genfstab"; then
 die "profile 'genfstab_uuid' depends on the profile 'genfstab'"
 fi
 if is_profile_enabled "genfstab_label" && ! is_profile_enabled "genfstab"; then
 die "profile 'genfstab_label' depends on the profile 'genfstab'"
 fi
}
install_base_system() {
 echo "Installing base system"
 pacstrap "${ARCH_BOOTSTRAP_OPTS[*]}" "${ARCH_MNT_PATH}" base
}
generate_fstab() {
 local genfstab_args
 genfstab_args=""
 echo "Generating /etc/fstab"
 is_profile_enabled "uuid" && genfstab_args="-U"
 is_profile_enabled "label" && genfstab_args="-L"
 genfstab "${genfstab_args}" "${ARCH_MNT_PATH}" >> "${ARCH_MNT_PATH}"/etc/fstab
}
set_timezone() {
 echo "Setting timezone"
 arch-chroot "${ARCH_MNT_PATH}" ln -sf /usr/share/zoneinfo/"${ARCH_TIMEZONE}" /etc/localtime
 case "${ARCH_HWCLOCK_TIMEZONE}" in
 "UTC")
 arch-chroot "${ARCH_MNT_PATH}" hwclock --systohc --utc
 ;;
 "localtime")
 arch-chroot "${ARCH_MNT_PATH}" hwclock --systohc --localtime
 ;;
 esac
}
set_locale() {
 local IFS
 IFS=":"
 echo "Setting locale"
 for locale in "${ARCH_ENABLED_LOCALES[@]}"; do
 sed -i "s/#${locale}/${locale}/g" "${ARCH_MNT_PATH}"/etc/locale.gen
 done
 echo "LANG=${ARCH_LOCALE}" > "${ARCH_MNT_PATH}"/etc/locale.conf
 arch-chroot "${ARCH_MNT_PATH}" locale-gen
}
set_keymap() {
 echo "Setting keymap"
 echo "KEYMAP=${ARCH_KEYMAP}" > "${ARCH_MNT_PATH}"/etc/vconsole.conf
}
set_hostname() {
 echo "Setting hostname"
 echo "${ARCH_HOSTNAME}" > "${ARCH_MNT_PATH}"/etc/hostname
 cat <<EOF > "${ARCH_MNT_PATH}"/etc/hosts
# Static table lookup for hostnames.
# See hosts(5) for details.
# The following lines are desirable for IPv4 capable hosts
127.0.0.1 localhost
# 127.0.1.1 is often used for the FQDN of the machine
127.0.1.1 ${ARCH_HOSTNAME}.${ARCH_DOMAIN} ${ARCH_HOSTNAME}
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF
}
enable_dhcpcd() {
 echo "Installing dhcpcd"
 pacstrap "${ARCH_MNT_PATH}" dhcpcd
 echo "Enabling dhcpcd"
 arch-chroot "${ARCH_MNT_PATH}" systemctl enable dhcpcd
}
install_devel_pkgs() {
 echo "Installing base development packages"
 pacstrap "${ARCH_MNT_PATH}" base-devel
 if is_profile_enabled "python"; then
 echo "Installing python development packages"
 pacstrap "${ARCH_MNT_PATH}" python python-pip python-setuptools
 fi
}
# shellcheck disable=SC2048
# shellcheck disable=SC2086
install_kernel() {
 echo "Installing kernel"
 pacstrap "${ARCH_MNT_PATH}" ${ARCH_KERNELS[*]}
 is_profile_enabled "firmware" && pacstrap "${ARCH_MNT_PATH}" linux-firmware
 is_profile_enabled "parabola_firmware" && pacstrap "${ARCH_MNT_PATH}" linux-libre-firmware
 if is_profile_enabled "kernel_headers"; then
 for kernel in "${ARCH_KERNELS[@]}"; do
 case "${kernel}" in
 "linux") pacstrap "${ARCH_MNT_PATH}" linux-headers ;;
 "linux-lts") pacstrap "${ARCH_MNT_PATH}" linux-lts-headers ;;
 "linux-hardened") pacstrap "${ARCH_MNT_PATH}" linux-hardened-headers ;;
 "linux-zen") pacstrap "${ARCH_MNT_PATH}" linux-zen-headers ;;
 "linux-libre") pacstrap "${ARCH_MNT_PATH}" linux-libre-headers ;;
 "linux-libre-hardened") pacstrap "${ARCH_MNT_PATH}" linux-libre-hardened-headers ;;
 *)
 if [[ -z "${ARCH_KERNELS_HEADERS[*]}" ]]; then
 pacstrap "${ARCH_MNT_PATH}" "${kernel}-headers"
 else
 for kernel_headers in "${ARCH_KERNELS_HEADERS[@]}"; do
 pacstrap "${ARCH_MNT_PATH}" "${kernel_headers}"
 done
 fi
 ;;
 esac
 done
 fi
}
install_zfs() {
 cat <<EOF >> "${ARCH_MNT_PATH}"/etc/pacman.conf
[archzfs]
# Origin Server - France
Server = http://archzfs.com/\$repo/x86_64
# Mirror - Germany
Server = http://mirror.sum7.eu/archlinux/archzfs/\$repo/x86_64
# Mirror - Germany
Server = https://mirror.biocrafting.net/archlinux/archzfs/\$repo/x86_64
# Mirror - India
Server = https://mirror.in.themindsmaze.com/archzfs/\$repo/x86_64
# Mirror - US
Server = https://zxcvfdsa.com/archzfs/\$repo/\$arch
EOF
 arch-chroot "${ARCH_MNT_PATH}" <<EOF
pacman-key --recv-keys ${ARCHZFS_REPO_KEY}
pacman-key --lsign-key ${ARCHZFS_REPO_KEY}
pacman -Syu --noconfirm
EOF
 if is_profile_enabled "zfs_force_dkms"; then
 arch-chroot "${ARCH_MNT_PATH}" <<EOF
pacman -S --noconfirm zfs-dkms
depmod
modprobe zfs
EOF
 else
 for kernel in "${ARCH_KERNELS[@]}"; do
 case "${kernel}" in
 "linux") pacstrap "${ARCH_MNT_PATH}" zfs-linux ;;
 "linux-lts") pacstrap "${ARCH_MNT_PATH}" zfs-linux-lts ;;
 "linux-hardened") pacstrap "${ARCH_MNT_PATH}" zfs-linux-hardened ;;
 "linux-zen") pacstrap "${ARCH_MNT_PATH}" zfs-linux-zen ;;
 *)
 if is_profile_enabled "!dkms"; then
 pacstrap "${ARCH_MNT_PATH}" "${ARCH_ZFS_MODULE_PKGNAME}"
 else
 arch-chroot "${ARCH_MNT_PATH}" <<EOF
pacman -S --noconfirm zfs-dkms
depmod
modprobe zfs
EOF
 fi
 ;;
 esac
 done
 fi
}
setup_mdadm() {
 mdadm --detail --scan >> "${ARCH_MNT_PATH}"/etc/mdadm.conf
 pacstrap "${ARCH_MNT_PATH}" mdadm
}
# shellcheck disable=SC2048
# shellcheck disable=SC2086
install_extra_software() {
 if [[ -n "${ARCH_EXTRA_SOFTWARE[*]}" ]]; then
 echo "Installing extra software"
 pacstrap "${ARCH_MNT_PATH}" ${ARCH_EXTRA_SOFTWARE[*]}
 fi
}
change_root_password() {
 echo "Setting root password"
 echo "root:${ARCH_ROOT_PASSWD}" | arch-chroot "${ARCH_MNT_PATH}" chpasswd --encrypted
}
create_regular_user() {
 arch-chroot "${ARCH_MNT_PATH}" useradd -m -g users -G wheel,games,power,optical,storage,scanner,lp,audio,video -s /bin/bash "${ARCH_USER}"
 echo "Setting ${ARCH_USER} password"
 echo "${ARCH_USER}:${ARCH_USER_PASSWD}" | arch-chroot "${ARCH_MNT_PATH}" chpasswd --encrypted
 sed -i "s/#\s*%wheel ALL=\(ALL:ALL\) ALL/%wheel ALL=\(ALL:ALL\) ALL/g" "${ARCH_MNT_PATH}"/etc/sudoers
 if is_profile_enabled "zfs"; then
 arch-chroot "${ARCH_MNT_PATH}" <<EOF
zfs allow ${ARCH_USER} create,mount,mountpoint,snapshot ${ARCH_ZFS_HOME_DATASET}
chown -R ${ARCH_USER} /home/${ARCH_USER}
EOF
 fi
}
setup_crypttab() {
 if is_profile_enabled "swap_encrypt"; then
 cat <<EOF >> /etc/crypttab
swap ${ARCH_SWAP_CRYPTDEV} /dev/urandom swap,cipher=aes-xts-plain64:sha256,size=512
EOF
 cat <<EOF >> /etc/fstab
/dev/mapper/swap none swap defaults 0 0
EOF
 fi
}
copy_custom_files() {
 echo "Copying custom files to the target system"
 cp -r "${CONFIG_DIR}"/custom_files/* "${ARCH_MNT_PATH}"
}
generate_initramfs() {
 local initcpio_conf
 initcpio_conf="${ARCH_MNT_PATH}"/etc/mkinitcpio.conf
 echo "Setting up mkinitcpio options"
 if [[ -n "${ARCH_INITRD_MODULES[*]}" ]]; then
 sed -ri "s/#?MODULES=\(.*\)/MODULES=\(${ARCH_INITRD_MODULES[*]}\)/g" "${initcpio_conf}"
 fi
 if [[ -n "${ARCH_INITRD_BINARIES[*]}" ]]; then
 sed -ri "s/#?BINARIES=\(.*\)/BINARIES=\(${ARCH_INITRD_BINARIES[*]}\)/g" "${initcpio_conf}"
 fi
 if [[ -n "${ARCH_INITRD_FILES[*]}" ]]; then
 sed -ri "s/#?FILES=\(.*\)/FILES=\(${ARCH_INITRD_FILES[*]}\)/g" "${initcpio_conf}"
 fi
 if [[ -n "${ARCH_INITRD_HOOKS[*]}" ]]; then
 if [[ -d "${CONFIG_DIR}/initcpio_hooks" ]] && [[ -d "${CONFIG_DIR}/initcpio_install" ]]; then
 echo "Copying mkinitcpio custom hooks files"
 cp "${CONFIG_DIR}"/initcpio_hooks/* "${ARCH_MNT_PATH}"/etc/initcpio/hooks
 cp "${CONFIG_DIR}"/initcpio_install/* "${ARCH_MNT_PATH}"/etc/initcpio/install
 fi
 sed -ri "s/#?HOOKS=\(.*\)/HOOKS=\(${ARCH_INITRD_HOOKS[*]}\)/g" "${initcpio_conf}"
 fi
 if [[ -n "${ARCH_INITRD_COMPRESSION[*]}" ]]; then
 sed -ri "s/#?COMPRESSION=\"${ARCH_INITRD_COMPRESSION}\"/COMPRESSION=\"${ARCH_INITRD_COMPRESSION}\"/g" "${initcpio_conf}"
 fi
 if [[ -n "${ARCH_INITRD_COMPRESSION_OPTS[*]}" ]]; then
 sed -ri "s/#?COMPRESSION_OPTIONS=\(.*\)/COMPRESSION_OPTIONS=\(${ARCH_INITRD_COMPRESSION_OPTS[*]}\)/g" "${initcpio_conf}"
 fi
 if [[ -n "${ARCH_INITRD_MODULES_DECOMPRESS}" ]]; then
 sed -ri "s/#?MODULES_DECOMPRESS=\".*\"/MODULES_DECOMPRESS=\"yes\"/g" "${initcpio_conf}"
 fi
 is_profile_enabled "zfs" && arch-chroot "${ARCH_MNT_PATH}" zgenhostid
 echo "Generating initramfs"
 arch-chroot "${ARCH_MNT_PATH}" mkinitcpio -P
}
install_grub() {
 if is_profile_enabled "zfs"; then
 echo "ZPOOL_VDEV_NAME_PATH=YES" >> "${ARCH_MNT_PATH}"/etc/environment
 fi
 pacstrap "${ARCH_MNT_PATH}" grub
 case "${ARCH_BOOT_MODE}" in
 "uefi")
 pacstrap "${ARCH_MNT_PATH}" dosfstools efibootmgr mtools
 mkdir -p "${ARCH_MNT_PATH%%/}${ARCH_GRUB_BOOT_DIR}"
 mkdir -p "${ARCH_MNT_PATH%%/}${ARCH_GRUB_EFI_DIR}"
 arch-chroot "${ARCH_MNT_PATH}" bash -c "ZPOOL_VDEV_NAME_PATH=YES grub-install --target x86_64-efi \
 --boot-directory ${ARCH_GRUB_BOOT_DIR} --efi-directory ${ARCH_GRUB_EFI_DIR} \
 --bootloader-id ${ARCH_EFI_BOOTLOADER_ID} --removable"
 ;;
 "legacy_bios")
 for boot_dev in "${ARCH_BOOT_DEVICES[@]}"; do
 arch-chroot "${ARCH_MNT_PATH}" bash -c "ZPOOL_VDEV_NAME_PATH=YES grub-install ${boot_dev}"
 done
 ;;
 *)
 die "install_grub: unknown boot mode '${ARCH_BOOT_MODE}'"
 ;;
 esac
 [[ -f "${CONFIG_DIR}/grub_default.conf" ]] && cp "${CONFIG_DIR}"/grub_default.conf "${ARCH_MNT_PATH}"/etc/default/grub
 sleep 1
 arch-chroot "${ARCH_MNT_PATH}" bash -c "ZPOOL_VDEV_NAME_PATH=YES grub-mkconfig -o /boot/grub/grub.cfg"
}
enable_extra_services() {
 echo "Enabling extra services"
 for systemd_service in "${ARCH_POSTINST_SERVICES[@]}"; do
 arch-chroot "${ARCH_MNT_PATH}" systemctl enable "${systemd_service}"
 done
}
run_extra_commands() {
 echo "Running post-install extra commands"
 arch-chroot "${ARCH_MNT_PATH}" "${ARCH_POSTINST_CMDS}"
}
snapshot_installation() {
 echo "Taking snapshot of the ZFS datasets"
 zfs snapshot -r "${ARCH_ZFS_BOOT_POOL}@${ARCH_ZFS_SNAPSHOT_NAME}"
 zfs snapshot -r "${ARCH_ZFS_ROOT_POOL}@${ARCH_ZFS_SNAPSHOT_NAME}"
}
validate_args
check_profiles_deps
if [[ -n "${APPLY}" ]]; then
 install_base_system
 sleep 1; is_profile_enabled "genfstab" && generate_fstab
 set_timezone
 set_locale
 set_keymap
 set_hostname
 sleep 2; enable_dhcpcd
 sleep 2; is_profile_enabled "devel" && install_devel_pkgs
 sleep 2; install_kernel
 sleep 2; is_profile_enabled "zfs" && install_zfs
 sleep 2; is_profile_enabled "raid" && setup_mdadm
 [[ -n "${ARCH_EXTRA_SOFTWARE[*]}" ]] && install_extra_software
 sleep 2; change_root_password
 sleep 2; create_regular_user
 sleep 2; setup_crypttab
 [[ -d "${CONFIG_DIR}/custom_files" ]] && copy_custom_files
 sleep 2; generate_initramfs
 sleep 2; install_grub
 sleep 2; [[ -n "${ARCH_POSTINST_SERVICES[*]}" ]] && enable_extra_services
 sleep 2; [[ -n "${ARCH_POSTINST_CMDS}" ]] && run_extra_commands
 if is_profile_enabled "zfs" && is_profile_enabled "zfs_snapshot_install"; then
 sleep 2; snapshot_installation
 fi
 echo "Done!"
else
 echo "Execute script with --apply option to confirm destructive action"
fi
asked Feb 28, 2023 at 3:02
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I think this message should go to the error stream (>&2):

if [ "$EUID" -ne 0 ]; then
 echo "Please run as root"
 exit 1
fi

Bash has read -s for reading passwords without echo. So we can replace

 stty -echo
 read -r -p "Password: " password
 echo
 read -r -p "Confirm password: " confirm_password
 echo
 stty echo

with

 read -srp "Password: " password
 echo
 read -srp "Confirm password: " confirm_password
 echo

case 1ドル in

Missing quotes: "1ドル"


shift; shift can be replaced with shift 2.


 echo "Error: command '1ドル' not recognized."
 usage

This error message and the usage should go to standard error. And we should exit with a failure status here (usage currently contains exit 0 which is harmful to this case).


if [[ -n "${ARCH_EXTRA_SOFTWARE[*]}" ]]; then
 echo "Installing extra software"
 pacstrap "${ARCH_MNT_PATH}" ${ARCH_EXTRA_SOFTWARE[*]}
fi

For testing an array is non-empty, consider [ ${#ARCH_EXTRA_SOFTWARE[*]} -ne 0 ]. And use $@ expansion ("${ARCH_EXTRA_SOFTWARE[@]}"). That should avoid the need to suppress shellcheck messages.


generate_initramfs repeatedly edits "$initcpio_conf" using sed -i. It's probably better to build a single sed program and run it just once. E.g.

 local transform=()
 if [ -n "${ARCH_INITRD_MODULES[*]}" ]
 then transform+=(-e "s/^#?(MODULES=)\(.*\)/1円\(${ARCH_INITRD_MODULES[*]}\)/")
 fi
 if [ -n "${ARCH_INITRD_BINARIES[*]}" ]
 then transform+=(-e "s/^#?(BINARIES=)\(.*\)/1円\(${ARCH_INITRD_BINARIES[*]}\)/")
 fi
 ⋮
 sed -ri "$(transform[@])" "$initcpio_conf"

What are all these sleeps for?

 sleep 2; enable_dhcpcd
 sleep 2; is_profile_enabled "devel" && install_devel_pkgs
 sleep 2; install_kernel
 sleep 2; is_profile_enabled "zfs" && install_zfs
 sleep 2; is_profile_enabled "raid" && setup_mdadm

If there is some race to be avoided, it's probably worth a comment to explain where the value of 2 seconds came from.

answered Mar 1, 2023 at 9:15
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.