#!/bin/sh
# ============================================================================
#
# ██╗ █████╗ ███████╗██╗ ██╗██╗ ██╗██████╗ ██╗███████╗ Z
# ██║ ██╔══██╗╚══███╔╝╚██╗ ██╔╝██║ ██╔╝██╔══██╗██║██╔════╝ Z
# ██║ ███████║ ███╔╝ ╚████╔╝ █████╔╝ ██████╔╝██║███████╗ z
# ██║ ██╔══██║ ███╔╝ ╚██╔╝ ██╔═██╗ ██╔══██╗██║╚════██║ z
# ███████╗██║ ██║███████╗ ██║ ██║ ██╗██║ ██║██║███████║
# ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝
#
# LazyKris - Kris's Auto Rice Bootstrapping Scripts
# "The only way to do great work is to automate the boring parts."
# - Every Linux User Ever
#
# Author: Kris Yotam (aka. khr1st)
# Contact: krisyotam@protonmail.com
# Date: 2026.01.23
# License: GNU GPLv3
# Repository: https://github.com/krisyotam/lazykris
# ============================================================================
# ============================================================================
# CONFIGURATION VARIABLES
# ============================================================================
# Repository URLs and branches
DOTFILES_REPO="https://github.com/krisyotam/srice.git"
DOTFILES_BRANCH="main"
PROGS_FILE="https://raw.githubusercontent.com/krisyotam/sdeploy/main/ksd-arch/progs.csv"
NOTES_FILE="https://raw.githubusercontent.com/krisyotam/sdeploy/main/ksd-arch/notes.csv"
DEV_FILE="https://raw.githubusercontent.com/krisyotam/sdeploy/main/ksd-arch/dev.csv"
# Doom Emacs repository
DOOM_REPO="https://github.com/doomemacs/doomemacs"
# Dev directory top-level directories
DEV_TLDS="sites systems formal foss labs parsing"
# AUR helper of choice (yay is recommended)
AUR_HELPER="yay"
# BlackArch repository setup script
BLACKARCH_STRAP="https://blackarch.org/strap.sh"
# AppImages directory (relative to user home)
APPIMAGES_DIR=".local/bin/appimages"
# Source directory for git builds (suckless, etc)
SRC_DIR=".local/src"
# Flatpak remote
FLATPAK_REMOTE="flathub"
FLATPAK_REMOTE_URL="https://dl.flathub.org/repo/flathub.flatpakrepo"
# Terminal settings
export TERM=ansi
# Device type (set during installation)
# Options: "desktop" or "macbook-2015"
DEVICE_TYPE=""
# Optional installations (set during installation)
INSTALL_NOTES="no"
INSTALL_DEV="no"
# ============================================================================
# UTILITY FUNCTIONS
# ============================================================================
# Display error message and exit
error() {
printf "\033[1;31mError:\033[0m %s\n" "$1" >&2
exit 1
}
# Display info message
info() {
printf "\033[1;34m==>\033[0m %s\n" "$1"
}
# Display success message
success() {
printf "\033[1;32m==>\033[0m %s\n" "$1"
}
# Display warning message
warn() {
printf "\033[1;33m==>\033[0m %s\n" "$1"
}
# Install a package from official repos (pacman)
installpkg() {
pacman --noconfirm --needed -S "$1" >/dev/null 2>&1
}
# ============================================================================
# DEVICE SELECTION
# ============================================================================
# Ask user which device they're installing on
selectdevice() {
DEVICE_TYPE=$(whiptail --title "Device Selection" \
--menu "Which device are you installing on?\n\nThis determines HiDPI settings, bar sizes, etc." 14 60 2 \
"desktop" "Custom PC / Standard Display" \
"macbook-2015" "MacBook Pro 2015 Retina (13\")" \
3>&1 1>&2 2>&3 3>&1) || exit 1
}
# Run device-specific post-installation scripts
run_device_setup() {
case "$DEVICE_TYPE" in
"macbook-2015")
whiptail --infobox "Applying MacBook Pro 2015 Retina settings..." 7 55
# Run the laptop HiDPI script
if [ -x "/home/$name/.local/bin/laptop" ]; then
sudo -u "$name" /home/$name/.local/bin/laptop
else
warn "Laptop script not found or not executable"
fi
;;
"desktop")
whiptail --infobox "Desktop setup - no additional configuration needed." 7 55
sleep 1
;;
*)
# Unknown device, skip
;;
esac
}
# ============================================================================
# GITHUB AUTHENTICATION
# ============================================================================
# Authenticate with GitHub for private repo access
setup_github_auth() {
# Check if gh is installed
if ! command -v gh >/dev/null 2>&1; then
warn "GitHub CLI not installed, skipping authentication"
return 1
fi
# Check if already authenticated
if sudo -u "$name" gh auth status >/dev/null 2>&1; then
whiptail --infobox "Already authenticated with GitHub." 7 50
sleep 1
return 0
fi
# Explain the process to user
whiptail --title "GitHub Authentication" --msgbox "To clone private repositories, you need to authenticate with GitHub.
The next screen will show a one-time code.
1. Visit: github.com/login/device
2. Enter the code shown
3. Approve access
Press OK to continue." 14 55
# Run gh auth login with device flow
# Using --git-protocol https so git operations use the gh credential helper
clear
echo ""
echo "=========================================="
echo " GitHub Device Authentication"
echo "=========================================="
echo ""
sudo -u "$name" gh auth login --hostname github.com --git-protocol https --scopes repo,read:org
# Verify authentication succeeded
if sudo -u "$name" gh auth status >/dev/null 2>&1; then
whiptail --infobox "GitHub authentication successful!" 7 45
sleep 1
return 0
else
warn "GitHub authentication failed or was cancelled"
return 1
fi
}
# ============================================================================
# OPTIONAL INSTALLATIONS
# ============================================================================
# Ask user about optional repo installations (notes, dev)
selectoptional() {
# Notes repositories (slipbox, etc.)
if whiptail --title "Notes Repositories" \
--yesno "Install notes repositories (slipbox, etc.)?
This clones Kris's personal notes repos into ~/notes/.
These are private repos and require GitHub authentication.
Skip this if you're not Kris or don't have access." 14 60; then
INSTALL_NOTES="yes"
else
INSTALL_NOTES="no"
fi
# Dev repositories
if whiptail --title "Dev Repositories" \
--yesno "Install development repositories?
This creates ~/dev/ structure with TLDs:
sites, systems, formal, foss, labs, parsing
And clones repos from dev.csv (many are private).
Skip this if you're not Kris or don't have access." 16 60; then
INSTALL_DEV="yes"
else
INSTALL_DEV="no"
fi
}
# ============================================================================
# WELCOME AND USER INPUT
# ============================================================================
# Display welcome message with ASCII art
welcomemsg() {
whiptail --title "Welcome!" --msgbox "
██╗ █████╗ ███████╗██╗ ██╗██╗ ██╗██████╗ ██╗███████╗ Z
██║ ██╔══██╗╚══███╔╝╚██╗ ██╔╝██║ ██╔╝██╔══██╗██║██╔════╝ Z
██║ ███████║ ███╔╝ ╚████╔╝ █████╔╝ ██████╔╝██║███████╗ z
██║ ██╔══██║ ███╔╝ ╚██╔╝ ██╔═██╗ ██╔══██╗██║╚════██║ z
███████╗██║ ██║███████╗ ██║ ██║ ██╗██║ ██║██║███████║
╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝
Welcome to LazyKris!
This script will automatically install a fully-featured
Arch Linux desktop with my custom builds of dwm, dmenu,
dwmblocks, st, and surf. In addition several extreme privacy
configs for the rotation of browsers ran.
Sit back, grab a coffee, and let the automation begin.
-Kris" 24 80
whiptail --title "Important Note!" \
--yes-button "All ready!" \
--no-button "Return..." \
--yesno "Before we begin, please ensure:
1. You are running this as root
2. Pacman is updated (pacman -Sy)
3. Arch keyrings are current
4. You have a stable internet connection
If any of these are not met, the installation
may fail partway through." 14 60
}
# Get username and password from user
getuserandpass() {
name=$(whiptail --inputbox "Enter a username for the new account.
This will be the user that owns the dotfiles
and runs the desktop environment." 12 60 3>&1 1>&2 2>&3 3>&1) || exit 1
# Validate username format
while ! echo "$name" | grep -q "^[a-z_][a-z0-9_-]*$"; do
name=$(whiptail --nocancel --inputbox "Invalid username.
Username must:
- Start with a lowercase letter or underscore
- Contain only lowercase letters, numbers, - or _
Enter a valid username:" 14 60 3>&1 1>&2 2>&3 3>&1)
done
# Get password
pass1=$(whiptail --nocancel --passwordbox "Enter a password for $name." 10 60 3>&1 1>&2 2>&3 3>&1)
pass2=$(whiptail --nocancel --passwordbox "Confirm the password." 10 60 3>&1 1>&2 2>&3 3>&1)
# Verify password match
while [ "$pass1" != "$pass2" ]; do
unset pass2
pass1=$(whiptail --nocancel --passwordbox "Passwords did not match.
Enter password again:" 12 60 3>&1 1>&2 2>&3 3>&1)
pass2=$(whiptail --nocancel --passwordbox "Confirm the password." 10 60 3>&1 1>&2 2>&3 3>&1)
done
}
# Warn if user already exists
usercheck() {
if id -u "$name" >/dev/null 2>&1; then
whiptail --title "User Exists" \
--yes-button "Continue" \
--no-button "Cancel" \
--yesno "The user '$name' already exists on this system.
LazyKris can install for an existing user, but it will
OVERWRITE any conflicting dotfiles and settings.
Your personal files (documents, videos, etc.) will NOT
be touched.
Note: The password will be changed to what you entered.
Continue with installation?" 16 65
fi
}
# Final confirmation before install
preinstallmsg() {
whiptail --title "Ready to Install" \
--yes-button "Let's go!" \
--no-button "Cancel" \
--yesno "Everything is ready!
The installation will now proceed automatically.
This may take 30-60 minutes depending on your
internet speed and the number of packages.
You can watch the progress, but no input is needed.
Press 'Let's go!' to begin installation." 14 60 || {
clear
exit 1
}
}
# ============================================================================
# USER MANAGEMENT
# ============================================================================
# Create user account and set password
adduserandpass() {
whiptail --infobox "Creating user '$name'..." 7 50
# Create user with fish shell, add to wheel group
useradd -m -g wheel -s /usr/bin/fish "$name" >/dev/null 2>&1 ||
usermod -a -G wheel "$name" && mkdir -p /home/"$name" && chown "$name":wheel /home/"$name"
# Set up source directory for git builds
export repodir="/home/$name/$SRC_DIR"
mkdir -p "$repodir"
chown -R "$name":wheel "$(dirname "$repodir")"
# Set password
echo "$name:$pass1" | chpasswd
unset pass1 pass2
}
# ============================================================================
# SYSTEM PREPARATION
# ============================================================================
# Refresh Arch keyrings (handles both Arch and Artix)
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 "Setting up Arch repos for Artix..." 7 50
pacman --noconfirm --needed -S \
artix-keyring artix-archlinux-support >/dev/null 2>&1
# Add Arch extra repo if not present
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
}
# Set up BlackArch repository for security tools
setup_blackarch() {
whiptail --infobox "Setting up BlackArch repository..." 7 50
# Check if BlackArch is already configured
if grep -q "^\[blackarch\]" /etc/pacman.conf; then
return 0
fi
# Download and run the strap script
curl -sL "$BLACKARCH_STRAP" -o /tmp/blackarch-strap.sh
chmod +x /tmp/blackarch-strap.sh
/tmp/blackarch-strap.sh >/dev/null 2>&1
# Sync repos
pacman -Sy --noconfirm >/dev/null 2>&1
}
# Set up Flatpak
setup_flatpak() {
whiptail --infobox "Setting up Flatpak..." 7 40
# Install flatpak if not present
installpkg flatpak
# Add flathub remote
flatpak remote-add --if-not-exists "$FLATPAK_REMOTE" "$FLATPAK_REMOTE_URL" >/dev/null 2>&1
}
# ============================================================================
# PACKAGE INSTALLATION FUNCTIONS
# ============================================================================
# Install AUR helper manually (yay)
manualinstall() {
# Skip if already installed
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" 2>/dev/null ||
{
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
}
# Install from official repos (pacman)
maininstall() {
whiptail --title "LazyKris Installation" \
--infobox "[$n/$total] Installing: $1
$2" 8 70
installpkg "$1"
}
# Install from AUR via helper
aurinstall() {
whiptail --title "LazyKris Installation" \
--infobox "[$n/$total] Installing from AUR: $1
$2" 8 70
# Skip if already installed
echo "$aurinstalled" | grep -q "^$1$" && return 0
sudo -u "$name" $AUR_HELPER -S --noconfirm "$1" >/dev/null 2>&1
}
# Install via git clone and make
gitmakeinstall() {
# Extract program name from URL
progname="${1##*/}"
progname="${progname%.git}"
dir="$repodir/$progname"
whiptail --title "LazyKris Installation" \
--infobox "[$n/$total] Building from source: $progname
$2" 8 70
# Clone or update repository
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
}
# Build and install
cd "$dir" || exit 1
make >/dev/null 2>&1
make install >/dev/null 2>&1
cd /tmp || return 1
}
# Install from Flatpak
flatpakinstall() {
whiptail --title "LazyKris Installation" \
--infobox "[$n/$total] Installing Flatpak: $1
$2" 8 70
flatpak install -y "$FLATPAK_REMOTE" "$1" >/dev/null 2>&1
}
# Install AppImage from URL
appimageinstall() {
# Extract filename from URL
filename="${1##*/}"
# Clean up the filename (remove version numbers for cleaner name)
cleanname=$(echo "$filename" | sed 's/-[0-9].*\.AppImage/.AppImage/')
whiptail --title "LazyKris Installation" \
--infobox "[$n/$total] Downloading AppImage: $cleanname
$2" 8 70
# Create appimages directory
appdir="/home/$name/$APPIMAGES_DIR"
sudo -u "$name" mkdir -p "$appdir"
# Download and make executable
sudo -u "$name" curl -sL "$1" -o "$appdir/$cleanname"
chmod +x "$appdir/$cleanname"
chown "$name":wheel "$appdir/$cleanname"
}
# Install from BlackArch repository
blackarchinstall() {
whiptail --title "LazyKris Installation" \
--infobox "[$n/$total] Installing from BlackArch: $1
$2" 8 70
# Ensure BlackArch is set up
grep -q "^\[blackarch\]" /etc/pacman.conf || setup_blackarch
installpkg "$1"
}
# ============================================================================
# MAIN INSTALLATION LOOP
# ============================================================================
installationloop() {
# Download or copy progs.csv
if [ -f "$PROGS_FILE" ]; then
cp "$PROGS_FILE" /tmp/progs.csv
else
curl -Ls "$PROGS_FILE" >/tmp/progs.csv
fi
# Remove comments and empty lines for counting
sed '/^#/d;/^$/d' /tmp/progs.csv >/tmp/progs-clean.csv
total=$(wc -l </tmp/progs-clean.csv)
aurinstalled=$(pacman -Qqm 2>/dev/null)
# Track if we need special setup
needs_blackarch=false
needs_flatpak=false
# First pass: check what we need
while IFS=, read -r tag program comment; do
case "$tag" in
"B") needs_blackarch=true ;;
"F") needs_flatpak=true ;;
esac
done </tmp/progs-clean.csv
# Set up special repositories if needed
[ "$needs_blackarch" = true ] && setup_blackarch
[ "$needs_flatpak" = true ] && setup_flatpak
# Second pass: install everything
n=0
while IFS=, read -r tag program comment; do
n=$((n + 1))
# Strip quotes from comment if present
echo "$comment" | grep -q "^\".*\"$" &&
comment="$(echo "$comment" | sed -E "s/(^\"|\"$)//g")"
case "$tag" in
"A") aurinstall "$program" "$comment" ;;
"G") gitmakeinstall "$program" "$comment" ;;
"F") flatpakinstall "$program" "$comment" ;;
"I") appimageinstall "$program" "$comment" ;;
"B") blackarchinstall "$program" "$comment" ;;
*) maininstall "$program" "$comment" ;;
esac
done </tmp/progs-clean.csv
}
# ============================================================================
# NOTES REPOSITORIES INSTALLATION
# ============================================================================
# Install notes repositories from notes.csv
notesinstallloop() {
whiptail --infobox "Setting up notes repositories..." 7 50
# Download or copy notes.csv
if [ -f "$NOTES_FILE" ]; then
cp "$NOTES_FILE" /tmp/notes.csv
else
curl -Ls "$NOTES_FILE" >/tmp/notes.csv
fi
# Remove comments and empty lines
sed '/^#/d;/^$/d' /tmp/notes.csv >/tmp/notes-clean.csv
notesdir="/home/$name/notes"
sudo -u "$name" mkdir -p "$notesdir"
# Clone each repository
while IFS=, read -r repo_url target_dir description; do
# Strip quotes if present
repo_url=$(echo "$repo_url" | sed -E "s/(^\"|\"$)//g")
target_dir=$(echo "$target_dir" | sed -E "s/(^\"|\"$)//g")
description=$(echo "$description" | sed -E "s/(^\"|\"$)//g")
whiptail --infobox "Cloning notes repo: $description" 7 60
if [ "$target_dir" = "." ] || [ -z "$target_dir" ]; then
# Clone directly into notes directory (merge contents)
clone_dir="$notesdir"
else
# Clone into subdirectory
clone_dir="$notesdir/$target_dir"
fi
# Clone or update the repository
if [ -d "$clone_dir/.git" ]; then
# Already exists, pull updates
sudo -u "$name" git -C "$clone_dir" pull --ff-only >/dev/null 2>&1
elif [ "$target_dir" = "." ] || [ -z "$target_dir" ]; then
# Clone into existing notes dir (init if empty)
sudo -u "$name" git clone --depth 1 "$repo_url" "$notesdir.tmp" >/dev/null 2>&1
# Move contents (except .git initially, then move .git)
sudo -u "$name" cp -rfT "$notesdir.tmp" "$notesdir"
rm -rf "$notesdir.tmp"
else
sudo -u "$name" git clone --depth 1 "$repo_url" "$clone_dir" >/dev/null 2>&1
fi
done </tmp/notes-clean.csv
chown -R "$name":wheel "$notesdir"
rm -f /tmp/notes.csv /tmp/notes-clean.csv
}
# ============================================================================
# DEV REPOSITORIES INSTALLATION
# ============================================================================
# Install dev repositories from dev.csv
devinstallloop() {
whiptail --infobox "Setting up development environment..." 7 50
devdir="/home/$name/dev"
# Create all TLD directories (even if empty)
for tld in $DEV_TLDS; do
sudo -u "$name" mkdir -p "$devdir/$tld"
done
# Download or copy dev.csv
if [ -f "$DEV_FILE" ]; then
cp "$DEV_FILE" /tmp/dev.csv
else
curl -Ls "$DEV_FILE" >/tmp/dev.csv
fi
# Remove comments and empty lines
sed '/^#/d;/^$/d' /tmp/dev.csv >/tmp/dev-clean.csv
# Count repos for progress
total_repos=$(wc -l </tmp/dev-clean.csv)
n=0
# Clone each repository
while IFS=, read -r repo_name repo_url description tld; do
n=$((n + 1))
# Strip quotes if present
repo_name=$(echo "$repo_name" | sed -E "s/(^\"|\"$)//g")
repo_url=$(echo "$repo_url" | sed -E "s/(^\"|\"$)//g")
description=$(echo "$description" | sed -E "s/(^\"|\"$)//g")
tld=$(echo "$tld" | sed -E "s/(^\"|\"$)//g")
# Target directory
clone_dir="$devdir/$tld/$repo_name"
whiptail --infobox "[$n/$total_repos] Cloning: $repo_name
Into: ~/dev/$tld/
$description" 9 60
# Clone or update the repository
if [ -d "$clone_dir/.git" ]; then
# Already exists, pull updates
sudo -u "$name" git -C "$clone_dir" pull --ff-only >/dev/null 2>&1
else
# Ensure parent directory exists
sudo -u "$name" mkdir -p "$(dirname "$clone_dir")"
# Clone the repo
sudo -u "$name" git clone --depth 1 "$repo_url" "$clone_dir" >/dev/null 2>&1
fi
done </tmp/dev-clean.csv
chown -R "$name":wheel "$devdir"
rm -f /tmp/dev.csv /tmp/dev-clean.csv
success "Development environment setup complete!"
}
# ============================================================================
# DOTFILES INSTALLATION
# ============================================================================
# Clone and deploy dotfiles directly (LARBS method)
putgitrepo() {
# Downloads a gitrepo $1 and places the files in $2 only overwriting conflicts
whiptail --infobox "Downloading and installing config files..." 7 60
dir=$(mktemp -d)
[ ! -d "$2" ] && mkdir -p "$2"
chown "$name":wheel "$dir" "$2"
sudo -u "$name" git -C "$dir" clone --depth 1 \
--single-branch --no-tags -q --recursive -b "$DOTFILES_BRANCH" \
--recurse-submodules "$1" "$dir/repo"
# Copy all dotfiles to target directory
sudo -u "$name" cp -rfT "$dir/repo" "$2"
# Clean up git metadata and repo files from home
rm -rf "$2/.git" "$2/README.md" "$2/LICENSE" "$2/FUNDING.yml" "$2/.stowrc" "$2/.stow-local-ignore"
# Keep a copy of the repo in .local/src for reference/updates
dotfiles_dir="/home/$name/.local/src/lazykris"
sudo -u "$name" mkdir -p "$(dirname "$dotfiles_dir")"
sudo -u "$name" cp -rfT "$dir/repo" "$dotfiles_dir"
rm -rf "$dir"
}
# Set wallpaper using setbg
setup_wallpaper() {
whiptail --infobox "Setting wallpaper..." 7 50
wallpaper="/home/$name/.local/share/lazykris/lazykris-wallpaper.png"
if [ -f "$wallpaper" ] && [ -x "/home/$name/.local/bin/setbg" ]; then
sudo -u "$name" /home/$name/.local/bin/setbg -s "$wallpaper"
fi
}
# ============================================================================
# POST-INSTALLATION SETUP
# ============================================================================
# Set up Doom Emacs
setup_doom_emacs() {
whiptail --infobox "Installing Doom Emacs..." 7 50
emacsdir="/home/$name/.config/emacs"
doomdir="/home/$name/.config/doom"
# Clone Doom Emacs if not present
if [ ! -d "$emacsdir" ]; then
sudo -u "$name" git clone --depth 1 "$DOOM_REPO" "$emacsdir" >/dev/null 2>&1
fi
# Ensure doom config directory exists (should be from dotfiles)
sudo -u "$name" mkdir -p "$doomdir"
# Run doom install (non-interactive)
whiptail --infobox "Running Doom Emacs install (this may take a while)..." 7 60
sudo -u "$name" DOOMDIR="$doomdir" "$emacsdir/bin/doom" install --no-config --no-env --no-fonts >/dev/null 2>&1
# Sync doom packages
whiptail --infobox "Syncing Doom Emacs packages..." 7 50
sudo -u "$name" DOOMDIR="$doomdir" "$emacsdir/bin/doom" sync >/dev/null 2>&1
# Note: doom bin path is already in fish config
}
# Set up Neovim (LazyVim auto-bootstraps, just ensure first run works)
setup_neovim() {
whiptail --infobox "Preparing Neovim (LazyVim)..." 7 50
nvimdir="/home/$name/.config/nvim"
nvimdata="/home/$name/.local/share/nvim"
# Ensure directories exist
sudo -u "$name" mkdir -p "$nvimdir"
sudo -u "$name" mkdir -p "$nvimdata"
# LazyVim bootstraps itself on first run via lazy.lua
# Run nvim headless to trigger plugin installation
whiptail --infobox "Installing Neovim plugins (LazyVim)..." 7 55
sudo -u "$name" nvim --headless "+Lazy! sync" +qa >/dev/null 2>&1 || true
chown -R "$name":wheel "$nvimdir" "$nvimdata"
}
# Create necessary directories
create_directories() {
whiptail --infobox "Creating directory structure..." 7 50
homedir="/home/$name"
# Core directories
sudo -u "$name" mkdir -p "$homedir/downloads"
sudo -u "$name" mkdir -p "$homedir/dev"
sudo -u "$name" mkdir -p "$homedir/notes"
sudo -u "$name" mkdir -p "$homedir/mail"
sudo -u "$name" mkdir -p "$homedir/tmp"
sudo -u "$name" mkdir -p "$homedir/archive"
# Media directories
sudo -u "$name" mkdir -p "$homedir/media/music"
sudo -u "$name" mkdir -p "$homedir/media/pics"
sudo -u "$name" mkdir -p "$homedir/media/videos"
sudo -u "$name" mkdir -p "$homedir/media/screenshots"
sudo -u "$name" mkdir -p "$homedir/media/wallpapers"
sudo -u "$name" mkdir -p "$homedir/media/recordings"
sudo -u "$name" mkdir -p "$homedir/media/books"
sudo -u "$name" mkdir -p "$homedir/media/docs"
# XDG directories (.local)
sudo -u "$name" mkdir -p "$homedir/.local/bin"
sudo -u "$name" mkdir -p "$homedir/.local/bin/appimages"
sudo -u "$name" mkdir -p "$homedir/.local/bin/statusbar"
sudo -u "$name" mkdir -p "$homedir/.local/src"
sudo -u "$name" mkdir -p "$homedir/.local/share"
sudo -u "$name" mkdir -p "$homedir/.local/share/man"
sudo -u "$name" mkdir -p "$homedir/.local/state"
# XDG config and cache
sudo -u "$name" mkdir -p "$homedir/.config"
sudo -u "$name" mkdir -p "$homedir/.cache"
sudo -u "$name" mkdir -p "$homedir/.cache/fish"
# Flatpak data directory
sudo -u "$name" mkdir -p "$homedir/.var/app"
# Keys and security (empty, managed by tools)
sudo -u "$name" mkdir -p "$homedir/.ssh"
sudo -u "$name" mkdir -p "$homedir/.gnupg"
chmod 700 "$homedir/.ssh" "$homedir/.gnupg"
# Claude Code
sudo -u "$name" mkdir -p "$homedir/.claude"
# Application-specific config directories
sudo -u "$name" mkdir -p "$homedir/.config/abook"
sudo -u "$name" mkdir -p "$homedir/.config/mpd/playlists"
sudo -u "$name" mkdir -p "$homedir/.config/newsboat"
sudo -u "$name" mkdir -p "$homedir/.config/btop/themes"
sudo -u "$name" mkdir -p "$homedir/.config/dunst"
sudo -u "$name" mkdir -p "$homedir/.config/calcurse"
sudo -u "$name" mkdir -p "$homedir/.config/rtorrent"
sudo -u "$name" mkdir -p "$homedir/.config/zathura"
sudo -u "$name" mkdir -p "$homedir/.config/wal/templates"
# Set proper ownership
chown -R "$name":wheel "$homedir"
}
# Set fish as default shell
setup_fish_shell() {
whiptail --infobox "Setting fish as default shell..." 7 50
chsh -s /usr/bin/fish "$name" >/dev/null 2>&1
}
# Make all user bin scripts executable
setup_bin_scripts() {
whiptail --infobox "Setting up user scripts..." 7 50
bindir="/home/$name/.local/bin"
# Make all scripts in .local/bin executable
if [ -d "$bindir" ]; then
find "$bindir" -type f -exec chmod +x {} \;
chown -R "$name":wheel "$bindir"
fi
# Also handle statusbar scripts
if [ -d "$bindir/statusbar" ]; then
find "$bindir/statusbar" -type f -exec chmod +x {} \;
fi
}
# System configuration tweaks
system_tweaks() {
whiptail --infobox "Applying system tweaks..." 7 50
# Disable system beep
rmmod pcspkr 2>/dev/null
echo "blacklist pcspkr" >/etc/modprobe.d/nobeep.conf
# Make dash the default /bin/sh (faster scripts)
ln -sfT /bin/dash /bin/sh 2>/dev/null
# Generate dbus UUID (needed for some systems)
dbus-uuidgen >/var/lib/dbus/machine-id 2>/dev/null
# Pacman tweaks: colors, parallel downloads, candy
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
}
# Enable tap to click for touchpads
setup_touchpad() {
[ ! -f /etc/X11/xorg.conf.d/40-libinput.conf ] && {
mkdir -p /etc/X11/xorg.conf.d
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
}
}
# Set up sudoers for wheel group
setup_sudoers() {
whiptail --infobox "Configuring sudo permissions..." 7 50
# Allow wheel users to sudo with password
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-wheel-can-sudo
# Allow certain commands without password
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-cmds-without-password
# Set neovim as default editor for visudo
echo "Defaults editor=/usr/bin/nvim" >/etc/sudoers.d/02-visudo-editor
# Allow dmesg for non-root
mkdir -p /etc/sysctl.d
echo "kernel.dmesg_restrict = 0" >/etc/sysctl.d/dmesg.conf
# Disable pam_faillock (prevents account lockouts on failed logins)
for pamfile in /etc/pam.d/system-login /etc/pam.d/system-auth /etc/pam.d/login /etc/pam.d/sudo; do
[ -f "$pamfile" ] && sed -i 's/^\([^#].*pam_faillock.*\)$/# \1/' "$pamfile"
done
}
# Clean up temporary files
cleanup() {
rm -f /tmp/progs.csv /tmp/progs-clean.csv
rm -f /etc/sudoers.d/lazykris-temp
}
# Display completion message
finalize() {
whiptail --title "Installation Complete!" --msgbox "
██╗ █████╗ ███████╗██╗ ██╗██╗ ██╗██████╗ ██╗███████╗ Z
██║ ██╔══██╗╚══███╔╝╚██╗ ██╔╝██║ ██╔╝██╔══██╗██║██╔════╝ Z
██║ ███████║ ███╔╝ ╚████╔╝ █████╔╝ ██████╔╝██║███████╗ z
██║ ██╔══██║ ███╔╝ ╚██╔╝ ██╔═██╗ ██╔══██╗██║╚════██║ z
███████╗██║ ██║███████╗ ██║ ██║ ██╗██║ ██║██║███████║
╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝
Congratulations! LazyKris installation is complete!
To start using your new system:
1. Log out of root
2. Log in as '$name'
3. Run 'startx' to start dwm
Your default shell is fish, and all your dotfiles
have been deployed.
Enjoy your new setup!
-Kris" 28 80
}
# ============================================================================
# MAIN SCRIPT EXECUTION
# ============================================================================
# Ensure we're running as root with necessary tools
pacman --noconfirm --needed -Sy libnewt ||
error "This script must be run as root on an Arch-based system with internet."
# Welcome and get user info
welcomemsg || error "User cancelled."
selectdevice || error "User cancelled."
selectoptional
getuserandpass || error "User cancelled."
usercheck || error "User cancelled."
preinstallmsg || error "User cancelled."
### Automated installation begins ###
# Refresh keyrings for package verification
refreshkeys || error "Failed to refresh keyrings."
# Install essential bootstrap packages
for pkg in curl ca-certificates base-devel git ntp fish dash; do
whiptail --infobox "Installing bootstrap package: $pkg" 7 60
installpkg "$pkg"
done
# Sync system time for package verification
whiptail --infobox "Synchronizing system time..." 7 50
ntpd -q -g >/dev/null 2>&1
# Create user account
adduserandpass || error "Failed to create user account."
# Handle any pacnew sudoers file
[ -f /etc/sudoers.pacnew ] && cp /etc/sudoers.pacnew /etc/sudoers
# Temporarily allow passwordless sudo for builds
trap 'rm -f /etc/sudoers.d/lazykris-temp' HUP INT QUIT TERM PWR EXIT
echo "%wheel ALL=(ALL) NOPASSWD: ALL
Defaults:%wheel,root runcwd=*" >/etc/sudoers.d/lazykris-temp
# Apply system tweaks
system_tweaks
# Install AUR helper
manualinstall "$AUR_HELPER" || error "Failed to install AUR helper."
# Enable automatic updates for git AUR packages
$AUR_HELPER -Y --save --devel
# Main installation loop - install all packages from progs.csv
installationloop
# Deploy dotfiles to home directory
putgitrepo "$DOTFILES_REPO" "/home/$name"
# Create directory structure
create_directories
# Make all bin scripts executable
setup_bin_scripts
# Set wallpaper
setup_wallpaper
# Set up fish shell
setup_fish_shell
# GitHub authentication (only if user wants private repos)
if [ "$INSTALL_NOTES" = "yes" ] || [ "$INSTALL_DEV" = "yes" ]; then
setup_github_auth
fi
# Clone notes repositories (slipbox, etc.) - optional
[ "$INSTALL_NOTES" = "yes" ] && notesinstallloop
# Clone dev repositories and create TLD structure - optional
[ "$INSTALL_DEV" = "yes" ] && devinstallloop
# Set up Doom Emacs (install + sync packages)
setup_doom_emacs
# Set up Neovim (LazyVim plugin installation)
setup_neovim
# Enable touchpad tap-to-click
setup_touchpad
# Configure sudoers
setup_sudoers
# Run device-specific setup (laptop HiDPI, etc.)
run_device_setup
# Clean up
cleanup
# All done!
finalize
# vim: ft=sh ts=4 sw=4 et