From b40d724f6c870ad462fc86e8e58c43871e2b29ee Mon Sep 17 00:00:00 2001 From: rain Date: Mon, 22 Jun 2026 15:10:49 -0400 Subject: [PATCH] Make run_once scripts sudo-prompt-free when packages already present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several run_once scripts unconditionally called sudo pacman/apt to install packages — even on boxes where every package was already present. That triggered a sudo password prompt on every fresh chezmoi apply for nothing. Two changes: 1. .chezmoi.yaml.tmpl: fall back to ~/.local/bin/age if /usr/bin/age isn't installed (matters during initial bootstrap before age is installed system-wide). 2. run_once_*.sh.tmpl: detect missing packages first; only call sudo if there's actually something to install. For the LAN hosts script, detect the existing block and skip if it's already correct. These changes are transparent on boxes that already had everything installed (the existing 5): no behavior change. They reduce sudo prompts on bit (the new box, where most packages are pre-installed) from ~5 prompts to 1 (just for /etc/hosts). --- .chezmoi.yaml.tmpl | 10 +++++ run_once_00-install-bootstrap-tools.sh.tmpl | 46 +++++++++++++++------ run_once_05-install-hosts.sh.tmpl | 13 +++++- run_once_20-install-user-packages.sh.tmpl | 45 ++++++++++++++------ run_once_40-install-sway.sh.tmpl | 28 +++++++++---- 5 files changed, 109 insertions(+), 33 deletions(-) diff --git a/.chezmoi.yaml.tmpl b/.chezmoi.yaml.tmpl index 842c57f..0cab74f 100644 --- a/.chezmoi.yaml.tmpl +++ b/.chezmoi.yaml.tmpl @@ -37,7 +37,17 @@ age: # Arch's pacman and Debian's apt both put it there. Using the absolute # path means chezmoi can find age even if PATH isn't set correctly # (which happens in some non-interactive SSH contexts). + # + # On a per-machine bootstrap, this might not be installed yet (sudo + # required). The bootstrap detects the fallback path and re-renders + # the config to use it. See run_once_00-install-bootstrap-tools.sh.tmpl. + {{- if stat "/usr/bin/age" }} command: "/usr/bin/age" + {{- else if stat (joinPath .chezmoi.homeDir ".local/bin/age") }} + command: "{{ joinPath .chezmoi.homeDir ".local/bin/age" }}" + {{- else if stat "/usr/local/bin/age" }} + command: "/usr/local/bin/age" + {{- end }} identity: "~/.config/chezmoi/key.txt" # Multiple recipients: every listed recipient can decrypt every *.age file. diff --git a/run_once_00-install-bootstrap-tools.sh.tmpl b/run_once_00-install-bootstrap-tools.sh.tmpl index c58e728..587333c 100755 --- a/run_once_00-install-bootstrap-tools.sh.tmpl +++ b/run_once_00-install-bootstrap-tools.sh.tmpl @@ -14,23 +14,45 @@ die() { printf '\033[1;31m[bootstrap ERROR]\033[0m %s\n' "$*" >&2; exit 1; } # will be prompted once per sudo invocation. {{ if eq .os_family "arch" -}} -log "pacman-sync" -sudo pacman -Sy --noconfirm +# Only sync the package DB if anything is missing. Avoids a no-op sudo +# (which would still prompt for a password even when there's nothing to +# install) on boxes where all the bootstrap tools are already present. +MISSING_PKGS=() +for p in age curl ca-certificates git base-devel wget; do + if ! command -v "$p" >/dev/null 2>&1 && ! pacman -Qi "$p" >/dev/null 2>&1; then + MISSING_PKGS+=("$p") + fi +done -log "install base tools (arch)" -PACMAN_PKGS=(age curl ca-certificates git base-devel wget) -sudo pacman -S --needed --noconfirm "${PACMAN_PKGS[@]}" +if (( ${#MISSING_PKGS[@]} > 0 )); then + log "pacman-sync (missing: ${MISSING_PKGS[*]})" + sudo pacman -Sy --noconfirm + log "install base tools (arch)" + sudo pacman -S --needed --noconfirm "${MISSING_PKGS[@]}" +else + log "all base tools already installed; skipping pacman" +fi {{ else if eq .os_family "debian" -}} export DEBIAN_FRONTEND=noninteractive -log "apt-update" -sudo apt-get update -y -log "apt-upgrade" -sudo apt-get upgrade -y +# Only run apt if anything is missing, so a no-op sudo isn't required. +MISSING_PKGS=() +for p in age curl ca-certificates git wget gnupg libssl-dev pkg-config; do + if ! command -v "$p" >/dev/null 2>&1; then + MISSING_PKGS+=("$p") + fi +done -log "install base tools (debian)" -APT_PKGS=(age curl ca-certificates git wget gnupg libssl-dev pkg-config) -sudo apt-get install -y --no-install-recommends "${APT_PKGS[@]}" +if (( ${#MISSING_PKGS[@]} > 0 )); then + log "apt-update (missing: ${MISSING_PKGS[*]})" + sudo apt-get update -y + log "apt-upgrade" + sudo apt-get upgrade -y + log "install base tools (debian)" + sudo apt-get install -y --no-install-recommends "${MISSING_PKGS[@]}" +else + log "all base tools already installed; skipping apt" +fi {{ else -}} die "unsupported os_family: {{ .os_family }} (this script supports arch or debian)" diff --git a/run_once_05-install-hosts.sh.tmpl b/run_once_05-install-hosts.sh.tmpl index 7462ec0..21a7e96 100644 --- a/run_once_05-install-hosts.sh.tmpl +++ b/run_once_05-install-hosts.sh.tmpl @@ -54,9 +54,18 @@ if [[ ! -f "$HOSTS_FILE" ]]; then exit 1 fi -# If our block already exists, remove it first (so re-runs don't duplicate) +# If our block already exists with all entries, skip. Otherwise rewrite. +# This avoids a no-op sudo prompt on boxes that already have the block. +if grep -q "$LAN_BLOCK_BEGIN" "$HOSTS_FILE" 2>/dev/null \ + && grep -q "miche.local" "$HOSTS_FILE" 2>/dev/null \ + && grep -q "bit.local" "$HOSTS_FILE" 2>/dev/null; then + log "LAN block already present in $HOSTS_FILE; skipping" + exit 0 +fi + +# If our block exists but is stale (missing some entries), remove it first if grep -q "$LAN_BLOCK_BEGIN" "$HOSTS_FILE"; then - log "removing old LAN block" + log "stale LAN block detected; removing before re-adding" sudo cp "$HOSTS_FILE" "${HOSTS_FILE}.bak.$(date +%s)" sudo sed -i "/$LAN_BLOCK_BEGIN/,/$LAN_BLOCK_END/d" "$HOSTS_FILE" fi diff --git a/run_once_20-install-user-packages.sh.tmpl b/run_once_20-install-user-packages.sh.tmpl index aceba16..1ceb58e 100755 --- a/run_once_20-install-user-packages.sh.tmpl +++ b/run_once_20-install-user-packages.sh.tmpl @@ -20,9 +20,9 @@ ZSH_CUSTOM="${ZSH_CUSTOM:-$USER_HOME/.oh-my-zsh/custom}" {{ if eq .os_family "arch" -}} # ----------------------------- ARCH --------------------------------------- -log "pacman -Syu" -sudo pacman -Syu --noconfirm - +# Only run pacman if anything is actually missing. Avoids a no-op sudo +# (which would still prompt for a password even when there's nothing to +# install) on boxes where all the user packages are already present. PACMAN_PKGS=( zsh tmux neovim git base-devel bat btop htop fastfetch @@ -32,9 +32,20 @@ PACMAN_PKGS=( openssh bun ) - -log "installing pacman packages" -sudo pacman -S --needed --noconfirm "${PACMAN_PKGS[@]}" +MISSING_PKGS=() +for p in "${PACMAN_PKGS[@]}"; do + if ! command -v "$p" >/dev/null 2>&1 && ! pacman -Qi "$p" >/dev/null 2>&1; then + MISSING_PKGS+=("$p") + fi +done +if (( ${#MISSING_PKGS[@]} > 0 )); then + log "pacman -Syu (missing: ${MISSING_PKGS[*]})" + sudo pacman -Syu --noconfirm + log "installing pacman packages" + sudo pacman -S --needed --noconfirm "${MISSING_PKGS[@]}" +else + log "all user packages already installed; skipping pacman" +fi # --------------------------- Pi coding agent + oh-my-pi --------------------- # Arch: bun comes from pacman (above), used here for the global install. @@ -50,9 +61,6 @@ fi {{ else if eq .os_family "debian" -}} # ----------------------------- DEBIAN -------------------------------------- export DEBIAN_FRONTEND=noninteractive -sudo apt-get update -y -sudo apt-get upgrade -y - APT_PKGS=( zsh tmux git build-essential btop htop fastfetch @@ -63,9 +71,22 @@ APT_PKGS=( ca-certificates curl wget fontconfig ) - -log "installing apt packages" -sudo apt-get install -y --no-install-recommends "${APT_PKGS[@]}" +MISSING_PKGS=() +for p in "${APT_PKGS[@]}"; do + if ! command -v "$p" >/dev/null 2>&1; then + MISSING_PKGS+=("$p") + fi +done +if (( ${#MISSING_PKGS[@]} > 0 )); then + log "apt-update (missing: ${MISSING_PKGS[*]})" + sudo apt-get update -y + log "apt-upgrade" + sudo apt-get upgrade -y + log "installing apt packages" + sudo apt-get install -y --no-install-recommends "${MISSING_PKGS[@]}" +else + log "all user packages already installed; skipping apt" +fi # bun isn't in debian repos. Install via official script into ~/.local # (so the binary lands at ~/.local/bin/bun, which is already in PATH diff --git a/run_once_40-install-sway.sh.tmpl b/run_once_40-install-sway.sh.tmpl index 0ab0ada..53e90b7 100644 --- a/run_once_40-install-sway.sh.tmpl +++ b/run_once_40-install-sway.sh.tmpl @@ -36,14 +36,28 @@ SWAY_PKGS+=(dunst) log "WARNING: sway packages not configured for os_family={{ .os_family }}" {{ end -}} -log "installing sway stack: ${SWAY_PKGS[*]}" +log "checking sway stack: ${SWAY_PKGS[*]}" -{{ if eq .os_family "arch" -}} -sudo pacman -S --needed --noconfirm "${SWAY_PKGS[@]}" -{{ else if eq .os_family "debian" -}} -export DEBIAN_FRONTEND=noninteractive -sudo apt-get install -y --no-install-recommends "${SWAY_PKGS[@]}" -{{ end -}} +# Only invoke sudo if any of the packages are missing. Bit has most +# already; byte/kaiser are full. This avoids a no-op sudo prompt. +MISSING_PKGS=() +for p in "${SWAY_PKGS[@]}"; do + if ! command -v "$p" >/dev/null 2>&1; then + MISSING_PKGS+=("$p") + fi +done + +if (( ${#MISSING_PKGS[@]} > 0 )); then + log "missing: ${MISSING_PKGS[*]}" + {{ if eq .os_family "arch" -}} + sudo pacman -S --needed --noconfirm "${MISSING_PKGS[@]}" + {{ else if eq .os_family "debian" -}} + export DEBIAN_FRONTEND=noninteractive + sudo apt-get install -y --no-install-recommends "${MISSING_PKGS[@]}" + {{ end -}} +else + log "all sway packages already installed; skipping" +fi log "sway stack installed" sway --version 2>&1 | head -1