#!/bin/bash # ArchSetup - Craig Jennings # https://cjennings.net/archsetup # License: GNU GPLv3 # Commentary # # There are two levels of errors: # * CRASH: Issues that will halt forward progress, aborting this script. # * ERROR: Issues not serious enough to halt the script. # Both are printed on screen and in the $logfile. # Stderr is also printed to the $logfile for all relevant info. # # This script creates a tmpfs RAM disk for all compilation. This speeds up # building and installing, and all source code no longer exists after reboot. # # # Code # uncomment to stop on any error # set -e ### Root Check if [ "$EUID" -ne 0 ]; then echo "ERROR: This script must be run as root" echo "Usage: sudo $0" exit 1 fi ### Parse Arguments skip_slow_packages=false fresh_install=false show_status_only=false skip_gpu_drivers=false enable_autologin="" # empty=auto-detect, true=force enable, false=skip while [ $# -gt 0 ]; do case "$1" in --skip-slow-packages) skip_slow_packages=true shift ;; --fresh) fresh_install=true shift ;; --status) show_status_only=true shift ;; --no-gpu-drivers) skip_gpu_drivers=true shift ;; --autologin) enable_autologin=true shift ;; --no-autologin) enable_autologin=false shift ;; --help|-h) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --skip-slow-packages Skip texlive-meta and topgrade" echo " --fresh Start fresh, ignore previous progress" echo " --status Show installation progress and exit" echo " --no-gpu-drivers Skip GPU driver detection/installation" echo " --autologin Enable automatic console login" echo " --no-autologin Disable automatic console login" echo " --help, -h Show this help message" exit 0 ;; *) echo "Unknown option: $1" echo "Usage: $0 [--skip-slow-packages] [--fresh] [--status] [--no-gpu-drivers] [--autologin]" exit 1 ;; esac done ### Constants username="cjennings" password="welcome" # will be changed on first login. :) archsetup_dir="$(dirname "$(readlink -f "$0")")" dotfiles_dir="$archsetup_dir/dotfiles" dwm_repo="https://git.cjennings.net/dwm.git" dmenu_repo="https://git.cjennings.net/dmenu.git" st_repo="https://git.cjennings.net/st.git" slock_repo="https://git.cjennings.net/slock.git" dotemacs_repo="https://git.cjennings.net/dotemacs.git" logfile="/var/log/archsetup-$(date +'%Y-%m-%d-%H-%M-%S').log" source_dir="/home/$username/.local/src" # aur/git source goes here packages_before="/var/log/archsetup-preexisting-package-list.txt" packages_after="/var/log/archsetup-post-install-package-list.txt" archsetup_packages="/var/log/archsetup-installed-packages.txt" min_disk_space_gb=20 state_dir="/var/lib/archsetup/state" error_messages=() ### Cleanup Trap # Ensures tmpfs is unmounted if script exits unexpectedly cleanup() { if mountpoint -q "$source_dir" 2>/dev/null; then umount "$source_dir" 2>/dev/null fi } trap cleanup EXIT ### State Tracking # Enables resuming from where the script left off if interrupted. # State is stored as marker files in $state_dir. # Use --fresh to start over, --status to check progress. step_completed() { [ -f "$state_dir/$1" ] } mark_complete() { mkdir -p "$state_dir" echo "$(date +'%Y-%m-%d %H:%M:%S')" > "$state_dir/$1" } run_step() { step_name="$1" step_func="$2" if step_completed "$step_name"; then printf "Skipping %s (already completed)\n" "$step_name" return 0 fi if $step_func; then mark_complete "$step_name" return 0 else printf "FAILED: %s\n" "$step_name" printf "To retry this step, remove: %s/%s\n" "$state_dir" "$step_name" return 1 fi } show_status() { echo "Archsetup State Status" echo "======================" echo "State directory: $state_dir" echo "" if [ ! -d "$state_dir" ]; then echo "No state found. Script has not been run or was run with --fresh." exit 0 fi echo "Completed steps:" for step in intro prerequisites create_user user_customizations \ aur_installer essential_services xorg dwm \ desktop_environment developer_workstation \ supplemental_software boot_ux; do if step_completed "$step"; then timestamp=$(cat "$state_dir/$step") printf " [x] %-25s (%s)\n" "$step" "$timestamp" else printf " [ ] %-25s\n" "$step" fi done exit 0 } # Handle --status flag (must be after state_dir is defined) if $show_status_only; then show_status fi # Handle --fresh flag if $fresh_install; then echo "Starting fresh installation (removing previous state)..." rm -rf "$state_dir" fi ### Pre-flight Checks preflight_checks() { echo "Running pre-flight checks..." # Check disk space (need at least 20GB free on root partition) available_kb=$(df / | awk 'NR==2 {print $4}') available_gb=$((available_kb / 1024 / 1024)) if [ "$available_gb" -lt "$min_disk_space_gb" ]; then echo "ERROR: Insufficient disk space" echo " Required: ${min_disk_space_gb}GB" echo " Available: ${available_gb}GB" echo " Free up disk space before running archsetup." exit 1 fi echo " [OK] Disk space: ${available_gb}GB available" # Check network connectivity if ! ping -c 1 -W 5 archlinux.org > /dev/null 2>&1; then echo "ERROR: No network connectivity" echo " Cannot reach archlinux.org" echo " Ensure network is configured before running archsetup." echo " Try: ip link, dhcpcd, or nmtui" exit 1 fi echo " [OK] Network: archlinux.org reachable" # Check pacman is available if ! command -v pacman > /dev/null 2>&1; then echo "ERROR: pacman not found" echo " This script requires Arch Linux with pacman installed." exit 1 fi echo " [OK] Package manager: pacman available" # Check we're on Arch Linux if [ ! -f /etc/arch-release ]; then echo "ERROR: Not running on Arch Linux" echo " This script is designed for Arch Linux only." exit 1 fi echo " [OK] System: Arch Linux detected" echo "Pre-flight checks passed." echo "" } ### Intro intro() { printf "\n\nArchSetup launched @ %s\n" "$(date +'%D %T')"| tee -a "$logfile" if $skip_slow_packages; then printf "Running with --skip-slow-packages (skipping texlive-meta, topgrade)\n" | tee -a "$logfile" fi errors_encountered=0 # begin with a clean logfile [ -f "$logfile" ] && rm -f "$logfile" touch "$logfile" # count the arch packages before install pacman -Q > "$packages_before" || \ error "crash" "generating pre-install package list" "$?" } ### General Functions # Error error () { # $1 = type ERROR, noted but the script will continue to run. # anything else will produce CRASH, which halts the script # $2 = what was happening (e.g., "adding $username to group $groupname") # $3 = the error code (i.e., "$?") errors_encountered=$((errors_encountered+1)) case "$1" in "error") msg="$2 (error code: $3)" error_messages+=("$msg") printf "ERROR: %s @ %s\n" "$msg" "$(date +'%T')" | tee -a "$logfile" return 1; ;; *) printf "CRASH: %s failed with error: %s @ %s. Script halted.\n" \ "$2" "$3" "$(date +'%T')" | tee -a "$logfile" exit 1; ;; esac } # Display display () { # $1 = type (TITLE, ACTION) # $2 = description (answers: "what are you trying to do?") case "$1" in "title") printf "\n##### %s\n" "$2" | tee -a "$logfile" return 0; ;; "subtitle") printf "\n%s\n" "$2" | tee -a "$logfile" return 0; ;; "task") printf "...%s @ %s\n" "$2" "$(date +'%T')" | tee -a "$logfile" return 0; ;; *) printf "CRASH: display () called with incorrect arguments.\n" printf "...called %s type, %s action @ %s\n" \ "$1" "$2" "$(date +'%T')" | tee -a "$logfile" exit 1; ;; esac } # Pacman Install pacman_install() { action="installing $1 via pacman" && display "task" "$action" if ! (pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1); then action="retrying $1" && display "task" "$action" if ! (pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1); then action="retrying $1 once more" && display "task" "$action" (pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1) || error "error" "$action" "$?" fi fi } # Git Install git_install() { prog_name="$(basename "$1" .git)" build_dir="$source_dir/$prog_name" action="building & installing $prog_name from source" display "task" "$action" if ! (sudo -u "$username" git clone --depth 1 "$1" "$build_dir" >> "$logfile" 2>&1); then error "error" "cloning source code for $prog_name - directory may exist, removing and retrying" "$?" (rm -rf "$build_dir" >> "$logfile" 2>&1) || \ error "error" "removing existing directory for $prog_name" "$?" (sudo -u "$username" git clone --depth 1 "$1" "$build_dir" >> "$logfile" 2>&1) || \ error "crash" "re-cloning source code for $prog_name after cleanup" "$?" fi (cd "$build_dir" && make install >> "$logfile" 2>&1) || \ error "error" "building $prog_name from source code" "$?" } # AUR Install aur_install() { action="installing $1 via the AUR" && display "task" "$action" if ! (sudo -u "$username" yay -S --noconfirm "$1" >> "$logfile" 2>&1); then action="retrying $1" && display "task" "$action" if ! (sudo -u "$username" yay -S --noconfirm "$1" >> "$logfile" 2>&1); then action="retrying $1 once more" && display "task" "$action" (sudo -u "$username" yay -S --noconfirm "$1" >> "$logfile" 2>&1) || error "error" "$action" "$?" fi fi } # PIP Install (using pipx for isolated environments) pip_install() { [ -x "$(command -v "pipx")" ] || pacman_install python-pipx action="installing $1 via pipx" && display "task" "$action" (sudo -u "$username" pipx install "$1" >> "$logfile" 2>&1) || \ error "error" "$action" "$?" } # Filesystem Detection is_zfs_root() { # Returns 0 (true) if root filesystem is ZFS, 1 (false) otherwise [ "$(findmnt -n -o FSTYPE /)" = "zfs" ] } # Encryption Detection is_encrypted_root() { # Returns 0 (true) if root filesystem is on an encrypted volume # Detects both LUKS (dm-crypt) and ZFS native encryption # Check for LUKS/dm-crypt: root device path contains dm- and backing device is crypt type local root_dev root_dev=$(findmnt -n -o SOURCE /) if lsblk -nlo TYPE "$root_dev" 2>/dev/null | grep -q "crypt"; then return 0 fi # Check for ZFS native encryption if is_zfs_root; then local root_dataset root_dataset=$(findmnt -n -o SOURCE /) local encryption encryption=$(zfs get -H -o value encryption "$root_dataset" 2>/dev/null) if [ -n "$encryption" ] && [ "$encryption" != "off" ]; then return 0 fi fi return 1 } # Automatic Login Configuration configure_autologin() { local do_autologin=false # Determine whether to enable autologin if [ "$enable_autologin" = "true" ]; then do_autologin=true elif [ "$enable_autologin" = "false" ]; then do_autologin=false else # Auto-detect: only prompt if root is encrypted if is_encrypted_root; then display "task" "Encrypted root detected" echo "" echo "Since the disk is encrypted, you already authenticate at boot." echo "Automatic login skips the redundant login prompt after decryption." echo "" read -r -p "Enable automatic console login for $username? [Y/n] " response case "$response" in [nN][oO]|[nN]) do_autologin=false ;; *) do_autologin=true ;; esac else # Not encrypted, skip autologin silently return 0 fi fi if [ "$do_autologin" = "true" ]; then action="configuring automatic console login" && display "task" "$action" mkdir -p /etc/systemd/system/getty@tty1.service.d cat << EOF > /etc/systemd/system/getty@tty1.service.d/autologin.conf [Service] ExecStart= ExecStart=-/sbin/agetty -o '-p -f -- \\\\u' --noclear --autologin $username %I \$TERM EOF else display "task" "Skipping automatic login configuration" fi return 0 } # GPU Driver Installation install_gpu_drivers() { if $skip_gpu_drivers; then display "task" "Skipping GPU driver installation (--no-gpu-drivers)" return 0 fi display "subtitle" "GPU Drivers" # Detect GPU vendors via lspci gpu_info=$(lspci 2>/dev/null | grep -E "VGA|3D") if echo "$gpu_info" | grep -qi "intel"; then display "task" "Intel GPU detected - installing drivers" pacman_install mesa pacman_install intel-media-driver # hardware video acceleration pacman_install vulkan-intel # Vulkan support fi if echo "$gpu_info" | grep -qi "amd\|radeon"; then display "task" "AMD GPU detected - installing drivers" pacman_install mesa pacman_install xf86-video-amdgpu pacman_install vulkan-radeon pacman_install libva-mesa-driver # hardware video acceleration fi if echo "$gpu_info" | grep -qi "nvidia"; then display "task" "NVIDIA GPU detected - installing drivers" pacman_install nvidia-dkms # DKMS version for kernel flexibility pacman_install nvidia-utils pacman_install nvidia-settings pacman_install libva-nvidia-driver # hardware video acceleration fi # Fallback for VMs or unknown hardware if ! echo "$gpu_info" | grep -qiE "intel|amd|radeon|nvidia"; then display "task" "No discrete GPU detected - installing generic drivers" pacman_install mesa pacman_install xf86-video-vesa fi } ### Prerequisites prerequisites() { # why these software packages are 'required' # linux-firmware - ensuring hardware can be detected properly # wireless-regdb - regulatory database for wireless (prevents kernel warnings) # base_devel - required tools to compile # ca_certificates - for validation of keyrings, etc. # coreutils - comparing package lists # curl - to transfer source code # git - tools required to work with git source respositories # go - required to build yay, the aur installer # ntp - must communicate with other servers in synchronized manner # python - required for python pip installs # stow - places the dotfiles (see: https://bit.ly/41GmysO) # tar - extract unix archives # vi - should things go wrong, we'll need an editor # zsh - we need a shell interpreter for yay; this one's mine display "title" "Prerequisites" display "subtitle" "Bootstrapping" action="ensuring current Arch Linux keyring" && display "task" "$action" (pacman -Syy) >> "$logfile" 2>&1 || error "crash" "$action" "$?" (pacman -S --noconfirm archlinux-keyring) >> "$logfile" 2>&1 || \ error "crash" "$action" "$?" display "task" "verifying Arch Linux keys" (pacman-key --populate archlinux >> "$logfile" 2>&1) || \ error "crash" "verifying Arch Linux keys" "$?" action="refreshing the package cache" && display "task" "$action" (pacman -Syu --noconfirm >> "$logfile" 2>&1) || error "crash" "$action" "$?" display "subtitle" "Required Software" for software in linux-firmware wireless-regdb base-devel ca-certificates \ coreutils curl git go ntp openssh python \ stow tar vi zsh; do pacman_install "$software" done display "subtitle" "Environment Configuration" # sync the time on this machine action="synchronizing system time" && display "task" "$action" (ntpdate 0.us.pool.ntp.org >> "$logfile" 2>&1) || error "error" "$action" "$?" action="configuring compiler to use all processor cores" && display "task" "$action" sed -i "s/-j2/-j$(nproc)/;s/^#MAKEFLAGS/MAKEFLAGS/" /etc/makepkg.conf >> "$logfile" 2>&1 action="disabling debug packages in makepkg" && display "task" "$action" sed -i 's/^OPTIONS=.*/OPTIONS=(strip docs !libtool !staticlibs emptydirs zipman purge !debug)/' /etc/makepkg.conf >> "$logfile" 2>&1 # enable pacman concurrent downloads and color action="enabling concurrent downloads" && display "task" "$action" sed -i "s/^#ParallelDownloads.*$/ParallelDownloads = 10/;s/^#Color$/Color/" /etc/pacman.conf action="Package Mirrors" && display "subtitle" "$action" pacman_install reflector action="configuring reflector" && display "task" "$action" cat << 'EOF' > /etc/xdg/reflector/reflector.conf --connection-timeout 3 --download-timeout 3 --protocol https --age 12 --latest 20 --score 10 --fastest 5 --sort score --save /etc/pacman.d/mirrorlist EOF # Skip running reflector during installation - it can cause SSH disconnections during testing # The reflector.timer will update mirrors automatically later # action="updating repository mirrors" && display "task" "$action" # (reflector --connection-timeout 3 \ # --download-timeout 3 \ # --protocol https \ # --age 12 \ # --latest 20 \ # --score 10 \ # --fastest 5 \ # --sort score \ # --save /etc/pacman.d/mirrorlist > /dev/null 2>&1) action="enabling the reflector timer" && display "task" "$action" (systemctl enable reflector.timer >> "$logfile" 2>&1) || \ error "error" "$action" "$?" action="replacing sudoers file if new package version exists" && display "task" "$action" [ -f /etc/sudoers.pacnew ] && cp /etc/sudoers.pacnew /etc/sudoers >> "$logfile" 2>&1 action="creating a directory to build/install software from git/AUR." (mkdir -p "$source_dir") || error "crash" "creating the directory $source_dir" } ### Create User create_user () { display "title" "User Creation" display "task" "checking if user exists" # halt if $username exists ( id -u "$username" >/dev/null 2>&1; ) && \ error "crash" "user '$username' already exists!" # create $username with home, group, shell, password action="creating user and home directory" && display "task" "$action" (useradd -m -G wheel -s /bin/zsh "$username" >> "$logfile" 2>&1) || \ error "crash" "adding user '$username" "$?" display "task" "assigning the password" echo "$username:$password" | chpasswd # any text is allowable! be careful! display "task" "adding to appropriate groups" (usermod -aG \ sys,adm,network,scanner,power,uucp,audio,lp,rfkill,video,storage,optical,users \ "$username" >> "$logfile" 2>&1) || error "crash" "adding $username to groups" "$?" display "task" "configuring shell" # zsh cache required: $username will install via yay; zsh will run those commands mkdir -p "/home/$username/.cache/zsh/" # give $username sudo nopasswd rights (required for aur installs) display "task" "granting permissions" (echo "%$username ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers) \ || error "error" "$action" "$?" # mount as ramdisk to speed aur/git build/installs (mount -t tmpfs -o size=4G archsetup "$source_dir" >> "$logfile" 2>&1) || \ error "crash" "mounting the RAM disk for archsetup" "$?" (chown -R "$username":wheel "$source_dir" >> "$logfile" 2>&1) || \ error "crash" "changing ownership of $source_dir" "$?" # Bootstrap DNS for git clones and AUR installs (full config in essential_services) if [ ! -L /etc/resolv.conf ] || [ "$(readlink /etc/resolv.conf)" != "/run/systemd/resolve/stub-resolv.conf" ]; then display "task" "bootstrapping DNS resolution" # Must start systemd-resolved first - stub-resolv.conf only exists when it's running systemctl start systemd-resolved >> "$logfile" 2>&1 || \ error "error" "starting systemd-resolved for DNS bootstrap" "$?" ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf >> "$logfile" 2>&1 || \ error "error" "bootstrapping DNS resolution" "$?" fi } ### User Customizations user_customizations() { action="User Customizations" && display "title" "$action" # Clone archsetup to user's home directory so dotfile symlinks are accessible. # This ensures symlinks point to a user-readable location regardless of how # archsetup was invoked (curl|bash, from /root, etc.) user_archsetup_dir="/home/$username/code/archsetup" action="cloning archsetup to user's home directory" && display "task" "$action" (mkdir -p "$(dirname "$user_archsetup_dir")" && \ git clone --depth 1 https://git.cjennings.net/archsetup.git "$user_archsetup_dir" && \ chown -R "$username": "/home/$username/code") \ >> "$logfile" 2>&1 || error "error" "$action" "$?" # Update dotfiles_dir to point to user-accessible location dotfiles_dir="$user_archsetup_dir/dotfiles" action="linking dotfiles into place" && display "task" "$action" (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt system \ >> "$logfile" 2>&1 ) || error "error" "$action" "$?" action="restoring dotfile versions" && display "task" "$action" (cd "$dotfiles_dir" && git config --global --add safe.directory "$user_archsetup_dir" && \ git restore . >> "$logfile" 2>&1 ) || error "error" "$action" "$?" action="creating common directories" && display "task" "$action" # Create default directories and grant permissions { mkdir -p -m 751 "/home/$username/code" mkdir -p -m 751 "/home/$username/documents" mkdir -p -m 751 "/home/$username/downloads/torrents/complete" mkdir -p -m 751 "/home/$username/downloads/torrents/incomplete" mkdir -p -m 751 "/home/$username/downloads/torrents/files" mkdir -p -m 751 "/home/$username/downloads/ebooks" mkdir -p -m 751 "/home/$username/music" mkdir -p -m 751 "/home/$username/projects" mkdir -p -m 751 "/home/$username/pictures/screenshots" mkdir -p -m 751 "/home/$username/videos" mkdir -p -m 751 "/home/$username/vms" chown -R "$username": "/home/$username" mkdir -p -m 751 /media/backup mkdir -p -m 751 /media/remote0 mkdir -p -m 751 /media/remote1 mkdir -p -m 751 /media/remote2 chown -R "$username": /media } >> "$logfile" 2>&1 } ### AUR Installer aur_installer () { display "title" "AUR Installer" yay_repo="https://aur.archlinux.org/yay.git" build_dir="$source_dir/yay" display "task" "fetching source code for yay" if ! (sudo -u "$username" git clone --depth 1 "$yay_repo" "$build_dir" >> "$logfile" 2>&1); then error "error" "cloning source code for yay - directory may exist, removing and retrying" "$?" (rm -rf "$build_dir" >> "$logfile" 2>&1) || \ error "crash" "removing existing directory for yay" "$?" (sudo -u "$username" git clone --depth 1 "$yay_repo" "$build_dir" >> "$logfile" 2>&1) || \ error "crash" "re-cloning source code for yay after cleanup" "$?" fi action="packaging and installing yay"; display "task" "$action" (cd "$build_dir" && sudo -u "$username" makepkg --noconfirm -si >> "$logfile" 2>&1) || \ error "crash" "$action" "$?" } ### Essential Services essential_services() { display "title" "Essential Services" # Randomness display "subtitle" "Randomness" pacman_install rng-tools action="enabling rngd service" && display "task" "$action" systemctl enable rngd >> "$logfile" 2>&1 || error "error" "$action" "$?" action="starting rngd service" && display "task" "$action" systemctl start rngd >> "$logfile" 2>&1 || error "error" "$action" "$?" # Networking display "subtitle" "Networking" pacman_install networkmanager action="enabling NetworkManager" && display "task" "$action" systemctl enable NetworkManager.service >> "$logfile" 2>&1 || error "error" "$action" "$?" action="configuring MAC address randomization" && display "task" "$action" mkdir -p /etc/NetworkManager/conf.d cat << 'EOF' > /etc/NetworkManager/conf.d/wifi-privacy.conf [device-mac-randomization] # Randomize MAC during WiFi scans wifi.scan-rand-mac-address=yes [connection-mac-randomization] # Random MAC for each WiFi connection (prevents tracking) wifi.cloned-mac-address=random # Stable MAC for ethernet (avoids issues with MAC-based DHCP reservations) ethernet.cloned-mac-address=stable EOF # Encrypted DNS (DNS over TLS) action="configuring encrypted DNS (DNS over TLS)" && display "task" "$action" mkdir -p /etc/systemd/resolved.conf.d cat << 'EOF' > /etc/systemd/resolved.conf.d/dns-over-tls.conf [Resolve] # Use Cloudflare and Quad9 with DNS-over-TLS DNS=1.1.1.1#cloudflare-dns.com 9.9.9.9#dns.quad9.net FallbackDNS=1.0.0.1#cloudflare-dns.com 149.112.112.112#dns.quad9.net DNSOverTLS=yes DNSSEC=yes EOF # Configure NetworkManager to use systemd-resolved cat << 'EOF' > /etc/NetworkManager/conf.d/dns.conf [main] dns=systemd-resolved EOF # Note: If Docker containers have DNS issues, systemd-resolved's stub resolver # (127.0.0.53) may be the cause. Fix: configure Docker to use direct DNS, or # disable systemd-resolved and use /etc/resolv.conf directly. (2026-01-18) action="enabling systemd-resolved" && display "task" "$action" systemctl enable systemd-resolved >> "$logfile" 2>&1 || error "error" "$action" "$?" # Create resolv.conf symlink to systemd-resolved action="linking resolv.conf to systemd-resolved" && display "task" "$action" ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf >> "$logfile" 2>&1 || error "error" "$action" "$?" # Power display "subtitle" "Power" pacman_install upower action="enabling upower service" && display "task" "$action" systemctl enable upower >> "$logfile" 2>&1 || error "error" "$action" "$?" # Secure Shell display "subtitle" "Secure Shell" pacman_install openssh action="enabling the openssh service to run at boot" && display "task" "$action" systemctl enable sshd >> "$logfile" 2>&1 || error "error" "$action" "$?" action="starting the openssh service" && display "task" "$action" systemctl start sshd >> "$logfile" 2>&1 || error "error" "$action" "$?" # SSH Brute Force Protection display "subtitle" "SSH Brute Force Protection" pacman_install fail2ban action="configuring fail2ban for SSH protection" && display "task" "$action" cat << 'EOF' > /etc/fail2ban/jail.local [DEFAULT] # Ban for 10 minutes by default bantime = 10m findtime = 10m maxretry = 5 # Use ufw for ban actions banaction = ufw [sshd] enabled = true port = ssh # Stricter settings for SSH: 3 attempts, 1 hour ban maxretry = 3 bantime = 1h EOF action="enabling fail2ban service" && display "task" "$action" systemctl enable fail2ban >> "$logfile" 2>&1 || error "error" "$action" "$?" action="starting fail2ban service" && display "task" "$action" systemctl start fail2ban >> "$logfile" 2>&1 || error "error" "$action" "$?" display "subtitle" "Firewall" pacman_install ufw action="configuring ufw to deny by default" && display "task" "$action" ufw default deny incoming >> "$logfile" 2>&1 || error "error" "$action" "$?" # Firewall rules - only open ports for services we actually run for protocol in \ "IMAP" "IMAPS" \ "ssh" \ "22000/tcp" "22000/udp" "21027/udp" \ "42000/tcp" "42000/udp" \ "42001/tcp" "42001/udp" \ "5353/tcp" "5353/udp" \ "transmission" \ ; do # IMAP/IMAPS: Thunderbird email client # ssh: OpenSSH server # 22000/tcp,udp + 21027/udp: Syncthing file sync # 42000/tcp,udp: Warpinator file transfers # 42001/tcp,udp: Warpinator registration/auth # 5353/tcp,udp: mDNS/Avahi local network discovery # transmission: BitTorrent client action="adding ufw rule to allow $protocol" && display "task" "$action" (ufw allow "$protocol" >> "$logfile" 2>&1) || error "error" "$action" "$?" done action="rate-limiting SSH to protect from brute force attacks" && display "task" "$action" (ufw limit 22/tcp >> "$logfile" 2>&1) || error "error" "$action" "$?" action="enabling firewall service to launch on boot" && display "task" "$action" systemctl enable ufw.service >> "$logfile" 2>&1 || error "error" "$action" "$?" action="starting firewall service" && display "task" "$action" systemctl start ufw.service >> "$logfile" 2>&1 || error "error" "$action" "$?" # Service Discovery display "subtitle" "Network Service Discovery" pacman_install nss-mdns # GNU Name Service Switch host name resolution # Note: systemd-resolved handles DNS (with DoT), avahi handles mDNS (.local) if systemctl is-active --quiet avahi-daemon.service; then display "task" "skipping avahi (already running)" else pacman_install avahi # service discovery on a local network using mdns action="enabling avahi for mDNS discovery" && display "task" "$action" systemctl enable avahi-daemon.service >> "$logfile" 2>&1 || error "error" "$action" "$?" fi pacman_install geoclue # geolocation service for location-aware apps action="enabling geoclue geolocation service" && display "task" "$action" systemctl enable geoclue.service >> "$logfile" 2>&1 || error "error" "$action" "$?" # Job Scheduling display "subtitle" "Job Scheduling" pacman_install cronie action="enabling cronie to launch at boot" && display "task" "$action" systemctl enable cronie >> "$logfile" 2>&1 || error "error" "$action" "$?" pacman_install at action="enabling the batch delayed command scheduler" && display "task" "$action" systemctl enable atd >> "$logfile" 2>&1 || error "error" "$action" "$?" # Package Repository Cache Maintenance display "subtitle" "Package Repository Cache Maintenance" pacman_install pacman-contrib action="enabling the package cache cleanup timer" && display "task" "$action" systemctl enable --now paccache.timer >> "$logfile" 2>&1 || error "error" "$action" "$?" # Snapshot Service - filesystem-aware display "subtitle" "Snapshot Service" if is_zfs_root; then # ZFS: Install sanoid for snapshot management display "task" "ZFS detected - installing sanoid" aur_install sanoid action="creating sanoid configuration" && display "task" "$action" mkdir -p /etc/sanoid cat << 'EOF' > /etc/sanoid/sanoid.conf # Sanoid configuration for ZFS snapshots # Less aggressive - TrueNAS handles long-term backups ############################# # Templates ############################# [template_production] # Local rollback capability hourly = 6 daily = 7 weekly = 2 monthly = 1 autosnap = yes autoprune = yes [template_backup] # Less frequent for large/static data hourly = 0 daily = 3 weekly = 2 monthly = 1 autosnap = yes autoprune = yes [template_none] autosnap = no autoprune = yes ############################# # Datasets ############################# [zroot/ROOT/default] use_template = production [zroot/home] use_template = production recursive = yes [zroot/media] use_template = backup [zroot/vms] use_template = backup [zroot/var/log] use_template = production [zroot/var/lib/pacman] use_template = production [zroot/var/cache] use_template = none [zroot/var/tmp] use_template = none [zroot/tmp] use_template = none EOF action="creating zfs-replicate script" && display "task" "$action" cat << 'EOF' > /usr/local/bin/zfs-replicate #!/bin/bash # zfs-replicate - Replicate ZFS datasets to TrueNAS # # Usage: # zfs-replicate # Replicate all configured datasets # zfs-replicate [dataset] # Replicate specific dataset set -e # TrueNAS Configuration # Try local network first, fall back to tailscale TRUENAS_LOCAL="truenas.local" TRUENAS_TAILSCALE="truenas" TRUENAS_USER="root" TRUENAS_POOL="vault" BACKUP_PATH="backups" # TODO: Configure actual path # Datasets to replicate DATASETS="zroot/ROOT/default zroot/home zroot/media zroot/vms" # Colors GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' info() { echo -e "${GREEN}[INFO]${NC} $1"; } warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } command -v syncoid >/dev/null 2>&1 || error "syncoid not found. Install sanoid package." # Determine which host to use determine_host() { if ping -c 1 -W 2 "$TRUENAS_LOCAL" &>/dev/null; then echo "$TRUENAS_LOCAL" elif ping -c 1 -W 2 "$TRUENAS_TAILSCALE" &>/dev/null; then echo "$TRUENAS_TAILSCALE" else error "Cannot reach TrueNAS at $TRUENAS_LOCAL or $TRUENAS_TAILSCALE" fi } TRUENAS_HOST=$(determine_host) info "Using TrueNAS host: $TRUENAS_HOST" # Single dataset mode if [[ -n "$1" ]]; then dataset="$1" dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}" info "Replicating $dataset -> $dest" syncoid --recursive "$dataset" "$dest" exit 0 fi # Full replication info "Starting ZFS replication to $TRUENAS_HOST" echo "" for dataset in $DATASETS; do dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}" info "Replicating $dataset -> $dest" if syncoid --recursive "$dataset" "$dest"; then info " Success" else warn " Failed (will retry next run)" fi echo "" done info "Replication complete." EOF chmod +x /usr/local/bin/zfs-replicate action="creating zfs-replicate systemd service" && display "task" "$action" cat << 'EOF' > /etc/systemd/system/zfs-replicate.service [Unit] Description=ZFS Replication to TrueNAS After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/zfs-replicate User=root [Install] WantedBy=multi-user.target EOF action="creating zfs-replicate systemd timer" && display "task" "$action" cat << 'EOF' > /etc/systemd/system/zfs-replicate.timer [Unit] Description=Run ZFS replication nightly [Timer] OnCalendar=*-*-* 02:00:00 RandomizedDelaySec=1800 Persistent=true [Install] WantedBy=timers.target EOF action="enabling sanoid timer" && display "task" "$action" systemctl enable sanoid.timer >> "$logfile" 2>&1 || error "error" "$action" "$?" action="enabling weekly ZFS scrub" && display "task" "$action" # Get pool name dynamically (usually zroot) zfs_pool=$(zpool list -H -o name | head -1) systemctl enable "zfs-scrub-weekly@${zfs_pool}.timer" >> "$logfile" 2>&1 || error "error" "$action" "$?" # Note: zfs-replicate.timer is NOT enabled automatically # User must set up SSH key auth to TrueNAS first, then run: # systemctl enable --now zfs-replicate.timer display "task" "zfs-replicate timer created (enable after SSH key setup to TrueNAS)" else # Non-ZFS (btrfs): Install timeshift-autosnap aur_install timeshift-autosnap pacman_install grub-btrfs action="enabling snapshot boot menu updates" && display "task" "$action" systemctl enable grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?" action="starting snapshot boot menu updates" && display "task" "$action" # starting and stopping service to generate the grub-btrfs config systemctl start grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?" systemctl stop grub-btrfsd >> "$logfile" 2>&1 || error "error" "$action" "$?" # edit grub-btrfs config for timeshift auto snapshot support sed -i \ 's|ExecStart=/usr/bin/grub-btrfsd --syslog /.snapshots|ExecStart=/usr/bin/grub-btrfsd --syslog --timeshift-auto|' \ /etc/systemd/system/grub-btrfsd.service action="regenerating boot menu" && display "task" "$action" grub-mkconfig -o /boot/grub/grub.cfg >> "$logfile" 2>&1 || error "error" "$action" "$?" fi } ### Xorg Display Manager xorg() { action="Xorg Display Server Dependencies" && display "subtitle" "$action" pacman_install libglvnd action="Xorg Display Server" && display "subtitle" "$action" for software in xorg-server xorg-xinit xorg-xsetroot \ xsel xorg-xbacklight xorg-xev \ xf86-input-libinput xorg-xeyes \ xorg-xdpyinfo xorg-xprop \ xorg-xwininfo xorg-xhost \ xorg-xinput xorg-xkill ; do pacman_install "$software" done # disallow vt switching or zapping the xorg server to bypass screen lock cat << EOF > /etc/X11/xorg.conf.d/00-no-vt-or-zap.conf Section "ServerFlags" Option "DontVTSwitch" "True" Option "DontZap" "True" EndSection EOF action="configuring xorg server" && display "task" "$action" chmod 644 /etc/X11/xorg.conf.d/00-no-vt-or-zap.conf >> "$logfile" 2>&1 || error "error" "$action" "$?" # Install GPU-specific drivers install_gpu_drivers } ### DWM Window Manager dwm() { action="DWM Window Manager Dependencies" && display "subtitle" "$action" for software in coreutils fontconfig freetype2 glibc harfbuzz libx11 libxft libxinerama; do pacman_install "$software" done; action="DWM Window Manager" && display "subtitle" "$action" git_install "$dwm_repo" git_install "$dmenu_repo" git_install "$st_repo" git_install "$slock_repo" aur_install pinentry-dmenu # password entry that leverages dmenu } ### Desktop Environment desktop_environment() { display "title" "Desktop Environment" # Fonts action="Fonts" && display "subtitle" "$action" pacman_install noto-fonts-emoji pacman_install terminus-font pacman_install ttf-firacode-nerd pacman_install ttf-hack-nerd pacman_install ttf-jetbrains-mono-nerd pacman_install ttf-meslo-nerd pacman_install ttf-nerd-fonts-symbols-mono aur_install ttf-all-the-icons aur_install ttf-lato aur_install ttf-ms-fonts aur_install ttf-ubraille # System Utilities action="System Utilities" && display "subtitle" "$action" pacman_install dmidecode pacman_install dosfstools pacman_install exfatprogs pacman_install lshw pacman_install ntfs-3g pacman_install sshfs pacman_install testdisk pacman_install tickrs pacman_install udisks2 aur_install touchpad-indicator-git aur_install dotpac aur_install downgrade aur_install duf aur_install inxi aur_install mkinitcpio-firmware # suppresses missing firmware warnings during mkinitcpio # File Associations action="File/Application Associations" && display "subtitle" "$action" pacman_install perl-file-mimeinfo pacman_install xdg-utils # Authentication Tools action="Authentication Tools" && display "subtitle" "$action" pacman_install gnupg pacman_install polkit pacman_install gnome-keyring pacman_install seahorse pacman_install pass # ensure correct permissions on .gpg directory # the colon means the user's group will have perms [ -d /home/"$username"/.gnupg ] || mkdir /home/"$username"/.gnupg chown -R "$username": /home/"$username"/.gnupg find /home/"$username"/.gnupg -type f -exec chmod 600 {} \; find /home/"$username"/.gnupg -type d -exec chmod 700 {} \; # Power Management action="Power Management" && display "subtitle" "$action" pacman_install acpi pacman_install powertop # Audio System action="Audio System" && display "subtitle" "$action" for software in alsa-utils pipewire wireplumber pipewire-pulse \ pipewire-docs pamixer pulsemixer ffmpeg; do pacman_install "$software" done; # disable the pc speaker beep rmmod pcspkr >> "$logfile" 2>&1 echo "blacklist pcspkr" > /etc/modprobe.d/nobeep.conf # Keyboard Shortcut Manager action="Keyboard Shortcut Manager" && display "subtitle" "$action" pacman_install sxhkd # Notifications action="Notification System" && display "subtitle" "$action" pacman_install libnotify pacman_install dunst # Screen Color Temperature action="Screen Color Temperature" && display "subtitle" "$action" pacman_install redshift # Bluetooth Devices action="Bluetooth System" && display "subtitle" "$action" for software in bluez bluez-utils blueman; do pacman_install "$software" done action="enabling bluetooth to launch at boot" && display "task" "$action" systemctl enable bluetooth.service >> "$logfile" 2>&1 || error "error" "$action" "$?" # Command Line Utilities action="Command Line Utilities" && display "subtitle" "$action" for software in htop mc ncdu tmux fzf zip unzip atool wget detox \ lsof usbutils moreutils; do pacman_install "$software" done; for software in task-spooler-cpu speedtest-go-bin gotop-bin rar; do aur_install "$software" done; # Help And Documentation action="Help and Documentation" && display "subtitle" "$action" pacman_install man-db pacman_install arch-wiki-docs pacman_install tealdeer aur_install cht.sh-git # Sync Services action="Sync Services" && display "subtitle" "$action" pacman_install syncthing systemctl enable "syncthing@$username.service" >> "$logfile" 2>&1 || error "error" "$action" "$?" # Desktop Environment Utilities action="Desktop Environment Utilities" && display "subtitle" "$action" for software in brightnessctl network-manager-applet xclip rofi \ conky qalculate-gtk feh; do pacman_install "$software" done; aur_install caffeine-ng aur_install flameshot aur_install python-pulsectl aur_install xautolock aur_install xcolor # Theme and Cursor action="UI Theme" && display "subtitle" "$action" for software in picom lxappearance gnome-themes-extra; do pacman_install "$software" done; for software in vimix-cursors \ papirus-icon-theme qt6ct qt5ct; do aur_install "$software" done; pacman_install libappindicator-gtk3 # required by some applets # Browsers action="Browsers" && display "subtitle" "$action" pacman_install firefox ## TESTING IF NEEDED aur_install librewolf-bin pacman_install torbrowser-launcher # downloads/updates tor browser automatically aur_install google-chrome # Install Printing action="Print System" && display "subtitle" "$action" pacman_install cups # the printing service pacman_install cups-pdf # allows printing to pdf pacman_install foomatic-db-engine # generates printer drivers, queues, and jobs pacman_install foomatic-db-ppds # printer driver descriptions pacman_install foomatic-db-nonfree-ppds # non-free printer driver descriptions pacman_install gutenprint # printer driver engine for older computers pacman_install foomatic-db-gutenprint-ppds # gutenprint prebuilt ppd files pacman_install system-config-printer # graphical printer config tool action="enabling printing service to launch at boot" && display "task" "$action" (systemctl enable cups.service >> "$logfile" 2>&1) || error "error" "$action" "$?" } ### Developer Workstation developer_workstation () { action="Developer Workstation" && display "title" "$action" action="Programming Languages and Utilities" && display "subtitle" "$action" # C pacman_install clang # C/C++ compiler pacman_install cmake # make system pacman_install gdb # the GNU debugger pacman_install splint # C programming static analysis pacman_install valgrind # memory management utility # java pacman_install jdk-openjdk # Java Development Kit pacman_install openjdk-doc # ...and the documentation # Lisps pacman_install guile # GNU Scheme pacman_install sbcl # Steel Bank Common Lisp pacman_install racket # Racket + SICP mit-scheme emulation # Python pacman_install pyright # Python language server pacman_install pyenv # Python environment manager # Shell pacman_install shellcheck # Shell script linter pacman_install shfmt # Shell script formatter # Go pacman_install delve # Go programming language debugger pacman_install go-tools # Go language utilities pacman_install gopls # Go language server pacman_install staticcheck # Go programming language linter # Typescript pacman_install jq # JSON processor pacman_install typescript # Typescript programming language pacman_install nodejs # Node-js JavaScript runtime environment pacman_install npm # Node-js package manager aur_install nvm # Node-js version manager # AI coding assistant (global install requires root) action="installing claude-code via npm" && display "task" "$action" (npm install -g @anthropic-ai/claude-code >> "$logfile" 2>&1) || \ error "error" "$action" "$?" # HTML pacman_install tidy # HTML formatter pacman_install prettier # Multi-language formatter # General Utilities pacman_install difftastic # Structural diff tool pacman_install eza # Modern ls replacement pacman_install meld # Visual diff pacman_install ripgrep # Fast grep utility aur_install the_silver_searcher # Another fast grep utility pacman_install zoxide # Smart cd command that learns your habits action="Programming Editors" && display "subtitle" "$action" pacman_install mg # mini emacs pacman_install neovim # mega vi pacman_install pycharm-community-edition # python ide action="Emacs Dependencies" && display "subtitle" "$action" pacman_install emacs # supporting utilities used by my emacs configuration aur_install exercism-bin # command line tool for exercism.io pacman_install libvips # image previewer library pacman_install isync # email sync aur_install epub-thumbnailer-git # epub previews in dirvish aur_install mu # email indexer and utilities aur_install multimarkdown # markdown conversion aur_install proselint # grammar checker aur_install gist # command-line gist poster pacman_install aspell # spell check system pacman_install aspell-en # spell check english files pacman_install fd # a faster find for dired/dirvish pacman_install ffmpegthumbnailer # video previews in dired/dirvish pacman_install imagemagick # image previews for dired/dirvish pacman_install libgccjit # native compilation for Emacs pacman_install mediainfo # generating media info in dired/dirvish pacman_install 7zip # archive info for dirvish pacman_install mpv # video viewer pacman_install mailutils # Emacs' IMAP mail support backend pacman_install msmtp # mail transport for Mu4e pacman_install msmtp-mta # mail transport for Mu4e pacman_install python-lsp-server # python language support pacman_install rlwrap # adds readline support to programs (SBCL-related) pacman_install sdcv # stardict dictionary system pacman_install yt-dlp # video download action="setting up emacs configuration files" && display "task" "$action" emacs_dir="/home/$username/.emacs.d" if [ -d "$emacs_dir" ]; then (cd "$emacs_dir" && sudo -u "$username" git pull --recurse-submodules >> "$logfile" 2>&1) || \ error "error" "$action" "$?" else (sudo -u "$username" git clone --recurse-submodules "$dotemacs_repo" "$emacs_dir" >> \ "$logfile" 2>&1) || error "error" "$action" "$?" fi action="Android Utilities" && display "subtitle" "$action" pacman_install android-file-transfer pacman_install android-tools action="VPN Tools" && display "subtitle" "$action" pacman_install wireguard-tools # VPN - add configs to /etc/wireguard/ pacman_install tailscale # mesh VPN - run 'tailscale up' to authenticate action="enabling tailscale service" && display "task" "$action" systemctl enable tailscaled >> "$logfile" 2>&1 || error "error" "$action" "$?" action="DevOps Utilities" && display "subtitle" "$action" action="installing devops virtualization and automation tools" && display "task" "$action" aur_install vagrant # VM management (moved to AUR) pacman_install ansible # distrobox related pacman_install podman pacman_install distrobox aur_install boxbuddy aur_install ptyxis # boxes pacman_install gnome-boxes # docker pacman_install docker pacman_install docker-compose action="adding user to docker group" && display "task" "$action" (gpasswd -a "$username" docker >> "$logfile" 2>&1) || error "error" "$action" "$?" if is_zfs_root; then action="configuring docker to use ZFS storage driver" && display "task" "$action" mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'EOF' { "storage-driver": "zfs" } EOF fi action="enabling docker service to launch on boot" && display "task" "$action" systemctl enable docker.service >> "$logfile" 2>&1 || error "error" "$action" "$?" } ### Supplemental Software supplemental_software() { display "title" "Supplemental Software" # pacman installs pacman_install arandr # xrandr gui for monitor settings pacman_install arch-install-scripts # for making arch installers pacman_install archinstall # the archinstall python program pacman_install aria2 # fast downloader pacman_install audacity # audio editor pacman_install bc # arbitrary precision calculator pacman_install calibre # ebook manager/viewer pacman_install dash # posix compliant /bin/sh pacman_install devtools # tools for arch linux package maintenance pacman_install dfc # better display of available space on mounted filesystems pacman_install docx2txt # recovers text from docx files pacman_install entr # run arbitrary commands when files change pacman_install faac # open source mpeg 3 and aac encoder pacman_install faad2 # processes an aac stream pacman_install fastfetch # cli system information tool pacman_install fdupes # identify binary duplicates pacman_install figlet # words into ascii art pacman_install filezilla # ftp gui pacman_install gimp # image editor pacman_install gparted # disk partition utility pacman_install gst-plugin-pipewire # gstreamer audio plugin for pipewire pacman_install gst-plugins-base # gstreamer base audio plugins pacman_install gst-plugins-good # gstreamer extra audio plugins pacman_install gstreamer # pipeline based multimedia framework pacman_install gucharmap # gui display of character maps pacman_install gzip # compression tool pacman_install handbrake # video transcoder pacman_install ledger # CLI accounting software pacman_install libconfig # library for processing structured config files pacman_install libmad # mpeg audio decoder pacman_install libmpeg2 # library for decoding mpeg video streams pacman_install maim # screenshot utility pacman_install mediainfo # technical and tag information about media files pacman_install mosh # alt SSH terminal with roaming and responsiveness support pacman_install odt2txt # converts from open document to text pacman_install p7zip # p7zip compression tool pacman_install pandoc-cli # universal document converter pacman_install protonmail-bridge # ProtonMail desktop bridge pacman_install perl-image-exiftool # reads/writes exif info for raw photo files pacman_install poppler-glib # poppler-glib document viewer library pacman_install pv # monitor progress of data through pipeline pacman_install ranger # terminal file manager pacman_install rclone # syncs files from gdrive, s3, dropbox, etc. pacman_install signal-desktop # secure messenger pacman_install smartmontools # monitors hard drives pacman_install lynis # security auditing tool pacman_install telegram-desktop # messenger application if ! $skip_slow_packages; then pacman_install texlive-meta # latex (SLOW: ~6 min, 1GB download) fi pacman_install thunderbird # email, calendar, rss feeds pacman_install transmission-cli # bittorrent client pacman_install transmission-remote-gtk # bittorrent client pacman_install unclutter # hides mouse cursor when not being used pacman_install vlc # media player pacman_install w3m # text based browser pacman_install wavpack # audio compression format pacman_install webkit2gtk # web content engine for GTK pacman_install xcb-util-cursor # calibre dependency (calibre installed manually) pacman_install xdotool # command line xorg automation tool pacman_install xz # general purpose data compression tool pacman_install zathura # document viewer pacman_install zathura-cb # zathura plugin for comics pacman_install zathura-djvu # zathura plugin for djvu books pacman_install zathura-pdf-mupdf # zathura plugin for pdf pacman_install zlib # compression library # aur installs aur_install dtrx # extraction tool aur_install figlet-fonts # fonts for figlet aur_install foliate # pretty epub reader aur_install hfsprogs # file system tools for Mac OS aur_install insync # Google Drive sync client aur_install mcomix # image viewer for comic books aur_install nsxiv # image viewer aur_install snore-git # sleep with feedback # aur_install tageditor # metadata editor for mkv, webm and related video files (DISABLED: hangs indefinitely building qt5-webengine dependency) pacman_install thunar # file manager pacman_install gvfs-smb # SMB network share browsing in Thunar if ! $skip_slow_packages; then aur_install topgrade # upgrade everything utility (SLOW: ~12 min, Rust compilation) fi aur_install ueberzug # allows for displaying images in terminals aur_install valent # connect and interact with your phone aur_install warpinator # secure file transfers aur_install zsh-fast-syntax-highlighting-git # Optimized and extended zsh-syntax-highlighting # working around an temp integ issue with python-lyricsgenius expiration date action="prep to workaround tidal-dl issue" && display "task" "$action" yay -S --noconfirm --mflags --skipinteg python-lyricsgenius >> "$logfile" 2>&1 || error "error" "$action" "$?" aur_install tidal-dl # tidal-dl:tidal as yt-dlp:youtube } ### Boot-UX boot_ux() { action="Boot UX" && display "title" "$action" action="removing distro and date/time from initial screen" && display "task" "$action" (cat /dev/null >/etc/issue) || error "error" "$action" "$?" action="preventing kernel messages on the console" && display "task" "$action" (echo "kernel.printk = 3 3 3 3" >/etc/sysctl.d/20-quiet-printk.conf) || \ error "error" "$action" "$?" action="configuring console font" && display "task" "$action" if grep -q "^FONT=" /etc/vconsole.conf 2>/dev/null; then sed -i 's/^FONT=.*/FONT=ter-132n/' /etc/vconsole.conf else echo "FONT=ter-132n" >> /etc/vconsole.conf fi action="delegating fsck messages from udev to systemd" && display "task" "$action" sed -i '/^HOOKS=/ s/\budev\b/systemd/' /etc/mkinitcpio.conf || error "error" "$action" "$?" mkinitcpio -P >> "$logfile" 2>&1 || error "error" "running mkinitcpio -P to silence fsck messages" "$?" action="configuring quiet fsck output" && display "task" "$action" mkdir -p /etc/systemd/system/systemd-fsck-root.service.d cat << 'EOF' > /etc/systemd/system/systemd-fsck-root.service.d/quiet.conf [Service] StandardOutput=null StandardError=journal+console EOF mkdir -p /etc/systemd/system/systemd-fsck@.service.d cat << 'EOF' > /etc/systemd/system/systemd-fsck@.service.d/quiet.conf [Service] StandardOutput=null StandardError=journal+console EOF # Automatic login for encrypted systems (prompts if no CLI flag and root is encrypted) configure_autologin action="silencing the unneeded and chatty watchdog module" && display "task" "$action" echo "blacklist iTCO_wdt" >/etc/modprobe.d/nowatchdog.conf || error "error" "$action" "$?" action="configuring journald retention" && display "task" "$action" mkdir -p /etc/systemd/journald.conf.d cat << 'EOF' > /etc/systemd/journald.conf.d/retention.conf [Journal] SystemMaxUse=500M EOF # GRUB: reset timeouts, adjust log levels, larger menu for HiDPI screens, and show splashscreen # Note: nvme.noacpi=1 disables NVMe ACPI power management to prevent freezes on some drives. # Safe to keep on newer drives (minor power cost), remove if battery life is critical. action="configuring boot menu for silence and bootsplash" && display "task" "$action" if [ -f /etc/default/grub ]; then action="resetting timeouts and adjusting log levels on grub boot" && display "task" "$action" sed -i "s/.*GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g" /etc/default/grub sed -i "s/.*GRUB_DEFAULT=.*/GRUB_DEFAULT=0/g" /etc/default/grub sed -i 's/.*GRUB_TERMINAL_OUTPUT=console/GRUB_TERMINAL_OUTPUT=gfxterm/' /etc/default/grub sed -i 's/.*GRUB_GFXMODE=auto/GRUB_GFXMODE=1024x768/' /etc/default/grub sed -i "s/.*GRUB_RECORDFAIL_TIMEOUT=.*/GRUB_RECORDFAIL_TIMEOUT=2/g" /etc/default/grub sed -i "s/.*GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"rw loglevel=2 rd.systemd.show_status=auto rd.udev.log_level=2 nvme.noacpi=1 mem_sleep_default=deep nowatchdog quiet splash\"/g" /etc/default/grub grub-mkconfig -o /boot/grub/grub.cfg >> "$logfile" 2>&1 || error "error" "generating grub config" "$?" fi } ### Outro outro() { action="Cleanup" && display "title" "$action" action="forcing user password change on first login" && display "task" "$action" chage -d 0 "$username" >> "$logfile" 2>&1 || error "error" "$action" "$?" display "subtitle" "Statistics" action="identifying newly installed packages" && display "task" "$action" pacman -Q > "$packages_after" || error "error" "$action" "$?" (comm -13 --nocheck-order "$packages_before" "$packages_after" > "$archsetup_packages") || \ error "error" "$action" "$?" action="comparing timestamps" && display "task" "$action" ENDTIME=$(date +%s) totalsecs=$(($ENDTIME - $STARTTIME)) mins=$(($totalsecs / 60)) secs=$(($totalsecs % 60)) new_packages=$(wc -l < "$archsetup_packages") printf "\n" printf "Completion time : %s\n" "$(date +'%D %T')" | tee -a "$logfile" printf "Elapsed time : %s minutes, %s seconds\n" "$mins" "$secs" | tee -a "$logfile" printf "Errors encountered : %s\n" "$errors_encountered" | tee -a "$logfile" printf "Log file location : %s\n" "$logfile" printf "Packages installed : %s\n" "$new_packages" if [ ${#error_messages[@]} -gt 0 ]; then printf "\nError Summary:\n" | tee -a "$logfile" for msg in "${error_messages[@]}"; do printf " - %s\n" "$msg" | tee -a "$logfile" done fi printf "\n" printf "Please reboot before working with your new workstation.\n\n" # Completion marker for automated testing printf "=== ARCHSETUP_EXECUTION_COMPLETE ===\n" | tee -a "$logfile" } ### Installation Steps preflight_checks # verify system requirements (always runs) STARTTIME=$(date +%s) # must be outside intro() since it may be skipped on resume run_step "intro" intro run_step "prerequisites" prerequisites run_step "create_user" create_user run_step "user_customizations" user_customizations run_step "aur_installer" aur_installer run_step "essential_services" essential_services run_step "xorg" xorg run_step "dwm" dwm run_step "desktop_environment" desktop_environment run_step "developer_workstation" developer_workstation run_step "supplemental_software" supplemental_software run_step "boot_ux" boot_ux outro # take end stats; show summary (always runs) exit 0