arch-installer/de-p1st-installer.sh

518 lines
15 KiB
Bash
Raw Normal View History

2022-07-16 17:18:03 +02:00
#!/bin/bash
2023-03-16 16:45:32 +01:00
# Exit on error.
2023-03-16 16:14:17 +01:00
set -e
2023-03-16 16:45:32 +01:00
# Exit on undefined variable reference.
2023-03-16 16:14:17 +01:00
set -u
2022-07-16 17:18:03 +02:00
script_dir="$(dirname "${BASH_SOURCE[0]}")"
function script_is_installed(){
# Check if this script is run installed or locally (development setup).
[ "${script_dir}" = '/usr/bin' ]
}
if script_is_installed; then
2023-03-16 16:45:32 +01:00
lib_dir='/usr/lib/de-p1st-installer'
else
lib_dir='./lib'
# Print commands before they get executed.
2023-03-16 16:45:32 +01:00
set -v
fi
# Source library files.
# shellcheck source=lib/util.sh
source "${lib_dir}"/util.sh
# shellcheck source=lib/user-input.sh
source "${lib_dir}"/user-input.sh
# shellcheck source=lib/block-device.sh
source "${lib_dir}"/block-device.sh
# Get path of configuration file.
if [ $# -eq 0 ]; then
# No arguments given.
if script_is_installed; then
cfg_file=/etc/de-p1st-installer/installer.cfg
else
cfg_file="${script_dir}"/installer.cfg
fi
else
# >= 1 arguments given.
# First argument is the configuration file!
cfg_file="${1}"
fi
2023-03-16 16:45:32 +01:00
# Source the configuration file.
# shellcheck source=installer.cfg
source "${cfg_file}"
2022-07-16 17:18:03 +02:00
function main() {
# @pre
# bash libraries imported
# @post
# installation finished
2023-03-16 16:14:17 +01:00
check_network
system_time
2022-07-16 17:18:03 +02:00
# in: BOOT_FIRMWARE, FS, HOSTNAME, USERNAME, USER_PWD, FDE, LUKS_PWD; (all variables are optional)
# out: BOOT_FIRMWARE, FS, HOSTNAME, USERNAME, USER_PWD, FDE, LUKS_PWD (if FDE='true')
2023-03-16 16:14:17 +01:00
get_user_input
2022-07-16 17:18:03 +02:00
# in: CPU_VENDOR (optional)
# out: CPU_VENDOR
2023-03-16 16:14:17 +01:00
get_cpu_vendor
2022-07-16 17:18:03 +02:00
# in: FS
# out: FS_DEFAULT_MOUNT_OPTIONS
2023-03-16 16:14:17 +01:00
get_default_mount_options
2022-07-16 17:18:03 +02:00
# in: FS
# out: FS_ADDITIONAL_MOUNT_OPTIONS
2023-03-16 16:14:17 +01:00
get_additional_mount_options
2022-07-16 17:18:03 +02:00
# in: TARGET_BLOCK_DEVICE, BOOT_FIRMWARE
# out: BOOT_PART, LUKS_PART
2023-03-16 16:14:17 +01:00
partition
2022-07-16 17:18:03 +02:00
# in: BOOT_FIRMWARE, BOOT_PART, LUKS_PART, FDE, LUKS_PWD, FS
# out: LUKS_PART_UUID (if FDE='true'), DATA_PART
2023-03-16 16:14:17 +01:00
format
2022-07-16 17:18:03 +02:00
# Combine default and additional mount options
# out: FS_MOUNT_OPTIONS
{
2023-03-16 16:14:17 +01:00
TMP1=("${FS_DEFAULT_MOUNT_OPTIONS[@]}" "${FS_ADDITIONAL_MOUNT_OPTIONS[@]}")
2022-07-16 17:18:03 +02:00
# Join array elements by ","
2023-03-16 16:14:17 +01:00
join_by ',' TMP1 FS_MOUNT_OPTIONS
2022-07-16 17:18:03 +02:00
}
2023-03-16 16:14:17 +01:00
mount_partitions
2022-07-16 17:18:03 +02:00
# in: BOOT_FIRMWARE, PACSTRAP_INTERACTIVE (optional)
2023-03-16 16:14:17 +01:00
run_pacstrap
2022-07-16 17:18:03 +02:00
# in: FS
2023-03-16 16:14:17 +01:00
run_genfstab
2022-07-16 17:18:03 +02:00
# in: HOSTNAME, FQDN (optional), STATIC_IP (optional), IPV6_CAPABLE (optional)
2023-03-16 16:14:17 +01:00
config_hostname_and_hosts
2022-07-16 17:18:03 +02:00
# in: USERNAME, USER_PWD, ROOT_PWD (optional)
2023-03-16 16:14:17 +01:00
user_and_pwd
2022-07-16 17:18:03 +02:00
2023-03-16 16:14:17 +01:00
sudo arch-chroot /mnt mkinitcpio -P
2022-07-16 17:18:03 +02:00
# in: TARGET_BLOCK_DEVICE, FDE, LUKS_PART_UUID
2023-03-16 16:14:17 +01:00
bootloader
2022-07-16 17:18:03 +02:00
if [ "${LEAVE_MOUNTED}" = 'true' ]; then
echo 'Leaving partitions below /mnt mounted and '"${DATA_PART}"' opened.'
else
2023-03-16 16:14:17 +01:00
sudo umount -R /mnt
if [ "${FDE}" = 'true' ]; then
sudo cryptsetup luksClose "$(basename "${DATA_PART}")"
2022-07-16 17:18:03 +02:00
fi
fi
echo 'Finished installation without errors!'
}
function check_network() {
echo 'Sending ping to wikipedia.de ...'
ping -c 1 wikipedia.de >/dev/null || {
echo 'Pleas set up network access.'
return 1
}
}
function system_time() {
# Use timedatectl(1) to ensure the system clock is accurate
timedatectl set-ntp true
}
function increase_cow_space() {
# May be useful when running 'pacman -Syu' on the live medium.
# Usually not necessary!
2023-03-16 16:45:32 +01:00
# Make sure that we are on a live medium:
2022-07-16 17:18:03 +02:00
findmnt /run/archiso/cowspace >/dev/null || {
echo 'Not on live medium, did not increase cowspace!'
return 1
}
echo 'Increasing cowspace partition of live medium ...'
2023-03-16 16:14:17 +01:00
sudo mount -o remount,size=2G /run/archiso/cowspace
2022-07-16 17:18:03 +02:00
}
function get_user_input() {
# @post
# BOOT_FIRMWARE: 'uefi' | 'bios'
# FS: 'BTRFS' | 'EXT4' | 'F2FS'
# FS_BTRFS_SUBVOL_LAYOUT: 'root_only' | '@root@home'
# HOSTNAME
# USERNAME, USER_PWD
# FDE: 'true' | 'false'
# LUKS_PWD: only set if FDE='true'
2023-03-16 16:14:17 +01:00
get_block_devices_with_size
single_choice_if_empty TARGET_BLOCK_DEVICE 'Select target device for installation' BLOCK_DEVICE_SIZES
2022-07-16 17:18:03 +02:00
if [ "${BOOT_FIRMWARE}" = 'autodetect' ]; then
# Detect boot firmware type: https://askubuntu.com/a/162573
# Check exit code; if 0 EFI, else BIOS.
# "-q": tell grep to output nothing
if dmesg | grep -q 'EFI v'; then
echo 'Detected EFI boot.'
BOOT_FIRMWARE='uefi'
else
echo 'Detected BIOS boot'
BOOT_FIRMWARE='bios'
fi
else # If $BOOT_FIRMWARE is empty: Let user select BIOS type
TMP1=('uefi' 'Newer mainboards' \
'bios' 'Legacy BIOS on older mainboards')
2023-03-16 16:14:17 +01:00
single_choice_if_empty BOOT_FIRMWARE 'Select your bios type' TMP1
2022-07-16 17:18:03 +02:00
fi
TMP1=('BTRFS' 'Allows snapshots and dynamic extension of the FS' \
'EXT4' 'Default FS of many distributions' \
'F2FS' 'Flash-Friendly-FS for SSD or NVMe')
2023-03-16 16:14:17 +01:00
single_choice_if_empty FS 'Select filesystem to use' TMP1
2022-07-16 17:18:03 +02:00
if [ "${FS}" = 'BTRFS' ]; then
TMP1=('root_only' 'Just one subvolume for "/".' \
'@root@home' 'Two subvolumes @ and @home. This configuration allows usage of "Timeshift".')
2023-03-16 16:14:17 +01:00
single_choice_if_empty FS_BTRFS_SUBVOL_LAYOUT 'Select your preferred subvolume layout' TMP1
2022-07-16 17:18:03 +02:00
fi
2023-03-16 16:14:17 +01:00
ask_user_if_empty HOSTNAME 'Enter hostname:'
ask_user_if_empty USERNAME 'Enter username:'
2022-07-16 17:18:03 +02:00
if [ -z "${USER_PWD}" ]; then
2023-03-16 16:14:17 +01:00
ask_user_if_empty USER_PWD 'Enter a user password:'
ask_user_if_empty USER_PWD2 'Please enter the password again:'
2022-07-16 17:18:03 +02:00
# shellcheck disable=SC2153
[[ "${USER_PWD}" == "${USER_PWD2}" ]] || {
2023-03-16 16:14:17 +01:00
echo 'Passwords did not match'
exit 1
2022-07-16 17:18:03 +02:00
}
fi
TMP1=('true' 'Yes' 'false' 'No')
2023-03-16 16:14:17 +01:00
single_choice_if_empty FDE 'Shall Full-Disk-Encryption be enabled?' TMP1
2022-07-16 17:18:03 +02:00
if [ "${FDE}" = 'true' ] && [ -z "${LUKS_PWD}" ]; then
2023-03-16 16:14:17 +01:00
ask_user_if_empty LUKS_PWD 'Enter a disk encryption password:'
ask_user_if_empty LUKS_PWD2 'Please enter the password again:'
2022-07-16 17:18:03 +02:00
# shellcheck disable=SC2153
[[ "${LUKS_PWD}" == "${LUKS_PWD2}" ]] || {
2023-03-16 16:14:17 +01:00
echo 'Passwords did not match'
exit 1
2022-07-16 17:18:03 +02:00
}
fi
}
function get_default_mount_options() {
# @pre
# FS
# @post
# FS_DEFAULT_MOUNT_OPTIONS (array)
FS_DEFAULT_MOUNT_OPTIONS=()
case "${FS}" in
BTRFS)
# "compress=lzo": archwiki -> Btrfs#Compression
# "compress=zstd:1":
# -> https://btrfs.wiki.kernel.org/index.php/Compression#What_are_the_differences_between_compression_methods.3F
# -> https://fedoraproject.org/wiki/Changes/BtrfsTransparentCompression#Q:_Why_use_zstd:1_specifically.3F
#
# "Enable compression (better performance, longer flash lifespan)"
FS_DEFAULT_MOUNT_OPTIONS+=('compress=zstd:1')
;;
EXT4)
# https://wiki.archlinux.org/index.php/Ext4#Enabling_metadata_checksums
# If the CPU supports SSE 4.2, make sure the crc32c_intel kernel module is loaded
FS_DEFAULT_MOUNT_OPTIONS+=('metadata_csum')
;;
F2FS)
# When mounting the filesystem, specify compress_algorithm=(lzo|lz4|zstd|lzo-rle).
# Using compress_extension=txt will cause all txt files to be compressed by default.
FS_DEFAULT_MOUNT_OPTIONS+=('compress_algorithm=lz4')
;;
*)
echo 'Filesystem '"${FS}"' not yet supported!'
return 1
;;
esac
}
function get_additional_mount_options() {
# @pre
# FS
# @post
# FS_ADDITIONAL_MOUNT_OPTIONS (array)
case "${FS}" in
BTRFS)
# noatime, nodiratime:
# - The atime options do impact drive performance;
# - 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.
# 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.
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on' 'ssd' 'Enable if using SSD/NVMe' 'off')
;;
EXT4)
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on')
;;
F2FS)
# shellcheck disable=SC2034
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on')
;;
*)
echo 'Filesystem '"${FS}"' not yet supported!'
return 1
;;
esac
2023-03-16 16:14:17 +01:00
multi_choice_if_empty FS_ADDITIONAL_MOUNT_OPTIONS 'Select mount options' TMP1
2022-07-16 17:18:03 +02:00
}
function mount_partitions() {
# @pre
# FS, FS_BTRFS_SUBVOL_LAYOUT, FS_MOUNT_OPTIONS, DATA_PART, BOOT_PART
if [ "${FS}" = 'BTRFS' ]; then
case "${FS_BTRFS_SUBVOL_LAYOUT}" in
'root_only')
# Nothing special; same steps as for a regular FS
echo 'Mounting data partition with options: '"${FS_MOUNT_OPTIONS}"
2023-03-16 16:14:17 +01:00
sudo mount -o "${FS_MOUNT_OPTIONS}" "${DATA_PART}" /mnt
2022-07-16 17:18:03 +02:00
;;
'@root@home')
# Timeshift BTRFS subvol layout:
# https://github.com/teejee2008/timeshift#supported-system-configurations
# Mount top level subvolume
2023-03-16 16:14:17 +01:00
sudo mount -o subvolid=5 "${DATA_PART}" /mnt
2022-07-16 17:18:03 +02:00
# Create subvolumes @ and @home
2023-03-16 16:14:17 +01:00
sudo btrfs subvolume create /mnt/@
sudo btrfs subvolume create /mnt/@home
2022-07-16 17:18:03 +02:00
# List the created subvolumes
2023-03-16 16:14:17 +01:00
sudo btrfs subvolume list /mnt
2022-07-16 17:18:03 +02:00
# Umount the top level subvolume
2023-03-16 16:14:17 +01:00
sudo umount -R /mnt
2022-07-16 17:18:03 +02:00
echo 'Mounting @ and @home subvolumes with options: '"${FS_MOUNT_OPTIONS}"
2023-03-16 16:14:17 +01:00
sudo mount -o 'subvol=@,'"${FS_MOUNT_OPTIONS}" "${DATA_PART}" /mnt
sudo mkdir /mnt/home
sudo mount -o 'subvol=@home,'"${FS_MOUNT_OPTIONS}" "${DATA_PART}" /mnt/home
2022-07-16 17:18:03 +02:00
;;
*)
echo 'BTRFS subvolume layout '"${FS_BTRFS_SUBVOL_LAYOUT}"' not supported!'
return 1
;;
esac
else
echo 'Mounting data partition with options: '"${FS_MOUNT_OPTIONS}"
2023-03-16 16:14:17 +01:00
sudo mount -o "${FS_MOUNT_OPTIONS}" "${DATA_PART}" /mnt
2022-07-16 17:18:03 +02:00
fi
echo 'Mounting boot partition ...'
2023-03-16 16:14:17 +01:00
sudo mkdir /mnt/boot
sudo mount "${BOOT_PART}" /mnt/boot
2022-07-16 17:18:03 +02:00
}
function run_pacstrap() {
# @pre
# BOOT_FIRMWARE
# PACSTRAP_INTERACTIVE: optional, 'true'
echo 'Running pacstrap ...'
PKGS=("${ADDITIONAL_PKGS[@]}")
case "${BOOT_FIRMWARE}" in
uefi)
PKGS+=('de-p1st-base')
;;
bios)
echo 'Not yet implemented'
return 1
;;
*)
echo 'Not yet implemented!'
return 1
;;
esac
# If CPU_VENDOR is not empty, then
if [ -n "${CPU_VENDOR}" ]; then
case "${CPU_VENDOR}" in
amd)
PKGS+=('de-p1st-ucode-amd')
;;
intel)
PKGS+=('de-p1st-ucode-intel')
;;
none)
PKGS+=('de-p1st-ucode-placeholder')
;;
*)
echo 'Invalid option: '"${CPU_VENDOR}"
return 1
;;
esac
fi
local args=()
if [ "${PACSTRAP_INTERACTIVE}" = 'true' ]; then
2023-03-16 16:45:32 +01:00
args+=('-i') # Run interactively
2022-07-16 17:18:03 +02:00
fi
2023-03-19 10:10:29 +01:00
sudo pacstrap -K "${args[@]}" /mnt "${PKGS[@]}"
2022-07-16 17:18:03 +02:00
}
function run_genfstab() {
# @pre
# FS
echo 'Generating fstab ...'
local fstab
fstab="$(genfstab -U /mnt)"
case "${FS}" in
BTRFS)
# Remove "subvolid=..." mount option but leave "subvol=..." mount option
2023-03-16 16:14:17 +01:00
fstab=$(printf '%s' "${fstab}" | sed 's/,subvolid=[^,\s]\+//')
2022-07-16 17:18:03 +02:00
# Check if fstab does still contain subvolid mount option
if printf '%s' "${fstab}" | grep -q 'subvolid='; then
echo 'This should not happen!'
return 1
fi
;;
EXT4)
true
;;
F2FS)
true
;;
*)
echo 'Filesystem '"${FS}"' not yet supported!'
return 1
;;
esac
2023-03-16 16:14:17 +01:00
printf '%s' "${fstab}" | sudo tee /mnt/etc/fstab >/dev/null
2022-07-16 17:18:03 +02:00
}
function config_hostname_and_hosts() {
# @pre
# HOSTNAME
# FQDN: optional, e.g. sub.domain.de
# STATIC_IP: optional, e.g. 93.133.433.133
# IPV6_CAPABLE: optional, 'true'
echo 'Set hostname ...'
2023-03-16 16:14:17 +01:00
echo "${HOSTNAME}" | sudo tee /mnt/etc/hostname >/dev/null
2022-07-16 17:18:03 +02:00
echo 'Create hosts file ...'
# If the system has a permanent IP address, it should be used instead of 127.0.1.1.
# * https://wiki.archlinux.org/index.php/Installation_guide#Network_configuration
# Desirable entries IPv4/IPv6:
# * https://man.archlinux.org/man/hosts.5#EXAMPLES
# If FQDN not given, use $HOSTNAME.localdomain instead
FQDN="${FQDN:="${HOSTNAME}.localdomain"}"
# If STATIC_IP not given, use 127.0.1.1 instead
STATIC_IP="${STATIC_IP:=127.0.1.1}"
echo '# 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
2023-03-16 16:14:17 +01:00
'"${STATIC_IP} ${FQDN} ${HOSTNAME}" | sudo tee /mnt/etc/hosts >/dev/null
2022-07-16 17:18:03 +02:00
if [ "${IPV6_CAPABLE}" = 'true' ]; then
echo '
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
2023-03-16 16:14:17 +01:00
ff02::2 ip6-allrouters' | sudo tee -a /mnt/etc/hosts >/dev/null
2022-07-16 17:18:03 +02:00
fi
}
function user_and_pwd() {
# @pre
# USERNAME
# USER_PWD
# ROOT_PWD (optional)
echo 'Adding user and changing shell to /bin/zsh ...'
2023-03-16 16:45:32 +01:00
# -m: Create home
2022-07-16 17:18:03 +02:00
# -U: Create a group with the same name as the user, and add the user to this group.
2023-03-16 16:14:17 +01:00
sudo arch-chroot /mnt useradd -m -s /usr/bin/zsh -g wheel "${USERNAME}"
sudo arch-chroot /mnt chsh -s /usr/bin/zsh
2022-07-16 17:18:03 +02:00
# If ROOT_PWD is not given, the use USER_PWD for root user
ROOT_PWD="${ROOT_PWD:="${USER_PWD}"}"
2023-03-16 16:14:17 +01:00
printf '%s:%s' "${USERNAME}" "${USER_PWD}" | sudo chpasswd --root /mnt
printf '%s:%s' "root" "${ROOT_PWD}" | sudo chpasswd --root /mnt
2022-07-16 17:18:03 +02:00
}
function bootloader() {
# @pre
# TARGET_BLOCK_DEVICE
# FDE: 'true' | 'false'
# LUKS_PART_UUID: required if FDE='true'
echo 'Installing grub ...'
case "${BOOT_FIRMWARE}" in
uefi)
2023-03-16 16:45:32 +01:00
# Portable fallback efi name for grub:
2022-07-16 17:18:03 +02:00
# * https://www.rodsbooks.com/efi-bootloaders/installation.html#alternative-naming
# * arch-chroot /mnt cp /boot/EFI/GRUB/grubx64.efi /boot/EFI/BOOT/bootx64.efi
2023-03-16 16:14:17 +01:00
sudo arch-chroot /mnt grub-install --target=x86_64-efi --bootloader-id=GRUB --efi-directory=/boot --removable
2022-07-16 17:18:03 +02:00
;;
bios)
2023-03-16 16:14:17 +01:00
sudo arch-chroot /mnt grub-install --target=i386-pc "${TARGET_BLOCK_DEVICE}"
2022-07-16 17:18:03 +02:00
;;
*)
echo 'Not yet implemented!'
return 1
;;
esac
echo 'Generating /boot/grub/grub.cfg ...'
{
case "${FDE}" in
true)
# /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
{
# Assert
grep --quiet '^GRUB_CMDLINE_LINUX=""$' < /mnt/etc/default/grub || return
# 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/
# The holoscript shall contain one 'sed "..."' command
2023-03-16 16:14:17 +01:00
sudo mkdir -p "${holoScriptDir}"
2022-07-16 17:18:03 +02:00
sudo echo '#!/bin/sh
sed "s|^GRUB_CMDLINE_LINUX=\"\"\$|GRUB_CMDLINE_LINUX=\"cryptdevice=/dev/disk/by-uuid/'"${LUKS_PART_UUID}"':crypt\"|"' \
2023-03-16 16:14:17 +01:00
| sudo tee "${holoScriptDir}"/grub.holoscript
sudo chmod 0544 "${holoScriptDir}"/grub.holoscript
2022-07-16 17:18:03 +02:00
}
# Then we apply the holoscript
2023-03-16 16:14:17 +01:00
sudo arch-chroot /mnt holo apply --force file:/etc/default/grub
2022-07-16 17:18:03 +02:00
;;
false)
true
;;
*)
echo 'Invalid option: '"${FDE}"
return 1
;;
esac
# And finally run grub-mkconfig
2023-03-16 16:14:17 +01:00
sudo arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg
2022-07-16 17:18:03 +02:00
}
}
main "$@"