Files
arch-i3/scripts/lib.sh
T

118 lines
3.9 KiB
Bash
Raw Normal View History

2026-09-14 15:31:20 -05:00
#!/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")"
}