diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..b53863d --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,328 @@ +#!/usr/bin/env bash +# +# Arch Linux installation +# +# Bootable USB: +# - [Download](https://archlinux.org/download/) ISO and GPG files +# - Verify the ISO file: `$ pacman-key -v archlinux--x86_64.iso.sig` +# - Create a bootable USB with: `# dd if=archlinux*.iso of=/dev/sdX && sync` +# +# UEFI setup: +# +# - Set boot mode to UEFI, disable Legacy mode entirely. +# - Temporarily disable Secure Boot. +# - Make sure a strong UEFI administrator password is set. +# - Delete preloaded OEM keys for Secure Boot, allow custom ones. +# - Set SATA operation to AHCI mode. +# +# Run installation: +# +# - Connect to wifi via: `# iwctl station wlan0 connect WIFI-NETWORK` +# - Run: `# bash <(curl -sL https://link.rafe.li/dot)` +# +# WARNING: this script will destroy data on the selected disk. +# + +set -uo pipefail +trap 's=$?; echo "$0: Error on line "$LINENO": $BASH_COMMAND"; exit $s' ERR + +exec 1> >(tee "stdout.log") +exec 2> >(tee "stderr.log" >&2) + +export SNAP_PAC_SKIP=y + +# Dialog +BACKTITLE="Arch Linux installation" + +get_input() { + title="$1" + description="$2" + + input=$(dialog --clear --stdout --backtitle "$BACKTITLE" --title "$title" --inputbox "$description" 0 0) + echo "$input" +} + +get_password() { + title="$1" + description="$2" + while : ; do + init_pass=$(dialog --clear --stdout --backtitle "$BACKTITLE" --title "$title" --passwordbox "$description" 0 0) + : "${init_pass:?dialog --clear --stdout --backtitle "$BACKTITLE" --title "$title" --msgbox "Password cannot be empty.\nTry again." 0 0}" + + test_pass=$(dialog --clear --stdout --backtitle "$BACKTITLE" --title "$title" --passwordbox "$description again" 0 0) + if [[ "$init_pass" != "$test_pass" ]]; then + dialog --clear --stdout --backtitle "$BACKTITLE" --title "$title" --msgbox "Passwords did not match.\nTry again." 0 0 + else + break + fi + done + echo "$init_pass" +} + +get_choice() { + title="$1" + description="$2" + shift 2 + options=("$@") + dialog --clear --stdout --backtitle "$BACKTITLE" --title "$title" --menu "$description" 0 0 0 "${options[@]}" +} + + +echo -e "\n### Checking UEFI boot mode" +if [ ! -f /sys/firmware/efi/fw_platform_size ]; then + echo >&2 "You must boot in UEFI mode to continue" + exit 2 +fi + +echo -e "\n### Ensure the system clock is accurate" +timedatectl set-ntp true +hwclock --systohc --utc + +echo -e "\n### Setting keyboard layout to de-latin1" +loadkeys de-latin1 + +echo -e "\n### Installing additional tools" +pacman -Sy --noconfirm --needed git reflector terminus-font dialog wget + +echo -e "\n### HiDPI screens" +noyes=("Yes" "The font is too small" "No" "The font size is just fine") +hidpi=$(get_choice "Font size" "Is your screen HiDPI?" "${noyes[@]}") || exit 1 +clear +[[ "$hidpi" == "Yes" ]] && font="ter-132n" || font="ter-716n" +setfont "$font" + +hostname=$(get_input "Hostname" "Enter hostname") || exit 1 +clear +: "${hostname:?"hostname cannot be empty"}" + +user=$(get_input "User" "Enter username") || exit 1 +clear +: "${user:?"user cannot be empty"}" + +password=$(get_password "User" "Enter password") || exit 1 +clear +: "${password:?"password cannot be empty"}" + +devicelist=$(lsblk -dplnx size -o name,size | grep -Ev "boot|rpmb|loop" | tac | tr '\n' ' ') +read -r -a devicelist <<< "$devicelist" + +device=$(get_choice "Installation" "Select installation disk" "${devicelist[@]}") || exit 1 + +clear + +echo -e "\n### Setting up fastest mirrors" +reflector --country 'Germany,France,' --protocol https --sort rate --save /etc/pacman.d/mirrorlist + +echo -e "\n### Setting up partitions" +umount -R /mnt 2> /dev/null || true +cryptsetup luksClose luks 2> /dev/null || true + +lsblk -plnx size -o name "${device}" | xargs -n1 wipefs --all +sgdisk --clear "${device}" --new 1::-551MiB "${device}" --new 2::0 --typecode 2:ef00 "${device}" +sgdisk --change-name=1:primary --change-name=2:ESP "${device}" + +part_root="$(ls "${device}"* | grep -E "^${device}p?1$")" +part_boot="$(ls "${device}"* | grep -E "^${device}p?2$")" + +echo -e "\n### Formatting partitions" +mkfs.vfat -n "EFI" -F 32 "${part_boot}" +echo -n "${password}" | cryptsetup luksFormat --type luks2 --pbkdf argon2id --label luks "${part_root}" +echo -n "${password}" | cryptsetup luksOpen --allow-discards --persistent "${part_root}" luks +mkfs.btrfs -L btrfs /dev/mapper/luks + +echo -e "\n### Setting up BTRFS subvolumes" +mount /dev/mapper/luks /mnt +btrfs subvolume create /mnt/root +btrfs subvolume create /mnt/home +btrfs subvolume create /mnt/pkgs +btrfs subvolume create /mnt/aurbuild +btrfs subvolume create /mnt/archbuild +btrfs subvolume create /mnt/docker +btrfs subvolume create /mnt/logs +btrfs subvolume create /mnt/temp +btrfs subvolume create /mnt/swap +btrfs subvolume create /mnt/snapshots +umount /mnt + +mount -o noatime,compress=zstd,subvol=root /dev/mapper/luks /mnt +mkdir -p /mnt/{mnt/btrfs-root,efi,home,var/{cache/pacman,log,tmp,lib/{aurbuild,archbuild,docker}},swap,.snapshots} +mount "${part_boot}" /mnt/efi +mount -o noatime,compress=zstd,subvol=/ /dev/mapper/luks /mnt/mnt/btrfs-root +mount -o noatime,compress=zstd,subvol=home /dev/mapper/luks /mnt/home +mount -o noatime,compress=zstd,subvol=pkgs /dev/mapper/luks /mnt/var/cache/pacman +mount -o noatime,compress=zstd,subvol=aurbuild /dev/mapper/luks /mnt/var/lib/aurbuild +mount -o noatime,compress=zstd,subvol=archbuild /dev/mapper/luks /mnt/var/lib/archbuild +mount -o noatime,compress=zstd,subvol=docker /dev/mapper/luks /mnt/var/lib/docker +mount -o noatime,compress=zstd,subvol=logs /dev/mapper/luks /mnt/var/log +mount -o noatime,compress=zstd,subvol=temp /dev/mapper/luks /mnt/var/tmp +mount -o noatime,compress=zstd,subvol=swap /dev/mapper/luks /mnt/swap +mount -o noatime,compress=zstd,subvol=snapshots /dev/mapper/luks /mnt/.snapshots + +echo -e "\n### Configuring custom repo" +mkdir "/mnt/var/cache/pacman/${user}-local" + +# if [[ "${user}" == "maximbaz" && "${hostname}" == "home-"* ]]; then +# wget -m -nH -np -q --show-progress --progress=bar:force --reject='index.html*' --cut-dirs=2 -P "/mnt/var/cache/pacman/${user}-local" 'https://pkgbuild.com/~maximbaz/repo/' +# rename -- 'maximbaz.' "${user}-local." "/mnt/var/cache/pacman/${user}-local"/* +# else + repo-add "/mnt/var/cache/pacman/${user}-local/${user}-local.db.tar" +# fi + +if ! grep "${user}" /etc/pacman.conf > /dev/null; then + cat >> /etc/pacman.conf << EOF + +[${user}-local] +Server = file:///mnt/var/cache/pacman/${user}-local + +[maximbaz] +Server = https://pkgbuild.com/~maximbaz/repo + +[options] +CacheDir = /mnt/var/cache/pacman/pkg +CacheDir = /mnt/var/cache/pacman/${user}-local +EOF +fi + +echo -e "\n### Installing packages" +kernel_packages=( + "linux" + "linux-headers" + "linux-lts" + "linux-firmware" + "intel-ucode" +) +fs_packages=( + "btrfs-progs" + "dosfstools" + "e2fsprogs" +) +network_packages=( + "iwd" + "systemd-resolvconf" +) +basic_packages=( + "man-db" + "man-pages" + "pacman-contrib" + "neovim" + "bash-completion" + "git" + "rsync" + "openssh" + "htop" + "fzf" + "sudo" +) +all_packages=( + ${kernel_packages[@]} + ${fs_packages[@]} + ${network_packages[@]} + ${basic_packages[@]} +) + +pacstrap /mnt base base-devel arch-secure-boot ${all_packages[@]} + +echo -e "\n### Generating base config files" +echo "cryptdevice=PARTLABEL=primary:luks:allow-discards root=LABEL=btrfs rootflags=subvol=root rw quiet mem_sleep_default=deep" > /mnt/etc/kernel/cmdline + +genfstab -L /mnt >> /mnt/etc/fstab + +echo "FONT=$font" > /mnt/etc/vconsole.conf +echo "KEYMAP=de-latin1" >> /mnt/etc/vconsole.conf +echo "${hostname}" > /mnt/etc/hostname +sed -i 's/^#en_US\.UTF-8/en_US\.UTF-8/' /mnt/etc/locale.gen +sed -i 's/^#de_DE\.UTF-8/de_DE\.UTF-8/' /mnt/etc/locale.gen +echo "LANG=en_US.UTF-8" > /mnt/etc/locale.conf +arch-chroot /mnt locale-gen + +ln -sf /usr/share/zoneinfo/Europe/Berlin /mnt/etc/localtime + +echo "$hostname" > /mnt/etc/hostname +echo -e "127.0.0.1\tlocalhost" >>/mnt/etc/hosts +echo -e "127.0.1.1\t$hostname" >>/mnt/etc/hosts +echo -e "\n::1\tlocalhost" >>/mnt/etc/hosts + +# Propagate the systemd-resolved managed configuration to all clients (stub mode) +ln -sf /run/systemd/resolve/stub-resolv.conf /mnt/etc/resolv.conf + +cat >/mnt/etc/systemd/network/20-wired.network </mnt/etc/systemd/network/25-wireless.network </mnt/etc/iwd/main.conf </mnt/etc/mkinitcpio.conf <> /mnt/etc/fstab + +# sudo +sed -i 's/# \(%wheel ALL=(ALL:ALL) ALL\)/\1/' /mnt/etc/sudoers + +echo -e "\n### Creating user" +arch-chroot /mnt useradd -m "$user" +for group in wheel network video audio input storage power; do + arch-chroot /mnt groupadd -rf "$group" + arch-chroot /mnt gpasswd -a "$user" "$group" +done +echo "$user:$password" | arch-chroot /mnt chpasswd +# disable root login +arch-chroot /mnt passwd -dl root + +echo -e "\n### Setting permissions on the custom repo" +arch-chroot /mnt chown -R "$user:$user" "/var/cache/pacman/${user}-local/" + +echo -e "\n### Reboot now, and after power off remember to unplug the installation USB"