#!/bin/bash # load config source /etc/de-p1st-installer/installer.cfg || { exit 1; } # load functions source /usr/lib/de-p1st-installer/util.sh || { exit 1; } source /usr/lib/de-p1st-installer/user-input.sh || { exit 1; } source /usr/lib/de-p1st-installer/block-device.sh || { exit 1; } 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 increase_cow_space() { # May be useful when running 'pacman -Syu' on the live medium. # Usually not necessary! # make sure that we are on a live medium: findmnt /run/archiso/cowspace >/dev/null || { echo "Not on live medium, did not increase cowspace!" return 1 } echo "Increasing cowspace partition of live medium ..." sudo mount -o remount,size=2G /run/archiso/cowspace || return $? } function get_user_input() { # @post # BIOS_TYPE (uefi or bios) # FS (BTRFS, EXT4, F2FS) # HOSTNAME # USERNAME, USER_PWD # LUKS_PWD get_block_devices_with_size || return $? get_single_choice TARGET_BLOCK_DEVICE "Select target device for installation" BLOCK_DEVICE_SIZES || return $? if [ "${BIOS_TYPE}" = "autodetect" ]; then # Detect BIOS 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." BIOS_TYPE="uefi" else echo "Detected BIOS boot" BIOS_TYPE="bios" fi else # Let user select BIOS type TMP1=('uefi' 'Newer mainboards' 'bios' 'Legacy BIOS on older mainboards') get_single_choice BIOS_TYPE "Select your bios type" TMP1 || return $? 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') get_single_choice FS "Select filesystem to use" TMP1 || return $? get_text_input HOSTNAME "Enter hostname:" || return $? get_text_input USERNAME "Enter username:" || return $? if [ -z "${USER_PWD}" ]; then get_text_input USER_PWD "Enter a user password:" || return $? get_text_input USER_PWD2 "Please enter the password again:" || return $? [[ "${USER_PWD}" == "${USER_PWD2}" ]] || { echo "Passwords did not match"; exit 1; } fi if [ -z "${LUKS_PWD}" ]; then get_text_input LUKS_PWD "Enter a disk encryption password:" || return $? get_text_input LUKS_PWD2 "Please enter the password again:" || return $? [[ "${LUKS_PWD}" == "${LUKS_PWD2}" ]] || { echo "Passwords did not match"; exit 1; } 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 choose_mount_options() { # @pre # FS # @post # FS_CHOSEN_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) TMP1=('noatime' "Don't write file/folder access times" 'on') ;; *) echo "Filesystem $FS not yet supported!" return 1 ;; esac get_multi_choice FS_CHOSEN_MOUNT_OPTIONS "Select mount options" TMP1 || return $? } function run_pacstrap() { # @pre # BIOS_TYPE echo "Running pacstrap ..." PKGS=("${ADDITIONAL_PKGS[@]}") case "${BIOS_TYPE}" 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}" = "1" ]; then args+=('-i') # run interactively fi args+=('/mnt') sudo pacstrap "${args[@]}" "${PKGS[@]}" || return $? } 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 fstab=$(printf "%s" "${fstab}" | sed 's/,subvolid=[^,\s]\+//') || return $? # 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 printf "%s" "${fstab}" | sudo tee /mnt/etc/fstab >/dev/null || return $? } 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, e.g. 1) echo "Set hostname ..." echo "${HOSTNAME}" | sudo tee /mnt/etc/hostname >/dev/null || return $? 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 ${STATIC_IP} ${FQDN} ${HOSTNAME}" | sudo tee /mnt/etc/hosts >/dev/null || return $? if [ "${IPV6_CAPABLE}" = "1" ]; then echo " # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters" | sudo tee -a /mnt/etc/hosts >/dev/null || return $? fi } function user_and_pwd() { # @pre # USERNAME # USER_PWD # ROOT_PWD (optional) echo "Adding user and changing shell to /bin/zsh ..." # -m: create home # -U: Create a group with the same name as the user, and add the user to this group. sudo arch-chroot /mnt useradd -m -s /usr/bin/zsh -g wheel "${USERNAME}" || return $? sudo arch-chroot /mnt chsh -s /usr/bin/zsh || return $? # If ROOT_PWD is not given, the use USER_PWD for root user ROOT_PWD="${ROOT_PWD:="${USER_PWD}"}" printf "%s:%s" "${USERNAME}" "${USER_PWD}" | sudo chpasswd --root /mnt || return $? printf "%s:%s" "root" "${ROOT_PWD}" | sudo chpasswd --root /mnt || return $? } function bootloader() { # @pre # TARGET_BLOCK_DEVICE # LUKS_PART_UUID echo "Installing grub ..." case "${BIOS_TYPE}" in uefi) # portable fallback efi name for grub: # * https://www.rodsbooks.com/efi-bootloaders/installation.html#alternative-naming # * arch-chroot /mnt cp /boot/EFI/GRUB/grubx64.efi /boot/EFI/BOOT/bootx64.efi sudo arch-chroot /mnt grub-install --target=x86_64-efi --bootloader-id=GRUB --efi-directory=/boot --removable || return $? ;; bios) sudo arch-chroot /mnt grub-install --target=i386-pc "${TARGET_BLOCK_DEVICE}" || return $? ;; *) echo "Not yet implemented!" return 1 ;; esac echo "Generating /boot/grub/grub.cfg ..." sudo sed -i "s|^GRUB_CMDLINE_LINUX=.*\$|GRUB_CMDLINE_LINUX=\"cryptdevice=/dev/disk/by-uuid/${LUKS_PART_UUID}:crypt\"|" \ /mnt/etc/default/grub || return $? sudo arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg || return $? } function main() { # @pre # bash libraries imported # @post # installation finished check_network || return $? # out: BIOS_TYPE, FS, HOSTNAME, USERNAME, USER_PWD, LUKS_PWD get_user_input || return $? # in: CPU_VENDOR (optional) # out: CPU_VENDOR get_cpu_vendor || return $? # in: FS # out: FS_DEFAULT_MOUNT_OPTIONS get_default_mount_options || return $? # in: FS # out: FS_CHOSEN_MOUNT_OPTIONS choose_mount_options || return $? # in: TARGET_BLOCK_DEVICE, BIOS_TYPE # out: BOOT_PART, LUKS_PART partition || return $? # in: BIOS_TYPE, BOOT_PART, LUKS_PART, LUKS_PWD, FS # out: LUKS_PART_UUID, DATA_PART format || return $? # Combine default and chosen mount options TMP1=("${FS_DEFAULT_MOUNT_OPTIONS[@]}" "${FS_CHOSEN_MOUNT_OPTIONS[@]}") || return $? # Join array elements by "," join_by "," TMP1 FS_MOUNT_OPTIONS || return $? echo "Mounting data partition with options: ${FS_MOUNT_OPTIONS}" sudo mount -o "${FS_MOUNT_OPTIONS}" "$DATA_PART" /mnt || return $? echo "Mounting boot partition ..." mkdir /mnt/boot || return $? sudo mount "$BOOT_PART" /mnt/boot || return $? # in: BIOS_TYPE run_pacstrap || return $? # in: FS run_genfstab || return $? # in: HOSTNAME, FQDN (optional), STATIC_IP (optional), IPV6_CAPABLE (optional) config_hostname_and_hosts || return $? # in: USERNAME, USER_PWD, ROOT_PWD (optional) user_and_pwd || return $? sudo arch-chroot /mnt mkinitcpio -P || return $? # in: TARGET_BLOCK_DEVICE, LUKS_PART_UUID bootloader || return $? if [ "${LEAVE_MOUNTED}" -eq "1" ]; then echo "Leaving partitions below /mnt mounted and ${DATA_PART} opened." else sudo umount -R /mnt || return $? sudo cryptsetup luksClose "$(basename "${DATA_PART}")" || return $? fi echo "Finished installation without errors!" } main "$@"