1
0
Fork 0
gnu-plus-dotfiles/run_once_20-install-user-packages-gentoo.sh.tmpl
rain 7a263fa5f1 zsh: adopt deja predictive autosuggestions; make deja install version-aware
- .zshrc: replace the zsh-autosuggestions plugin with deja (deja supersedes
  it), and move the auto-dedup PATH block to the top so ~/.local/bin is on
  PATH before plugins/modules load.
- bootstrap scripts (arch/debian + gentoo): clone the deja OMZ plugin and
  install the deja binary via the official checksum-verified installer. The
  binary install now compares the installed version to the latest release
  tag and re-runs the installer when they differ, so re-running the script
  UPGRADES deja (0.4.1 -> 0.4.2) rather than skipping an existing install.
2026-09-07 03:29:50 -04:00

336 lines
No EOL
14 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# run_once_20-install-user-packages-gentoo.sh.tmpl (gentoo-only)
# Install the user package set on gentoo via emerge.
#
# Equivalent to run_once_20-install-user-packages.sh.tmpl's arch/debian
# branches, but using portage. Key differences:
# - USE flags matter (e.g. eza has git feature flag, fzf has shell-completion)
# - Some packages are only in GURU overlay (eza, lazygit, topgrade, etc.)
# - emerge is slow (compiles from source); distcc handles remote builds
# - Bun isn't in any gentoo overlay; use the official curl install
#
# Runs after 00-install-bootstrap-tools.sh and 10-add-gentoo-overlays.sh.
# =============================================================================
set -euo pipefail
# Make user-local bins (bun, omp, cargo) visible to the script even when
# invoked from a non-interactive context (e.g. `chezmoi apply` over SSH).
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; }
USER_HOME="${HOME:-$(eval echo "~$(whoami)")}"
ZSH_CUSTOM="${ZSH_CUSTOM:-$USER_HOME/.oh-my-zsh/custom}"
{{ if eq .os_family "gentoo" -}}
# ----------------------------- GENTOO ---------------------------------------
# On gentoo, `emerge` is the only PM (plus the GURU overlay for some
# packages). Many of the user packages live in GURU, so we run emerge with
# GURU enabled. Distcc is recommended for this box (2-core Ivy Bridge +
# MAKEOPTS=-j50 means it's relying on remote build hosts via DISTCC_HOSTS).
#
# Package selection mirrors the arch/debian script. Where a package is
# only available with USE flags, we set them in /etc/portage/package.use/
# before emerging. Where a package is ~amd64-only, we accept_keywords.
log "checking user packages (gentoo)"
# Step 1: USE flags + accept_keywords for packages that need them
# Some packages need explicit USE flags or keyword unmask. Set them in
# package.use/package.accept_keywords so emerge sees them.
# eza is at sys-apps/eza (not app-misc/eza — that's a common typo).
USE_DIR="/etc/portage/package.use"
ACCEPT_DIR="/etc/portage/package.accept_keywords"
mkdir -p "$USE_DIR" "$ACCEPT_DIR" 2>/dev/null || true
sudo mkdir -p "$USE_DIR" "$ACCEPT_DIR"
# eza: USE=git enables git status column
# Skip if already set
if ! grep -q "^sys-apps/eza" "$USE_DIR/zz-gentoo-bootstrap" 2>/dev/null; then
log "writing USE flags: sys-apps/eza git"
echo "sys-apps/eza git" | sudo tee "$USE_DIR/zz-gentoo-bootstrap" >/dev/null
fi
# fzf-tab, zsh-autosuggestions: gentoo has them in app-shells/ but newer
# versions may need unmask. Use the ones in the tree first; if too old,
# we'll install via oh-my-zsh custom plugins (gentoo's ebuilds can lag).
# Step 2: Define the package set
# Note: on Gentoo there's no `base-devel` meta-package. The toolchain
# (gcc, binutils, glibc, make, patch, etc.) is part of the @system set
# which is always installed. We only need to list user-space packages.
GENTOO_PKGS=(
app-shells/zsh
app-shells/zsh-completions
app-shells/zsh-syntax-highlighting
app-admin/tmux
app-editors/neovim
dev-vcs/git
app-text/bat
sys-process/btop
sys-process/htop
app-text/fastfetch
sys-apps/eza # in main, sys-apps category (not app-misc!)
app-shells/fzf
sys-apps/fd # in main, sys-apps category (not app-misc!)
sys-apps/ripgrep
app-shells/zoxide
app-shells/starship
# lazygit is in GURU (app-misc/lazygit)
# topgrade is in GURU (app-misc/topgrade)
# media-video/yt-dlp is in main
app-text/jq
app-arch/unzip
app-arch/p7zip
net-misc/openssh
# bun: NOT in gentoo (main or GURU). Install via official curl.
)
# Step 3: Determine what's missing
# Many gentoo packages don't ship a binary of the same name as the
# package (zsh-completions, zsh-syntax-highlighting install files into
# /usr/share/zsh, not /usr/bin). The simplest "is it installed?"
# check for our purposes is `qlist -I <pkg>` — returns 0 if the
# package is in the installed-db, 1 if not. But qlist is from
# gentoolkit and may not be available. Fall back to `equery`.
#
# A package may also be installed OUTSIDE portage (e.g. cargo install
# or a tarball extract that landed a binary at /usr/bin/<name>). In
# that case qlist reports missing but the binary exists. To avoid
# re-installing these, also check for the binary on PATH and skip
# the package if it's there.
MISSING_PKGS=()
for p in "${GENTOO_PKGS[@]}"; do
bin_name=$(basename "$p")
bin_on_path=0
command -v "$bin_name" >/dev/null 2>&1 && bin_on_path=1
if command -v qlist >/dev/null 2>&1; then
if qlist -I "$p" >/dev/null 2>&1; then
: # installed via portage
elif (( bin_on_path )); then
log "$p: not in portage, but $bin_name found on PATH (skipped)"
else
MISSING_PKGS+=("$p")
fi
elif command -v equery >/dev/null 2>&1; then
if equery -q list "$p" 2>/dev/null | grep -q "^$p"; then
: # installed via portage
elif (( bin_on_path )); then
log "$p: not in portage, but $bin_name found on PATH (skipped)"
else
MISSING_PKGS+=("$p")
fi
else
# Last resort: just check the binary on PATH
if ! (( bin_on_path )); then
MISSING_PKGS+=("$p")
fi
fi
done
# Step 4: Install
if (( ${#MISSING_PKGS[@]} > 0 )); then
log "emerge --newuse user packages (missing: ${MISSING_PKGS[*]})"
log "this may take a while (gentoo compiles from source; distcc helps)"
# --ask=n: no prompts
# --nospinner: cleaner output
# --quiet-build: only show errors during compile
# --keep-going: don't abort if one package fails
# --with-bdeps=y: include build deps so we don't accidentally skip them
# --autounmask=y --autounmask-write=y: auto-write ~amd64 keyword
# unmask files (lazygit is ~amd64 only). Without --autounmask-write,
# emerge only DISPLAYS the needed changes and aborts.
# --autounmask-license=y: auto-accept license unmask
# --autounmask-continue=y: auto-apply ~amd64 keyword unmask AND
# continue (instead of aborting after the unmask). Needed for
# GURU packages like dev-vcs/lazygit which are ~amd64-only.
# Without this, --autounmask-write will write the unmask file
# but emerge aborts because CONFIG_PROTECT marks the file as
# needing manual review.
sudo emerge --ask=n --nospinner --quiet-build --keep-going \
--with-bdeps=y --autounmask-continue=y --autounmask-license=y \
"${MISSING_PKGS[@]}"
else
log "all user packages already installed; skipping emerge"
fi
# Step 5: GURU-only packages
# Note: topgrade is NOT in any gentoo overlay (not main, not GURU).
# It's installed via `cargo install topgrade --locked` (handled by
# run_onchange_30-ensure-cargo.sh). The cargo binary is at
# ~/.cargo/bin which we put on PATH at the top of this script.
# lazygit IS in GURU but at dev-vcs/lazygit (not app-misc/lazygit).
GURU_PKGS=(
dev-vcs/lazygit
)
GURU_MISSING=()
for p in "${GURU_PKGS[@]}"; do
bin_name=$(basename "$p")
bin_on_path=0
command -v "$bin_name" >/dev/null 2>&1 && bin_on_path=1
if command -v qlist >/dev/null 2>&1; then
if qlist -I "$p" >/dev/null 2>&1; then
: # already in portage
elif (( bin_on_path )); then
log "$p: not in portage, but $bin_name found on PATH (skipped)"
else
GURU_MISSING+=("$p")
fi
else
if ! (( bin_on_path )); then
GURU_MISSING+=("$p")
fi
fi
done
if (( ${#GURU_MISSING[@]} > 0 )); then
log "emerge GURU packages (missing: ${GURU_MISSING[*]})"
sudo emerge --ask=n --nospinner --quiet-build --keep-going \
--with-bdeps=y --autounmask-continue=y --autounmask-license=y \
"${GURU_MISSING[@]}"
fi
# Step 6: bun (no gentoo package) — install via official curl, fallback path
if ! command -v bun >/dev/null 2>&1; then
log "installing bun via official curl installer (no gentoo package)"
curl -fsSL https://bun.sh/install | BUN_INSTALL="$HOME/.local" bash
fi
# Post-install verification: bun must be on PATH
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; gentoo has no package)"
# Step 6.5: Pi coding agent (oh-my-pi) — installs omp via bun global.
# Gentoo: bun is in ~/.local/bin (from the curl install above), not
# /usr/bin, so the package-based install paths in the universal
# script don't apply. We do this here.
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
# Step 7: oh-my-zsh + plugins
if [[ ! -d "$USER_HOME/.oh-my-zsh" ]]; then
log "installing oh-my-zsh"
RUNZSH=no KEEP_ZSHRC=yes \
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
else
log "oh-my-zsh already installed"
fi
# Plugins: zsh-autosuggestions, zsh-syntax-highlighting, zsh-history-substring-search, fzf-tab, deja
declare -A PLUGINS=(
[zsh-autosuggestions]="https://github.com/zsh-users/zsh-autosuggestions"
[zsh-syntax-highlighting]="https://github.com/zsh-users/zsh-syntax-highlighting"
[zsh-history-substring-search]="https://github.com/zsh-users/zsh-history-substring-search"
[fzf-tab]="https://github.com/Aloxaf/fzf-tab"
[deja]="https://github.com/Giammarco-Ferranti/deja"
)
for plugin in "${!PLUGINS[@]}"; do
if [[ -d "$ZSH_CUSTOM/plugins/$plugin" ]]; then
log "plugin already present: $ZSH_CUSTOM/plugins/$plugin"
else
log "cloning plugin: $plugin"
git clone --depth=1 "${PLUGINS[$plugin]}" "$ZSH_CUSTOM/plugins/$plugin"
fi
done
# deja predictive autosuggestions: binary via official installer (into
# ~/.local/bin), then seed DB from history. SHELL neutralized so the
# installer never touches .zshrc — the plugin above handles activation.
# The installer always fetches the LATEST release, so this also UPGRADES an
# existing install (0.4.1 -> 0.4.2) when the installed version lags the
# latest release tag — not just a first-run bootstrap.
DEJA_BIN="$USER_HOME/.local/bin/deja"
deja_install_or_upgrade() {
log "installing/upgrading deja binary via official installer"
curl -fsSL https://raw.githubusercontent.com/Giammarco-Ferranti/deja/main/install.sh \
| INSTALL_DIR="$USER_HOME/.local/bin" SHELL=/bin/sh sh \
|| log "WARNING: deja install failed; retry manually."
log "deja now: $("$DEJA_BIN" --version 2>/dev/null | head -1)"
}
if command -v deja >/dev/null 2>&1; then
INSTALLED_VER="$(deja --version 2>/dev/null | awk '{print $2}')"
LATEST_TAG="$(curl -fsSL https://api.github.com/repos/Giammarco-Ferranti/deja/releases/latest 2>/dev/null \
| grep '"tag_name":' | head -n1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')"
LATEST_VER="${LATEST_TAG#v}"
if [[ -z "$LATEST_VER" ]]; then
log "deja present ($INSTALLED_VER); could not check latest, leaving as-is"
elif [[ "$INSTALLED_VER" == "$LATEST_VER" ]]; then
log "deja already at latest ($INSTALLED_VER)"
else
log "deja $INSTALLED_VER != latest $LATEST_TAG — upgrading"
deja_install_or_upgrade
fi
else
log "deja not installed; installing"
deja_install_or_upgrade
fi
if command -v deja >/dev/null 2>&1 && [[ ! -s "$USER_HOME/.local/share/deja/deja.db" ]]; then
log "seeding deja DB from shell history"
HISTFILE="${HISTFILE:-$USER_HOME/.zsh_history}" deja import \
|| log "WARNING: deja import failed; run 'deja import' later."
fi
# Step 8: tpm (tmux plugin manager)
if [[ ! -d "$USER_HOME/.tmux/plugins/tpm" ]]; then
log "installing tpm"
git clone https://github.com/tmux-plugins/tpm "$USER_HOME/.tmux/plugins/tpm"
else
log "tpm already installed"
fi
# Step 9: Maple Mono NF font
# Match EXACT family "Maple Mono NF", not "Maple Mono NF CN" or other variants.
# See run_once_20-install-user-packages.sh.tmpl for the rationale.
if ! fc-list : family 2>/dev/null | grep -qxF 'Maple Mono NF'; then
log "installing Maple Mono NF font"
# Download the latest release zip
TMPFONT=$(mktemp -d)
curl -fsSL "https://github.com/subframe7536/Maple-font/releases/latest/download/MapleMono-NF.zip" \
-o "$TMPFONT/MapleMono-NF.zip"
mkdir -p "$USER_HOME/.local/share/fonts"
unzip -qo "$TMPFONT/MapleMono-NF.zip" -d "$USER_HOME/.local/share/fonts"
fc-cache -fv >/dev/null 2>&1
rm -rf "$TMPFONT"
else
log "Maple Mono NF already installed"
fi
# Step 10: zsh as login shell
if [[ "$(getent passwd rain | cut -d: -f7)" != "/usr/bin/zsh" ]]; then
log "changing default shell to /usr/bin/zsh"
sudo chsh -s /usr/bin/zsh rain
else
log "zsh already the login shell"
fi
log "all user packages installed"
zsh --version
nvim --version | head -1
tmux -V
{{ else -}}
# Not a gentoo box — nothing to do (guarded by os_family above).
log "skipping gentoo user-packages (os_family={{ .os_family }}, not gentoo)"
{{ end -}}