diff --git a/run_once_20-install-user-packages.sh.tmpl b/run_once_20-install-user-packages.sh.tmpl index f56b823..1ebfb63 100755 --- a/run_once_20-install-user-packages.sh.tmpl +++ b/run_once_20-install-user-packages.sh.tmpl @@ -16,7 +16,13 @@ set -euo pipefail # invoked from a non-interactive context (e.g. `chezmoi apply` over SSH). # These are normally added by .zshrc, but this script runs in a plain # shell where those rc files aren't sourced. -export PATH="$HOME/.local/bin:$HOME/.bun/bin:$HOME/.cargo/bin:$PATH" +# +# For `bun` on arch: we deliberately put /usr/bin BEFORE the user +# locations so the pacman-managed binary wins over any curl-installed +# copy that might be shadowing it. The post-install verification below +# fails loudly if pacman doesn't actually have bun, so the operator +# can remove the curl shadow and re-run. +export PATH="/usr/bin:/bin:$HOME/.local/bin:$HOME/.bun/bin:$HOME/.cargo/bin:$PATH" log() { printf '\033[1;34m[packages]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[packages ERROR]\033[0m %s\n' "$*" >&2; exit 1; } @@ -29,6 +35,13 @@ ZSH_CUSTOM="${ZSH_CUSTOM:-$USER_HOME/.oh-my-zsh/custom}" # 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. +# +# For `bun` specifically, we check `pacman -Qi bun` (not `command -v +# bun`) so the script forces a system-package install via pacman, not a +# curl-installed binary in ~/.bun/bin or ~/.local/bin. Per user +# preference: PMs first (pacman via chaotic-aur on arch, apt on debian), +# `curl | bash` only as last-resort fallback when the package isn't in +# any repo. bun is in [extra] on arch since 1.2.x, so pacman handles it. PACMAN_PKGS=( zsh tmux neovim git base-devel bat btop htop fastfetch @@ -40,7 +53,15 @@ 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 + if [[ "$p" == "bun" ]]; then + # System-package check for bun: must be tracked by pacman, not + # just a binary on PATH. A curl-installed bun at ~/.bun/bin/bun + # would otherwise pass the `command -v` check and skip pacman, + # leaving us with a non-PM-managed install. + if ! pacman -Qi "$p" >/dev/null 2>&1; then + MISSING_PKGS+=("$p") + fi + elif ! command -v "$p" >/dev/null 2>&1 && ! pacman -Qi "$p" >/dev/null 2>&1; then MISSING_PKGS+=("$p") fi done @@ -53,6 +74,19 @@ else log "all user packages already installed; skipping pacman" fi +# Post-install verification: bun must come from the system package +# manager, not a curl-install. If it does, fail loudly so the operator +# can remove the curl-installed binary and re-run. +if ! pacman -Qi bun >/dev/null 2>&1; then + log "ERROR: bun is not tracked by pacman after install" + log " (a curl-installed binary may be shadowing the package)" + log " remove ~/.bun/ and ~/.local/bin/bun, then re-run" + exit 1 +fi +BUN_FROM=$(pacman -Qi bun 2>/dev/null | awk -F': *' '/^Installed From/ {print $2}') +BUN_VER=$(pacman -Qi bun 2>/dev/null | awk -F': *' '/^Version/ {print $2}') +log "bun: $BUN_VER (from $BUN_FROM) — system-package install verified" + # --------------------------- Pi coding agent + oh-my-pi --------------------- # Arch: bun comes from pacman (above), used here for the global install. if command -v bun >/dev/null 2>&1; then @@ -103,12 +137,26 @@ 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 -# via .zshrc — no extra PATH config needed). +# via .zshrc — no extra PATH config needed). Per user preference, this +# is a last-resort fallback: PMs first, then pinned binaries from +# official sources, then `curl | bash`. apt on debian-stable has no +# bun, so we land on the official install. if ! command -v bun >/dev/null 2>&1; then log "installing bun to ~/.local/bin (debian: not in apt)" curl -fsSL https://bun.sh/install | BUN_INSTALL="$HOME/.local" bash fi +# Post-install verification: confirm bun is reachable on PATH. On +# debian, the official install puts it at $BUN_INSTALL/bin/bun, so we +# expect ~/.local/bin/bun in this case. +if ! command -v bun >/dev/null 2>&1; then + log "ERROR: bun install succeeded but bun is not on PATH" + log " check \$BUN_INSTALL/bin/bun exists and is in PATH" + exit 1 +fi +BUN_VER=$(bun --version 2>/dev/null) +log "bun: $BUN_VER (from official bun.sh installer; debian has no apt package)" + # fd on Debian ships as 'fdfind' to avoid clashing with fd (the dedupe tool). # Symlink so .zshrc can find 'fd' on PATH. if command -v fdfind >/dev/null 2>&1 && ! command -v fd >/dev/null 2>&1; then