mkinitcpio or dracut for initramfs generation
This commit is contained in:
Daniel Langbein 2023-03-21 14:11:10 +01:00
parent 5dcc56c62f
commit bce90bf7d8
10 changed files with 531 additions and 409 deletions

View File

@ -2,7 +2,7 @@
_pkgname=installer _pkgname=installer
_reponame=arch-installer _reponame=arch-installer
pkgname="de-p1st-$_pkgname" pkgname="de-p1st-$_pkgname"
pkgver=0.2.4 pkgver=0.3.0
pkgrel=1 pkgrel=1
pkgdesc="Installer for Arch Linux written in Bash" pkgdesc="Installer for Arch Linux written in Bash"
arch=('any') arch=('any')

View File

@ -19,7 +19,7 @@ ls out/out_dir/*.iso
2) Boot into the live medium 2) Boot into the live medium
3) Connect to the Internet 3) Connect to the Internet
* [wifi instructions](https://wiki.archlinux.org/title/Iwd#iwctl) * [Wi-Fi instructions](https://wiki.archlinux.org/title/Iwd#iwctl)
**On some other computer:** **On some other computer:**

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Exit on error. # Exit on error.
set -e set -e
# Exit on undefined variable reference. # Exit on undefined variable reference.
@ -20,6 +20,8 @@ else
fi fi
# Source library files. # Source library files.
# shellcheck source=lib/validate-args.sh
source "${lib_dir}"/validate-args.sh
# shellcheck source=lib/util.sh # shellcheck source=lib/util.sh
source "${lib_dir}"/util.sh source "${lib_dir}"/util.sh
# shellcheck source=lib/user-input.sh # shellcheck source=lib/user-input.sh
@ -51,40 +53,42 @@ function main() {
# @post # @post
# installation finished # installation finished
scrollback
check_network check_network
system_time system_time
# in: BOOT_FIRMWARE, FS, HOSTNAME, USERNAME, USER_PWD, FDE, LUKS_PWD; (all variables are optional) repository
# out: BOOT_FIRMWARE, FS, HOSTNAME, USERNAME, USER_PWD, FDE, LUKS_PWD (if FDE='true')
get_user_input
# in: CPU_VENDOR (optional)
# out: CPU_VENDOR
get_cpu_vendor
# in: FS # in: BOOT_PART_SIZE, BOOT_FIRMWARE, INITRAMFS, FS, HOSTNAME, USERNAME, USER_PWD, FDE, LUKS_PWD; (all variables are optional)
# out: BOOT_PART_SIZE, BOOT_FIRMWARE, INITRAMFS, FS, HOSTNAME, USERNAME, USER_PWD, FDE, LUKS_PWD (if FDE='true')
get_user_input
# in: CPU_VENDOR (optional)
# out: CPU_VENDOR
get_cpu_vendor CPU_VENDOR
# in: FS
# out: FS_DEFAULT_MOUNT_OPTIONS # out: FS_DEFAULT_MOUNT_OPTIONS
get_default_mount_options get_default_mount_options
# in: FS # in: FS
# out: FS_ADDITIONAL_MOUNT_OPTIONS # out: FS_ADDITIONAL_MOUNT_OPTIONS
get_additional_mount_options get_additional_mount_options
# in: TARGET_BLOCK_DEVICE, BOOT_FIRMWARE
# out: BOOT_PART, LUKS_PART # out: BOOT_PART, LUKS_PART
partition partition \
# in: BOOT_FIRMWARE, BOOT_PART, LUKS_PART, FDE, LUKS_PWD, FS "${TARGET_BLOCK_DEVICE}" "${BOOT_FIRMWARE}" "${BOOT_PART_SIZE}" \
BOOT_PART LUKS_PART
# out: LUKS_PART_UUID (if FDE='true'), DATA_PART # out: LUKS_PART_UUID (if FDE='true'), DATA_PART
format format \
"${BOOT_PART}" "${LUKS_PART}" "${LUKS_PWD}" "${FDE}" "${FS}" \
LUKS_PART_UUID DATA_PART
# Combine default and additional mount options # Join default and additional mount options by ','
# out: FS_MOUNT_OPTIONS # shellcheck disable=SC2034
{ FS_MOUNT_OPTIONS_ARRAY=("${FS_DEFAULT_MOUNT_OPTIONS[@]}" "${FS_ADDITIONAL_MOUNT_OPTIONS[@]}")
TMP1=("${FS_DEFAULT_MOUNT_OPTIONS[@]}" "${FS_ADDITIONAL_MOUNT_OPTIONS[@]}") join_by ',' FS_MOUNT_OPTIONS_ARRAY FS_MOUNT_OPTIONS
# Join array elements by ","
join_by ',' TMP1 FS_MOUNT_OPTIONS
}
mount_partitions mount_partitions
# in: BOOT_FIRMWARE, PACSTRAP_INTERACTIVE (optional) # in: BOOT_FIRMWARE, INITRAMFS, PACSTRAP_INTERACTIVE (optional)
run_pacstrap run_pacstrap
# in: FS # in: FS
run_genfstab run_genfstab
@ -94,25 +98,33 @@ function main() {
# in: USERNAME, USER_PWD, ROOT_PWD (optional) # in: USERNAME, USER_PWD, ROOT_PWD (optional)
user_and_pwd user_and_pwd
sudo arch-chroot /mnt mkinitcpio -P # in: INITRAMFS
initramfs
# in: TARGET_BLOCK_DEVICE, FDE, LUKS_PART_UUID # in: TARGET_BLOCK_DEVICE, FDE, LUKS_PART_UUID
bootloader bootloader
if [ "${LEAVE_MOUNTED}" = 'true' ]; then unmount_partitions
echo 'Leaving partitions below /mnt mounted and '"${DATA_PART}"' opened.'
else
sudo umount -R /mnt
if [ "${FDE}" = 'true' ]; then
sudo cryptsetup luksClose "$(basename "${DATA_PART}")"
fi
fi
echo 'Finished installation without errors!' echo 'Finished installation without errors!'
} }
function scrollback(){
if ! grep --quiet '^defscrollback' /etc/screenrc; then
printf '\n%s\n' "defscrollback 100000" | sudo tee -a /etc/screenrc
printf '%s\n' 'Extended screen scrollback history. Pleas create a new screen session and run the installer again.'
return 1
fi
if [ -z "${STY:-}" ]; then
printf '%s\n' 'Pleas run the installer from a screen session.'
return 1
fi
}
function check_network() { function check_network() {
echo 'Sending ping to wikipedia.de ...' echo 'Sending ping to wikipedia.de ...'
ping -c 1 wikipedia.de >/dev/null || { ping -c 1 wikipedia.de >/dev/null || {
echo 'Pleas set up network access.' echo 'Pleas set up network access and then run the installer again.'
return 1 return 1
} }
} }
@ -122,6 +134,19 @@ function system_time() {
timedatectl set-ntp true timedatectl set-ntp true
} }
function repository(){
# Check if the [de-p1st] repository is available.
# If not, add it to pacman.conf
if ! grep --quiet 'de-p1st' /etc/pacman.conf; then
printf '%s\n' 'Enabling [de-p1st] package repository:'
printf '\n%s\n' "[de-p1st]
SigLevel = Optional TrustAll
Server = https://arch.p1st.de" | sudo tee -a /etc/pacman.conf
fi
}
function increase_cow_space() { function increase_cow_space() {
# May be useful when running 'pacman -Syu' on the live medium. # May be useful when running 'pacman -Syu' on the live medium.
# Usually not necessary! # Usually not necessary!
@ -138,7 +163,9 @@ function increase_cow_space() {
function get_user_input() { function get_user_input() {
# @post # @post
# BOOT_PART_SIZE
# BOOT_FIRMWARE: 'uefi' | 'bios' # BOOT_FIRMWARE: 'uefi' | 'bios'
# INITRAMFS: 'mkinitcpio' | 'dracut'
# FS: 'BTRFS' | 'EXT4' | 'F2FS' # FS: 'BTRFS' | 'EXT4' | 'F2FS'
# FS_BTRFS_SUBVOL_LAYOUT: 'root_only' | '@root@home' # FS_BTRFS_SUBVOL_LAYOUT: 'root_only' | '@root@home'
# HOSTNAME # HOSTNAME
@ -146,15 +173,17 @@ function get_user_input() {
# FDE: 'true' | 'false' # FDE: 'true' | 'false'
# LUKS_PWD: only set if FDE='true' # LUKS_PWD: only set if FDE='true'
get_block_devices_with_size # out: BLOCK_DEVICE_SIZES
get_block_devices_with_size BLOCK_DEVICE_SIZES
single_choice_if_empty TARGET_BLOCK_DEVICE 'Select target device for installation' BLOCK_DEVICE_SIZES single_choice_if_empty TARGET_BLOCK_DEVICE 'Select target device for installation' BLOCK_DEVICE_SIZES
ask_user_if_empty BOOT_PART_SIZE 'Enter boot partition size in MiB, at least 260:'
if [ "${BOOT_FIRMWARE}" = 'autodetect' ]; then if [ "${BOOT_FIRMWARE}" = 'autodetect' ]; then
# Detect boot firmware type: https://askubuntu.com/a/162573 # Detect boot firmware type: https://askubuntu.com/a/162573
# Check exit code; if 0 EFI, else BIOS. # Check exit code; if 0 EFI, else BIOS.
# "-q": tell grep to output nothing if dmesg | grep --quiet --fixed-strings 'EFI v'; then
if dmesg | grep -q 'EFI v'; then
echo 'Detected EFI boot.' echo 'Detected EFI boot.'
BOOT_FIRMWARE='uefi' BOOT_FIRMWARE='uefi'
else else
@ -163,46 +192,64 @@ function get_user_input() {
fi fi
else # If $BOOT_FIRMWARE is empty: Let user select BIOS type else # If $BOOT_FIRMWARE is empty: Let user select BIOS type
TMP1=('uefi' 'Newer mainboards' \ # shellcheck disable=SC2034
'bios' 'Legacy BIOS on older mainboards') BOOT_FIRMWARE_QUESTION=('uefi' 'Newer mainboards' \
single_choice_if_empty BOOT_FIRMWARE 'Select your bios type' TMP1 'bios' 'Legacy BIOS on older mainboards')
single_choice_if_empty BOOT_FIRMWARE 'Select your bios type' BOOT_FIRMWARE_QUESTION
fi fi
TMP1=('BTRFS' 'Allows snapshots and dynamic extension of the FS' \ # shellcheck disable=SC2034
'EXT4' 'Default FS of many distributions' \ INITRAMFS_QUESTION=('mkinitcpio' 'Default for Arch Linux' \
'F2FS' 'Flash-Friendly-FS for SSD or NVMe') 'dracut' 'Testing')
single_choice_if_empty FS 'Select filesystem to use' TMP1 single_choice_if_empty INITRAMFS 'Select filesystem to use' INITRAMFS_QUESTION
# shellcheck disable=SC2034
FS_QUESTION=('BTRFS' 'Allows snapshots and dynamic extension of the FS' \
'EXT4' 'Default FS of many distributions' \
'F2FS' 'Flash-Friendly-FS for SSD or NVMe')
single_choice_if_empty FS 'Select filesystem to use' FS_QUESTION
if [ "${FS}" = 'BTRFS' ]; then if [ "${FS}" = 'BTRFS' ]; then
TMP1=('root_only' 'Just one subvolume for "/".' \ # shellcheck disable=SC2034
FS_QUESTION2=('root_only' 'Just one subvolume for "/".' \
'@root@home' 'Two subvolumes @ and @home. This configuration allows usage of "Timeshift".') '@root@home' 'Two subvolumes @ and @home. This configuration allows usage of "Timeshift".')
single_choice_if_empty FS_BTRFS_SUBVOL_LAYOUT 'Select your preferred subvolume layout' TMP1 single_choice_if_empty FS_BTRFS_SUBVOL_LAYOUT 'Select your preferred subvolume layout' FS_QUESTION2
fi fi
ask_user_if_empty HOSTNAME 'Enter hostname:' ask_user_if_empty HOSTNAME 'Enter hostname:'
ask_user_if_empty USERNAME 'Enter username:' ask_user_if_empty USERNAME 'Enter username:'
# If USER_PWD is empty
if [ -z "${USER_PWD}" ]; then if [ -z "${USER_PWD}" ]; then
ask_user_if_empty USER_PWD 'Enter a user password:' ask_user_if_empty USER_PWD 'Enter a user password:'
ask_user_if_empty USER_PWD2 'Please enter the password again:' ask_user_if_empty USER_PWD2 'Please enter the password again:'
# shellcheck disable=SC2153 # shellcheck disable=SC2153
[[ "${USER_PWD}" == "${USER_PWD2}" ]] || { if [ "${USER_PWD}" != "${USER_PWD2}" ]; then
echo 'Passwords did not match' echo 'Passwords did not match'
exit 1 exit 1
} fi
fi fi
TMP1=('true' 'Yes' 'false' 'No') # shellcheck disable=SC2034
single_choice_if_empty FDE 'Shall Full-Disk-Encryption be enabled?' TMP1 FDE_QUESTION=('true' 'Yes' 'false' 'No')
single_choice_if_empty FDE 'Shall Full-Disk-Encryption be enabled?' FDE_QUESTION
# If FDE enabled but LUKS_PWD is empty
if [ "${FDE}" = 'true' ] && [ -z "${LUKS_PWD}" ]; then if [ "${FDE}" = 'true' ] && [ -z "${LUKS_PWD}" ]; then
ask_user_if_empty LUKS_PWD 'Enter a disk encryption password:' ask_user_if_empty LUKS_PWD 'Enter a disk encryption password:'
ask_user_if_empty LUKS_PWD2 'Please enter the password again:' ask_user_if_empty LUKS_PWD2 'Please enter the password again:'
# shellcheck disable=SC2153 # shellcheck disable=SC2153
[[ "${LUKS_PWD}" == "${LUKS_PWD2}" ]] || { if [ "${LUKS_PWD}" != "${LUKS_PWD2}" ]; then
echo 'Passwords did not match' echo 'Passwords did not match'
exit 1 exit 1
} fi
fi
# ADDITIONAL_PKGS is an array, but all elements should be non-empty.
# Thus, it is ok if we do only check the first element.
if [ -z "${ADDITIONAL_PKGS:-}" ]; then
ask_user_if_empty ADDITIONAL_PKGS 'Additional packages to install (separated by space):'
space_separated_to_array ADDITIONAL_PKGS ADDITIONAL_PKGS || return $?
fi fi
} }
@ -225,9 +272,7 @@ function get_default_mount_options() {
FS_DEFAULT_MOUNT_OPTIONS+=('compress=zstd:1') FS_DEFAULT_MOUNT_OPTIONS+=('compress=zstd:1')
;; ;;
EXT4) EXT4)
# https://wiki.archlinux.org/index.php/Ext4#Enabling_metadata_checksums FS_DEFAULT_MOUNT_OPTIONS+=()
# If the CPU supports SSE 4.2, make sure the crc32c_intel kernel module is loaded
FS_DEFAULT_MOUNT_OPTIONS+=('metadata_csum')
;; ;;
F2FS) F2FS)
# When mounting the filesystem, specify compress_algorithm=(lzo|lz4|zstd|lzo-rle). # When mounting the filesystem, specify compress_algorithm=(lzo|lz4|zstd|lzo-rle).
@ -250,19 +295,19 @@ function get_additional_mount_options() {
case "${FS}" in case "${FS}" in
BTRFS) BTRFS)
# noatime, nodiratime: # noatime, nodiratime:
# - The atime options do impact drive performance; # - `atime` impacts drive performance
# - noatime implies nodiratime, one does not need to specify both; # - `noatime` implies `nodiratime`, one does not need to specify both
# - The noatime option fully disables writing file access times to the drive every time you read a file. # - `noatime` fully disables writing file access times to the drive every time you read a file.
# This works well for almost all applications, except for those that need to know if a file has been # This works well for almost all applications, except for those that need to know if a file has been
# read since the last time it was modified. # read since the last time it was modified.
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on' 'ssd' 'Enable if using SSD/NVMe' 'off') FS_ADDITIONAL_MOUNT_OPTIONS_QUESTION=('noatime' 'Don'\''t write file/folder access times' 'on' 'ssd' 'Enable if using SSD/NVMe' 'off')
;; ;;
EXT4) EXT4)
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on') FS_ADDITIONAL_MOUNT_OPTIONS_QUESTION=('noatime' 'Don'\''t write file/folder access times' 'on')
;; ;;
F2FS) F2FS)
# shellcheck disable=SC2034 # shellcheck disable=SC2034
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on') FS_ADDITIONAL_MOUNT_OPTIONS_QUESTION=('noatime' 'Don'\''t write file/folder access times' 'on')
;; ;;
*) *)
echo 'Filesystem '"${FS}"' not yet supported!' echo 'Filesystem '"${FS}"' not yet supported!'
@ -270,7 +315,7 @@ function get_additional_mount_options() {
;; ;;
esac esac
multi_choice_if_empty FS_ADDITIONAL_MOUNT_OPTIONS 'Select mount options' TMP1 multi_choice_if_empty FS_ADDITIONAL_MOUNT_OPTIONS 'Select mount options' FS_ADDITIONAL_MOUNT_OPTIONS_QUESTION
} }
function mount_partitions() { function mount_partitions() {
@ -316,6 +361,9 @@ function mount_partitions() {
echo 'Mounting boot partition ...' echo 'Mounting boot partition ...'
sudo mkdir /mnt/boot sudo mkdir /mnt/boot
sudo mount "${BOOT_PART}" /mnt/boot sudo mount "${BOOT_PART}" /mnt/boot
# Print information about partitions.
lsblk --tree=PATH -o PATH,TYPE,UUID
} }
function run_pacstrap() { function run_pacstrap() {
@ -340,24 +388,37 @@ function run_pacstrap() {
;; ;;
esac esac
# If CPU_VENDOR is not empty, then case "${INITRAMFS}" in
if [ -n "${CPU_VENDOR}" ]; then mkinitcpio)
case "${CPU_VENDOR}" in PKGS+=('de-p1st-mkinitcpio')
amd) ;;
PKGS+=('de-p1st-ucode-amd') dracut)
;; PKGS+=('dracut')
intel) # PKGS+=('binutils') # for --uefi (Unified Kernel Image) option
PKGS+=('de-p1st-ucode-intel') PKGS+=('elfutils') # reduced initramfs size
;; PKGS+=('multipath-tools') # dracut module support
none) ;;
PKGS+=('de-p1st-ucode-placeholder') *)
;; echo 'Invalid option: '"${INITRAMFS}"
*) return 1
echo 'Invalid option: '"${CPU_VENDOR}" ;;
return 1 esac
;;
esac case "${CPU_VENDOR}" in
fi amd)
PKGS+=('de-p1st-ucode-amd')
;;
intel)
PKGS+=('de-p1st-ucode-intel')
;;
none)
PKGS+=('de-p1st-ucode-none')
;;
*)
echo 'Invalid option: '"${CPU_VENDOR}"
return 1
;;
esac
local args=() local args=()
if [ "${PACSTRAP_INTERACTIVE}" = 'true' ]; then if [ "${PACSTRAP_INTERACTIVE}" = 'true' ]; then
@ -380,7 +441,7 @@ function run_genfstab() {
# Remove "subvolid=..." mount option but leave "subvol=..." mount option # Remove "subvolid=..." mount option but leave "subvol=..." mount option
fstab=$(printf '%s' "${fstab}" | sed 's/,subvolid=[^,\s]\+//') fstab=$(printf '%s' "${fstab}" | sed 's/,subvolid=[^,\s]\+//')
# Check if fstab does still contain subvolid mount option # Check if fstab does still contain subvolid mount option
if printf '%s' "${fstab}" | grep -q 'subvolid='; then if printf '%s' "${fstab}" | grep --quiet 'subvolid='; then
echo 'This should not happen!' echo 'This should not happen!'
return 1 return 1
fi fi
@ -392,12 +453,13 @@ function run_genfstab() {
true true
;; ;;
*) *)
echo 'Filesystem '"${FS}"' not yet supported!' printf '%s\n' "Filesystem ${FS} not yet supported!"
return 1 return 1
;; ;;
esac esac
printf '%s' "${fstab}" | sudo tee /mnt/etc/fstab >/dev/null echo 'Generating fstab'
printf '%s' "${fstab}" | sudo tee /mnt/etc/fstab
} }
function config_hostname_and_hosts() { function config_hostname_and_hosts() {
@ -448,20 +510,77 @@ function user_and_pwd() {
sudo arch-chroot /mnt useradd -m -s /usr/bin/zsh -g wheel "${USERNAME}" sudo arch-chroot /mnt useradd -m -s /usr/bin/zsh -g wheel "${USERNAME}"
sudo arch-chroot /mnt chsh -s /usr/bin/zsh sudo arch-chroot /mnt chsh -s /usr/bin/zsh
# If ROOT_PWD is not given, the use USER_PWD for root user if [ -z "${ROOT_PWD:-}" ]; then
ROOT_PWD="${ROOT_PWD:="${USER_PWD}"}" printf '%s\n' 'No root password given, using the user password for root'
ROOT_PWD="${USER_PWD}"
fi
printf '%s:%s' "${USERNAME}" "${USER_PWD}" | sudo chpasswd --root /mnt printf '%s:%s' "${USERNAME}" "${USER_PWD}" | sudo chpasswd --root /mnt
printf '%s:%s' "root" "${ROOT_PWD}" | sudo chpasswd --root /mnt printf '%s:%s' "root" "${ROOT_PWD}" | sudo chpasswd --root /mnt
} }
function initramfs(){
# @pre
# INITRAMFS
case "${INITRAMFS}" in
mkinitcpio)
sudo arch-chroot /mnt mkinitcpio -P
;;
dracut)
# configuration
install -m0644 /dev/stdin /mnt/etc/dracut.conf.d/de-p1st.conf << 'EOF'
# --fstab
use_fstab=yes
# --show-modules
show_modules=yes
# https://wiki.archlinux.org/title/Dracut#LVM_/_software_RAID_/_LUKS
kernel_cmdline="rd.auto rd.luks=1"
EOF
# TODO:
# Use a hook to do this
# on the running system after each
# kernel upgrade.
# https://wiki.archlinux.org/title/Dracut#Generate_a_new_initramfs_on_kernel_upgrade
# - detect kernel version
# - install vmlinuz
# - run dracut
for module in /mnt/lib/modules/*; do
kernel="$(basename "${module}")"
pkgbase="$(cat "${module}/pkgbase")"
install -Dm644 "${module}/vmlinuz" "/mnt/boot/vmlinuz-${pkgbase}"
echo 'Kernel command line:'
arch-chroot /mnt dracut \
--kver "${kernel}" \
--print-cmdline \
"/boot/initramfs-${pkgbase}.img"
echo 'Creating initramfs:'
arch-chroot /mnt dracut \
--kver "${kernel}" \
"/boot/initramfs-${pkgbase}.img"
done
;;
*)
echo 'Invalid option: '"${INITRAMFS}"
return 1
;;
esac
}
function bootloader() { function bootloader() {
# @pre # @pre
# TARGET_BLOCK_DEVICE # TARGET_BLOCK_DEVICE
# FDE: 'true' | 'false' # FDE: 'true' | 'false'
# LUKS_PART_UUID: required if FDE='true' # LUKS_PART_UUID: required if FDE='true'
echo 'Installing grub ...' echo 'Installing grub:'
case "${BOOT_FIRMWARE}" in case "${BOOT_FIRMWARE}" in
uefi) uefi)
# Portable fallback efi name for grub: # Portable fallback efi name for grub:
@ -478,40 +597,47 @@ function bootloader() {
;; ;;
esac esac
echo 'Generating /boot/grub/grub.cfg ...' # grub.cfg adjustments
{ if [ "${INITRAMFS}" = 'mkinitcpio' ] && [ "${FDE}" = "true" ]; then
case "${FDE}" in # /etc/default/grub is managed by Holo. Therefore we should not manually modify it.
true) # Instead, we create a holosript which writes $LUKS_PART_UUID into GRUB_CMDLINE_LINUX of /etc/default/grub
# /etc/default/grub is managed by Holo. Therefore we should not manually modify it. {
# Instead, we create a holosript which writes $LUKS_PART_UUID into GRUB_CMDLINE_LINUX of /etc/default/grub # Use filename .../20- for the holoscript so that it gets executed after the one from de-p1st-grub
{ local holoScriptDir=/mnt/usr/share/holo/files/20-de-p1st-installer/etc/default/
# Assert # The holoscript shall contain one 'sed "..."' command
grep --quiet '^GRUB_CMDLINE_LINUX=""$' < /mnt/etc/default/grub || return sudo mkdir -p "${holoScriptDir}"
sudo echo "#!/bin/sh
py-regex-replace -p '^GRUB_CMDLINE_LINUX=\"\"\$' -r 'GRUB_CMDLINE_LINUX=\"cryptdevice=/dev/disk/by-uuid/${LUKS_PART_UUID}:crypt\"'
" | sudo tee "${holoScriptDir}"/grub.holoscript
sudo chmod 0544 "${holoScriptDir}"/grub.holoscript
}
# Then we apply the holoscript
sudo arch-chroot /mnt holo apply --force file:/etc/default/grub
# Use filename .../20- for the holoscript so that it gets executed after the one from de-p1st-grub # Print start of grub file containing GRUB_CMDLINE_LINUX
local holoScriptDir=/mnt/usr/share/holo/files/20-de-p1st-installer/etc/default/ head /mnt/etc/default/grub
# The holoscript shall contain one 'sed "..."' command fi
sudo mkdir -p "${holoScriptDir}"
sudo echo '#!/bin/sh
sed "s|^GRUB_CMDLINE_LINUX=\"\"\$|GRUB_CMDLINE_LINUX=\"cryptdevice=/dev/disk/by-uuid/'"${LUKS_PART_UUID}"':crypt\"|"' \
| sudo tee "${holoScriptDir}"/grub.holoscript
sudo chmod 0544 "${holoScriptDir}"/grub.holoscript
}
# Then we apply the holoscript
sudo arch-chroot /mnt holo apply --force file:/etc/default/grub
;;
false)
true
;;
*)
echo 'Invalid option: '"${FDE}"
return 1
;;
esac
# And finally run grub-mkconfig echo 'Generating /boot/grub/grub.cfg:'
sudo arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg sudo arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg
}
if [ "${INITRAMFS}" = 'mkinitcpio' ] && [ "${FDE}" = "true" ]; then
printf '%s\n' 'Occurrence of "cryptdevice" in /boot/grub/grub.cfg:'
grep 'cryptdevice' /mnt/boot/grub/grub.cfg
fi
}
function unmount_partitions(){
if [ "${LEAVE_MOUNTED}" = 'true' ]; then
echo 'Leaving partitions below /mnt mounted and '"${DATA_PART}"' opened.'
else
echo 'unmount /mnt'
sudo umount -R /mnt
if [ "${FDE}" = 'true' ]; then
echo "luksClose ${DATA_PART}"
sudo cryptsetup luksClose "$(basename "${DATA_PART}")"
fi
fi
} }
main "$@" main "$@"

View File

@ -3,26 +3,30 @@
FQDN=v2202012136854137045.happysrv.de FQDN=v2202012136854137045.happysrv.de
STATIC_IP=45.83.105.88 STATIC_IP=45.83.105.88
IPV6_CAPABLE=true IPV6_CAPABLE=true
HOSTNAME=p1st-arch-1 HOSTNAME=netcup-example
USERNAME=yoda USERNAME=yoda
FDE=false
TARGET_BLOCK_DEVICE=/dev/sda TARGET_BLOCK_DEVICE=/dev/sda
BOOT_PART_SIZE=500 # MiB BOOT_PART_SIZE=260
FS=BTRFS FS=BTRFS
FS_ADDITIONAL_MOUNT_OPTIONS=('noatime') FS_ADDITIONAL_MOUNT_OPTIONS=('noatime')
CPU_VENDOR=none CPU_VENDOR=none
BOOT_FIRMWARE=uefi BOOT_FIRMWARE=uefi
INITRAMFS=dracut
LEAVE_MOUNTED=false LEAVE_MOUNTED=false
PACSTRAP_INTERACTIVE=true PACSTRAP_INTERACTIVE=true
############## ADDITIONAL_PKGS from here on ##############
ADDITIONAL_PKGS=() ADDITIONAL_PKGS=()
# to skip pacman selections
ADDITIONAL_PKGS+=('mkinitcpio' 'de-p1st-kernel-lts' 'de-p1st-ucode-placeholder' 'qemu-guest-agent') # Remote SSH access
# remote ssh access
ADDITIONAL_PKGS+=('de-p1st-ssh' 'de-p1st-ssh-key-yoda') ADDITIONAL_PKGS+=('de-p1st-ssh' 'de-p1st-ssh-key-yoda')
# docker and docker-compose # Docker and Docker Compose
ADDITIONAL_PKGS+=('de-p1st-docker') ADDITIONAL_PKGS+=('de-p1st-docker')
# cronie # cronie
ADDITIONAL_PKGS+=('de-p1st-cronie') ADDITIONAL_PKGS+=('de-p1st-cronie')

View File

@ -1,64 +1,47 @@
# Example config for testing in VirtualBox # Example config for testing in VirtualBox
# FQDN=domain.name.of.this.host.de #FQDN=domain.name.of.this.host.de
# STATIC_IP=123.123.123.123 #STATIC_IP=123.123.123.123
# IPV6_CAPABLE=true # IPV6_CAPABLE: 'true' | 'false'
IPV6_CAPABLE=true
HOSTNAME=yodaTest HOSTNAME=yodaTest
USERNAME=yoda USERNAME=yoda
# One should rather enter these interactively than saving in this cfg.
USER_PWD='test' USER_PWD='test'
FDE=false # enable FULL-DISK-ENCRYPTION #ROOT_PWD=test
# FDE: 'true' | 'false'
FDE=true
LUKS_PWD='test' LUKS_PWD='test'
# If unset, then USER_PWD will be used for ROOT_PWD
# ROOT_PWD=test
TARGET_BLOCK_DEVICE=/dev/sda # /dev/vda for Virtual Machine Manager TARGET_BLOCK_DEVICE=/dev/sda # /dev/vda for Virtual Machine Manager
BOOT_PART_SIZE=500 # MiB # BOOT_PART_SIZE: in MiB, at least 260
BOOT_PART_SIZE=260
# FS: 'BTRFS' | 'EXT4' | 'F2FS'
FS=BTRFS FS=BTRFS
FS_BTRFS_SUBVOL_LAYOUT='@root@home' # Subvolume layout that is supported by Timeshift # FS_BTRFS_SUBVOL_LAYOUT: 'root_only' | '@root@home'
FS_BTRFS_SUBVOL_LAYOUT='@root@home'
FS_ADDITIONAL_MOUNT_OPTIONS=('noatime') FS_ADDITIONAL_MOUNT_OPTIONS=('noatime')
# If not booted into the target system, these values should be set: # If not booted into the target system, these values should be set:
# CPU_VENDOR: 'autodetect' | 'amd' | 'intel' | 'none' # CPU_VENDOR: 'autodetect' | 'amd' | 'intel' | 'none'
CPU_VENDOR=autodetect CPU_VENDOR=none
# BOOT_FIRMWARE: 'autodetect' | 'uefi' | 'bios' # BOOT_FIRMWARE: 'autodetect' | 'uefi' | 'bios'
BOOT_FIRMWARE=uefi BOOT_FIRMWARE=uefi
# INITRAMFS: 'mkinitcpio' | 'dracut'
INITRAMFS=dracut
# If set to 'true', then the data, boot and luks partitions # If set to 'true', then the data, boot and luks partitions
# will be left mounted/opened for manual inspection # will be left mounted/opened for manual inspection
# after the installation # after the installation
LEAVE_MOUNTED=true # LEAVE_MOUNTED: 'true' | 'false'
LEAVE_MOUNTED=false
# PACSTRAP_INTERACTIVE: 'true' | 'false'
PACSTRAP_INTERACTIVE=true PACSTRAP_INTERACTIVE=true
############## ADDITIONAL_PKGS from here on ############## ############## ADDITIONAL_PKGS from here on ##############
ADDITIONAL_PKGS=() ADDITIONAL_PKGS=()
# Specify the preferred providers for initramfs, de-p1st-kernel, de-p1st-ucode, de-p1st-sddm-theme
ADDITIONAL_PKGS+=('mkinitcpio' 'de-p1st-kernel-lts' 'de-p1st-ucode-placeholder' 'de-p1st-sddm-theme-nordic')
# VBox guest additions # VBox guest additions
ADDITIONAL_PKGS+=('virtualbox-guest-utils') ADDITIONAL_PKGS+=('virtualbox-guest-utils')
# XFCE4 desktop with HiDPI
ADDITIONAL_PKGS+=('de-p1st-gpu-generic' 'de-p1st-xfce4-hidpi' 'de-p1st-sddm-tablet')
# To be able to install ungoogled-chromium
ADDITIONAL_PKGS+=('de-p1st-pacman-repo-chromium')
# If your network is unsafe, e.g. if you live in Germany, then include a VPN client
ADDITIONAL_PKGS+=('riseup-vpn')
# Smartcard (e.g. Nitrokey or Yubikey)
ADDITIONAL_PKGS+=('de-p1st-smartcard')
# Other programs
ADDITIONAL_PKGS+=('firefox' 'signal-desktop' 'nextcloud-client' 'keepassxc' 'xournalpp' 'zotero')
# Development
ADDITIONAL_PKGS+=('intellij-idea-ultimate-edition' 'intellij-idea-ultimate-edition-jre')
# Document viewer
ADDITIONAL_PKGS+=('evince')
# Gallery / image viewer
ADDITIONAL_PKGS+=('nomacs' 'qt5-imageformats')
# LibreOffice with spell checking (de, us) and hyphenation rules (de, us)
ADDITIONAL_PKGS+=('libreoffice-fresh')
ADDITIONAL_PKGS+=('hunspell-de' 'hunspell-en_US')
ADDITIONAL_PKGS+=('hyphen' 'hyphen-de' 'hyphen-en')

View File

@ -6,87 +6,103 @@
# -l, --list Produce output in the form of a list. # -l, --list Produce output in the form of a list.
# -n, --noheadings # -n, --noheadings
# -x, --sort column # -x, --sort column
#
# lsblk --tree=PATH -o PATH,TYPE,PARTUUID
function get_uuid() { function get_uuid() {
# arg $1: Partition # arg $1: Partition
# arg $2: Variable name to store UUID # arg $2: Variable name to store UUID
validate_args 2 "$@" || return $?
local partition="${1}"
local -n uuid_ptr=$2
local -n ptr=$2 || return $?
# shellcheck disable=SC2034 # shellcheck disable=SC2034
ptr="$(blkid -o value -s UUID "${1}")" || return $? uuid_ptr="$(blkid -o value -s UUID "${partition}")" || return $?
} }
function get_partitions(){ function get_partitions(){
# arg $1: block-device # arg $1: block device
# @post # arg $2: name of variable to store partitions
# PARTITIONS (array with one partition per entry) validate_args 2 "$@" || return $?
local block_device="$1"
local -n partitions_ptr=$2
# Remove first line of output (which is just the block device $1 itself) # Remove first line of output (which is just the block device itself)
# with sed: sed 1d # with sed: `sed 1d`
PARTITIONS="$(lsblk -pln -o name "${1}" | sed 1d)" || return $? # shellcheck disable=SC2034
partitions_ptr="$(lsblk -pln -o name "${block_device}" | sed 1d)" || return $?
newline_separated_to_array PARTITIONS PARTITIONS || return $? newline_separated_to_array partitions_ptr partitions_ptr || return $?
} }
function get_block_devices() { function get_block_devices() {
# @post # arg $1: name of variable to store block devices
# BLOCK_DEVICES (array with one entry for each block device) validate_args 1 "$@" || return $?
local -n block_devices_ptr=$1
# Get list of devices, one per line # Get list of devices, one per line
BLOCK_DEVICES="$(lsblk -dplnx size -o name | grep -Ev 'boot|rpmb|loop' | tac)" || return $? # shellcheck disable=SC2034
block_devices_ptr="$(lsblk -dplnx size -o name | grep -Ev 'boot|rpmb|loop' | tac)" || return $?
newline_separated_to_array BLOCK_DEVICES BLOCK_DEVICES || return $? newline_separated_to_array block_devices_ptr block_devices_ptr || return $?
} }
function get_block_devices_with_size() { function get_block_devices_with_size() {
# @post # Example: variable with name $1 has content ('/dev/nvme0n1' '1,9T')
# BLOCK_DEVICE_SIZES (array with two entries for each block device: device path and device size) #
# arg $1: name of variable to store block device paths and their sizes
validate_args 1 "$@" || return $?
local -n block_device_sizes_ptr=$1
# Get list of devices and their sizes, one pair per line. # Get list of devices and their sizes, one pair per line.
# Use sed to remove multiple white spaces: sed 's|\s\s*| |' # Use sed to remove multiple white spaces: sed 's|\s\s*| |'
BLOCK_DEVICE_SIZES="$(lsblk -dplnx size -o name,size | grep -Ev 'boot|rpmb|loop' | sed 's|\s\s*| |' | tac)" || return $? # shellcheck disable=SC2034
block_device_sizes_ptr="$(lsblk -dplnx size -o name,size | grep -Ev 'boot|rpmb|loop' | sed 's|\s\s*| |' | tac)" || return $?
newline_to_space BLOCK_DEVICE_SIZES || return $? newline_to_space block_device_sizes_ptr || return $?
space_separated_to_array BLOCK_DEVICE_SIZES BLOCK_DEVICE_SIZES || return $? space_separated_to_array block_device_sizes_ptr block_device_sizes_ptr || return $?
} }
function partition() { function partition() {
# Creates two partitions: # Creates two partitions:
# - BOOT_PART by default 261mb # - boot partition, by default 260MiB
# - LUKS_PART rest of the device for (encrypted) data # - luks partition, rest of the device for (encrypted) data
# #
# @pre # arg $1: target block device
# TARGET_BLOCK_DEVICE # arg $2: boot firmware: 'uefi' | 'bios'
# BOOT_FIRMWARE # arg $3: boot partition size in MiB: >=260 | 'none'
# @post # arg $4: name of variable to store boot partition
# BOOT_PART # arg $5: name of variable to store luks partition
# LUKS_PART validate_args 5 "$@" || return $?
local target_block_device="${1}"
local boot_firmware="${2}"
local boot_part_size="${3}"
local -n boot_part=$4
local -n luks_part=$5
# If BOOT_PART_SIZE not given, set to default value local minimum=260
BOOT_PART_SIZE="${BOOT_PART_SIZE:='261'}" # Check if too small
# If too small, print warning and exit if [ "${boot_part_size}" -lt "${minimum}" ]; then
if [ "${BOOT_PART_SIZE}" -lt '261' ]; then printf '%s\n' "boot_part_size should be >= ${minimum}"
echo 'BOOT_PART_SIZE should be larger than 260!'; return 1
return 1;
fi fi
# As our data partition is encrypted, # As our data partition is encrypted,
# we need a separate boot partition! # we need a separate boot partition!
case "${BOOT_FIRMWARE}" in case "${boot_firmware}" in
uefi) uefi)
# EFI boot partition # EFI boot partition
# #
# Create a partition with fat32 as the file system type and set the # Create a partition with fat32 as the file system type and set the
# esp flag on it. # esp flag on it.
sudo parted --script "${TARGET_BLOCK_DEVICE}" -- mklabel gpt \ sudo parted --script "${target_block_device}" -- mklabel gpt \
mkpart ESP fat32 2Mib "${BOOT_PART_SIZE}MiB" \ mkpart ESP fat32 2Mib "${boot_part_size}MiB" \
set 1 esp on \ set 1 esp on \
mkpart primary "${BOOT_PART_SIZE}MiB" 100% || return $? mkpart primary "${boot_part_size}MiB" 100% || return $?
get_partitions "${TARGET_BLOCK_DEVICE}" || return $? get_partitions "${target_block_device}" PARTITIONS || return $?
BOOT_PART="${PARTITIONS[0]}" boot_part="${PARTITIONS[0]}"
LUKS_PART="${PARTITIONS[1]}" luks_part="${PARTITIONS[1]}"
;; ;;
bios) bios)
# > On a BIOS/GPT configuration, a BIOS boot partition is required. GRUB embeds its `core.img` # > On a BIOS/GPT configuration, a BIOS boot partition is required. GRUB embeds its `core.img`
@ -95,97 +111,111 @@ function partition() {
# #
# archwiki -> GRUB#GUID_Partition_Table_(GPT)_specific_instructions # archwiki -> GRUB#GUID_Partition_Table_(GPT)_specific_instructions
# https://www.gnu.org/software/grub/manual/grub/html_node/BIOS-installation.html#BIOS-installation # https://www.gnu.org/software/grub/manual/grub/html_node/BIOS-installation.html#BIOS-installation
sudo parted --script "${TARGET_BLOCK_DEVICE}" -- mklabel gpt \ sudo parted --script "${target_block_device}" -- mklabel gpt \
mkpart primary 1MiB 2MiB \ mkpart primary 1MiB 2MiB \
set 1 bios_grub on \ set 1 bios_grub on \
mkpart primary 2MiB "${BOOT_PART_SIZE}MiB" \ mkpart primary 2MiB "${boot_part_size}MiB" \
mkpart primary "${BOOT_PART_SIZE}MiB" 100% || return $? mkpart primary "${boot_part_size}MiB" 100% || return $?
get_partitions "${TARGET_BLOCK_DEVICE}" || return $? get_partitions "${target_block_device}" PARTITIONS || return $?
BOOT_PART="${PARTITIONS[2]}" boot_part="${PARTITIONS[2]}"
LUKS_PART="${PARTITIONS[3]}" luks_part="${PARTITIONS[3]}"
;; ;;
*) *)
echo 'Expected uefi or bios but got '"${BOOT_FIRMWARE}"' instead!' printf '%s\n' "Expected uefi or bios but got ${boot_firmware} instead!"
return 1 return 1
;; ;;
esac esac
echo 'boot partition: '"${BOOT_PART}" printf '%s\n' "boot partition: ${boot_part}"
echo 'luks partition: '"${LUKS_PART}" printf '%s\n' "luks partition: ${luks_part}"
} }
function format() { function format() {
# Formats BOOT_PART and LUKS_PART. # Formats the `boot` and `luks` partitions.
# If encryption is enabled (FDE='true'), then an encrypted partition will be created at LUKS_PART #
# which can be accessed under DATA_PART. # If FDE is enabled ('true'),
# If encryption is disabled, then DATA_PART is identical to LUKS_PART. # then $2 will be encrypted with password $3 and opened at $7.
#
# If FDE is disabled,
# then $7 is identical with $2.
#
# arg $1: boot partition
# arg $2: (encrypted) luks partition
# arg $3: password of luks partition
# arg $4: full-disk-encryption (FDE): 'true' | 'false'
# arg $5: filesystem: 'BTRFS' | 'EXT4' | 'F2FS'
# arg $6: name of variable to store partition of encrypted luks partition (if FDE='true').
# Set to /dev/mapper/$(basename $LUKS_PART)
# arg $7: name of variable to store data partition
# #
# @pre
# BOOT_FIRMWARE
# BOOT_PART
# LUKS_PART
# LUKS_PWD
# FDE: 'true' | 'false'
# FS
# @post # @post
# LUKS_PART_UUID: only set if FDE='true'
# DATA_PART: if FDE='true', then set to /dev/mapper/$(basename "${LUKS_PART}")
# boot partition formatted # boot partition formatted
# luks partition formatted and accessible under DATA_PART # luks partition formatted and accessible under $7
validate_args 7 "$@" || return $?
local boot_part="${1}"
local luks_part="${2}"
local luks_pwd="${3}"
local fde="${4}"
local fs="${5}"
# shellcheck disable=SC2034
local -n luks_part_uuid=$6
local -n data_part=$7
echo 'Wiping old signatures from partitions ...' printf '%s\n' 'Wiping old signatures from partitions ...'
sudo wipefs "${BOOT_PART}" || return $? sudo wipefs --all "${boot_part}" || return $?
sudo wipefs "${LUKS_PART}" || return $? sudo wipefs --all "${luks_part}" || return $?
echo 'Formatting boot partition '"${BOOT_PART}"' ...' printf '%s\n' "Formatting boot partition ${boot_part} ..."
sudo mkfs.fat -F32 "${BOOT_PART}" || return $? sudo mkfs.fat -F32 "${boot_part}" || return $?
case "${FDE}" in case "${fde}" in
true) true)
# Note: # FDE:
# FDE: GRUB does support LUKS2 since this commit: https://git.savannah.gnu.org/cgit/grub.git/commit/?id=365e0cc3e7e44151c14dd29514c2f870b49f9755 # GRUB does support LUKS2 since this commit:
# -> Using "--type luks1" is no longer required. # https://git.savannah.gnu.org/cgit/grub.git/commit/?id=365e0cc3e7e44151c14dd29514c2f870b49f9755
echo 'Creating encrypted luks partition '"${LUKS_PART}"' ...' # Using "--type luks1" is no longer required.
printf '%s' "${LUKS_PWD}" | sudo cryptsetup luksFormat \ printf '%s\n' "Creating encrypted luks partition ${luks_part} ..."
printf '%s' "${luks_pwd}" | sudo cryptsetup luksFormat \
--cipher aes-xts-plain64 --key-size 512 --hash sha512 \ --cipher aes-xts-plain64 --key-size 512 --hash sha512 \
--iter-time 10000 --use-random "${LUKS_PART}" || return $? --iter-time 10000 --use-random "${luks_part}" || return $?
get_uuid "${LUKS_PART}" LUKS_PART_UUID || return $? get_uuid "${luks_part}" luks_part_uuid || return $?
local luks_name local luks_name
luks_name=$(basename "${LUKS_PART}") luks_name="$(basename "${luks_part}")"
DATA_PART="/dev/mapper/${luks_name}" data_part="/dev/mapper/${luks_name}"
# Open luks partition # Open luks partition
printf '%s' "${LUKS_PWD}" | sudo cryptsetup luksOpen "${LUKS_PART}" "${luks_name}" || return $? printf '%s' "${luks_pwd}" | sudo cryptsetup luksOpen "${luks_part}" "${luks_name}" || return $?
;; ;;
false) false)
DATA_PART="${LUKS_PART}" data_part="${luks_part}"
;; ;;
*) *)
echo 'Invalid option: '"${FDE}" printf '%s\n' "Invalid option: ${fde}"
return 1 return 1
;; ;;
esac esac
echo 'Formatting the data partition '"${DATA_PART}"' ...' printf '%s\n' "Formatting the data partition ${data_part} ..."
case "${FS}" in case "${fs}" in
BTRFS) BTRFS)
sudo mkfs.btrfs "${DATA_PART}" || return $? sudo mkfs.btrfs "${data_part}" || return $?
;; ;;
EXT4) EXT4)
# archwiki -> Ext4#Enabling_metadata_checksums # https://wiki.archlinux.org/index.php/Ext4
sudo mkfs.ext4 -O metadata_csum "${DATA_PART}" || return $? # If the CPU supports SSE 4.2, make sure the crc32c_intel kernel module is loaded
sudo mkfs.ext4 -O metadata_csum "${data_part}" || return $?
;; ;;
F2FS) F2FS)
# archwiki -> F2FS#Creating_a_F2FS_file_system # archwiki -> F2FS#Creating_a_F2FS_file_system
# - requires f2fs-tools # - requires f2fs-tools
# - compression: "-O compression" and when mounting the filesystem, specify compress_algorithm=(lzo|lz4|zstd|lzo-rle) # - compression: "-O compression" and when mounting the filesystem, specify compress_algorithm=(lzo|lz4|zstd|lzo-rle)
sudo mkfs.f2fs -O extra_attr,inode_checksum,sb_checksum,compression "${DATA_PART}" || return $? sudo mkfs.f2fs -O extra_attr,inode_checksum,sb_checksum,compression "${data_part}" || return $?
;; ;;
*) *)
echo 'Filesystem '"${FS}"' is not yet supported!' printf '%s\n' "Filesystem ${fs} is not yet supported!"
return 1 return 1
;; ;;
esac esac

View File

@ -1,102 +1,84 @@
function ask_user_if_empty { function ask_user_if_empty {
# If variable with name $1 is empty, then ask for user input. # If variable named $1 is empty, then ask for user input.
# #
# Only one line of user input is allowed. # One line of user input is read.
# The input must not be empty. # The user input must not be empty.
# #
# arg $1: Name of variable to store user input # arg $1: Name of variable to store user input
# arg $2: Text to display (e.g. "Enter hostname:") # arg $2: Text to display (e.g. "Enter hostname:")
if [ "$#" -ne 2 ]; then validate_args 2 "$@" || return $?
echo 'ask_user_if_empty requires two args!'; local -n user_input_ptr=$1
local display_text="${2}"
# If user_input_ptr is not empty, return
if [ -n "${user_input_ptr:-}" ]; then
return 0
fi
# Else, ask for user input!
printf '%s\n' "${display_text}"
read -r user_input_ptr || return $?
# User input must not be empty
if [ -z "${user_input_ptr}" ]; then
echo 'The input must not be empty!'
return 1 return 1
fi fi
for i in "$@"; do
if [ -z "${i}" ]; then
echo 'ask_user_if_empty: all given args must not be empty';
return 1;
fi
done
local -n ptr=$1 || return $?
if [ -z "${ptr}" ]; then
# If ptr has no value yet, ask user for input!
echo "${2}"
read -r ptr || return $?
fi
# Check string length to be greater than zero!
if [ "${#ptr}" -lt 1 ]; then
echo 'The input must not be empty!';
return 1;
fi
} }
function single_choice_if_empty { function single_choice_if_empty {
# If variable with name $1 is empty, then let user select one of the given options. # If variable named $1 is empty,
# then let user select one of the given options.
# #
# arg $1: Name of variable to store the selected option # arg $1: Name of variable to store the selected option-name
# arg $2: Text to display # arg $2: Text to display
# arg $3: Name of variable with array of options to display (for each option there must be two entries in the array: Item and description) # arg $3: Name of variable with array of options to display (for each option there must be two entries in the array: name and description)
if [ "$#" -ne 3 ]; then validate_args 3 "$@" || return $?
echo 'single_choice_if_empty requires three args!'; local -n selected_option_ptr=$1
local display_text="${2}"
local -n menu_options_ptr=$3
# If selected_option_ptr is not empty, return.
if [ -n "${selected_option_ptr:-}" ]; then
return 0
fi
# Else, ask for user input!
selected_option_ptr=$(dialog --stdout --menu "${display_text}" 0 0 0 "${menu_options_ptr[@]}") || {
echo 'Error during menu selection!'
return 1 return 1
fi }
for i in "$@"; do clear
if [ -z "${i}" ]; then
echo 'single_choice_if_empty: all given args must not be empty';
return 1;
fi
done
local -n ptr=$1 || return $?
if [ -z "${ptr}" ]; then
# If ptr has no value yet, ask user for input!
local -n MENU_OPTIONS=$3 || return $?
ptr=$(dialog --stdout --menu "${2}" 0 0 0 "${MENU_OPTIONS[@]}") || {
echo 'Error during menu selection!'
exit 1
}
clear
fi
} }
function multi_choice_if_empty { function multi_choice_if_empty {
# If variable with name $1 is empty, then let user select one or more of the given options. # If variable named $1 is empty,
# then let user select one or more of the given options.
# #
# arg $1: Name of variable to store array of selected options # arg $1: Name of variable to store the selected option-names as array
# arg $2: Text to display # arg $2: Text to display
# arg $3: Name of variable with array of options to display (for each option there must be three entries in the array: Item, description, on/off) # arg $3: Name of variable with array of options to display (for each option there must be three entries in the array: name, description, on/off)
if [ "$#" -ne 3 ]; then validate_args 3 "$@" || return $?
echo 'multi_choice_if_empty requires three args!'; local -n selected_option_ptr=$1
local display_text="${2}"
local -n menu_options_ptr=$3
# If selected_option_ptr is not empty, return.
if [ -n "${selected_option_ptr:-}" ]; then
return 0
fi
# Else, ask for user input!
selected_option_ptr=$(dialog --stdout --checklist "${display_text}" 0 0 0 "${menu_options_ptr[@]}") || {
echo 'Error during menu selection!'
return 1 return 1
fi }
for i in "$@"; do clear
if [ -z "${i}" ]; then
echo 'multi_choice_if_empty: all given args must not be empty';
return 1;
fi
done
# Result of dialog is a space separated list.
local -n ptr=$1 || return $? # Store this as an array.
if [ -z "${ptr}" ]; then # Without newline at last array element: https://unix.stackexchange.com/a/519917/315162
# If ptr has no value yet, ask user for input! # readarray -d " " -t ptr < <(printf '%s' "selected_option_ptr")
#
local -n MENU_OPTIONS=$3 || return $? space_separated_to_array selected_option_ptr selected_option_ptr || return $?
# shellcheck disable=SC2034 # Variable name used below.
TMP1=$(dialog --stdout --checklist "${2}" 0 0 0 "${MENU_OPTIONS[@]}") || {
echo 'Error during menu selection!'
exit 1
}
clear
# Result of dialog is a space separated list.
# Store this as an array.
# Without newline at last array element: https://unix.stackexchange.com/a/519917/315162
# readarray -d " " -t ptr < <(printf '%s' "$TMP1")
#
space_separated_to_array TMP1 "$1"
fi
} }

View File

@ -1,83 +1,104 @@
function join_by() { function join_by() {
# Join array elements with character $1 # Join array elements with character $1
# #
# arg $1: Delimiter # arg $1: A single-character delimiter, e.g. ','
# arg $2: Name of source array # arg $2: Name of source array
# arg $3: Variable name to store result # arg $3: Variable name to store result
validate_args 3 "$@" || return $?
local delimiter="${1}"
local -n source_array_ptr=$2
local -n joined_ptr=$3
local -n ptr=$2 || return $? # https://stackoverflow.com/a/2317171
local -n ptr2=$3 || return $? joined_ptr=$(printf "${delimiter}%s" "${source_array_ptr[@]}") || return $?
ptr2=$(printf ',%s' "${ptr[@]}") joined_ptr=${joined_ptr:1} || return $?
ptr2=${ptr2:1}
} }
function newline_to_space() { function newline_to_space() {
# Replaces all newlines with spaces # Replaces all newlines with spaces
# #
# arg $1: Name of variable # arg $1: Name of variable
validate_args 1 "$@" || return $?
local -n ptr=$1
local -n ptr=$1 || return $?
# Replace newlines with spaces. # Replace newlines with spaces.
# See bash string substitution: https://gist.github.com/JPvRiel/b279524d3e56229a896477cb8082a72b # See bash string substitution: https://gist.github.com/JPvRiel/b279524d3e56229a896477cb8082a72b
# echo "replacing newlines in str: -->$ptr<--"
ptr="${ptr//$'\n'/' '}" ptr="${ptr//$'\n'/' '}"
# echo "new str is: -->$ptr<--"
} }
function newline_separated_to_array() { function newline_separated_to_array() {
# Watch out for tailing newlines as these will get an empty array entry at the end. # Watch out for tailing newlines as these result in additional elements "" at the end of the array.
# $1 and $2 can be equal (if the result shall be written to the input variable). # $1 and $2 can be equal (if the result shall be written to the input variable).
# #
# arg $1: Name of variable with newline separated list # arg $1: Name of variable with newline separated list
# arg $2: Name of array to store values into # arg $2: Name of array to store values into
validate_args 2 "$@" || return $?
local -n source_ptr=$1
local -n array1_ptr=$2
local -n ptr=$1 || return $? # source_ptr is a newline separated list.
local -n ptr2=$2 || return $? # Store this as array array1_ptr.
# ptr newline separated list. # shellcheck disable=SC2034
# Store this as array ptr2. readarray -t array1_ptr <<<"${source_ptr}"
readarray -t ptr2 <<<"${ptr}"
} }
function space_separated_to_array() { function space_separated_to_array() {
# arg $1: Name of variable with space separated list # arg $1: Name of variable with space separated list
# arg $2: Name of array to store values into # arg $2: Name of array to store values into
validate_args 2 "$@" || return $?
local -n ptr=$1 || return $? local -n source_ptr=$1
# shellcheck disable=SC2178 # shellcheck disable=SC2178
local -n ptr2=$2 || return $? local -n array2_ptr=$2
# ptr space separated list.
# Store this as array ptr2. # source_ptr space separated list.
# Store this as array array2_ptr.
# Without newline at last array element: https://unix.stackexchange.com/a/519917/315162 # Without newline at last array element: https://unix.stackexchange.com/a/519917/315162
readarray -d ' ' -t ptr2 < <(printf '%s' "${ptr}") # shellcheck disable=SC2034
readarray -d ' ' -t array2_ptr < <(printf '%s' "${source_ptr}")
} }
function get_cpu_vendor() { function get_cpu_vendor() {
# If $1 does already contain a known CPU vendor, nothing is done.
#
# Otherwise, we try to detect the CPU vendor and save it into $1.
#
# arg $1: Name of variable to store CPU vendor into.
#
# @pre # @pre
# CPU_VENDOR ("", "autodetect") # $1 ("amd", "intel", "none", *)
# @post # @post
# CPU_VENDOR ("amd", "intel", "none", "") # $1 ("amd", "intel", "none")
validate_args 1 "$@" || return $?
local -n cpu_vendor_ptr=$1
if [[ -z "${CPU_VENDOR}" ]] || [[ "${CPU_VENDOR}" == 'autodetect' ]]; then # Check if variable does already contain known CPU vendor.
case "${cpu_vendor_ptr}" in
amd|intel|none)
return 0
;;
*)
:
;;
esac
# If run virtual environment (e.g. VirtualBox) then no CPU microcode is required. # If in virtual environment (e.g. VirtualBox), then no CPU microcode is required.
if grep '^flags.*hypervisor' /proc/cpuinfo >/dev/null; then if grep --quiet '^flags.*hypervisor' /proc/cpuinfo; then
echo 'Detected virtual environment.' echo 'Detected virtual environment.'
CPU_VENDOR='none' cpu_vendor_ptr='none'
return 0
fi
else local vendor_id
local vendor_id vendor_id="$(grep vendor_id /proc/cpuinfo | head -1 | sed 's|vendor_id\s*:\s*||')"
vendor_id=$(grep vendor_id /proc/cpuinfo | head -1 | sed 's|vendor_id\s*:\s*||')
if [ "${vendor_id}" = 'AuthenticAMD' ]; then
CPU_VENDOR='amd'
elif [ "${vendor_id}" = 'GenuineIntel' ]; then
CPU_VENDOR='intel'
else
echo 'Unknown CPU vendor'
return 1
fi
fi
if [ "${vendor_id}" = 'AuthenticAMD' ]; then
cpu_vendor_ptr='amd'
return 0
elif [ "${vendor_id}" = 'GenuineIntel' ]; then
cpu_vendor_ptr='intel'
return 0
else
echo 'Unknown CPU vendor'
return 1
fi fi
} }

15
lib/validate-args.sh Normal file
View File

@ -0,0 +1,15 @@
function validate_args(){
local num_args="${1}"
shift
if [ "$#" -ne "${num_args}" ]; then
printf '%s\n' "ERROR: ${num_args} arguments required"
return 1
fi
for i in "$@"; do
if [ -z "${i}" ]; then
printf '%s\n' 'ERROR: All given args must not be empty'
return 1
fi
done
}

View File

@ -1,39 +0,0 @@
#!/bin/bash
# source: https://stackoverflow.com/a/52678279/6334421
# A variable can be assigned the nameref attribute using the -n option
# to the declare or local builtin commands (see Bash Builtins) to create
# a nameref, or a reference to another variable. This allows variables to
# be manipulated indirectly.
function text_input {
# If variable of name $1 is zero, then ask user for input.
# Only one line user input is allowed.
# User input must not be empty.
#
# $1: variable name to store input to
# $2: text to display (e.g. "Enter hostname:")
if [ -z "${1}" ] || [ -z "${2}" ]; then
echo 'text_input requires two args!';
return 1
fi
local -n ptr=$1 || return $?
if [ -z "${ptr}" ]; then
echo "${2}"
read -r ptr || return $?
fi
# check string length to be greater than zero!
if [ "${#ptr}" -lt 1 ]; then
echo 'text_input must not be empty!';
return 1;
fi
}
text_input foo 'Enter something funny:'
echo "Input: ${foo}"
bar=''
text_input bar 'Enter something even funnier:'
echo "Input: ${bar}"