install.sh -
#!/bin/bash
set -euo pipefail
--- Prompt Section (collect all user input here) ---
Disk Selection

echo "Available disks:"
lsblk -d -o NAME,SIZE,MODEL
while true; do
read -p "Enter Disk (e.g. sda, nvme0n1): " disk_input
disk="/dev/${disk_input%/}"
if [ ! -b "$disk" ]; then
echo "Disk $disk does not exist. Try again."
continue
fi
break
done
Hostname

while true; do
read -p "Hostname: " hostname
[[ -z "$hostname" ]] && echo "Hostname cannot be empty." && continue
break
done
Root Password

while true; do
read -s -p "Root password: " root_password
echo
read -s -p "Confirm root password: " root_password2
echo
[[ "$root_password" != "$root_password2" ]] && echo "Passwords do not match." && continue
[[ -z "$root_password" ]] && echo "Password cannot be empty." && continue
break
done
Username

while true; do
read -p "Username: " user
[[ -z "$user" ]] && echo "Username cannot be empty." && continue
break
done
User Password

while true; do
read -s -p "User password: " user_password
echo
read -s -p "Confirm user password: " user_password2
echo
[[ "$user_password" != "$user_password2" ]] && echo "Passwords do not match." && continue
[[ -z "$user_password" ]] && echo "Password cannot be empty." && continue
break
done
Export variables for later use

export disk hostname root_password user user_password
Partition Naming

if [[ "$disk" == nvme ]]; then
part1="${disk}p1"
part2="${disk}p2"
else
part1="${disk}1"
part2="${disk}2"
fi
Partitioning --

parted -s "$disk" mklabel gpt
parted -s "$disk" mkpart ESP fat32 1MiB 1025MiB
parted -s "$disk" set 1 esp on
parted -s "$disk" mkpart primary btrfs 1025MiB 100%
Formatting

mkfs.fat -F32 "$part1"
mkfs.btrfs -f -L ROOT "$part2"

mount "$part2" /mnt
--
mount -o subvolid=5 "$part2" /mnt
btrfs subvolume delete /mnt/@ || true

btrfs subvolume create /mnt/@
[ ! -d /mnt/@home ] && btrfs subvolume create /mnt/@home
[ ! -d /mnt/@var ] && btrfs subvolume create /mnt/@var

umount /mnt

mount -o noatime,compress=lzo,ssd,space_cache=v2,discard=async,subvol=@ "$part2" /mnt
mkdir -p /mnt/{home,var}
mount -o noatime,compress=lzo,ssd,space_cache=v2,discard=async,subvol=@home "$part2" /mnt/home
mount -o noatime,compress=lzo,ssd,space_cache=v2,discard=async,subvol=@var "$part2" /mnt/var
Mount EFI System Partition

mkdir -p /mnt/boot
mount "$part1" /mnt/boot
Base Installation

install_pkgs=(
base base-devel linux-zen linux-zen-headers linux-firmware
sudo man-db man-pages btrfs-progs networkmanager network-manager-applet bash-completion ananicy-cpp zram-generator
ntfs-3g exfat-utils mtools dosfstools intel-ucode inotify-tools
grub grub-btrfs efibootmgr os-prober snapper
qemu-desktop virt-manager vde2 dnsmasq libvirt bridge-utils openbsd-netcat
openssh ncdu bat eza fzf git github-cli ripgrep ripgrep-all sqlite dysk cronie ufw clamav
sassc udiskie gvfs yt-dlp aria2 unrar 7zip unzip rsync jq reflector polkit polkit-gnome
pipewire wireplumber pipewire-pulse pipewire-alsa pipewire-audio pipewire-jack
xorg-xwayland xdg-desktop-portal-wlr xdg-desktop-portal-gtk
sway swaybg swaylock swayidle swayimg autotiling flatpak
mpv fuzzel qalculate-gtk discord firefox zathura kanshi pcmanfm-gtk3 gimp
easyeffects lsp-plugins-lv2 mda.lv2 zam-plugins-lv2 calf
foot nvtop htop fastfetch newsboat neovim tmux asciinema
papirus-icon-theme noto-fonts noto-fonts-cjk noto-fonts-emoji ttc-iosevka ttf-iosevkaterm-nerd gnu-free-fonts
wl-clip-persist wl-clipboard cliphist libnotify swaync grim slurp swayosd
texlive-latex pandoc zathura-pdf-mupdf hunspell hunspell-en_us nuspell enchant languagetool
lua python uv python-black stylua pyright
)
Rate and install the base system

reflector --country 'India' --latest 10 --age 24 --sort rate --save /etc/pacman.d/mirrorlist
Pacstrap with error handling

set +e
pacstrap /mnt "${install_pkgs[@]}"
if [ $? -ne 0 ]; then
echo "pacstrap failed. Please check the package list and network connection."
exit 1
fi
set -e
System Configuration

genfstab -U /mnt >> /mnt/etc/fstab
Exporting variables for chroot

cat > /mnt/root/install.conf <<EOF
hostname=$hostname
root_password=$root_password
user=$user
user_password=$user_password
EOF
Run chroot.sh

cp "$(dirname "$0")/chroot.sh" /mnt/root/chroot.sh
chmod +x /mnt/root/chroot.sh
arch-chroot /mnt /root/chroot.sh
Unmount and finalize

umount -lR /mnt
echo "Installation completed. Please reboot your system."

chroot.sh -
#!/bin/bash
set -euo pipefail
Configuration

timezone="Asia/Kolkata"
Load variables from install.conf

source /root/install.conf
--- Set hostname ---

echo "$hostname" > /etc/hostname
echo "127.0.0.1 localhost" > /etc/hosts
echo "::1 localhost" >> /etc/hosts
echo "127.0.1.1 $hostname.localdomain $hostname" >> /etc/hosts
--- Set root password ---

echo "root:$root_password" | chpasswd
--- Create user and set password ---

if ! id "$user" &>/dev/null; then
useradd -m -G wheel,storage,power,video,audio,libvirt,kvm -s /bin/bash "$user"
echo "$user:$user_password" | chpasswd
else
echo "User $user already exists, skipping creation."
fi
Local Setup

ln -sf "/usr/share/zoneinfo/$timezone" /etc/localtime
hwclock --systohc
sed -i 's/#en_US.UTF-8/en_US.UTF-8/g' /etc/locale.gen
locale-gen
echo "LANG=en_US.UTF-8" > /etc/locale.conf
Sudo Configuration

echo "%wheel ALL=(ALL) ALL" > /etc/sudoers.d/wheel
Bootloader

grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
sed -i '/^#GRUB_DISABLE_OS_PROBER=false$/c\GRUB_DISABLE_OS_PROBER=false' /etc/default/grub
grub-mkconfig -o /boot/grub/grub.cfg
Reflector and pacman Setup

sed -i '/^#Color$/c\Color' /etc/pacman.conf
mkdir -p /etc/xdg/reflector
cat > /etc/xdg/reflector/reflector.conf << REFCONF
--save /etc/pacman.d/mirrorlist
--protocol https
--country India
--latest 10
--age 24
--sort rate
REFCONF
systemctl enable reflector.timer
Copy config and dotfiles as the user

su - "$user" -c '
mkdir -p ~/Downloads ~/Documents/home ~/Public ~/Templates ~/Videos ~/Pictures/Screenshots ~/.config

git clone https://github.com/zedonix/scripts.git ~/.scripts
git clone https://github.com/zedonix/dotfiles.git ~/.dotfiles
git clone https://github.com/zedonix/GruvboxGtk.git ~/Downloads/GruvboxGtk

cp ~/.dotfiles/archpfp.png ~/Pictures/
ln -sf ~/.dotfiles/.bashrc ~/.bashrc
ln -sf ~/.dotfiles/home.html ~/Documents/home/home.html
ln -sf ~/.dotfiles/archlinux.png ~/Documents/home/archlinux.png

for link in foot fuzzel newsboat nvim sway tmux zathura swaync mpv mako; do
ln -sf ~/.dotfiles/.config/$link/ ~/.config
done
git clone https://github.com/tmux-plugins/tpm ~/.config/tmux/plugins/tpm
'
Polkit/Firefox policy

mkdir -p /etc/firefox/policies
ln -sf /home/"$user"/.dotfiles/policies.json /etc/firefox/policies/policies.json
Services

systemctl enable NetworkManager
systemctl enable libvirtd
freshclam
systemctl enable clamav-daemon.service
Clean up package cache and Wrapping up

pacman -Scc --noconfirm

echo "Chroot configuration complete."

run.sh -
#!/usr/bin/env bash

set -e
mkdir -p "$XDG_STATE_HOME"/bash

SRC_DIR="/home/${USER}/Downloads/GruvboxGtk"
DEST_DIR="${HOME}/.local/share"
THEME_NAME="Gruvbox-Dark"
THEME_DIR="${DEST_DIR}/${THEME_NAME}"
rm -rf "${THEME_DIR}"
mkdir -p "${THEME_DIR}"
--- GTK2 ---

mkdir -p "${THEME_DIR}/gtk-2.0"
cp -r "${SRC_DIR}/main/gtk-2.0/common/"'.rc' "${THEME_DIR}/gtk-2.0" 2>/dev/null || true
cp -r "${SRC_DIR}/assets/gtk-2.0/assets-common-Dark" "${THEME_DIR}/gtk-2.0/assets" 2>/dev/null || true
cp -r "${SRC_DIR}/assets/gtk-2.0/assets-Dark/".png "${THEME_DIR}/gtk-2.0/assets" 2>/dev/null || true
--- GTK3 ---

mkdir -p "${THEME_DIR}/gtk-3.0"
cp -r "${SRC_DIR}/assets/gtk/scalable" "${THEME_DIR}/gtk-3.0/assets" 2>/dev/null || true
if [ -f "${SRC_DIR}/main/gtk-3.0/gtk-Dark.scss" ]; then
sassc -M -t expanded "${SRC_DIR}/main/gtk-3.0/gtk-Dark.scss" "${THEME_DIR}/gtk-3.0/gtk.css"
cp "${THEME_DIR}/gtk-3.0/gtk.css" "${THEME_DIR}/gtk-3.0/gtk-dark.css"
fi
--- GTK4 ---

mkdir -p "${THEME_DIR}/gtk-4.0"
cp -r "${SRC_DIR}/assets/gtk/scalable" "${THEME_DIR}/gtk-4.0/assets" 2>/dev/null || true
if [ -f "${SRC_DIR}/main/gtk-4.0/gtk-Dark.scss" ]; then
sassc -M -t expanded "${SRC_DIR}/main/gtk-4.0/gtk-Dark.scss" "${THEME_DIR}/gtk-4.0/gtk.css"
cp "${THEME_DIR}/gtk-4.0/gtk.css" "${THEME_DIR}/gtk-4.0/gtk-dark.css"
fi
--- index.theme ---

cat > "${THEME_DIR}/index.theme" <<EOF
[Desktop Entry]
Type=X-GNOME-Metatheme
Name=${THEME_NAME}
Comment=Gruvbox Dark GTK Theme
EOF
gsettings set org.gnome.desktop.interface gtk-theme 'Gruvbox-Dark'
gsettings set org.gnome.desktop.interface icon-theme "Papirus-Dark"
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'

echo "Gruvbox setup Done"
Mime setup

find /usr/share/applications -iname '*.desktop' -print0 | while IFS= read -r -d $'\0' d; do
for m in $(grep MimeType "$d" | cut -d= -f2 | tr ";" " "); do
xdg-mime default "$(basename "$d")" "$m"
done
done
for type in pdf x-pdf fdf xdp xfdf pdx; do xdg-mime default org.pwmt.zathura.desktop application/$type; done
for type in jpeg svg png gif webp bmp tiff; do xdg-mime default swayimg.desktop image/$type; done
Snapper setup

sudo snapper -c root create-config / || true
sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer
sudo systemctl enable --now grub-btrfsd
Libvirt setup

sudo virsh net-autostart default
Firefox user.js linking

firefox
if [ -d ~/.mozilla/firefox ]; then
dir=$(ls ~/.mozilla/firefox/ | grep ".default-release" | head -n1)
if [ -n "$dir" ]; then
ln -sf /home/$USER/.dotfiles/user.js /home/$USER/.mozilla/firefox/$dir/user.js
fi
fi
UFW setup

sudo ufw allow 20/tcp
sudo ufw allow 21/tcp
sudo ufw limit 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 6881:6999/tcp
sudo ufw allow 6881:6999/udp
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable
sudo systemctl enable ufw
Enable profile-sync-daemon
systemctl --user enable --now psd
Enable ananicy-cpp(nice value) and fstrim(ssd)

sudo systemctl enable --now ananicy-cpp.service
sudo systemctl enable fstrim.timer
Flatpak setup

flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
zram setup

printf '[zram0]\nzram-size = ram * 2\ncompression-algorithm = zstd\nswap-priority = 100\nfs-type = swap\n' | sudo tee /etc/systemd/zram-generator.conf
Running aur.sh

bash ~/.scripts/aur.sh

I want to use timeshift instead of snapper for snapshotting