#!/bin/bash
#
# Arch Linux installer (amd64, UEFI, systemd-boot).
#
# Run from the Arch ISO as root with network access:
#
#   bash arch.sh [--desktop] [--vm] [--hostname NAME] [--cpu intel|amd] [--yes]
#
#   --desktop   also install the Wayland desktop (Hyprland) and GUI applications
#   --vm        this is a virtual machine guest (/dev/vda, guest agents)
#   --hostname  hostname for the new system (default: arch, or archvm with --vm)
#   --cpu       microcode package to install (default: intel)
#   --yes       skip the confirmation before partitioning
#
# This is a half-automated procedure on purpose. Partitioning is hard-coded
# below in partition(): edit it for each machine before running. After the
# packages are installed the script drops into a shell inside the new system
# so that a user account and dotfiles can be set up by hand.
#

set -euo pipefail

msg() { echo -e "[+] ${1-}" >&2; }

die() {
    echo -e "[!] ${1-}" >&2
    exit 1
}

usage() {
    sed -n '2,/^$/{s/^# \{0,1\}//p}' "${BASH_SOURCE[0]}"
}

#
# options
#

DESKTOP=0
VM=0
NEW_HOSTNAME=''
CPU_VENDOR=intel
ASSUME_YES=0

while [[ $# -gt 0 ]]; do
    case "$1" in
    -h | --help)
        usage
        exit
        ;;
    --desktop) DESKTOP=1 ;;
    --vm) VM=1 ;;
    --hostname | --cpu)
        [[ -n ${2-} ]] || die "$1 needs a value"
        if [[ $1 == --hostname ]]; then NEW_HOSTNAME=$2; else CPU_VENDOR=$2; fi
        shift
        ;;
    -y | --yes) ASSUME_YES=1 ;;
    *) die "Unknown option: $1\n\n$(usage)" ;;
    esac
    shift
done

[[ $CPU_VENDOR == intel || $CPU_VENDOR == amd ]] || die '--cpu must be intel or amd'
if [[ -z $NEW_HOSTNAME ]]; then
    NEW_HOSTNAME=arch
    [[ $VM -eq 1 ]] && NEW_HOSTNAME=archvm
fi

#
# machine-specific settings (edit before running)
#

DISK=/dev/sda
[[ $VM -eq 1 ]] && DISK=/dev/vda
TIMEZONE=America/Chicago
# Extra kernel parameters (the pci= ones work around Thunderbolt hotplug limits).
KERNEL_OPTS='rw quiet loglevel=3 pci=hpbussize=0x33,hpmemsize=256M'

# Partition device prefix: /dev/sda -> /dev/sda1, /dev/nvme0n1 -> /dev/nvme0n1p1.
PART="$DISK"
[[ $DISK == *[0-9] ]] && PART="${DISK}p"

# Partition layout: 1 GiB EFI, 2 GiB swap, rest root.
partition() {
    parted --script "$DISK" mklabel gpt
    parted --script "$DISK" mkpart primary fat32 1M 1G
    parted --script "$DISK" set 1 esp on
    parted --script "$DISK" mkpart primary linux-swap 1G 3G
    parted --script "$DISK" mkpart primary ext4 3G 100%
    mkfs.fat -F32 "${PART}1"
    mkswap "${PART}2"
    mkfs.ext4 "${PART}3"
    swapon "${PART}2"
    mount "${PART}3" /mnt
    mount --mkdir "${PART}1" /mnt/boot
}
ROOT_PART="${PART}3"

#
# packages
#

depends=(reflector git) # needed by this script

base_pkgs=(
    # shell and utilities
    bash-completion openssh sshpass autossh htop trash-cli zoxide
    neovim fd fzf ripgrep go luarocks
    "$CPU_VENDOR-ucode"
    pacman-contrib reflector devtools
    # development
    gcc clang rustup the_silver_searcher gdb valgrind git diffutils
    tmux less jq man-db man-pages nvchecker docker docker-compose uv fnm bun
    # network
    networkmanager tcpdump wireshark-cli nmap bind inetutils net-tools whois
    traceroute linux-wifi-hotspot openvpn tailscale trayscale
    ettercap gobuster john
    # filesystems and archives
    e2fsprogs ntfs-3g dosfstools gptfdisk parted
    zip unzip 7zip unrar unarchiver
    # files and sync
    rsync wget curl syncthing
    yazi ffmpeg poppler imagemagick
    # system
    linux linux-firmware mkinitcpio-firmware systemd-boot-pacman-hook tlp-rdw
)

desktop_pkgs=(
    # wayland, compositor, session
    wl-clipboard xdg-desktop-portal-hyprland qt6-wayland dotool-git wlrctl
    greetd greetd-regreet
    hyprland lswt swaybg swaylock-effects-git wlogout
    waybar rofi dunst
    archlinux-wallpaper nordic-wallpapers trougnouf-backgrounds pscircle
    kanagawa-gtk-theme-git kanagawa-icon-theme-git
    everforest-gtk-theme-git everforest-icon-theme-git phinger-cursors nwg-look
    foot logiops blueman blueberry
    nwg-displays wlr-randr brightnessctl gammastep
    grim flameshot
    networkmanager-dmenu-git network-manager-applet gnome-keyring
    # applications
    wireshark-qt nautilus nautilus-python file-roller nextcloud-client
    firefox google-chrome
    browserpass-firefox browserpass-chrome pass ente-auth
    thunderbird newsboat transmission-gtk
    texlive-meta biber zathura-pdf-poppler pdfcrop pdftk jabref hunspell hunspell-en_us
    calibre imv speedcrunch obsidian safeeyes nethack
    baobab gnome-disk-utility
    # audio and video
    pipewire-pulse wireplumber pavucontrol cmus spotify-launcher soundconverter
    vlc gaupol gst-plugins-good gst-plugins-bad gst-plugins-ugly
    gst-plugins-base gst-libav gst-plugin-pipewire yt-dlp
    zoom obs-studio libva-intel-driver v4l2loopback-dkms v4l2loopback-utils
    # printing
    cups cups-pdf system-config-printer ghostscript
    # input and fonts
    fcitx5 fcitx5-configtool fcitx5-nord fcitx5-chewing fcitx5-gtk fcitx5-qt
    font-manager noto-fonts noto-fonts-cjk noto-fonts-emoji cantarell-fonts
    ttc-iosevka ttc-iosevka-aile ttc-iosevka-etoile ttf-iosevka-nerd
    ttf-nerd-fonts-symbols ttf-hack ttf-hack-nerd
    aleo-fonts otf-besley ttf-besley otf-drafting ttf-drafting
)

# Virtualization: host tools on real hardware, guest agents in a VM.
vm_host_pkgs=(virt-manager qemu-desktop lxsession iptables-nft dnsmasq dmidecode openbsd-netcat quickgui)
vm_guest_pkgs=(qemu-guest-agent spice-vdagent)

pkgs=("${base_pkgs[@]}")
[[ $DESKTOP -eq 1 ]] && pkgs+=("${desktop_pkgs[@]}")
if [[ $VM -eq 1 ]]; then
    pkgs+=("${vm_guest_pkgs[@]}")
else
    [[ $DESKTOP -eq 1 ]] && pkgs+=("${vm_host_pkgs[@]}")
fi

services=(NetworkManager.service systemd-resolved.service)
[[ $DESKTOP -eq 1 ]] && services+=(greetd.service)
if [[ $VM -eq 1 ]]; then
    services+=(qemu-guest-agent.service)
    [[ $DESKTOP -eq 1 ]] && services+=(spice-vdagentd.service)
else
    [[ $DESKTOP -eq 1 ]] && services+=(libvirtd.service)
fi

#
# pre-installation
#

[[ $UID -eq 0 ]] || die 'Please run this script as root'
ping -c2 archlinux.org >/dev/null || die 'Cannot connect to the Internet'
[[ -b $DISK ]] || die "$DISK is not a block device"

msg "Host: $NEW_HOSTNAME  CPU: $CPU_VENDOR  desktop: $DESKTOP  vm: $VM"
msg "About to repartition $DISK. Everything on it will be erased:"
lsblk "$DISK" >&2
if [[ $ASSUME_YES -ne 1 ]]; then
    read -rp '[?] Type "yes" to continue: ' answer
    [[ $answer == yes ]] || die 'Aborted'
fi

set -x
partition

timedatectl set-ntp true
pacman -Sy --needed --noconfirm "${depends[@]}"
reflector --save /etc/pacman.d/mirrorlist \
    --connection-timeout 3 --download-timeout 3 \
    --sort rate --threads 2 --protocol https \
    --country US,CA,TW,JP,DE,NL --exclude ".*\.cn/.*" \
    --age 12 --delay 1 --latest 40 --score 20 --fastest 10

#
# base system
#

pacstrap -K /mnt base base-devel linux linux-firmware
genfstab -U /mnt >>/mnt/etc/fstab

#
# configuration inside the new system
#

cat <<EOF >/mnt/archsetup.sh
set -ex
pacman -Sy --needed --noconfirm ${depends[*]}

ln -sf /usr/share/zoneinfo/$TIMEZONE /etc/localtime
hwclock --systohc --utc
echo 'en_US.UTF-8 UTF-8' >/etc/locale.gen
locale-gen
echo 'LANG=en_US.UTF-8' >/etc/locale.conf
echo '$NEW_HOSTNAME' >/etc/hostname

# boot loader
bootctl --path=/boot install
cat <<EOF2 >/boot/loader/loader.conf
default arch.conf
timeout 0
EOF2
cat <<EOF2 >/boot/loader/entries/arch.conf
title   Arch Linux
linux   /vmlinuz-linux
initrd  /$CPU_VENDOR-ucode.img
initrd  /initramfs-linux.img
options root=PARTUUID=\$(blkid -s PARTUUID -o value $ROOT_PART) $KERNEL_OPTS
EOF2

# AUR helper and packages, built as a throwaway unprivileged user
useradd -m -s /bin/bash tmpuser
echo 'tmpuser ALL=(ALL) NOPASSWD: ALL' >/etc/sudoers.d/tmpuser
cat <<EOF2 >/home/tmpuser/aur.sh
set -ex
git clone https://aur.archlinux.org/paru.git
(cd paru && makepkg -srci --noconfirm)
rm -rf paru
paru -Syu --needed --asexplicit ${pkgs[*]}
EOF2
sudo -i -u tmpuser /bin/bash /home/tmpuser/aur.sh
rm -f /etc/sudoers.d/tmpuser
userdel -rf tmpuser

systemctl enable ${services[*]}
EOF

arch-chroot /mnt /bin/bash /archsetup.sh

# Finish by hand: set the root password, create a user, deploy dotfiles.
set +x
msg 'Base install done. Opening a shell in the new system; exit when finished.'
arch-chroot /mnt /bin/bash

rm -f /mnt/archsetup.sh
umount -R /mnt
sync
msg 'Done. You can reboot now.'
