diff --git a/install.sh b/install.sh index 44e5a27..823e294 100755 --- a/install.sh +++ b/install.sh @@ -1,217 +1,264 @@ -#!/bin/sh +#!/bin/bash + +set -euo pipefail # User inputs HOSTNAME="" ROOT_PASSWORD="" USERNAME="" USER_PASSWORD="" +WANT_AUTOLOGIN="" + +# Cleanup on failure +cleanup() { + local exit_code=$? + if [ $exit_code -ne 0 ]; then + echo "An error occurred during installation. Unmounting..." + umount -A -R /mnt 2>/dev/null || true + fi +} +trap cleanup EXIT install_deps() { - echo "You will need commands like mkfs.ext4, sgisk and pacstrap." - printf "Try to install them with pacman (if you are on arch linux)? [Y/n] " - read CONFIRMATION - case "$CONFIRMATION" in - "n" | "N") - echo "Operation canceled." - return - ;; - "" | "y" | "Y") - echo "Installing dependencies" - sudo pacman -S --needed --noconfirm dosfstools gptfdisk arch-install-scripts - echo "" - ;; - *) - echo "Invalid input. Defaulting to 'No'." - exit 0 - ;; - esac + echo "You will need commands like mkfs.ext4, sgdisk and pacstrap." + printf "Try to install them with pacman (if you are on arch linux)? [Y/n] " + read -r CONFIRMATION + case "$CONFIRMATION" in + [nN]*) + echo "Operation canceled." + return + ;; + "" | [yY]*) + echo "Installing dependencies" + sudo pacman -S --needed --noconfirm dosfstools gptfdisk arch-install-scripts + echo "" + ;; + *) + echo "Invalid input. Defaulting to 'No'." + exit 1 + ;; + esac } send_status_message() { - term_width=$(tput cols 2>/dev/null || echo 80) - msg="$1" - edge=$(printf '%*s' "$term_width" '' | tr ' ' '=') - printf "\n%s\n" "$edge" - printf "%*s\n" $(((${#msg} + term_width) / 2)) "$msg" - printf "%s\n\n" "$edge" + local term_width + term_width=$(tput cols 2>/dev/null || echo 80) + local msg="$1" + local edge + edge=$(printf '%*s' "$term_width" '' | tr ' ' '=') + printf "\n%s\n" "$edge" + printf "%*s\n" $(((${#msg} + term_width) / 2)) "$msg" + printf "%s\n\n" "$edge" } get_config_inputs() { - send_status_message "CONFIGURATION: Getting user inputs" + send_status_message "CONFIGURATION: Getting user inputs" - while [ -z "$HOSTNAME" ]; do - printf "Enter hostname: " - read HOSTNAME + while [ -z "$WANT_AUTOLOGIN" ]; do + printf "Do you want to enable autologin? (TTY only) [y/n]: " + read -r WANT_AUTOLOGIN + case "$WANT_AUTOLOGIN" in + [yY]*) WANT_AUTOLOGIN="y" ;; + [nN]*) WANT_AUTOLOGIN="n" ;; + *) WANT_AUTOLOGIN="" ;; + esac + done + echo "" - if [ -z "$HOSTNAME" ]; then - echo "Hostname cannot be empty." - continue - fi - break - done - echo "" + while [ -z "$HOSTNAME" ]; do + printf "Enter hostname: " + read -r HOSTNAME + # Validate hostname + if [[ ! "$HOSTNAME" =~ ^[a-zA-Z0-9.-]+$ ]]; then + echo "Invalid hostname. Use alphanumeric, dots, and hyphens only." + HOSTNAME="" + continue + fi + done + echo "" - while [ -z "$ROOT_PASSWORD" ]; do - printf "Enter root password: " - read -s ROOT_PASSWORD - printf "\nConfirm root password: " - read -s CONFIRM_ROOT_PASSWORD + while [ -z "$ROOT_PASSWORD" ]; do + printf "Enter root password: " + read -rs ROOT_PASSWORD + printf "\nConfirm root password: " + read -rs CONFIRM_ROOT_PASSWORD + echo "" - if [ "$ROOT_PASSWORD" != "$CONFIRM_ROOT_PASSWORD" ]; then - echo "Root passwords do not match." - ROOT_PASSWORD="" - CONFIRM_ROOT_PASSWORD="" - continue - fi - break - done - echo "" - echo "" + if [ "$ROOT_PASSWORD" != "$CONFIRM_ROOT_PASSWORD" ] || [ -z "$ROOT_PASSWORD" ]; then + echo "Root passwords do not match or are empty." + ROOT_PASSWORD="" + continue + fi + done + echo "" - while [ -z "$USERNAME" ]; do - printf "Enter username: " - read USERNAME + while [ -z "$USERNAME" ]; do + printf "Enter username: " + read -r USERNAME + # Validate username + if [[ ! "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]]; then + echo "Invalid username. Must start with lowercase/underscore and contain no spaces." + USERNAME="" + continue + fi + done + echo "" - if [ -z "$USERNAME" ]; then - echo "Username cannot be empty." - continue - fi - break - done - echo "" + while [ -z "$USER_PASSWORD" ]; do + printf "Enter user password: " + read -rs USER_PASSWORD + printf "\nConfirm user password: " + read -rs CONFIRM_USER_PASSWORD + echo "" - while [ -z "$USER_PASSWORD" ]; do - printf "Enter user password: " - read -s USER_PASSWORD - printf "\nConfirm user password: " - read -s CONFIRM_USER_PASSWORD - - if [ "$USER_PASSWORD" != "$CONFIRM_USER_PASSWORD" ]; then - echo "User passwords do not match." - USER_PASSWORD="" - CONFIRM_USER_PASSWORD="" - continue - fi - break - done + if [ "$USER_PASSWORD" != "$CONFIRM_USER_PASSWORD" ] || [ -z "$USER_PASSWORD" ]; then + echo "User passwords do not match or are empty." + USER_PASSWORD="" + continue + fi + done } # Function to get drive name DISK="" get_drive() { - send_status_message "DRIVE SETUP: Selecting and confirming target drive" + send_status_message "DRIVE SETUP: Selecting and confirming target drive" - lsblk - printf "\nEnter the drive name (e.g., sda, sdb, vda, nvme0n1): " - read DISK - if [ -z "${DISK}" ]; then - echo "No drive name entered. Exiting." - exit 1 - fi + printf '%-8s %-8s %-30s %s\n' NAME SIZE MODEL + lsblk # -do NAME,SIZE,MODEL + printf "\nEnter the drive name (e.g., sda, nvme0n1): " + read -r DISK + if [ -z "${DISK}" ]; then + echo "No drive name entered. Exiting." + exit 1 + fi - DISK="/dev/${DISK}" - PART_PREFIX="$DISK" - case "$DISK" in - *nvme*n*) PART_PREFIX="${DISK}p" ;; # nvme devices need a 'p' before partition number - *) PART_PREFIX="${DISK}" ;; - esac + DISK="/dev/${DISK}" + if [ ! -b "$DISK" ]; then + echo "Error: ${DISK} is not a valid block device." + exit 1 + fi - printf "WARNING: All data on %s will be erased. Are you sure? [Y/n] " "${DISK}" - read CONFIRMATION - case "$CONFIRMATION" in - "n" | "N") - echo "Operation canceled." - exit 0 - ;; - "" | "y" | "Y") - echo "Proceeding with the operation on ${DISK}." - printf "Wiping drive in " - for i in 3 2 1; do - printf '%d...' $i - sleep 1 - done - echo "" - ;; - *) - echo "Invalid input. Defaulting to 'No'." - exit 0 - ;; - esac + PART_PREFIX="$DISK" + if [[ "$DISK" =~ [0-9]$ ]]; then + PART_PREFIX="${DISK}p" + fi - echo "Drive ${DISK} confirmed for installation." + printf "WARNING: All data on %s will be erased. Are you sure? [Y/n] " "${DISK}" + read -r CONFIRMATION + case "$CONFIRMATION" in + [nN]*) + echo "Operation canceled." + exit 0 + ;; + "" | [yY]*) + echo "Proceeding with the operation on ${DISK}." + printf "Wiping drive in " + for i in 3 2 1; do + printf '%d...' "$i" + sleep 1 + done + echo "" + ;; + *) + echo "Invalid input. Defaulting to 'No'." + exit 1 + ;; + esac + + echo "Drive ${DISK} confirmed for installation." } setup_partitions() { - send_status_message "PARTITIONING: Creating partitions and filesystems" + send_status_message "PARTITIONING: Creating partitions and filesystems" - if [ ! -d "/sys/firmware/efi" ]; then # Checking for BIOS system - echo "BIOS systems are not supported." - fi + if [ ! -d "/sys/firmware/efi" ]; then + echo "BIOS systems are not supported." + exit 1 + fi - umount -A -R /mnt # make sure everything is unmounted before we start - sgdisk -Z ${DISK} # zap all on disk - sgdisk -a 2048 -o ${DISK} # new GPT disk 2048 alignment + echo "Ensuring $DISK is not in use..." + grep "${DISK}" /proc/swaps | cut -d' ' -f1 | xargs -r swapoff || true + grep "${DISK}" /proc/mounts | cut -d' ' -f2 | xargs -r umount -l || true + wipefs -a "${DISK}" || true + sgdisk -Z "${DISK}" || true - # Create partitions - sgdisk -n 1::+1G --typecode=1:ef00 --change-name=1:"BOOT" ${DISK} # partition 1 (UEFI boot partition) - sgdisk -n 2::+4G --typecode=2:8200 --change-name=2:"SWAP" ${DISK} # partition 2 (Swap partition) - sgdisk -n 3::-0 --typecode=3:8300 --change-name=3:"ROOT" ${DISK} # partition 3 (Root), default start, remaining - partprobe ${DISK} # reread partition table to ensure it is correct + sgdisk -a 2048 -o "${DISK}" # new GPT disk 2048 alignment - # Create filesystems - mkfs.fat -F32 -n "BOOT" ${PART_PREFIX}1 - mkswap -f ${PART_PREFIX}2 - mkfs.ext4 -F -L "ROOT" ${PART_PREFIX}3 + # Create partitions + sgdisk -n 1::+1G --typecode=1:ef00 --change-name=1:"BOOT" "${DISK}" + sgdisk -n 2::-0 --typecode=2:8300 --change-name=2:"ROOT" "${DISK}" + # sgdisk -n 3::+4G --typecode=3:8200 --change-name=3:"SWAP" "${DISK}" - # Mount - mount ${PART_PREFIX}3 /mnt - if [ $? -ne 0 ]; then - echo "Failed to mount the ROOT partition." - exit 2 # Exit if mounting ROOT partition fails - fi + partprobe "${DISK}" + sleep 2 - mount --mkdir ${PART_PREFIX}1 /mnt/boot - if [ $? -ne 0 ]; then - echo "Failed to mount the BOOT partition." - exit 2 # Exit if mounting BOOT partition fails - fi + # Create filesystems + mkfs.fat -F32 -n "BOOT" "${PART_PREFIX}1" + mkfs.ext4 -F -L "ROOT" "${PART_PREFIX}2" + # mkswap -f -L "SWAP" "${PART_PREFIX}3" - swapon ${PART_PREFIX}2 + # Mount + mount -t ext4 "${PART_PREFIX}2" /mnt + mount --mkdir -t vfat "${PART_PREFIX}1" /mnt/boot + # swapon "${PART_PREFIX}3" } # Setup systemdboot as the bootloader setup_bootloader() { - send_status_message "BOOTLOADER: Installing and configuring systemd-boot" + send_status_message "BOOTLOADER: Installing and configuring systemd-boot" - # Run bootctl inside chroot - arch-chroot /mnt bootctl install - if [ $? -ne 0 ]; then - echo "Error: Failed to install systemd-boot." - exit 3 - fi + arch-chroot /mnt bootctl install - BOOTLOADER_ENTRY="/mnt/boot/loader/entries/arch.conf" - ARCH_ENTRY_TEMPLATE="/mnt/usr/share/systemd/bootctl/arch.conf" + BOOTLOADER_ENTRY="/mnt/boot/loader/entries/arch.conf" - # Get PARTUUID for the root partition - PARTUUID=$(blkid | grep "${PART_PREFIX}3" | awk -F= '{print $NF}' | tr -d '\"') + PARTUUID=$(blkid -s PARTUUID -o value "${PART_PREFIX}2") - if [ -z "$PARTUUID" ]; then - echo "Error: Unable to find PARTUUID for ${DISK}3." - exit 3 - fi + if [ -z "$PARTUUID" ]; then + echo "Error: Unable to find PARTUUID for ${PART_PREFIX}2." + exit 3 + fi - # Create a new entry based on the template and PARTUUID - echo "Creating bootloader entry with PARTUUID=$PARTUUID" + echo "Creating bootloader entry with PARTUUID=$PARTUUID" - sed "s/^options.*/options root=PARTUUID=$PARTUUID rw splash/" "$ARCH_ENTRY_TEMPLATE" >"$BOOTLOADER_ENTRY" + cat < "$BOOTLOADER_ENTRY" +title Arch Linux +linux /vmlinuz-linux +initrd /initramfs-linux.img +options root=PARTUUID=$PARTUUID rw splash +EOF - if [ $? -eq 0 ]; then - echo "Successfully created bootloader entry: $BOOTLOADER_ENTRY" - else - echo "Error: Failed to create the bootloader entry." - exit 3 - fi + echo "default arch.conf" > /mnt/boot/loader/loader.conf +} + +setup_autologin() { + if [ "$WANT_AUTOLOGIN" != "y" ]; then + return + fi + + send_status_message "CONFIG: Configuring autologin for $USERNAME" + + cat > "/mnt/etc/systemd/system/autologin@.service" <------ Main execution flow ------< @@ -235,37 +282,45 @@ setup_partitions send_status_message "BASE INSTALLATION: Installing base system with pacstrap" pacstrap -K /mnt base linux linux-firmware networkmanager lemurs sudo if [ $? -ne 0 ]; then - echo "Error: Failed to install base system." - exit 4 + echo "Error: Failed to install base system." + exit 4 fi send_status_message "SYSTEM CONFIG: Generating fstab" -genfstab -U /mnt >>/mnt/etc/fstab +genfstab -U /mnt >> /mnt/etc/fstab # Setup base locales send_status_message "SYSTEM CONFIG: Setting timezone, locales, and hostname" ln -sf /usr/share/zoneinfo/Europe/Paris /mnt/etc/localtime -echo 'en_US.UTF-8 UTF-8' >>/mnt/etc/locale.gen +echo 'en_US.UTF-8 UTF-8' >> /mnt/etc/locale.gen arch-chroot /mnt locale-gen -echo LANG=en_US.UTF-8 >/mnt/etc/locale.conf -echo $HOSTNAME >/mnt/etc/hostname +echo "LANG=en_US.UTF-8" > /mnt/etc/locale.conf +echo "$HOSTNAME" > /mnt/etc/hostname send_status_message "USER SETUP: Creating user and setting passwords" # Set root password -arch-chroot /mnt bash -c "echo 'root:$ROOT_PASSWORD' | chpasswd" +arch-chroot /mnt chpasswd < /mnt/etc/sudoers.d/10-wheel +chmod 440 /mnt/etc/sudoers.d/10-wheel send_status_message "SERVICES: Enabling NetworkManager and lemurs" # Setup services and display manager arch-chroot /mnt systemctl enable NetworkManager lemurs +# Configure autologin for lemurs if requested +setup_autologin + setup_bootloader send_status_message "CLEANUP: Unmounting all filesystems"