#!/usr/bin/env bash # Interactive Arch Linux installer script # Prompts for actions and, for phase 2, username, hostname, locale, and timezone. # Usage: sudo ./install.sh # Note: Phase 1 runs on the live system; Phase 2 runs inside chroot. set -euo pipefail IFS=$'\n\t' [[ $EUID -ne 0 ]] && { echo "Error: This script must be run as root." >&2; exit 1; } LOG_FILE="/tmp/arch_install.log" exec 1> >(tee -a "$LOG_FILE") exec 2>&1 echo "Installation log started at $(date)" >> "$LOG_FILE" # Detect UEFI once has_efi=false [[ -d /sys/firmware/efi ]] && has_efi=true # Cleanup function for error recovery cleanup() { if [[ "${phase:-}" == "1" && -n "${mount_point:-}" ]]; then echo "Cleaning up mounts..." >&2 umount -R "$mount_point" 2>/dev/null || true fi exit 1 } trap cleanup ERR # Prompt function for user input with default values prompt() { local prompt_text="$1" local default_value="${2:-}" local result if [[ -n "$default_value" ]]; then read -rp "$prompt_text [$default_value]: " result echo "${result:-$default_value}" else read -rp "$prompt_text: " result echo "$result" fi } # Preliminary installation steps _show_steps() { cat <<-'EOF' ==================================================== ARCH INSTALLATION PRELIMINARY STEPS ==================================================== 1. Command Lookup & Execution (help) • grep -n 'your_command' → Show line number of matches • sed -n '20,22p' → Print lines 20 to 22 • sed -n '70p' install.sh | bash → Execute 70th line 2. Disk Wipe (only if you know what you are doing) • dd if=/dev/zero of=/dev/nvme0n1 bs=32M status=progress 3. Networking • Ethernet: plug in cable • Wi-Fi with iwctl: - iwctl - device list - station scan - station get-networks - station connect • Or with nmcli: - nmcli device wifi list - nmcli device wifi connect password iface 4. Disk Partitioning • lsblk → Identify target disk (e.g., /dev/nvme0n1 or /dev/sda) • fdisk /dev/nvme0n1 → Partition: - g: GPT table - n: #1 +512M (EFI, if UEFI) - t: change type (1 = EFI) - n: #2 +20G (root) - n: #3 rest (home or swap, optional) - w: write & exit • For LVM or encryption, use `cryptsetup` or `pvcreate`/`vgcreate`/`lvcreate` 5. Formatting & Mounting • mkfs.fat -F32 /dev/nvme0n1p1 → Format EFI (if UEFI) • mkfs.ext4 /dev/nvme0n1p2 → Format root • mkfs.ext4 /dev/nvme0n1p3 → Format home (if created) • mkswap /dev/nvme0n1p3 && swapon → Format and enable swap (if created) • mount /dev/nvme0n1p2 /mnt → Mount root • mkdir /mnt/boot • mount /dev/nvme0n1p1 /mnt/boot → Mount EFI (if UEFI) • mkdir /mnt/home • mount /dev/nvme0n1p3 /mnt/home → Mount home (if created) ==================================================== EOF echo if [[ ! "$(prompt 'Sure to continue?' 'y/N')" =~ ^[Yy]$ ]]; then exit 0 fi } # Validate partition layout (phase 1) validate_partitions() { # root if ! mountpoint -q "$mount_point"; then echo "Error: Root partition not mounted at $mount_point." >&2 exit 1 fi # EFI if [[ "$has_efi" == true ]]; then if ! mountpoint -q "$mount_point/boot"; then echo "Error: EFI mount ($mount_point/boot) not found." >&2 exit 1 fi local efi_dev efi_dev=$(df "$mount_point/boot" | awk 'END{print $1}') if [[ "$(blkid -o value -s TYPE "$efi_dev")" != "vfat" ]]; then echo "Error: EFI partition must be FAT32." >&2 exit 1 fi fi # Swap detection if swapon --show | grep -q .; then echo "Swap partition detected and enabled." fi } # Check internet connectivity check_internet() { if curl -s --connect-timeout 5 archlinux.org >/dev/null || ping -c 1 8.8.8.8 >/dev/null 2>/dev/null; then return 0 else echo "Error: No internet connectivity. Check network and try again." >&2 exit 1 fi } # Improved chroot detection is_chroot() { local host_root proc1_root host_root=$(stat -c %d:%i /) proc1_root=$(stat -c %d:%i /proc/1/root/.) [[ "$host_root" != "$proc1_root" ]] } # GPU driver installation _install_gpu_pkgs() { if command -v virt-what &>/dev/null && [[ -n "$(virt-what)" ]]; then echo "Virtual machine detected; skipping GPU driver installation." return 0 fi local gpu_output nvidia_pkgs gpu_output="$(lspci -k 2>/dev/null || echo '')" if echo "$gpu_output" | grep -iE "nvidia" &>/dev/null; then echo "NVIDIA GPU detected." if [[ "$(prompt 'Install NVIDIA drivers?' 'y/N')" =~ ^[Yy]$ ]]; then nvidia_pkgs=(nvidia-dkms nvidia-utils nvidia-settings) echo "Installing Nvidia drivers..." pacman -S --noconfirm --needed "${nvidia_pkgs[@]}" >/dev/null 2>&1 || echo "Warning: NVIDIA install failed." >&2 fi fi if echo "$gpu_output" | grep -iE "amd| ati " &>/dev/null; then echo "AMD GPU detected." if [[ "$(prompt 'Install AMD drivers?' 'y/N')" =~ ^[Yy]$ ]]; then pacman -S --noconfirm --needed mesa vulkan-radeon libva-mesa-driver >/dev/null 2>&1 || echo "Warning: AMD install failed." >&2 fi fi if echo "$gpu_output" | grep -iE "intel" &>/dev/null; then echo "Intel GPU detected." if [[ "$(prompt 'Install Intel drivers?' 'y/N')" =~ ^[Yy]$ ]]; then pacman -S --noconfirm --needed mesa vulkan-intel intel-media-driver libva-intel-driver >/dev/null 2>&1 || echo "Warning: Intel install failed." >&2 fi fi } # Package installer install_packages() { local core_pkgs=(base-devel sudo grub efibootmgr networkmanager nano git reflector man-db man-pages bash-completion htop unzip zip wget curl openssh vim less file which) local optional_pkgs=() if [[ "$(prompt 'Audio packages (PipeWire, ALSA)?' 'y/N')" =~ ^[Yy]$ ]]; then optional_pkgs+=(pipewire pipewire-alsa pipewire-jack wireplumber sof-firmware pavucontrol) fi if [[ "$(prompt 'Bluetooth packages?' 'y/N')" =~ ^[Yy]$ ]]; then optional_pkgs+=(bluez bluez-utils) fi echo "Installing packages..." pacman -S --noconfirm --needed "${core_pkgs[@]}" "${optional_pkgs[@]}" >/dev/null 2>&1 || echo "Warning: Some installs failed." >&2 } if [[ "${1:-}" != "--noask" ]]; then if [[ "$(prompt 'Show preliminary steps?' 'y/N')" =~ ^[Yy]$ ]]; then _show_steps fi fi if [[ "${1:-}" == "--noask" ]]; then phase=2 if ! is_chroot; then echo "Error: Phase 2 must run inside chroot." >&2; exit 1 fi else echo "Select phase:" echo " 1) Base system" echo " 2) Post-install" until [[ "$phase" =~ ^[12]$ ]]; do phase=$(prompt "Enter 1 or 2") done fi # Phase 1 mount-point if [[ "$phase" == "1" ]]; then mount_point=$(prompt "Mount point for installation" "/mnt") [[ -d "$mount_point" ]] || { echo "Error: $mount_point not found." >&2; exit 1; } mountpoint -q "$mount_point" || { echo "Error: $mount_point is not mounted." >&2; exit 1; } fi # Phase 2 parameters if [[ "$phase" == "2" ]]; then if ! is_chroot; then echo "Error: Phase 2 must run inside chroot." >&2; exit 1 fi username=$(prompt "Enter new username") hostname_val=$(prompt "Enter hostname" "myarch") locale_val=$(prompt "Enter locale" "en_US.UTF-8") timezone_val=$(prompt "Enter timezone" "Asia/Kolkata") # Validate locale & timezone localectl list-locales | grep -qx "$locale_val" || { echo "Invalid locale." >&2; exit 1; } [[ -f "/usr/share/zoneinfo/$timezone_val" ]] || { echo "Invalid timezone." >&2; exit 1; } fi INSTALLER_SCRIPT=$(basename "$0") ### Phase 1: Install base system _install() { echo "==> Checking internet connectivity..." check_internet echo "==> Validating partitions..." validate_partitions echo "==> Reflector: optimizing mirrors..." reflector --verbose --latest 5 --sort rate --save /etc/pacman.d/mirrorlist || true echo "==> Installing base system to $mount_point" pacstrap "$mount_point" base linux linux-headers linux-firmware >/dev/null 2>&1 || { echo "Error: pacstrap failed." >&2; exit 1; } echo "==> Generating fstab" genfstab -U "$mount_point" >> "$mount_point/etc/fstab" echo "==> Copying installer into new system" install -m755 "$0" "$mount_point/root/$INSTALLER_SCRIPT" echo "==> Entering chroot for post-install" arch-chroot "$mount_point" /usr/bin/env bash -c "/root/$INSTALLER_SCRIPT --noask" } ### Phase 2: Post-install configuration _post_install() { echo "==> Setting root password" passwd || { echo "Error: root password." >&2; exit 1; } echo "==> Creating user '$username'" if id "$username" &>/dev/null; then echo "User exists; skipping." else useradd -m -G wheel,audio,video,optical,storage "$username" || echo "Warning: useradd failed." >&2 fi passwd "$username" || echo "Warning: set password manually." >&2 echo "==> Sudoers for wheel group" mkdir -p /etc/sudoers.d if [[ "$(prompt 'Require password for sudo?' 'Y/n')" =~ ^[Yy]$ ]]; then echo "%wheel ALL=(ALL:ALL) ALL" > /etc/sudoers.d/wheel else echo "%wheel ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/wheel fi chmod 0440 /etc/sudoers.d/wheel echo "==> Updating system & installing packages" pacman -Syu --noconfirm --needed || { echo "Error: system update." >&2; exit 1; } install_packages _install_gpu_pkgs echo "==> Generating initramfs" mkinitcpio -P || { echo "Error: mkinitcpio." >&2; exit 1; } echo "==> Timezone & locale" ln -sf "/usr/share/zoneinfo/$timezone_val" /etc/localtime hwclock --systohc sed -i "s/^#${locale_val}/${locale_val}/" /etc/locale.gen locale-gen echo "LANG=$locale_val" > /etc/locale.conf echo "LC_ALL=$locale_val" >> /etc/environment echo "==> Hostname & hosts" hostnamectl set-hostname "$hostname_val" cat > /etc/hosts < Enabling services" systemctl enable NetworkManager $has_efi && systemctl enable fstrim.timer pacman -Q bluez &>/dev/null && systemctl enable bluetooth echo "==> Installing GRUB" if [[ "$has_efi" == true ]]; then grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB else root_dev=$(lsblk -dno NAME | grep -E '^(nvme[0-9]+n[1-9]|sd[a-z])' | head -1) root_dev="/dev/$root_dev" root_dev=$(prompt "GRUB device" "$root_dev") grub-install --target=i386-pc "$root_dev" fi grub-mkconfig -o /boot/grub/grub.cfg echo "==> Cleanup" rm -f "/root/$INSTALLER_SCRIPT" echo "Post-install complete. Log: $LOG_FILE" echo "Reboot to start your new Arch system." } if [[ "${1:-}" == "--noask" ]]; then _post_install else [[ "$phase" == "1" ]] && _install || _post_install fi