#!/usr/bin/env bash # Shared helpers for the arch-i3 bootstrap scripts. # Sourced, never executed directly. set -Eeuo pipefail # --- paths ----------------------------------------------------------------- REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" DOTFILES_DIR="$REPO_ROOT/dotfiles" PACKAGES_DIR="$REPO_ROOT/packages" WALLPAPER_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/wallpapers" BACKUP_DIR="$HOME/.config-backup-$(date +%Y%m%d-%H%M%S)" # --- flags (exported by install.sh) --------------------------------------- : "${DRY_RUN:=0}" : "${ASSUME_YES:=0}" # --- colours --------------------------------------------------------------- if [[ -t 1 ]]; then C_RESET=$'\e[0m'; C_BOLD=$'\e[1m'; C_DIM=$'\e[2m' C_BLUE=$'\e[38;5;111m'; C_GREEN=$'\e[38;5;115m' C_YELLOW=$'\e[38;5;222m'; C_RED=$'\e[38;5;210m'; C_MAUVE=$'\e[38;5;140m' else C_RESET=''; C_BOLD=''; C_DIM='' C_BLUE=''; C_GREEN=''; C_YELLOW=''; C_RED=''; C_MAUVE='' fi log() { printf '%s==>%s %s\n' "$C_BLUE$C_BOLD" "$C_RESET" "$*"; } step() { printf '%s ->%s %s\n' "$C_MAUVE" "$C_RESET" "$*"; } ok() { printf '%s ok%s %s\n' "$C_GREEN" "$C_RESET" "$*"; } warn() { printf '%s !!%s %s\n' "$C_YELLOW" "$C_RESET" "$*" >&2; } die() { printf '%serror%s %s\n' "$C_RED$C_BOLD" "$C_RESET" "$*" >&2; exit 1; } skip() { printf '%s --%s %s\n' "$C_DIM" "$C_RESET" "$*"; } # Run a command, honouring DRY_RUN. run() { if [[ "$DRY_RUN" == "1" ]]; then printf '%s dry%s %s\n' "$C_DIM" "$C_RESET" "$*" return 0 fi "$@" } confirm() { [[ "$ASSUME_YES" == "1" ]] && return 0 [[ "$DRY_RUN" == "1" ]] && return 0 local reply read -rp "$(printf '%s ??%s %s [y/N] ' "$C_YELLOW" "$C_RESET" "$1")" reply [[ "$reply" =~ ^[Yy]$ ]] } have() { command -v "$1" >/dev/null 2>&1; } # Print a path with $HOME collapsed to '~'. Done as a function because the # obvious ${path/#$HOME/~} keeps a literal backslash in the replacement. tildify() { local p="$1" if [[ "$p" == "$HOME"/* ]]; then printf '~/%s' "${p#"$HOME"/}" elif [[ "$p" == "$HOME" ]]; then printf '~' else printf '%s' "$p" fi } # Read a package list file, dropping comments and blanks. read_pkg_list() { local file="$1" [[ -f "$file" ]] || die "package list not found: $file" sed -e 's/#.*$//' -e 's/[[:space:]]\+$//' "$file" | grep -v '^[[:space:]]*$' || true } # Guard rails common to every stage. require_arch() { [[ -f /etc/arch-release ]] || die "this bootstrap targets Arch Linux (no /etc/arch-release found)" } require_not_root() { [[ "$(id -u)" -ne 0 ]] || die "run this as your normal user, not root — sudo is invoked where needed" } require_sudo() { have sudo || die "sudo is not installed; install it and add your user to the wheel group first" if [[ "$DRY_RUN" != "1" ]]; then sudo -v || die "sudo authentication failed" # Keep the sudo timestamp alive for the length of the run. ( while true; do sudo -n true; sleep 50; kill -0 "$$" 2>/dev/null || exit; done ) & SUDO_KEEPALIVE_PID=$! trap 'kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true' EXIT fi } # Back up a path (file, dir or symlink) into $BACKUP_DIR, preserving structure. backup_path() { local target="$1" [[ -e "$target" || -L "$target" ]] || return 0 local rel="${target#"$HOME"/}" local dest="$BACKUP_DIR/$rel" step "backing up $(tildify "$target") -> $(tildify "$dest")" run mkdir -p "$(dirname "$dest")" run mv "$target" "$dest" } # Copy a file from the repo into place, backing up whatever was there. install_file() { local src="$1" dest="$2" mode="${3:-644}" [[ -f "$src" ]] || die "missing source file: $src" if [[ -f "$dest" ]] && cmp -s "$src" "$dest"; then skip "$(tildify "$dest") already up to date" return 0 fi backup_path "$dest" run mkdir -p "$(dirname "$dest")" run install -m "$mode" "$src" "$dest" ok "installed $(tildify "$dest")" }