Three real bugs caught during the bit-cachyos deploy: 1. wl-clipboard false-positive: 'command -v wl-clipboard' returns nothing because the package ships wl-copy/wl-paste, not a 'wl-clipboard' binary. Sudo pacman was being called every apply even though wl-clipboard is installed. Fix: declare -A PKG_BIN map in the script. 2. omp segfault aborts the whole bootstrap: on rye, 'bun add -g @oh-my-pi/pi-coding-agent' segfaults (Pi undervoltage, see pitfall #30). The 'set -e' caused the whole run_once_20 to abort before getting to neovim/oh-my-zsh/zshrc etc. Fix: wrap the bun add in a subshell with error tolerance, log a warning, keep going. 3. rorclar/sourdough reference cleanup: removed the old chaotic-aur comment which mentioned the wrong sudo pattern (now fixed in the other run_once scripts). Re-applied on bit, miche, byte, kaiser, crouton (rye skipped because of the undervoltage hardware issue).
297 lines
No EOL
11 KiB
Bash
Executable file
297 lines
No EOL
11 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# run_once_20-install-user-packages.sh.tmpl
|
|
# Install zsh, tmux, neovim, fastfetch, plus the modern CLI replacements
|
|
# the .zshrc aliases depend on (bat, btop, eza, fzf, fd, ripgrep, zoxide,
|
|
# starship, lazygit, yt-dlp, etc.)
|
|
#
|
|
# Also: install oh-my-zsh, zsh-autosuggestions, zsh-syntax-highlighting,
|
|
# zsh-history-substring-search, fzf-tab.
|
|
#
|
|
# Runs as the unprivileged user, but uses sudo for system packages.
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
log() { printf '\033[1;34m[packages]\033[0m %s\n' "$*"; }
|
|
die() { printf '\033[1;31m[packages ERROR]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
USER_HOME="${HOME:-$(eval echo "~$(whoami)")}"
|
|
ZSH_CUSTOM="${ZSH_CUSTOM:-$USER_HOME/.oh-my-zsh/custom}"
|
|
|
|
{{ if eq .os_family "arch" -}}
|
|
# ----------------------------- ARCH ---------------------------------------
|
|
# 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
|
|
eza fzf fd ripgrep zoxide starship
|
|
lazygit yt-dlp jq
|
|
unzip p7zip
|
|
openssh
|
|
bun
|
|
)
|
|
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.
|
|
if command -v bun >/dev/null 2>&1; then
|
|
if ! command -v omp >/dev/null 2>&1; then
|
|
log "installing @oh-my-pi/pi-coding-agent via bun global"
|
|
# Wrap in subshell so a segfault/timeout (e.g. Pi undervoltage
|
|
# killing the install mid-run, see pitfall #30) doesn't abort
|
|
# the whole bootstrap. omp can be retried manually later.
|
|
if (bun add -g @oh-my-pi/pi-coding-agent 2>&1 | tail -10); then
|
|
log "omp installed: $(omp --version 2>&1 | head -1)"
|
|
else
|
|
log "WARNING: bun add -g failed; omp not installed. Retry manually."
|
|
fi
|
|
else
|
|
log "omp already installed: $(omp --version 2>&1 | head -1)"
|
|
fi
|
|
fi
|
|
|
|
{{ else if eq .os_family "debian" -}}
|
|
# ----------------------------- DEBIAN --------------------------------------
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
APT_PKGS=(
|
|
zsh tmux git build-essential
|
|
btop htop fastfetch
|
|
eza fzf fd-find ripgrep zoxide starship
|
|
lazygit yt-dlp jq
|
|
unzip p7zip
|
|
openssh-client
|
|
ca-certificates curl wget
|
|
fontconfig
|
|
)
|
|
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
|
|
# via .zshrc — no extra PATH config needed).
|
|
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
|
|
|
|
# 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
|
|
log "symlinking fdfind -> fd in ~/.local/bin"
|
|
mkdir -p "$USER_HOME/.local/bin"
|
|
ln -sf "$(command -v fdfind)" "$USER_HOME/.local/bin/fd"
|
|
fi
|
|
|
|
# Debian ships 'bat' as 'batcat' due to a name clash with an unrelated
|
|
# package. The install happens in run_onchange_30 (after rustup is ready,
|
|
# via `cargo install bat`).
|
|
|
|
# --------------------------- Pi coding agent + oh-my-pi ---------------------
|
|
# Install via bun global (arch already has /usr/bin/bun from pacman, debian
|
|
# got it from the curl install above). Both OSes land in the same dir.
|
|
if command -v bun >/dev/null 2>&1; then
|
|
if ! command -v omp >/dev/null 2>&1; then
|
|
log "installing @oh-my-pi/pi-coding-agent via bun global"
|
|
# Wrap in subshell so a segfault/timeout (e.g. Pi undervoltage
|
|
# killing the install mid-run, see pitfall #30) doesn't abort
|
|
# the whole bootstrap. omp can be retried manually later.
|
|
if (bun add -g @oh-my-pi/pi-coding-agent 2>&1 | tail -10); then
|
|
log "omp installed: $(omp --version 2>&1 | head -1)"
|
|
else
|
|
log "WARNING: bun add -g failed; omp not installed. Retry manually."
|
|
fi
|
|
else
|
|
log "omp already installed: $(omp --version 2>&1 | head -1)"
|
|
fi
|
|
fi
|
|
|
|
# Neovim — install official binary tarball, pinned to a known-good version.
|
|
# Bump NVIM_TARGET_VERSION to upgrade. ~/.local/bin/update-neovim.sh does
|
|
# the same check + download so topgrade can invoke it for upgrades.
|
|
NVIM_TARGET_VERSION="v0.11.4"
|
|
ARCH="$(uname -m)"
|
|
case "$ARCH" in
|
|
x86_64)
|
|
NVIM_TARBALL="nvim-linux64.tar.gz"
|
|
NVIM_EXTRACT_DIR="nvim-linux64"
|
|
;;
|
|
aarch64|arm64)
|
|
NVIM_TARBALL="nvim-linux-arm64.tar.gz"
|
|
NVIM_EXTRACT_DIR="nvim-linux-arm64"
|
|
;;
|
|
*)
|
|
die "unsupported arch for neovim tarball: $ARCH"
|
|
;;
|
|
esac
|
|
|
|
if command -v nvim >/dev/null 2>&1; then
|
|
INSTALLED_VER="$(nvim --version 2>/dev/null | head -1 | awk '{print $2}')"
|
|
if [[ "$INSTALLED_VER" == "$NVIM_TARGET_VERSION" ]]; then
|
|
log "neovim $INSTALLED_VER matches target — skipping"
|
|
NVIM_TARBALL=""
|
|
else
|
|
log "neovim $INSTALLED_VER != target $NVIM_TARGET_VERSION — upgrading"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$NVIM_TARBALL" ]]; then
|
|
log "downloading neovim $NVIM_TARGET_VERSION ($NVIM_TARBALL)"
|
|
cd /tmp
|
|
if ! curl -fL --retry 3 \
|
|
"https://github.com/neovim/neovim/releases/download/${NVIM_TARGET_VERSION}/${NVIM_TARBALL}" \
|
|
-o nvim.tar.gz; then
|
|
die "failed to download neovim tarball"
|
|
fi
|
|
sudo rm -rf /opt/nvim-linux* /usr/local/bin/nvim
|
|
sudo tar -xzf nvim.tar.gz -C /opt/
|
|
sudo ln -sf "/opt/${NVIM_EXTRACT_DIR}/bin/nvim" /usr/local/bin/nvim
|
|
rm -f nvim.tar.gz
|
|
fi
|
|
|
|
# Verify neovim is reachable (PATH may need /usr/local/bin explicitly for this run)
|
|
if ! command -v nvim >/dev/null 2>&1 && [[ -x /usr/local/bin/nvim ]]; then
|
|
export PATH="/usr/local/bin:$PATH"
|
|
fi
|
|
|
|
{{ else -}}
|
|
die "unsupported os_family: {{ .os_family }}"
|
|
{{ end -}}
|
|
|
|
# --------------------------- OH-MY-ZSH -------------------------------------
|
|
if [[ ! -d "$USER_HOME/.oh-my-zsh" ]]; then
|
|
log "installing oh-my-zsh (unattended)"
|
|
RUNZSH=no CHSH=no sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
|
|
else
|
|
log "oh-my-zsh already installed"
|
|
fi
|
|
|
|
# zsh plugins (loaded by .zshrc)
|
|
install_zsh_plugin() {
|
|
local repo="$1" target="$2"
|
|
if [[ -d "$target" ]]; then
|
|
log "plugin already present: $target"
|
|
else
|
|
log "cloning plugin: $repo"
|
|
git clone --depth 1 "https://github.com/$repo.git" "$target"
|
|
fi
|
|
}
|
|
|
|
install_zsh_plugin zsh-users/zsh-autosuggestions "$ZSH_CUSTOM/plugins/zsh-autosuggestions"
|
|
install_zsh_plugin zsh-users/zsh-syntax-highlighting "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting"
|
|
install_zsh_plugin zsh-users/zsh-history-substring-search "$ZSH_CUSTOM/plugins/zsh-history-substring-search"
|
|
install_zsh_plugin Aloxaf/fzf-tab "$ZSH_CUSTOM/plugins/fzf-tab"
|
|
|
|
# --------------------------- TPM (tmux plugin manager) ---------------------
|
|
if [[ ! -d "$USER_HOME/.tmux/plugins/tpm" ]]; then
|
|
log "cloning tmux plugin manager"
|
|
mkdir -p "$USER_HOME/.tmux/plugins"
|
|
git clone --depth 1 https://github.com/tmux-plugins/tpm "$USER_HOME/.tmux/plugins/tpm"
|
|
else
|
|
log "tpm already installed"
|
|
fi
|
|
|
|
# --------------------------- Maple Mono NF font ----------------------------
|
|
# nvim init.lua references "Maple Mono NF". Install it so the default font works.
|
|
# Pin Maple-font version. Bump manually if a release breaks things.
|
|
MAPLE_FONT_VERSION="v7.9"
|
|
|
|
# fc-list check: bash string match instead of pipeline, because `set -o pipefail`
|
|
# in this script causes `fc-list | grep -q` to fail with SIGPIPE (exit 141) when
|
|
# grep exits early on first match — the pipeline then reports non-zero, the
|
|
# `if` evaluates to false, and the bootstrap re-installs the font unnecessarily.
|
|
if [[ "$(fc-list 2>/dev/null)" == *"Maple Mono NF"* ]]; then
|
|
log "Maple Mono NF already installed"
|
|
else
|
|
{{ if eq .os_family "arch" -}}
|
|
# Try paru first (clean install via AUR). If it fails (e.g. sudo TTY prompt
|
|
# in non-interactive runs, or AUR helper missing), fall back to downloading
|
|
# the upstream zip release directly — no sudo needed.
|
|
FONT_DIR="$USER_HOME/.local/share/fonts/maple-mono-nf"
|
|
mkdir -p "$FONT_DIR"
|
|
TMP_ZIP="$(mktemp /tmp/maple-font.XXXXXX.zip)"
|
|
if curl -fL --retry 3 \
|
|
"https://github.com/subframe7536/Maple-font/releases/download/${MAPLE_FONT_VERSION}/MapleMono-NF.zip" \
|
|
-o "$TMP_ZIP"; then
|
|
log "extracting font files"
|
|
unzip -q -o "$TMP_ZIP" -d "$FONT_DIR"
|
|
rm -f "$TMP_ZIP"
|
|
log "refreshing font cache (fc-cache)"
|
|
if command -v fc-cache >/dev/null 2>&1; then
|
|
fc-cache -fv >/dev/null
|
|
else
|
|
log "WARNING: fc-cache not found — install fontconfig package"
|
|
fi
|
|
else
|
|
log "WARNING: failed to download Maple Mono NF from GitHub"
|
|
log "manual install: https://github.com/subframe7536/Maple-font"
|
|
rm -f "$TMP_ZIP"
|
|
fi
|
|
{{ else if eq .os_family "debian" -}}
|
|
log "downloading MapleMono-NF.zip from subframe7536/Maple-font $MAPLE_FONT_VERSION"
|
|
FONT_DIR="$USER_HOME/.local/share/fonts/maple-mono-nf"
|
|
mkdir -p "$FONT_DIR"
|
|
TMP_ZIP="$(mktemp /tmp/maple-font.XXXXXX.zip)"
|
|
if curl -fL --retry 3 \
|
|
"https://github.com/subframe7536/Maple-font/releases/download/${MAPLE_FONT_VERSION}/MapleMono-NF.zip" \
|
|
-o "$TMP_ZIP"; then
|
|
log "extracting font files"
|
|
unzip -q -o "$TMP_ZIP" -d "$FONT_DIR"
|
|
rm -f "$TMP_ZIP"
|
|
log "refreshing font cache (fc-cache)"
|
|
if command -v fc-cache >/dev/null 2>&1; then
|
|
fc-cache -fv >/dev/null
|
|
else
|
|
log "WARNING: fc-cache not found — install fontconfig package"
|
|
fi
|
|
else
|
|
log "WARNING: failed to download Maple Mono NF — nvim will warn about missing font"
|
|
log "manual install: https://github.com/subframe7536/Maple-font"
|
|
rm -f "$TMP_ZIP"
|
|
fi
|
|
{{ else -}}
|
|
log "WARNING: font install not configured for os_family={{ .os_family }}"
|
|
{{ end -}}
|
|
fi
|
|
|
|
# --------------------------- set zsh as default shell ---------------------
|
|
USER_SHELL="$(getent passwd "$(whoami)" | cut -d: -f7)"
|
|
if [[ "$USER_SHELL" != "$(command -v zsh)" ]]; then
|
|
log "changing login shell to zsh"
|
|
sudo chsh -s "$(command -v zsh)" "$(whoami)"
|
|
else
|
|
log "zsh already the login shell"
|
|
fi
|
|
|
|
log "all user packages installed"
|
|
zsh --version
|
|
nvim --version | head -1
|
|
tmux -V |