#!/bin/bash set -eo pipefail confirmation() { local message message="${1}" local choice while :; do printf "%s (Y/n) " "${message}" read -r choice case "${choice}" in y | Y) return 0 ;; n | N) return 1 ;; *) printf "\nInput must be either y, Y, n, or N" ;; esac done } print-break() { local green green="$(tput setaf 2)" local reset reset="$(tput sgr0)" printf "${green}%.s─${reset}" $(seq 1 "$(tput cols)") } print-title() { local title title="${*}" local bold bold="$(tput bold)" local underline underline="$(tput smul)" local cyan cyan="$(tput setaf 6)" local reset reset="$(tput sgr0)" printf "%s%s%s%s%s\n" "${bold}" "${underline}" "${cyan}" "${title}" "${reset}" >&2 } echo-rgb() { # Echo a colored string to the terminal based on rgb values # # Positional Arguments: # # message # - The message to be printed to stdout # red # - The red value from 0 to 255 # green # - The green value from 0 to 255 # blue # - The blue value from 0 to 255 # # Usage: # echo-rgb "Yep" 10 8 30 # # POSIX Compliant: # N/A # local red local green local blue local input input="${1}" red="${2}" green="${3}" blue="${4}" printf "\e[0;38;2;%s;%s;%sm%s\e[m\n" "${red}" "${green}" "${blue}" "${input}" } important() { echo-rgb "${1}" 135 195 255 } log() { # Print a message and send it to stdout or stderr depending upon log level, also configurable with debug etc. # # Arguments: # level # - The log level, defined within a case check in this function # message # - The info message # line_number # - The line number of the calling function (${LINNO}) # # Usage: # log "info" "Could not find that directory" # # POSIX Compliant: # Yes # # Set debug status depending if a global debug variable has been set to either 1 or 0 local debug if [ ${DEBUG} ]; then debug=${DEBUG} else debug=0 fi local FORMAT FORMAT="[$(echo-rgb "$(date +%Y-%m-%dT%H:%M:%S)" 180 140 255)]" # Convert the level to uppercase local level level=$(echo "${1}" | tr '[:lower:]' '[:upper:]') local message message="${2}" case "${level}" in INFO) # Output all info log levels to stdout printf "${FORMAT}[$(echo-rgb "INFO" 0 140 255)] %s\n" "${message}" >&2 return 0 ;; WARN | WARNING) # Output all warning log levels to stdout printf "${FORMAT}[$(echo-rgb "WARNING" 255 255 0)] %s\n" "${message}" >&2 return 0 ;; DEBUG) # Output all debug log levels to stdout if [ "${DEBUG}" ]; then printf "${FORMAT}[$(echo-rgb "DEBUG" 0 160 110)] %s\n" "${message}" >&2 fi return 0 ;; ERROR) # Output all error log levels to stderr printf "${FORMAT}[$(echo-rgb "ERROR" 255 0 0)] %s\n" "${message}" >&2 return 0 ;; # Further log levels can be added by extending this switch statement with more comparisons *) # Default case, no matches # Returns non-zero code as an improper log option was passed, this helps with using `set -e` printf "${FORMAT}[ERROR] %s\n" "Invalid log level passed, received level \"${level}\" with message \"${message}\"" >&2 return 1 ;; esac } get-available-disks() { local available_disks=() local disk_line local disk while read -r; do # tr is used to change multiple whitespaces into a single space for processing by cut disk_line="$(printf "%s" "${REPLY}" | tr -s " ")" disk=$(printf "%s" "${disk_line}" | cut -d " " -f2) if [[ "${disk}" = "disk" ]]; then available_disks+=("$(printf "/dev/%s" "${disk_line}" | cut -d " " -f1)") fi done <<<"$(lsblk --output NAME,TYPE)" printf "%s" "${available_disks[*]}" } get-disk-partitions() { local disk="${1}" if [[ -z "${disk}" ]]; then log "error" "No disk passed!" return 1 fi if ! blkid | grep "${disk}" >/dev/null; then return 1 fi local partitions IFS=$'\n' partitions=("$(blkid -o device | grep "${disk}")") printf "%s" "${partitions[@]}" } check-connectivity() { if ! host "google.com" >/dev/null; then log "error" "Unable to reach google, network is down. This script requires network connectivity with active DNS resolution" return 1 fi } select-item() { local item_name="${1}" shift local items # shellcheck disable=2206 items=($@) local item_selected while :; do local count=1 for item in "${items[@]}"; do printf "%s %s\n" "${count}.)" "$(important "${item}")" >&2 count=$((count + 1)) done count=1 read -rp "Select a ${item_name} number: " item_selected case "${item_selected}" in '' | *[!0-9]*) log "error" "A number was not passed" continue ;; esac for item in "${items[@]}"; do # printf -- "--> %s | %s\n" "${item_selected}" "${count}" if [[ ${count} -eq ${item_selected} ]]; then printf "%s" "${item}" return 0 fi count="$((count + 1))" done log "error" "Invalid selection made, received: \"${item_selected}\"" done } partition-disk() { local disk="${1}" ( echo n echo "${BOOT_PARTITION_NUMBER}" echo echo +512M echo ef00 echo n echo "${ROOT_PARTITION_NUMBER}" echo echo echo 8300 echo w echo Y ) | gdisk "${disk}" >/dev/null mkfs.vfat -F32 -n EFI "${disk}"*"${BOOT_PARTITION_NUMBER}" } main() { local ARCH_TIMEZONE="CST" local LOCALE="en_US.UTF-8 UTF-8" local LANG="en_US.UTF-8" local DISK_PARTITION_LETTER="" local HOSTNAME="Arch-Linux" local MICROCODE="amd" clear if ! check-connectivity; then exit 1 fi print-title "Install Needed PGP Keys" pacman -Syy --noconfirm pacman -S --noconfirm archlinux-keyring print-break local selected_disk local i_selected_disk while :; do print-title "Select the installation disk" selected_disk="$(select-item "disk" "$(get-available-disks)")" i_selected_disk="$(important "${selected_disk}")" if confirmation "Is ${i_selected_disk} the correct disk to install to?"; then break fi done printf "Disk set as %s\n" "${i_selected_disk}" >&2 print-break print-title "Partition Creation" export BOOT_PARTITION_NUMBER=120 export ROOT_PARTITION_NUMBER=121 if ! confirmation "Begin installation of Arch Linux to ${i_selected_disk}?"; then printf "Install request denied, exiting..." >&2 exit 0 fi printf "Writing new partitions to %s\n" "${i_selected_disk}" >&2 partition-disk "${selected_disk}" local boot_partion="${selected_disk}${DISK_PARTITION_LETTER}${BOOT_PARTITION_NUMBER}" local root_partition="${selected_disk}${DISK_PARTITION_LETTER}${ROOT_PARTITION_NUMBER}" mkfs.vfat -F32 -n EFI "${boot_partion}" printf "Successfully wrote partitions to %s\n" "${i_selected_disk}" >&2 print-break print-title "Partition Encryption" printf "Encrypting %s\n" "$(important "${root_partition}")" >&2 cryptsetup -y -v luksFormat "${root_partition}" || exit printf "Opening encrypted device %s\n" \ "$(important "${root_partition}")" >&2 cryptsetup open "${root_partition}" cryptroot || exit printf "Configuring LVM\n" >&2 pvcreate /dev/mapper/cryptroot vgcreate vg0 /dev/mapper/cryptroot lvcreate --size 16G vg0 --name swap lvcreate -l +100%FREE vg0 --name root printf "Writing filesystems\n" >&2 mkfs.ext4 -L root "/dev/mapper/vg0-root" mkswap /dev/mapper/vg0-swap print-break print-title "Install Arch Into Encrypted Volume" printf "Mounting Needed Directores\n" >&2 mount /dev/mapper/vg0-root /mnt swapon /dev/mapper/vg0-swap mkdir /mnt/boot mount "${boot_partion}" /mnt/boot printf "Doing Pacstrap\n" >&2 pacstrap /mnt linux linux-firmware base base-devel grub efibootmgr vim git "${MICROCODE}"-ucode networkmanager genfstab -U /mnt >>/mnt/etc/fstab print-break print-title "Configure System" printf "Entering Chroot\n" >&2 arch-chroot /mnt <<-__END_CHROOT_CMDS__ printf "Setting default password as toor\n" printf "toor\ntoor\n" | passwd printf "Linking timezone as %s\n" "$(important "${ARCH_TIMEZONE}")" >&2 ln -sf "/usr/share/zoneinfo/${ARCH_TIMEZONE}" "/etc/localtime" printf "Setting Hardware Clock\n" >&2 hwclock --systohc printf "Setting LOCALE to %s\n" "$(important "${LOCALE}")" >&2 printf "%s\n" "${LOCALE}" >/etc/locale.gen printf "Generting Locale\n" >&2 locale-gen printf "Setting LANG to %s\n" "$(important "${LANG}")" >&2 printf "%s\n" "${LANG}" >>/etc/locale.conf printf "Setting HOSTNAME to %s\n" "$(important "${HOSTNAME}")" printf "%s\n" "${HOSTNAME}" >>/etc/hostname printf "Set mkinitcipio hooks\n" >&2 sed -i 's/HOOKS=.*/HOOKS=(base udev autodetect keyboard modconf block encrypt filesystems fsck)/' /etc/mkinitcpio.conf mkinitcpio -P printf "Configure Grub\n" >&2 yes | pacman -S grub efibootmgr intel-ucode amd-ucode sed -i "s/GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"cryptdevice=UUID=$(blkid -s UUID -o value "${root_partition}"):cryptroot root=\/dev\/mapper\/cryptroot"/" /etc/default/grub printf "GRUB_ENABLE_CRYPTO_DISK=y\n" >> /etc/default/grub grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB grub-mkconfig -o /boot/grub/grub.cfg printf "Setting up networkmanager\n" >&2 yes | pacman -S networkmanager systemctl enable NetworkManager __END_CHROOT_CMDS__ print-break printf "%s\n\tUsername: root\n\tPassword: toor\n" "$(important "Finished with installation, login is:")" } main