#!/usr/bin/env bash # arch-autoinstall.sh — automated Arch Linux base installer # # If /answerfile.json exists (e.g. embedded in the ISO via build.sh --preconf), # all prompts are answered from it. Missing fields fall back to interactive prompts. # # Answerfile fields: drive, kernel, hostname, username, encrypt, fido2_root, # fido2_user, run_tui (password always prompted interactively) set -euo pipefail ############################################ # LOGGING ############################################ LOGFILE="$HOME/arch-autoinstall.log" { echo echo "############################################" echo " Arch Auto-Install Log - Started $(date)" echo "############################################" echo } >> "$LOGFILE" exec > >(tee -a "$LOGFILE") 2>&1 ############################################ # ANSWERFILE ############################################ ANSWERFILE="${ANSWERFILE:-/answerfile.json}" AF_MODE=false [[ -f "$ANSWERFILE" ]] && AF_MODE=true af_get() { # af_get [default] local val val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true) if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi } af_bool() { # Returns YES or NO from a JSON boolean field local val; val=$(jq -r "${1} // false" "$ANSWERFILE" 2>/dev/null || true) [[ "$val" == "true" ]] && echo "YES" || echo "NO" } get_mac_suffix() { local mac mac=$(ip link show 2>/dev/null \ | awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}') printf '%s' "${mac//:/}" } if $AF_MODE; then echo "Answerfile detected: $ANSWERFILE" # Ensure jq is available command -v jq &>/dev/null || pacman -Sy --noconfirm jq fi ############################################ # SAFETY WARNING ############################################ if $AF_MODE; then echo "WARNING: Automated install — all data on $(af_get '.drive' '/dev/?') will be ERASED." echo "Proceeding in 5 seconds... (Ctrl-C to abort)" sleep 5 else echo "WARNING: This will ERASE ALL DATA on the selected drive!" read -rp "Type 'YES' to continue: " confirm [[ "$confirm" == "YES" ]] || { echo "Aborted."; exit 1; } fi ############################################ # REQUIRED PACKAGES FOR INSTALL ENVIRONMENT ############################################ pacman -Sy --noconfirm parted cryptsetup libfido2 pam-u2f ############################################ # DRIVE SELECTION ############################################ lsblk if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then DRIVE=$(af_get '.drive') echo "Drive (from answerfile): $DRIVE" else read -rp "Enter target drive (e.g., /dev/sda): " DRIVE fi ############################################ # USER INPUT ############################################ if $AF_MODE; then KERNEL=$(af_get '.kernel' 'linux') RAW_HOSTNAME=$(af_get '.hostname' '') if [[ -n "$RAW_HOSTNAME" ]]; then HOSTNAME="${RAW_HOSTNAME}-$(get_mac_suffix)" else HOSTNAME="arch" fi USERNAME=$(af_get '.username' '') ENCRYPT_DISK=$(af_bool '.encrypt') FIDO_ROOT=$(af_bool '.fido2_root') FIDO_USER=$(af_bool '.fido2_user') RUN_TUI=$(af_bool '.run_tui') echo "Kernel: $KERNEL" echo "Hostname: $HOSTNAME" echo "Username: $USERNAME" echo "Encrypt: $ENCRYPT_DISK / FIDO2 root: $FIDO_ROOT / FIDO2 user: $FIDO_USER" else read -rp "Enter kernel package (e.g., linux, linux-lts): " KERNEL read -rp "Enter hostname: " HOSTNAME read -rp "Enter username: " USERNAME read -rp "Enable disk encryption? (YES/NO): " ENCRYPT_DISK FIDO_ROOT="NO" if [[ "$ENCRYPT_DISK" == "YES" ]]; then read -rp "Enable FIDO2 unlocking for root partition? (YES/NO): " FIDO_ROOT fi read -rp "Enable FIDO2 authentication for user login? (YES/NO): " FIDO_USER fi # Password always prompted — never stored in answerfile read -rsp "Enter password for $USERNAME: " USERPASS; echo [[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; } if ! $AF_MODE; then read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _RUN_TUI_IN _RUN_TUI_IN="${_RUN_TUI_IN:-YES}" [[ "${_RUN_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO" fi ############################################ # RAM / PARTITION SIZING ############################################ RAM_GB=$(free --giga | awk '/^Mem:/ {print $2}') BOOT_SIZE=15GiB SWAP_SIZE="${RAM_GB}GiB" DISK_SIZE=$(lsblk -b -dn -o SIZE "$DRIVE") DISK_GIB=$((DISK_SIZE / 1024 / 1024 / 1024)) ROOT_GIB=$((DISK_GIB - RAM_GB - 15)) echo "Partition plan:" echo " Boot: ${BOOT_SIZE}" echo " Root: ${ROOT_GIB}GiB" echo " Swap: ${SWAP_SIZE}" ############################################ # PARTITION DISK ############################################ parted "$DRIVE" --script mklabel gpt \ mkpart ESP fat32 1MiB 15GiB \ set 1 boot on \ mkpart ROOT 15GiB "$((15 + ROOT_GIB))"GiB \ mkpart SWAP "$((15 + ROOT_GIB))"GiB 100% BOOT_PART="${DRIVE}1" ROOT_PART="${DRIVE}2" SWAP_PART="${DRIVE}3" ############################################ # FORMAT BOOT + SWAP ############################################ mkfs.fat -F32 "$BOOT_PART" mkswap "$SWAP_PART" swapon "$SWAP_PART" ############################################ # ENCRYPTION (OPTIONAL) ############################################ LUKS_BACKUP_KEY="" # path to key file, set only when encryption is active if [[ "$ENCRYPT_DISK" == "YES" ]]; then echo "Encrypting root partition..." cryptsetup -v luksFormat "$ROOT_PART" cryptsetup open "$ROOT_PART" cryptroot # ── Auto-generate backup LUKS key ────────────────────────────────────────── # A random key is enrolled as a second LUKS slot so recovery is possible # without the primary passphrase. It is written to /_LUKS_BACKUP_KEY in the # new system (inside the encrypted container) where only root can read it. LUKS_BACKUP_KEY=$(mktemp /tmp/luks-backup-key.XXXXXX) dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY" echo "Enrolling auto-generated backup LUKS key..." cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY" # ── Optional FIDO2 enrollment ───────────────────────────────────────────── if [[ "$FIDO_ROOT" == "YES" ]]; then echo "Insert FIDO2 key for LUKS and touch when prompted..." systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no fi ############################################ # BTRFS ON ENCRYPTED ROOT ############################################ mkfs.btrfs /dev/mapper/cryptroot mount /dev/mapper/cryptroot /mnt btrfs subvolume create /mnt/@ btrfs subvolume create /mnt/@home umount /mnt mount -o subvol=@ /dev/mapper/cryptroot /mnt mkdir -p /mnt/home mount -o subvol=@home /dev/mapper/cryptroot /mnt/home else echo "Skipping encryption — formatting root directly." ############################################ # BTRFS ON UNENCRYPTED ROOT ############################################ mkfs.btrfs "$ROOT_PART" mount "$ROOT_PART" /mnt btrfs subvolume create /mnt/@ btrfs subvolume create /mnt/@home umount /mnt mount -o subvol=@ "$ROOT_PART" /mnt mkdir -p /mnt/home mount -o subvol=@home "$ROOT_PART" /mnt/home fi mkdir -p /mnt/boot mount "$BOOT_PART" /mnt/boot # Place backup key inside the new system (only accessible when disk is unlocked) if [[ -n "$LUKS_BACKUP_KEY" ]]; then install -m 400 "$LUKS_BACKUP_KEY" /mnt/_LUKS_BACKUP_KEY rm -f "$LUKS_BACKUP_KEY" echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system." fi ############################################ # GPU DETECTION ############################################ GPU_INFO=$(lspci | grep -E "VGA|3D" || true) GPU_PKGS="" if echo "$GPU_INFO" | grep -qi "NVIDIA"; then GPU_PKGS="nvidia nvidia-utils" elif echo "$GPU_INFO" | grep -qi "AMD"; then GPU_PKGS="xf86-video-amdgpu" elif echo "$GPU_INFO" | grep -qi "Intel"; then GPU_PKGS="xf86-video-intel" fi echo "Detected GPU: ${GPU_INFO:-none}" ############################################ # BASE INSTALL ############################################ # shellcheck disable=SC2086 pacstrap -K /mnt base base-devel "$KERNEL" linux-firmware vim bash zsh git less btop fastfetch \ networkmanager grub cryptsetup libfido2 pam-u2f efibootmgr sudo btrfs-progs lvm2 jq $GPU_PKGS ############################################ # FSTAB ############################################ genfstab -U /mnt >> /mnt/etc/fstab ############################################ # COPY ANSWERFILE INTO NEW SYSTEM ############################################ if $AF_MODE; then install -m 644 "$ANSWERFILE" /mnt/answerfile.json fi ############################################ # PASS VARIABLES INTO CHROOT ############################################ export HOSTNAME USERNAME USERPASS ROOT_PART KERNEL FIDO_ROOT FIDO_USER ENCRYPT_DISK ############################################ # CHROOT CONFIGURATION ############################################ arch-chroot /mnt /bin/bash <<'CHROOT_EOF' set -euo pipefail # Locale echo "en_US.UTF-8 UTF-8" > /etc/locale.gen locale-gen echo "LANG=en_US.UTF-8" > /etc/locale.conf # Time / hostname ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime hwclock --systohc echo "$HOSTNAME" > /etc/hostname # NetworkManager systemctl enable NetworkManager # User useradd -m -G wheel -s /bin/zsh "$USERNAME" echo "$USERNAME:$USERPASS" | chpasswd echo "%wheel ALL=(ALL:ALL) ALL" >> /etc/sudoers ################################################### # INITRAMFS CONFIG ################################################### if [[ "$ENCRYPT_DISK" == "YES" && "$FIDO_ROOT" == "YES" ]]; then sed -i 's/^HOOKS=.*/HOOKS=(base udev systemd autodetect microcode modconf kms consolefont block sd-encrypt lvm2 btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf elif [[ "$ENCRYPT_DISK" == "YES" ]]; then sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block encrypt lvm2 btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf else sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck)/' /etc/mkinitcpio.conf fi mkinitcpio -p "$KERNEL" ################################################### # GRUB CONFIG ################################################### UUID=$(blkid -s UUID -o value "$ROOT_PART") if [[ "$ENCRYPT_DISK" == "YES" ]]; then if [[ "$FIDO_ROOT" == "YES" ]]; then KERNEL_CMD="rd.luks.name=${UUID}=cryptroot root=/dev/mapper/cryptroot" else KERNEL_CMD="cryptdevice=UUID=${UUID}:cryptroot root=/dev/mapper/cryptroot" fi else KERNEL_CMD="root=UUID=${UUID} rootflags=subvol=@" fi sed -i "s|^GRUB_CMDLINE_LINUX=.*|GRUB_CMDLINE_LINUX=\"$KERNEL_CMD\"|" /etc/default/grub grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=M-Archy-GRUB grub-mkconfig -o /boot/grub/grub.cfg ################################################### # USER FIDO2 LOGIN ################################################### if [[ "$FIDO_USER" == "YES" ]]; then mkdir -p "/home/$USERNAME/.config/Yubico" echo "Insert FIDO2 key for user login and touch when prompted..." sudo -u "$USERNAME" pamu2fcfg -u "$USERNAME" > "/home/$USERNAME/.config/Yubico/u2f_keys" chown "$USERNAME":"$USERNAME" "/home/$USERNAME/.config/Yubico/u2f_keys" echo "auth required pam_u2f.so" >> /etc/pam.d/system-local-login fi ################################################### # CLONE DOTFILES ################################################### echo "Cloning dotfiles..." git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git "/home/$USERNAME/Dotfiles" \ && chown -R "$USERNAME":"$USERNAME" "/home/$USERNAME/Dotfiles" \ || echo "Warning: dotfiles clone failed — clone manually after first boot." CHROOT_EOF ############################################ # DOTFILES TUI SETUP (in-chroot, optional) ############################################ if [[ "${RUN_TUI^^}" == "YES" ]]; then echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" \ | arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null echo "Running tui-install.sh as ${USERNAME} inside chroot..." arch-chroot /mnt runuser -u "${USERNAME}" -- \ bash "/home/${USERNAME}/Dotfiles/setup/tui-install.sh" \ || echo "Warning: tui-install exited with errors — check ~/dotfiles-install.log in the new system." arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd fi # Remove answerfile from new system after setup is complete (contains sensitive paths/config) if $AF_MODE && [[ -f /mnt/answerfile.json ]]; then rm -f /mnt/answerfile.json fi ############################################ # SUMMARY ############################################ echo echo "############################################" echo " INSTALL SUMMARY" echo "############################################" echo "Drive: $DRIVE" echo "Boot partition: $BOOT_PART" echo "Root partition: $ROOT_PART" echo "Swap partition: $SWAP_PART" echo echo "Hostname: $HOSTNAME" echo "Username: $USERNAME" echo "Kernel: $KERNEL" echo "GPU detected: ${GPU_INFO:-none}" echo echo "Disk encryption: $ENCRYPT_DISK" echo "FIDO2 root unlock: $FIDO_ROOT" echo "FIDO2 user login: $FIDO_USER" [[ "$ENCRYPT_DISK" == "YES" ]] && echo "LUKS backup key: /_LUKS_BACKUP_KEY (in new system)" echo echo "Boot size: $BOOT_SIZE" echo "Root size: ${ROOT_GIB}GiB" echo "Swap size: $SWAP_SIZE" echo echo "Log file: $LOGFILE" echo "############################################" echo cp "$LOGFILE" /mnt/boot/ 2>/dev/null || true echo "Installation complete! Unmount and reboot:" echo " umount -R /mnt && reboot" if [[ "${RUN_TUI^^}" != "YES" ]]; then echo echo "After first boot, login as $USERNAME and run:" echo " ~/Dotfiles/setup/tui-install.sh" fi