1
0
Fork 0

Make run_once scripts sudo-prompt-free when packages already present

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).
This commit is contained in:
Rain 2026-06-22 15:10:49 -04:00
parent a2cc669b22
commit b40d724f6c
5 changed files with 109 additions and 33 deletions

View file

@ -37,7 +37,17 @@ age:
# Arch's pacman and Debian's apt both put it there. Using the absolute # 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 # path means chezmoi can find age even if PATH isn't set correctly
# (which happens in some non-interactive SSH contexts). # (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" 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" identity: "~/.config/chezmoi/key.txt"
# Multiple recipients: every listed recipient can decrypt every *.age file. # Multiple recipients: every listed recipient can decrypt every *.age file.

View file

@ -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. # will be prompted once per sudo invocation.
{{ if eq .os_family "arch" -}} {{ if eq .os_family "arch" -}}
log "pacman-sync" # Only sync the package DB if anything is missing. Avoids a no-op sudo
sudo pacman -Sy --noconfirm # (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)" if (( ${#MISSING_PKGS[@]} > 0 )); then
PACMAN_PKGS=(age curl ca-certificates git base-devel wget) log "pacman-sync (missing: ${MISSING_PKGS[*]})"
sudo pacman -S --needed --noconfirm "${PACMAN_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" -}} {{ else if eq .os_family "debian" -}}
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
log "apt-update" # Only run apt if anything is missing, so a no-op sudo isn't required.
sudo apt-get update -y MISSING_PKGS=()
log "apt-upgrade" for p in age curl ca-certificates git wget gnupg libssl-dev pkg-config; do
sudo apt-get upgrade -y if ! command -v "$p" >/dev/null 2>&1; then
MISSING_PKGS+=("$p")
fi
done
log "install base tools (debian)" if (( ${#MISSING_PKGS[@]} > 0 )); then
APT_PKGS=(age curl ca-certificates git wget gnupg libssl-dev pkg-config) log "apt-update (missing: ${MISSING_PKGS[*]})"
sudo apt-get install -y --no-install-recommends "${APT_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 -}} {{ else -}}
die "unsupported os_family: {{ .os_family }} (this script supports arch or debian)" die "unsupported os_family: {{ .os_family }} (this script supports arch or debian)"

View file

@ -54,9 +54,18 @@ if [[ ! -f "$HOSTS_FILE" ]]; then
exit 1 exit 1
fi 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 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 cp "$HOSTS_FILE" "${HOSTS_FILE}.bak.$(date +%s)"
sudo sed -i "/$LAN_BLOCK_BEGIN/,/$LAN_BLOCK_END/d" "$HOSTS_FILE" sudo sed -i "/$LAN_BLOCK_BEGIN/,/$LAN_BLOCK_END/d" "$HOSTS_FILE"
fi fi

View file

@ -20,9 +20,9 @@ ZSH_CUSTOM="${ZSH_CUSTOM:-$USER_HOME/.oh-my-zsh/custom}"
{{ if eq .os_family "arch" -}} {{ if eq .os_family "arch" -}}
# ----------------------------- ARCH --------------------------------------- # ----------------------------- ARCH ---------------------------------------
log "pacman -Syu" # Only run pacman if anything is actually missing. Avoids a no-op sudo
sudo pacman -Syu --noconfirm # (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=( PACMAN_PKGS=(
zsh tmux neovim git base-devel zsh tmux neovim git base-devel
bat btop htop fastfetch bat btop htop fastfetch
@ -32,9 +32,20 @@ PACMAN_PKGS=(
openssh openssh
bun bun
) )
MISSING_PKGS=()
log "installing pacman packages" for p in "${PACMAN_PKGS[@]}"; do
sudo pacman -S --needed --noconfirm "${PACMAN_PKGS[@]}" 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 --------------------- # --------------------------- Pi coding agent + oh-my-pi ---------------------
# Arch: bun comes from pacman (above), used here for the global install. # Arch: bun comes from pacman (above), used here for the global install.
@ -50,9 +61,6 @@ fi
{{ else if eq .os_family "debian" -}} {{ else if eq .os_family "debian" -}}
# ----------------------------- DEBIAN -------------------------------------- # ----------------------------- DEBIAN --------------------------------------
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
sudo apt-get update -y
sudo apt-get upgrade -y
APT_PKGS=( APT_PKGS=(
zsh tmux git build-essential zsh tmux git build-essential
btop htop fastfetch btop htop fastfetch
@ -63,9 +71,22 @@ APT_PKGS=(
ca-certificates curl wget ca-certificates curl wget
fontconfig fontconfig
) )
MISSING_PKGS=()
log "installing apt packages" for p in "${APT_PKGS[@]}"; do
sudo apt-get install -y --no-install-recommends "${APT_PKGS[@]}" 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 # 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 # (so the binary lands at ~/.local/bin/bun, which is already in PATH

View file

@ -36,14 +36,28 @@ SWAY_PKGS+=(dunst)
log "WARNING: sway packages not configured for os_family={{ .os_family }}" log "WARNING: sway packages not configured for os_family={{ .os_family }}"
{{ end -}} {{ end -}}
log "installing sway stack: ${SWAY_PKGS[*]}" log "checking sway stack: ${SWAY_PKGS[*]}"
{{ if eq .os_family "arch" -}} # Only invoke sudo if any of the packages are missing. Bit has most
sudo pacman -S --needed --noconfirm "${SWAY_PKGS[@]}" # already; byte/kaiser are full. This avoids a no-op sudo prompt.
{{ else if eq .os_family "debian" -}} MISSING_PKGS=()
export DEBIAN_FRONTEND=noninteractive for p in "${SWAY_PKGS[@]}"; do
sudo apt-get install -y --no-install-recommends "${SWAY_PKGS[@]}" if ! command -v "$p" >/dev/null 2>&1; then
{{ end -}} 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" log "sway stack installed"
sway --version 2>&1 | head -1 sway --version 2>&1 | head -1