--- /dev/null
+#TAG,NAME IN REPO (or git url),DESCRIPTION
+,xorg-server,"A display server for the X Window System."
+,xorg-xwininfo,"A utility for querying information about X windows."
+,xorg-xinit,"A program for starting the X display server."
+,xorg-xset,"A utility for configuring X server settings."
+,polkit,"A toolkit for managing user authorization policies."
+,otf-libertinus,"A family of serif and sans-serif fonts."
+,ttf-font-awesome,"An icon font with extended glyph support."
+,ttf-dejavu,"A font package with broad Unicode and emoji coverage."
+,ttf-junicode,"A medievalist font."
+A,lf-git,"A terminal file manager with extensive customization."
+,bc,"A command-line calculator and math language."
+,xcompmgr,"A compositor for transparency and reducing screen tearing."
+,xorg-xprop,"A tool for inspecting X window properties."
+,arandr,"A graphical front-end for configuring monitor layouts."
+,dosfstools,"A set of utilities for FAT filesystem management."
+,libnotify,"A library for sending desktop notifications."
+,dunst,"A lightweight, customizable notification daemon."
+,exfat-utils,"A set of utilities for managing exFAT filesystems."
+,nsxiv,"A simple, scriptable image viewer."
+,xwallpaper,"A utility for setting the desktop wallpaper."
+,ffmpeg,"A command-line tool for recording and converting audio and video."
+,ffmpegthumbnailer,"A tool for generating video thumbnail previews."
+,gnome-keyring,"A daemon for securely storing credentials and secrets."
+A,gtk-theme-arc-gruvbox-git,"A dark GTK theme based on the Gruvbox color scheme."
+,python-qdarkstyle,"A dark stylesheet for Qt applications."
+,neovim,"An extensible, modern fork of Vim."
+,mpd,"A daemon for playing music from a library."
+,mpc,"A command-line client for MPD."
+,mpv,"A versatile, scriptable media player."
+,man-db,"A tool for reading and managing manual pages."
+,ncmpcpp,"A terminal music player interface with tag editing."
+,newsboat,"A terminal RSS and Atom feed reader."
+A,librewolf-bin,"A privacy-focused browser based on Firefox."
+A,librewolf-extension-localcdn-bin,"An extension that serves common web libraries locally."
+A,librewolf-extension-istilldontcareaboutcookies-bin,"An extension that automatically dismisses cookie consent banners."
+A,librewolf-extension-ublock-origin-bin,"A broad-spectrum content and ad blocker."
+A,arkenfox-user.js,"A hardened Firefox/Librewolf user.js for privacy and security."
+,noto-fonts,"A comprehensive font family with broad language coverage."
+,noto-fonts-emoji,"An emoji font from the Noto family."
+,ntfs-3g,"A driver for reading and writing NTFS partitions."
+,wireplumber,"A session manager for PipeWire audio."
+,pipewire-pulse,"A PulseAudio compatibility layer for PipeWire."
+,pipewire-jack,"A JACK compatibility layer for PipeWire."
+,pulsemixer,"A terminal mixer for controlling audio levels."
+,qpwgraph,"A graphical patchbay for PipeWire audio and MIDI routing."
+A,sc-im,"A terminal spreadsheet application."
+,imagemagick,"A suite of tools for image conversion and manipulation."
+,maim,"A command-line screenshot tool."
+,unclutter,"A utility that hides the mouse cursor when idle."
+,unzip,"A tool for extracting ZIP archives."
+,lynx,"A text-based web browser."
+,xcape,"A tool for remapping modifier keys to secondary key outputs."
+,xclip,"A utility for reading and writing the X clipboard."
+,xdotool,"A tool for scripting X window and input actions."
+,yt-dlp,"A command-line downloader for YouTube and other video sites."
+,zathura,"A document viewer with Vim-style keybindings."
+,zathura-pdf-mupdf,"A PDF rendering backend for Zathura."
+,poppler,"A PDF rendering library and command-line utilities."
+,mediainfo,"A tool for displaying technical metadata about media files."
+,atool,"A utility for managing and inspecting archive files."
+,fzf,"A general-purpose fuzzy finder for the command line."
+,bat,"A syntax-highlighting pager and file viewer."
+,xorg-xbacklight,"A utility for adjusting screen backlight brightness."
+A,zsh-fast-syntax-highlighting-git,"A syntax highlighting plugin for Zsh."
+A,task-spooler,"A queue manager for running commands sequentially."
+A,simple-mtpfs,"A tool for mounting MTP devices such as phones."
+A,htop-vim,"A terminal system monitor with Vim-style keybindings."
+,slock,"A minimal screen locker for X."
+,socat,"A relay for bidirectional data transfer between two streams."
+,moreutils,"A collection of supplementary Unix command-line utilities."
+,tesseract,"An OCR engine for extracting text from images."
+,tesseract-data-eng,"English language data for Tesseract OCR."
+,reaper,"A digital audio workstation for recording and production."
+,supercollider,"An audio synthesis and algorithmic composition environment."
+,pd,"A visual programming environment for audio and multimedia."
+,sox,"A powerful and scriptable set of audio processing tools."
+,libreoffice-fresh,"An open-source office suite for documents, spreadsheets, and presentations."
+,lilypond,"A text-based music notation and engraving program."
+,remind,"A calendar and reminder program with a scripting language."
+,tailscale,"A mesh VPN for securely connecting devices across networks."
+,urlscan,"A utility for extracting and selecting URLs from terminal output."
--- /dev/null
+#!/bin/sh
+
+# TuCLS - Tucker's Configured Linux System
+# Installation Script
+
+### OPTIONS AND VARIABLES ###
+
+dwmrepo="https://git.newer.systems/dwm.git"
+dmenurepo="https://git.newer.systems/dmenu.git"
+strepo="https://git.newer.systems/st.git"
+dwmblocksrepo="https://git.newer.systems/dwmblocks.git"
+nvimrepo="https://git.newer.systems/nvim.git"
+configsrepo="https://git.newer.systems/configs.git"
+setclassrepo="https://git.newer.systems/setclass.git"
+progsfile="$(dirname "$(readlink -f "$0")")/programs.csv"
+aurhelper="yay"
+export TERM=ansi
+
+### FUNCTIONS ###
+
+installpkg() { pacman --noconfirm --needed -S "$1" >/dev/null 2>&1; }
+
+error() { printf "%s\n" "$1" >&2; exit 1; }
+
+welcomemsg() {
+ whiptail --title "Welcome to TuCLS!" \
+ --msgbox "This script will install Tucker's Configured Linux System.\n\nIt will install all needed programs, build the suckless tools, and deploy all configuration files for a complete Artix Linux desktop." 12 65
+
+ whiptail --title "Important Note!" --yes-button "All ready!" \
+ --no-button "Return..." \
+ --yesno "Be sure this machine has current pacman updates and refreshed Artix keyrings.\n\nIf it does not, some programs may fail to install." 8 70
+}
+
+getuserandpass() {
+ name=$(whiptail --inputbox "Enter a name for the user account." 10 60 3>&1 1>&2 2>&3 3>&1) || exit 1
+ while ! echo "$name" | grep -q "^[a-z_][a-z0-9_-]*$"; do
+ name=$(whiptail --nocancel --inputbox "Username not valid. Give a username beginning with a letter, with only lowercase letters, - or _." 10 60 3>&1 1>&2 2>&3 3>&1)
+ done
+ pass1=$(whiptail --nocancel --passwordbox "Enter a password for that user." 10 60 3>&1 1>&2 2>&3 3>&1)
+ pass2=$(whiptail --nocancel --passwordbox "Retype password." 10 60 3>&1 1>&2 2>&3 3>&1)
+ while ! [ "$pass1" = "$pass2" ]; do
+ unset pass2
+ pass1=$(whiptail --nocancel --passwordbox "Passwords do not match.\n\nEnter password again." 10 60 3>&1 1>&2 2>&3 3>&1)
+ pass2=$(whiptail --nocancel --passwordbox "Retype password." 10 60 3>&1 1>&2 2>&3 3>&1)
+ done
+}
+
+usercheck() {
+ ! { id -u "$name" >/dev/null 2>&1; } ||
+ whiptail --title "WARNING" --yes-button "CONTINUE" \
+ --no-button "No wait..." \
+ --yesno "The user \`$name\` already exists. TuCLS will OVERWRITE any conflicting dotfiles.\n\nYour personal files (documents, music, etc.) will not be touched.\n\nOnly continue if you want existing settings overwritten." 12 70
+}
+
+preinstallmsg() {
+ whiptail --title "Ready to install!" --yes-button "Let's go!" \
+ --no-button "Nevermind" \
+ --yesno "The rest of the installation will now be fully automated.\n\nThis will take some time depending on your connection speed." 10 60 || { clear; exit 1; }
+}
+
+adduserandpass() {
+ whiptail --infobox "Adding user \"$name\"..." 7 50
+ useradd -m -g wheel -s /bin/zsh "$name" >/dev/null 2>&1 ||
+ usermod -a -G wheel "$name" && mkdir -p "/home/$name" && chown "$name":wheel "/home/$name"
+ export repodir="/home/$name/.local/src/TuCLS"
+ mkdir -p "$repodir"
+ chown -R "$name":wheel "$(dirname "$repodir")"
+ echo "$name:$pass1" | chpasswd
+ unset pass1 pass2
+}
+
+refreshkeys() {
+ case "$(readlink -f /sbin/init)" in
+ *systemd*)
+ whiptail --infobox "Refreshing Arch keyring..." 7 40
+ pacman --noconfirm -S archlinux-keyring >/dev/null 2>&1
+ ;;
+ *)
+ whiptail --infobox "Enabling Arch repositories and refreshing keyrings..." 7 40
+ pacman --noconfirm --needed -S artix-keyring artix-archlinux-support >/dev/null 2>&1
+ grep -q "^\[extra\]" /etc/pacman.conf ||
+ echo "[extra]
+Include = /etc/pacman.d/mirrorlist-arch" >>/etc/pacman.conf
+ pacman -Sy --noconfirm >/dev/null 2>&1
+ pacman-key --populate archlinux >/dev/null 2>&1
+ ;;
+ esac
+}
+
+manualinstall() {
+ pacman -Qq "$1" >/dev/null 2>&1 && return 0
+ whiptail --infobox "Installing \"$1\" manually." 7 50
+ sudo -u "$name" mkdir -p "$repodir/$1"
+ sudo -u "$name" git -C "$repodir" clone --depth 1 --single-branch \
+ --no-tags -q "https://aur.archlinux.org/$1.git" "$repodir/$1" ||
+ { cd "$repodir/$1" || return 1; sudo -u "$name" git pull --force origin master; }
+ cd "$repodir/$1" || exit 1
+ sudo -u "$name" makepkg --noconfirm -si >/dev/null 2>&1 || return 1
+}
+
+maininstall() {
+ whiptail --title "TuCLS Installation" \
+ --infobox "Installing \`$1\` ($n of $total). $2" 9 70
+ installpkg "$1"
+}
+
+aurinstall() {
+ whiptail --title "TuCLS Installation" \
+ --infobox "Installing \`$1\` ($n of $total) from the AUR. $2" 9 70
+ echo "$aurinstalled" | grep -q "^$1$" && return 0
+ sudo -u "$name" $aurhelper -S --noconfirm "$1" >/dev/null 2>&1
+}
+
+gitmakeinstall() {
+ progname="${1##*/}"
+ progname="${progname%.git}"
+ dir="$repodir/$progname"
+ whiptail --title "TuCLS Installation" \
+ --infobox "Installing \`$progname\` ($n of $total) via git and make. $2" 8 70
+ sudo -u "$name" git -C "$repodir" clone --depth 1 --single-branch \
+ --no-tags -q "$1" "$dir" 2>/dev/null ||
+ { cd "$dir" || return 1; sudo -u "$name" git pull --force origin master; }
+ cd "$dir" || exit 1
+ make >/dev/null 2>&1
+ make install >/dev/null 2>&1
+ cd /tmp || return 1
+}
+
+pipinstall() {
+ whiptail --title "TuCLS Installation" \
+ --infobox "Installing Python package \`$1\` ($n of $total). $2" 9 70
+ [ -x "$(command -v pip)" ] || installpkg python-pip >/dev/null 2>&1
+ yes | pip install "$1"
+}
+
+installationloop() {
+ ([ -f "$progsfile" ] && cp "$progsfile" /tmp/progs.csv) ||
+ curl -Ls "$progsfile" | sed '/^#/d' >/tmp/progs.csv
+ total=$(wc -l </tmp/progs.csv)
+ aurinstalled=$(pacman -Qqm)
+ while IFS=, read -r tag program comment; do
+ n=$((n + 1))
+ echo "$comment" | grep -q "^\".*\"$" &&
+ comment="$(echo "$comment" | sed -E "s/(^\"|\"$)//g")"
+ case "$tag" in
+ "A") aurinstall "$program" "$comment" ;;
+ "G") gitmakeinstall "$program" "$comment" ;;
+ "P") pipinstall "$program" "$comment" ;;
+ *) maininstall "$program" "$comment" ;;
+ esac
+ done </tmp/progs.csv
+}
+
+cloneandmake() {
+ # Clone a suckless repo and install it system-wide via make.
+ progname="${1##*/}"
+ progname="${progname%.git}"
+ dir="$repodir/$progname"
+ whiptail --infobox "Cloning and building \`$progname\`..." 7 60
+ sudo -u "$name" git -C "$repodir" clone -q "$1" "$dir" 2>/dev/null ||
+ { cd "$dir" || return 1; sudo -u "$name" git pull --force origin master 2>/dev/null || return 1; }
+ cd "$dir" || return 1
+ make >/dev/null 2>&1
+ make install >/dev/null 2>&1
+ cd /tmp || return 1
+}
+
+cloneandstow() {
+ # Clone a config repo and deploy it to $HOME via stow.
+ # Also runs make install if a Makefile exists (for system-wide scripts).
+ progname="${1##*/}"
+ progname="${progname%.git}"
+ dir="$repodir/$progname"
+ whiptail --infobox "Cloning and deploying \`$progname\`..." 7 60
+ sudo -u "$name" git -C "$repodir" clone -q "$1" "$dir" 2>/dev/null ||
+ { cd "$dir" || return 1; sudo -u "$name" git pull --force origin master 2>/dev/null || return 1; }
+ cd "$repodir" || return 1
+ sudo -u "$name" stow -t "/home/$name" "$progname"
+ [ -f "$dir/Makefile" ] && { cd "$dir" || return 1; make install >/dev/null 2>&1; }
+ cd /tmp || return 1
+}
+
+getenvanpaths() {
+ whiptail --title "Path Configuration" \
+ --msgbox "The next prompts will configure paths for your personal files. Press Enter to accept the defaults." 8 65
+
+ notes=$(whiptail --inputbox "Where would you like to store your notes?" 10 60 \
+ "/home/$name/Notes" 3>&1 1>&2 2>&3 3>&1) || notes="/home/$name/Notes"
+
+ recordings=$(whiptail --inputbox "Where would you like to save screen recordings?" 10 60 \
+ "/home/$name/Videos/Recordings" 3>&1 1>&2 2>&3 3>&1) || recordings="/home/$name/Videos/Recordings"
+
+ cat > "/home/$name/.config/shell/localenvrc" << EOF
+export NOTES="$notes"
+export RECORDINGS="$recordings"
+EOF
+ chown "$name:wheel" "/home/$name/.config/shell/localenvrc"
+}
+
+vimplugininstall() {
+ whiptail --infobox "Installing neovim plugins..." 7 60
+ sudo -u "$name" git -C "$repodir" clone -q "$setclassrepo" "$repodir/setclass" 2>/dev/null ||
+ { cd "$repodir/setclass" || return 1; sudo -u "$name" git pull --force origin master 2>/dev/null; }
+ mkdir -p "/home/$name/.config/nvim/autoload"
+ curl -Ls "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" \
+ > "/home/$name/.config/nvim/autoload/plug.vim"
+ chown -R "$name:wheel" "/home/$name/.config/nvim"
+ sudo -u "$name" nvim --headless -c "PlugInstall" -c "qa" >/dev/null 2>&1
+}
+
+finalize() {
+ whiptail --title "All done!" \
+ --msgbox "Installation complete!\n\nLog out and log back in as $name. The graphical environment will start automatically on tty1.\n\nNote: populate ~/.config/newsboat/urls with your RSS feeds before running newsboat." 13 65
+}
+
+### THE ACTUAL SCRIPT ###
+
+# Check root and install whiptail
+pacman --noconfirm --needed -Sy libnewt ||
+ error "Are you sure you're running as root on an Arch-based distribution with an internet connection?"
+
+# User interaction - all prompts happen up front
+welcomemsg || error "User exited."
+getuserandpass || error "User exited."
+usercheck || error "User exited."
+preinstallmsg || error "User exited."
+
+### No more user input until getenvanpaths ###
+
+# Refresh keyrings
+refreshkeys || error "Error refreshing Arch keyring. Consider doing so manually."
+
+# Base dependencies (includes stow for config deployment)
+for x in curl ca-certificates base-devel git ntp zsh dash stow; do
+ whiptail --title "TuCLS Installation" \
+ --infobox "Installing \`$x\`, required for installation." 8 70
+ installpkg "$x"
+done
+
+# Sync time
+whiptail --title "TuCLS Installation" \
+ --infobox "Synchronizing system time..." 8 70
+ntpd -q -g >/dev/null 2>&1
+
+# Create user account
+adduserandpass || error "Error adding username and/or password."
+
+# Temporarily allow passwordless sudo for AUR builds
+[ -f /etc/sudoers.pacnew ] && cp /etc/sudoers.pacnew /etc/sudoers
+trap 'rm -f /etc/sudoers.d/tucls-temp' HUP INT QUIT TERM PWR EXIT
+echo "%wheel ALL=(ALL) NOPASSWD: ALL
+Defaults:%wheel,root runcwd=*" >/etc/sudoers.d/tucls-temp
+
+# Configure pacman
+grep -q "ILoveCandy" /etc/pacman.conf || sed -i "/#VerbosePkgLists/a ILoveCandy" /etc/pacman.conf
+sed -Ei "s/^#(ParallelDownloads).*/\1 = 5/;/^#Color$/s/#//" /etc/pacman.conf
+
+# Use all cores for compilation
+sed -i "s/-j2/-j$(nproc)/;/^#MAKEFLAGS/s/^#//" /etc/makepkg.conf
+
+# Install AUR helper
+manualinstall $aurhelper || error "Failed to install AUR helper."
+$aurhelper -Y --save --devel
+
+# Install all programs from progs.csv
+installationloop
+
+# Build and install suckless tools
+for repo in "$dwmrepo" "$dmenurepo" "$strepo" "$dwmblocksrepo"; do
+ cloneandmake "$repo" || error "Failed to install $(basename "$repo" .git)."
+done
+
+# Deploy config repos via stow
+cloneandstow "$configsrepo" || error "Failed to deploy configs."
+cloneandstow "$nvimrepo" || error "Failed to deploy nvim config."
+
+# Generate shell/lf shortcut files now that configs are in place
+sudo -u "$name" shortcuts >/dev/null 2>&1
+
+# Prompt for machine-specific paths and write localenvrc
+getenvanpaths
+
+# Clone setclass and install neovim plugins
+vimplugininstall
+
+# Create directories that programs expect to exist
+sudo -u "$name" mkdir -p \
+ "/home/$name/.cache/zsh" \
+ "/home/$name/.config/mpd/playlists" \
+ "/home/$name/.local/share/groff" \
+ "/home/$name/.config/newsboat"
+
+# Blank newsboat urls - user populates this themselves
+[ -f "/home/$name/.config/newsboat/urls" ] ||
+ sudo -u "$name" touch "/home/$name/.config/newsboat/urls"
+
+# Disable terminal bell
+rmmod pcspkr 2>/dev/null
+echo "blacklist pcspkr" >/etc/modprobe.d/nobeep.conf
+
+# Set zsh as default shell
+chsh -s /bin/zsh "$name" >/dev/null 2>&1
+
+# Make dash the default /bin/sh
+ln -sfT /bin/dash /bin/sh >/dev/null 2>&1
+
+# dbus UUID for Artix runit
+dbus-uuidgen >/var/lib/dbus/machine-id 2>/dev/null
+
+# dbus environment for non-systemd init
+[ "$(readlink -f /sbin/init)" != "/usr/lib/systemd/systemd" ] &&
+ echo "export \$(dbus-launch)" >/etc/profile.d/dbus.sh
+
+# Enable tap-to-click for touchpads
+[ ! -f /etc/X11/xorg.conf.d/40-libinput.conf ] && printf 'Section "InputClass"
+ Identifier "libinput touchpad catchall"
+ MatchIsTouchpad "on"
+ MatchDevicePath "/dev/input/event*"
+ Driver "libinput"
+ Option "Tapping" "on"
+EndSection' >/etc/X11/xorg.conf.d/40-libinput.conf
+
+# Final sudoers: wheel can sudo with password, passwordless for common system commands
+echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-tucls-wheel-can-sudo
+echo "%wheel ALL=(ALL:ALL) NOPASSWD: /usr/bin/shutdown,/usr/bin/reboot,/usr/bin/systemctl suspend,/usr/bin/mount,/usr/bin/umount,/usr/bin/pacman -Syu,/usr/bin/pacman -Syyu,/usr/bin/pacman -Syyu --noconfirm,/usr/bin/loadkeys,/usr/bin/pacman -Syyuw --noconfirm" >/etc/sudoers.d/01-tucls-cmds-without-password
+echo "Defaults editor=/usr/bin/nvim" >/etc/sudoers.d/02-tucls-visudo-editor
+mkdir -p /etc/sysctl.d
+echo "kernel.dmesg_restrict = 0" >/etc/sysctl.d/dmesg.conf
+
+# Remove temp sudoers (also handled by trap above)
+rm -f /etc/sudoers.d/tucls-temp
+
+finalize